def hook_mem_access(self, uc, access, address, size, value, user_data): curr_section = self.sample.unpacker.get_section(address) access_type = "" if access == UC_MEM_READ: access_type = "READ" if curr_section not in self.sections_read: self.sections_read[curr_section] = 1 else: self.sections_read[curr_section] += 1 self.log_mem_read and print(">>> Memory is being READ at 0x%x, data size = %u" % (address, size)) elif access == UC_MEM_WRITE: access_type = "WRITE" self.write_targets = list(merge(self.write_targets + [(address, address + size)])) if curr_section not in self.sections_written: self.sections_written[curr_section] = 1 else: self.sections_written[curr_section] += 1 self.log_mem_write and print( ">>> Memory is being WRITTEN at 0x%x, data size = %u, data value = 0x%x" % (address, size, value)) else: for access_name, val in unicorn_const.__dict__.items(): if val == access and "UC_MEM" in access_name: access_type = access_name[6:] # remove UC_MEM from the access type print(f"Unexpected mem access type {access_type}, addr: 0x{address:02x}") if any(lower <= address <= upper for lower, upper in self.mem_breakpoints): print(f"\x1b[31mMemory breakpoint hit! Access {access_type} to 0x{address:02x}") self.pause()
def do_del(self, args): """Removes breakpoints. Usage is the same as 'b', but the selected breakpoints and breakpoint ranges are being deleted this time.""" code_targets = [] mem_targets = [] if not args: self.engine.breakpoints.clear() self.engine.mem_breakpoints.clear() self.engine.apicall_handler.pending_breakpoints.clear() for arg in args.split(" "): if not arg: continue if arg == "stack": mem_targets += [ (self.engine.STACK_ADDR, self.engine.STACK_ADDR + self.engine.STACK_SIZE) ] elif "m" == arg[0]: try: parts = list(map(lambda p: int(p, 0), arg[1:].split("-"))) if len(parts) == 1: lower = upper = parts[0] else: lower = min(parts) upper = max(parts) mem_targets += [(lower, upper)] except ValueError: print(f"Error parsing address or range {arg}") elif "$" == arg[0]: arg = arg[1:] if arg in self.engine.apicall_handler.hooks.values(): for addr, func_name in self.engine.apicall_handler.hooks.items( ): if arg == func_name: code_targets += [addr] break elif arg in self.engine.apicall_handler.pending_breakpoints: self.engine.apicall_handler.pending_breakpoints.remove(arg) else: print( f"Unknown method {arg}, not imported or used in pending breakpoint" ) else: try: code_targets += [int(arg, 0)] except ValueError: print(f"Error parsing address {arg}") with self.engine.data_lock: for t in code_targets: try: self.engine.breakpoints.remove(t) except KeyError: pass new_mem_breakpoints = [] for b_lower, b_upper in self.engine.mem_breakpoints: for t_lower, t_upper in mem_targets: new_mem_breakpoints += remove_range((b_lower, b_upper), (t_lower, t_upper)) self.engine.mem_breakpoints = list(merge(new_mem_breakpoints)) self.print_breakpoints()
def VirtualFree(self, uc, esp, log, address, size, free_type): log and print(f"VirtualFree: chunk to free: 0x{address:02x}, size 0x{size:02x}, type 0x{free_type:02x}") new_chunks = [] success = False for start, end in sorted(self.sample.allocated_chunks): if start <= address <= end: if free_type & 0x8000 and size == 0: # MEM_RELEASE, clear whole allocated range if address in self.alloc_sizes: size = self.alloc_sizes[address] end_addr = address + size uc.mem_unmap(address, size) new_chunks += remove_range((start, end), (address, end_addr)) success = True else: log and print(f"\t0x{address} is not an alloc base address!") new_chunks += [(start, end)] elif free_type & 0x4000 and size > 0: # MEM_DECOMMIT, free requested size end_addr = address + align(size) uc.mem_unmap(address, align(size)) new_chunks += remove_range((start, end), (address, end_addr)) success = True else: log and print("\tIncorrect size + type combination!") new_chunks += [(start, end)] else: new_chunks += [(start, end)] self.sample.allocated_chunks = list(merge(new_chunks)) log and self.print_allocs() if success: return 1 log and print("\tAddress range not allocated!") return 0
def alloc(self, log, size, uc, offset=None): page_size = 4 * 1024 aligned_size = align(size, page_size) log and print( f"\tUnaligned size: 0x{size:02x}, aligned size: 0x{aligned_size:02x}" ) if offset is None: for chunk_start, chunk_end in self.sample.allocated_chunks: if chunk_start <= self.dynamic_mem_offset <= chunk_end: # we have to push back the dynamic mem offset as it is inside an already allocated chunk! self.dynamic_mem_offset = chunk_end + 1 offset = self.dynamic_mem_offset self.dynamic_mem_offset += aligned_size new_offset_m = offset % page_size aligned_address = offset # TODO Remove hacky fix, chunks are not merged if (aligned_address % page_size) != 0: aligned_address = align(offset) # check if we have mapped parts of it already mapped_partial = False for chunk_start, chunk_end in self.sample.allocated_chunks: if chunk_start <= aligned_address < chunk_end: if aligned_address + aligned_size <= chunk_end: log and print(f"\tAlready fully mapped") else: log and print( f"\tMapping missing piece 0x{chunk_end + 1:02x} to 0x{aligned_address + aligned_size:02x}" ) uc.mem_map(chunk_end, aligned_address + aligned_size - chunk_end) mapped_partial = True break if not mapped_partial: uc.mem_map(aligned_address, aligned_size) log and print( f"\tfrom 0x{aligned_address:02x} to 0x{(aligned_address + aligned_size):02x}" ) self.sample.allocated_chunks = list( merge(self.sample.allocated_chunks + [(aligned_address, aligned_address + aligned_size)])) log and self.print_allocs() self.alloc_sizes[aligned_address] = aligned_size return aligned_address
def do_b(self, args): """Set breakpoints. All of the options below can be combined in one command any number of times Code breakpoint: b <address> [<addr2> ...] Classic breakpoint: Emulation will stop before executing the instruction at the given address. API call breakpoint: b $<api_call_name> Special case of code breakpoint: Stop the emulation when a certain API call is being made. If this function has been declared in the sample's import table, the breakpoint will be set instantly. If this function will be called in the future, but is somehow not known at the moment (dynamically resolved via GetProcAddress), we will still stop the execution on call. But until GetProcAddress is instructed to return the address of this function, the breakpoint will be marked as 'pending'. At this point we create a hook for the function and mark it as a normal breakpoint. Memory breakpoint: b m<address>[-<upper_limit>] ... When prefixing the address with an 'm', emulation will stop when this address is being read from or written to. Optionally you can set the breakpoint to watch over a whole range of memory, e.g. b m0x100-0x200. Stack breakpoint: b stack Special case of memory range breakpoint: watches the whole stack space Show current breakpoints: b""" code_targets = [] mem_targets = [] for arg in args.split(" "): if not arg: continue if arg == "stack": mem_targets += [ (self.engine.STACK_ADDR, self.engine.STACK_ADDR + self.engine.STACK_SIZE) ] elif "m" == arg[0]: try: parts = list(map(lambda p: int(p, 0), arg[1:].split("-"))) if len(parts) == 1: lower = upper = parts[0] else: lower = min(parts) upper = max(parts) mem_targets += [(lower, upper)] except ValueError: print(f"Error parsing address or range {arg}") elif "$" == arg[0]: arg = arg[1:] if arg in self.engine.apicall_handler.hooks.values(): for addr, func_name in self.engine.apicall_handler.hooks.items( ): if arg == func_name: code_targets += [addr] break else: self.engine.apicall_handler.register_pending_breakpoint( arg) else: try: code_targets += [int(arg, 0)] except ValueError: print(f"Error parsing address {arg}") with self.engine.data_lock: self.engine.breakpoints.update(code_targets) self.engine.mem_breakpoints = list( merge(self.engine.mem_breakpoints + mem_targets)) self.print_breakpoints()