def _enum_apis(self, all_mods): """Enumerate all exported functions from kernel or process space. @param all_mods: list of _LDR_DATA_TABLE_ENTRY To enum kernel APIs, all_mods is a list of drivers. To enum process APIs, all_mods is a list of DLLs. The function name is used if available, otherwise we take the ordinal value. """ exports = {} for i, mod in enumerate(all_mods): self.session.report_progress("Scanning imports %s/%s" % (i, len(all_mods))) pe = pe_vtypes.PE(address_space=mod.obj_vm, session=self.session, image_base=mod.DllBase) for _, func_pointer, func_name, ordinal in pe.ExportDirectory(): function_name = func_name or ordinal or '' exports[func_pointer.v()] = (mod, func_pointer, function_name) return exports
def detect_IAT_hooks(self): """Detect Import Address Table hooks. An IAT hook is where malware changes the IAT entry for a dll after its loaded so that when it is called from within the DLL, flow control is directed to the malware instead. We determine the IAT entry is hooked if the address is outside the dll which is imported. """ pe = pe_vtypes.PE(image_base=self.image_base, session=self.session) # First try to find all the names of the imported functions. imports = [(dll, func_name) for dll, func_name, _ in pe.ImportDirectory()] resolver = self.session.address_resolver for idx, (dll, func_address, _) in enumerate(pe.IAT()): target_dll, target_func_name = imports[idx] target_dll = self.session.address_resolver.NormalizeModuleName( target_dll) self.session.report_progress("Checking function %s!%s", target_dll, target_func_name) # We only want the containing module. _, _, name = resolver.FindContainingModule(func_address) if target_dll == name: continue function_name = "%s:%s" % (target_dll, target_func_name) yield function_name, func_address
def render(self, renderer): # The filename is an executable if self.guid is None: self.pe = pe_vtypes.PE(filename=self.filename, session=self.session) data_directory = self.pe.nt_header.OptionalHeader.DataDirectory[ "IMAGE_DIRECTORY_ENTRY_DEBUG"].VirtualAddress.dereference_as( "_IMAGE_DEBUG_DIRECTORY") # We only support the more recent RSDS format. debug = data_directory.AddressOfRawData.dereference_as( "CV_RSDS_HEADER") if debug.Signature != "RSDS": logging.error("PDB stream %s not supported.", debug.Signature) return self.pdb_filename = ntpath.basename(str(debug.Filename)) self.guid = self.pe.RSDS.GUID_AGE elif self.filename is None: raise RuntimeError( "Filename must be provided when GUI is specified.") else: self.pdb_filename = self.filename self.guid = self.guid.upper() for url in self.SYM_URLS: try: basename = ntpath.splitext(self.pdb_filename)[0] url += "/%s/%s/%s.pd_" % (self.pdb_filename, self.guid, basename) renderer.format("Trying to fetch {0}\n", url) request = urllib2.Request( url, None, headers={'User-Agent': self.USER_AGENT}) data = urllib2.urlopen(request).read() renderer.format("Received {0} bytes\n", len(data)) output_file = os.path.join(self.dump_dir, "%s.pd_" % basename) with open(output_file, "wb") as fd: fd.write(data) try: subprocess.check_call( ["cabextract", os.path.basename(output_file)], cwd=self.dump_dir) except subprocess.CalledProcessError: renderer.format( "Failed to decompress output file {0}. " "Ensure cabextract is installed.\n", output_file) break except IOError as e: logging.error(e) continue
def LoadProfileForDll(self, module_base, module_name): self._EnsureInitialized() if module_name in self.profiles: return self.profiles[module_name] # Try to determine the DLL's GUID. pe_helper = pe_vtypes.PE( address_space=self.session.GetParameter("default_address_space"), image_base=module_base, session=self.session) # TODO: Apply profile index to detect the profile. guid_age = pe_helper.RSDS.GUID_AGE if guid_age: profile = self.session.LoadProfile("%s/GUID/%s" % ( module_name, guid_age)) profile.name = module_name profile.image_base = module_base if profile: self.profiles[module_name] = profile return profile result = self._build_profile_from_exports(module_base, module_name) self.profiles[module_name] = result return result
def _enum_apis(self, all_mods): """Enumerate all exported functions from process space. @param all_mods: list of _LDR_DATA_TABLE_ENTRY To enum process APIs, all_mods is a list of DLLs. The function name is used if available, otherwise we take the ordinal value. """ exports = {} for i, mod in enumerate(all_mods): self.session.report_progress("Scanning exports %s/%s" % (i, len(all_mods))) pe = pe_vtypes.PE(address_space=mod.obj_vm, session=self.session, image_base=mod.DllBase) export_directory = pe.nt_header.OptionalHeader.DataDirectory[ 'IMAGE_DIRECTORY_ENTRY_EXPORT'].dereference() dll = export_directory.Name.dereference() function_table = export_directory.AddressOfFunctions.dereference() for func_index, pointer in enumerate(function_table): exports[pointer.v()] = (mod, pointer, export_directory, func_index) return exports
def detect_inline_hooks(self): """A Generator of hooked exported functions from this PE file. Yields: A tuple of (function, name, jump_destination) """ # Inspect the export directory for inline hooks. pe = pe_vtypes.PE( image_base=self.plugin_args.image_base, address_space=self.session.GetParameter("default_address_space"), session=self.session) pfn_db = self.session.profile.get_constant_object("MmPfnDatabase") heuristic = HookHeuristic(session=self.session) ok_pages = set() for _, function, name, _ in pe.ExportDirectory(): # Dereference the function pointer. function_address = function.deref().obj_offset self.session.report_progress("Checking function %#x (%s)", function, name) # Check if the page is private or a file mapping. Usually if a # mapped page is modified it will be converted to a private page due # to Windows copy on write semantics. We assume that hooks are only # placed in memory, and therefore functions which are still mapped # to disk files are not hooked and can be safely skipped. if not self.plugin_args.thorough: # We must do the vtop in the process address space. This is the # physical page backing the function preamble. phys_address = function.obj_vm.vtop(function_address) # Page not mapped. if phys_address == None: continue phys_page = phys_address >> 12 # We determined this page is ok before - we can skip it. if phys_page in ok_pages: continue # Get the PFN DB record. pfn_obj = pfn_db[phys_page] # The page is controlled by a prototype PTE which means it is # still a file mapping. It has not been changed. if pfn_obj.IsPrototype: ok_pages.add(phys_page) continue # Try to detect an inline hook. destination = heuristic.Inspect(function, instructions=3) or "" # If we did not detect a hook we skip this function. if destination: yield function, name, destination
def __init__(self, **kwargs): super(PEAddressResolver, self).__init__(**kwargs) self.address_map = utils.SortedCollection(key=lambda x: x[0]) self.section_map = utils.SortedCollection(key=lambda x: x[0]) self.image_base = self.kernel_address_space.image_base self.pe_helper = pe_vtypes.PE( address_space=self.session.kernel_address_space, image_base=self.image_base, session=self.session) # Delay initialization until we need it. self._initialized = False
def LoadProfileForDll(self, module_base, module_name): """Loads a profile for the PE file located at the module_base. This method gets a profile using the following methods in order: 1 - Have session load it from the profile repository. 2 - Build locally from the MS symbols server. 3 - Look up a the profile using index if an index exists for this binary. 4 - Build it from export tables of the PE binary. """ self._EnsureInitialized() if module_name in self.profiles: return self.profiles[module_name] result = None # Try to determine the DLL's GUID. pe_helper = pe_vtypes.PE( address_space=self.session.GetParameter("default_address_space"), image_base=module_base, session=self.session) guid_age = pe_helper.RSDS.GUID_AGE if guid_age: profile_name = "%s/GUID/%s" % (module_name, guid_age) result = (self.session.LoadProfile(profile_name) or self._build_local_profile(module_name, profile_name)) if result: result.name = module_name result.image_base = module_base if not result: # Try to apply the profile index if possible. index_profile = self.session.LoadProfile("%s/index" % module_name) if index_profile: for test_profile, _ in index_profile.LookupIndex( module_base, minimal_match=1): result = self.session.LoadProfile(test_profile) result.image_base = module_base break if not result: result = self._build_profile_from_exports(module_base, module_name) self.profiles[module_name] = result return result
def detect_IAT_hooks(self): """Detect Import Address Table hooks. An IAT hook is where malware changes the IAT entry for a dll after its loaded so that when it is called from within the DLL, flow control is directed to the malware instead. We determine the IAT entry is hooked if the address is outside the dll which is imported. """ pe = pe_vtypes.PE(image_base=self.plugin_args.image_base, session=self.session) # First try to find all the names of the imported functions. imports = [(dll, func_name) for dll, func_name, _ in pe.ImportDirectory()] resolver = self.session.address_resolver for idx, (dll, func_address, _) in enumerate(pe.IAT()): try: target_dll, target_func_name = imports[idx] target_dll = self.session.address_resolver.NormalizeModuleName( target_dll) except IndexError: # We can not retrieve these function's name from the # OriginalFirstThunk array - possibly because it is not mapped # in. target_dll = dll target_func_name = "" self.session.report_progress("Checking function %s!%s", target_dll, target_func_name) # We only want the containing module. module = resolver.GetContainingModule(func_address) if module and target_dll == module.name: continue # Use ordinal if function has no name if not len(target_func_name): target_func_name = "(%s)" % idx function_name = "%s!%s" % (target_dll, target_func_name) # Function_name is the name which the PE file want yield function_name, func_address
def findExportsFromLoadedDLLs(pid): p = process(pid) eprocess = p['_EPROCESS'] modules = eprocess.get_load_modules() exports = {} for i, mod in enumerate(modules): pe = pe_vtypes.PE(address_space=mod.obj_vm, session=s, image_base=mod.DllBase) for _, func_pointer, func_name, ordinal in pe.ExportDirectory(): function_name = func_name or ordinal or '' exports[func_pointer.v()] = (mod, func_pointer, function_name) return exports
def detect_EAT_hooks(self, size=0): """Detect Export Address Table hooks. An EAT hook is where malware changes the EAT entry for a dll after its loaded so that a new DLL wants to link against it, the new DLL will use the malware's function instead of the exporting DLL's function. We determine the EAT entry is hooked if the address lies outside the exporting dll. """ address_space = self.session.GetParameter("default_address_space") pe = pe_vtypes.PE(image_base=self.plugin_args.image_base, session=self.session, address_space=address_space) start = self.plugin_args.image_base end = self.plugin_args.image_base + size # If the dll size is not provided we parse it from the PE header. if not size: for _, _, virtual_address, section_size in pe.Sections(): # Only count executable sections. section_end = (self.plugin_args.image_base + virtual_address + section_size) if section_end > end: end = section_end resolver = self.session.address_resolver for dll, func, name, hint in pe.ExportDirectory(): self.session.report_progress("Checking export %s!%s", dll, name) # Skip zero or invalid addresses. if address_space.read(func.v(), 10) == "\x00" * 10: continue # Report on exports which fall outside the dll. if start < func.v() < end: continue function_name = "%s:%s (%s)" % (resolver.NormalizeModuleName(dll), name, hint) yield function_name, func
def RSDS(self): helper = pe_vtypes.PE(address_space=self.obj_vm, image_base=self.DllBase, session=self.obj_session) return helper.RSDS