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 load_dll(self, path_dll, start_addr): filename = os.path.splitext(os.path.basename(path_dll))[0] if not os.path.exists(f"{os.path.dirname(unipacker.__file__)}/DLLs/{filename}.ldll"): dll = pefile.PE(path_dll) loaded_dll = dll.get_memory_mapped_image(ImageBase=start_addr) with open(f"{os.path.dirname(unipacker.__file__)}/DLLs/{filename}.ldll", 'wb') as f: f.write(loaded_dll) self.uc.mem_map(start_addr, align(len(loaded_dll) + 0x1000)) self.uc.mem_write(start_addr, loaded_dll) else: with open(f"{os.path.dirname(unipacker.__file__)}/DLLs/{filename}.ldll", 'rb') as dll: loaded_dll = dll.read() self.uc.mem_map(start_addr, align((len(loaded_dll) + 0x1000))) self.uc.mem_write(start_addr, loaded_dll)
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 init_uc(self): # Calculate required memory pe = pefile.PE(self.sample.path) self.sample.BASE_ADDR = pe.OPTIONAL_HEADER.ImageBase # 0x400000 self.sample.unpacker.BASE_ADDR = self.sample.BASE_ADDR self.sample.virtualmemorysize = self.getVirtualMemorySize() self.STACK_ADDR = 0x0 self.STACK_SIZE = 1024 * 1024 STACK_START = self.STACK_ADDR + self.STACK_SIZE # self.sample.unpacker.secs += [{"name": "stack", "vaddr": self.STACK_ADDR, "vsize": self.STACK_SIZE}] stack_sec_header = IMAGE_SECTION_HEADER( "stack".encode('ascii'), self.STACK_SIZE, self.STACK_ADDR, self.STACK_SIZE, 0, 0, 0, 0, 0, 0, ) self.sample.unpacker.secs.append(SectionHeader(stack_sec_header)) self.HOOK_ADDR = STACK_START + 0x3000 + 0x1000 # Start unicorn emulator with x86-32bit architecture self.uc = Uc(UC_ARCH_X86, UC_MODE_32) if self.sample.unpacker.startaddr is None: self.sample.unpacker.startaddr = self.entrypoint(pe) self.sample.loaded_image = pe.get_memory_mapped_image(ImageBase=self.sample.BASE_ADDR) self.sample.virtualmemorysize = align(self.sample.virtualmemorysize + 0x10000, page_size=4096) # Space possible IAT rebuilding self.sample.unpacker.virtualmemorysize = self.sample.virtualmemorysize self.uc.mem_map(self.sample.BASE_ADDR, self.sample.virtualmemorysize) self.uc.mem_write(self.sample.BASE_ADDR, self.sample.loaded_image) self.setup_processinfo() # Load DLLs self.load_dll(f"{os.path.dirname(unipacker.__file__)}/DLLs/KernelBase.dll", 0x73D00000) self.load_dll(f"{os.path.dirname(unipacker.__file__)}/DLLs/kernel32.dll", 0x755D0000) self.load_dll(f"{os.path.dirname(unipacker.__file__)}/DLLs/ntdll.dll", 0x77400000) # initialize machine registers self.uc.mem_map(self.STACK_ADDR, self.STACK_SIZE) self.uc.reg_write(UC_X86_REG_ESP, self.STACK_ADDR + int(self.STACK_SIZE / 2)) self.uc.reg_write(UC_X86_REG_EBP, self.STACK_ADDR + int(self.STACK_SIZE / 2)) self.uc.mem_write(self.uc.reg_read(UC_X86_REG_ESP) + 0x8, bytes([1])) # -> PEtite Stack Operations? self.uc.reg_write(UC_X86_REG_EAX, self.sample.unpacker.startaddr) self.uc.reg_write(UC_X86_REG_EBX, self.PEB_BASE) self.uc.reg_write(UC_X86_REG_ECX, self.sample.unpacker.startaddr) self.uc.reg_write(UC_X86_REG_EDX, self.sample.unpacker.startaddr) self.uc.reg_write(UC_X86_REG_ESI, self.sample.unpacker.startaddr) self.uc.reg_write(UC_X86_REG_EDI, self.sample.unpacker.startaddr) self.uc.reg_write(UC_X86_REG_EFLAGS, 0x244) new_pe = PE(self.uc, self.sample.BASE_ADDR) prot_val = lambda x, y: True if x & y != 0 else False for s in new_pe.section_list: self.sample.atn[( s.VirtualAddress + self.sample.BASE_ADDR, s.VirtualAddress + self.sample.BASE_ADDR + s.VirtualSize)] = convert_to_string( s.Name) self.sample.ntp[convert_to_string(s.Name)] = ( prot_val(s.Characteristics, 0x20000000), prot_val(s.Characteristics, 0x40000000), prot_val(s.Characteristics, 0x80000000)) # for s in pe.sections: # atn[(s.VirtualAddress + self.sample.BASE_ADDR, s.VirtualAddress + self.sample.BASE_ADDR + s.Misc_VirtualSize)] = s.Name # ntp[s.Name] = (s.IMAGE_SCN_MEM_EXECUTE, s.IMAGE_SCN_MEM_READ, s.IMAGE_SCN_MEM_WRITE) # init syscall handling and prepare hook memory for return values self.apicall_handler = WinApiCalls(self) self.uc.mem_map(self.HOOK_ADDR, 0x1000) # self.sample.unpacker.secs += [{"name": "hooks", "vaddr": self.HOOK_ADDR, "vsize": 0x1000}] hook_sec_header = IMAGE_SECTION_HEADER( "hooks".encode('ascii'), 0x1000, self.HOOK_ADDR, 0x1000, 0, 0, 0, 0, 0, 0, ) self.sample.unpacker.secs.append(SectionHeader(stack_sec_header)) hexstr = bytes.fromhex('000000008b0425') + struct.pack('<I', self.HOOK_ADDR) + bytes.fromhex( 'c3') # mov eax, [HOOK]; ret -> values of syscall are stored in eax self.uc.mem_write(self.HOOK_ADDR, hexstr) # handle imports # TODO Update when custom loader available for lib in pe.DIRECTORY_ENTRY_IMPORT: descriptor = ImportDescriptor(None, lib.struct.Characteristics, lib.struct.TimeDateStamp, lib.struct.ForwarderChain, lib.struct.Name, lib.struct.FirstThunk) fct_list = [] for i in lib.imports: fct_list.append(i.name) imp = Import(descriptor, lib.dll.decode('ascii'), fct_list) self.sample.original_imports.append(imp) for func in lib.imports: func_name = func.name.decode() if func.name is not None else f"no name: 0x{func.address:02x}" dll_name = lib.dll.decode() if lib.dll is not None else "-- unknown --" self.sample.imports.add(func_name) curr_hook_addr = self.apicall_handler.add_hook(self.uc, func_name, dll_name) self.uc.mem_write(func.address, struct.pack('<I', curr_hook_addr)) hdr = PE(self.uc, self.sample.BASE_ADDR) # Patch DLLs with hook # Hardcoded values used for speed improvement -> Offsets can be calculated with utils.calc_export_offset_of_dll self.apicall_handler.add_hook(self.uc, "VirtualProtect", "KernelBase.dll", 0x73D00000 + 0x1089f0) self.apicall_handler.add_hook(self.uc, "VirtualAlloc", "KernelBase.dll", 0x73D00000 + 0xd4600) self.apicall_handler.add_hook(self.uc, "VirtualFree", "KernelBase.dll", 0x73D00000 + 0xd4ae0) self.apicall_handler.add_hook(self.uc, "LoadLibraryA", "KernelBase.dll", 0x73D00000 + 0xf20d0) self.apicall_handler.add_hook(self.uc, "GetProcAddress", "KernelBase.dll", 0x73D00000 + 0x102870) self.apicall_handler.add_hook(self.uc, "VirtualProtect", "kernel32.dll", 0x755D0000 + 0x16760) self.apicall_handler.add_hook(self.uc, "VirtualAlloc", "kernel32.dll", 0x755D0000 + 0x166a0) self.apicall_handler.add_hook(self.uc, "VirtualFree", "kernel32.dll", 0x755D0000 + 0x16700) self.apicall_handler.add_hook(self.uc, "LoadLibraryA", "kernel32.dll", 0x755D0000 + 0x157b0) self.apicall_handler.add_hook(self.uc, "GetProcAddress", "kernel32.dll", 0x755D0000 + 0x14ee0) # Add hooks self.uc.hook_add(UC_HOOK_CODE, self.hook_code) self.uc.hook_add(UC_HOOK_MEM_READ | UC_HOOK_MEM_WRITE | UC_HOOK_MEM_FETCH, self.hook_mem_access) self.uc.hook_add(UC_HOOK_MEM_READ_UNMAPPED | UC_HOOK_MEM_WRITE_UNMAPPED, self.hook_mem_invalid)
def setup_processinfo(self): self.TEB_BASE = 0x200000 self.PEB_BASE = self.TEB_BASE + 0x1000 LDR_PTR = self.PEB_BASE + 0x1000 LIST_ENTRY_BASE = LDR_PTR + 0x1000 teb = TEB( -1, # fs:00h self.STACK_ADDR + self.STACK_SIZE, # fs:04h self.STACK_ADDR, # fs:08h 0, # fs:0ch 0, # fs:10h 0, # fs:14h self.TEB_BASE, # fs:18h (teb base) 0, # fs:1ch 0xdeadbeef, # fs:20h (process id) 0xdeadbeef, # fs:24h (current thread id) 0, # fs:28h 0, # fs:2ch self.PEB_BASE, # fs:3ch (peb base) ) peb = PEB( 0, 0, 0, 0, 0xffffffff, self.sample.BASE_ADDR, LDR_PTR, ) ntdll_entry = LIST_ENTRY( LIST_ENTRY_BASE + 12, LIST_ENTRY_BASE + 24, 0x77400000, ) kernelbase_entry = LIST_ENTRY( LIST_ENTRY_BASE + 24, LIST_ENTRY_BASE + 0, 0x73D00000, ) kernel32_entry = LIST_ENTRY( LIST_ENTRY_BASE + 0, LIST_ENTRY_BASE + 12, 0x755D0000, ) ldr = PEB_LDR_DATA( 0x30, 0x1, 0x0, LIST_ENTRY_BASE, LIST_ENTRY_BASE + 24, LIST_ENTRY_BASE, LIST_ENTRY_BASE + 24, LIST_ENTRY_BASE, LIST_ENTRY_BASE + 24, ) teb_payload = bytes(teb) peb_payload = bytes(peb) ldr_payload = bytes(ldr) ntdll_payload = bytes(ntdll_entry) kernelbase_payload = bytes(kernelbase_entry) kernel32_payload = bytes(kernel32_entry) self.uc.mem_map(self.TEB_BASE, align(0x5000)) self.uc.mem_write(self.TEB_BASE, teb_payload) self.uc.mem_write(self.PEB_BASE, peb_payload) self.uc.mem_write(LDR_PTR, ldr_payload) self.uc.mem_write(LIST_ENTRY_BASE, ntdll_payload) self.uc.mem_write(LIST_ENTRY_BASE + 12, kernelbase_payload) self.uc.mem_write(LIST_ENTRY_BASE + 24, kernel32_payload) self.uc.windows_tib = self.TEB_BASE