def init_exports(self, pe: pefile.PE): if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_EXPORT'): return # Do a full load if IMAGE_DIRECTORY_ENTRY_EXPORT is present so we can load the exports pe.full_load() iat = {} # parse directory entry export for entry in pe.DIRECTORY_ENTRY_EXPORT.symbols: ea = self.pe_image_address + entry.address self.export_symbols[ea] = { 'name': entry.name, 'ordinal': entry.ordinal } if entry.name: iat[entry.name] = ea iat[entry.ordinal] = ea dll_name = os.path.basename(self.path) self.import_address_table[dll_name.casefold()] = iat
def init_imports(self, pe: pefile.PE, is_driver: bool): if not Process.directory_exists(pe, 'IMAGE_DIRECTORY_ENTRY_IMPORT'): return pe.full_load() for entry in pe.DIRECTORY_ENTRY_IMPORT: dll_name = entry.dll.decode().casefold() self.ql.log.debug(f'Requesting imports from {dll_name}') orig_dll_name = dll_name redirected = False if dll_name.startswith('api-ms-win-'): # DLLs starting with this prefix contain no actual code. Instead, the windows loader loads the actual # code from one of the main windows dlls. # see https://github.com/lucasg/Dependencies for correct replacement dlls # # The correct way to find the dll that replaces all symbols from this dll involves using the hashmap # inside of apisetschema.dll (see https://lucasg.github.io/2017/10/15/Api-set-resolution/ ). # # Currently, we use a simpler, more hacky approach, that seems to work in a lot of cases: we just scan # through some key dlls and hope that we find the requested symbols there. some symbols may appear on # more than one dll though; in that case we proceed to the next symbol to see which key dll includes it. # # Note: You might be tempted to load the actual dll (dll_name), because they also contain a reference to # the replacement dll. However, chances are, that these dlls do not exist in the rootfs and maybe they # don't even exist on windows. Therefore this approach is a bad idea. # DLLs that seem to contain most of the requested symbols key_dlls = ('ntdll.dll', 'kernelbase.dll', 'ucrtbase.dll') imports = iter(entry.imports) failed = False fallback = None while not redirected and not failed: # find all possible redirection options by scanning key dlls for the current imported symbol imp = next(imports, None) redirection_options = [fallback] if imp is None else [ filename for filename in key_dlls if filename in self.import_address_table and imp.name in self.import_address_table[filename] ] # no redirection options: failed to redirect dll if not redirection_options: failed = True # exactly one redirection options: use it elif len(redirection_options) == 1: key_dll = redirection_options[0] redirected = True # more than one redirection options: remember one of them and proceed to next symbol else: fallback = redirection_options[-1] if not redirected: self.ql.log.warning(f'Failed to resolve {dll_name}') continue self.ql.log.debug(f'Redirecting {dll_name} to {key_dll}') dll_name = key_dll unbound_imports = [imp for imp in entry.imports if not imp.bound] if unbound_imports: # Only load dll if encountered unbound symbol if not redirected: dll_base = self.load_dll(entry.dll.decode(), is_driver) if not dll_base: continue for imp in unbound_imports: iat = self.import_address_table[dll_name] if imp.name: if imp.name not in iat: self.ql.log.debug( f'Error in loading function {imp.name.decode()} ({orig_dll_name}){", probably misdirected" if redirected else ""}' ) continue addr = iat[imp.name] else: addr = iat[imp.ordinal] self.ql.mem.write_ptr(imp.address, addr)