Package rekall :: Package plugins :: Package windows :: Package malware :: Module impscan
[frames] | no frames]

Source Code for Module rekall.plugins.windows.malware.impscan

  1  # Rekall Memory Forensics 
  2  # 
  3  # Copyright (c) 2010 - 2012 Michael Ligh <michael.ligh@mnin.org> 
  4  # Copyright 2013 Google Inc. All Rights Reserved. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or (at 
  9  # your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, but 
 12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 14  # General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 19  # 
 20   
 21  from rekall import plugin 
 22  from rekall import obj 
 23  from rekall import testlib 
 24   
 25  from rekall.plugins.overlays.windows import pe_vtypes 
 26  from rekall.plugins.windows import common 
27 28 29 -class ImpScan(common.WinProcessFilter):
30 """Scan for calls to imported functions.""" 31 32 __name = "impscan" 33 34 FORWARDED_IMPORTS = { 35 "RtlGetLastWin32Error" : "kernel32.dll!GetLastError", 36 "RtlSetLastWin32Error" : "kernel32.dll!SetLastError", 37 "RtlRestoreLastWin32Error" : "kernel32.dll!SetLastError", 38 "RtlAllocateHeap" : "kernel32.dll!HeapAlloc", 39 "RtlReAllocateHeap" : "kernel32.dll!HeapReAlloc", 40 "RtlFreeHeap" : "kernel32.dll!HeapFree", 41 "RtlEnterCriticalSection" : "kernel32.dll!EnterCriticalSection", 42 "RtlLeaveCriticalSection" : "kernel32.dll!LeaveCriticalSection", 43 "RtlDeleteCriticalSection" : "kernel32.dll!DeleteCriticalSection", 44 "RtlZeroMemory" : "kernel32.dll!ZeroMemory", 45 "RtlSizeHeap" : "kernel32.dll!HeapSize", 46 "RtlUnwind" : "kernel32.dll!RtlUnwind", 47 } 48 49 @classmethod
50 - def args(cls, parser):
51 """Declare the command line args we need.""" 52 super(ImpScan, cls).args(parser) 53 parser.add_argument("-b", "--base", default=None, type="IntParser", 54 help="Base address in process memory if --pid is " 55 "supplied, otherwise an address in kernel space") 56 57 parser.add_argument("-s", "--size", default=None, type="IntParser", 58 help="Size of memory to scan") 59 60 parser.add_argument("-k", "--kernel", default=None, type="Boolean", 61 help="Scan in kernel space.")
62
63 - def __init__(self, base=None, size=None, kernel=None, **kwargs):
64 """Scans the imports from a module. 65 66 Often when dumping a PE executable from memory the import address tables 67 are over written. This makes it hard to resolve function names when 68 disassembling the binary. 69 70 This plugin enumerates all dlls in the process address space and 71 examines their export address tables. It then disassembles the 72 executable code for calls to external functions. We attempt to resolve 73 the names of the calls using the known exported functions we gathered in 74 step 1. 75 76 This technique can be used for a process, or the kernel itself. In the 77 former case, we examine dlls, while in the later case we examine kernel 78 modules using the modules plugin. 79 80 Args: 81 82 base: Start disassembling at this address - this is normally the base 83 address of the dll or module we care about. If omitted we use the 84 kernel base (if in kernel mode) or the main executable (if in 85 process mode). 86 87 size: Disassemble this many bytes from the address space. If omitted 88 we use the module which starts at base. 89 90 kernel: The mode to use. If set, we operate in kernel mode. 91 """ 92 super(ImpScan, self).__init__(**kwargs) 93 self.base = base 94 self.size = size 95 self.kernel = kernel
96
97 - def _enum_apis(self, all_mods):
98 """Enumerate all exported functions from kernel 99 or process space. 100 101 @param all_mods: list of _LDR_DATA_TABLE_ENTRY 102 103 To enum kernel APIs, all_mods is a list of drivers. 104 To enum process APIs, all_mods is a list of DLLs. 105 106 The function name is used if available, otherwise 107 we take the ordinal value. 108 """ 109 exports = {} 110 111 for i, mod in enumerate(all_mods): 112 self.session.report_progress("Scanning imports %s/%s" % ( 113 i, len(all_mods))) 114 115 pe = pe_vtypes.PE(address_space=mod.obj_vm, 116 session=self.session, image_base=mod.DllBase) 117 118 for _, func_pointer, func_name, ordinal in pe.ExportDirectory(): 119 function_name = func_name or ordinal or '' 120 121 exports[func_pointer.v()] = (mod, func_pointer, function_name) 122 123 return exports
124
125 - def _iat_scan(self, addr_space, calls_imported, apis, base_address, 126 end_address):
127 """Scan forward from the lowest IAT entry found for new import entries. 128 129 Args: 130 addr_space: an AS 131 calls_imported: Import database - a dict. 132 apis: dictionary of exported functions in the AS. 133 base_address: memory base address for this module. 134 end_address: end of valid address range. 135 """ 136 if not calls_imported: 137 return 138 139 # Search the iat from the earliest function address to the latest 140 # address for references to other functions. 141 start_addr = min(calls_imported.keys()) 142 iat_size = min(max(calls_imported.keys()) - start_addr, 2000) 143 144 # The IAT is a table of pointers to functions. 145 iat = self.profile.Array( 146 offset=start_addr, vm=addr_space, target="Pointer", 147 count=iat_size, target_args=dict(target="Function")) 148 149 for func_pointer in iat: 150 func = func_pointer.dereference() 151 152 if (not func or 153 (func.obj_offset > base_address and 154 func.obj_offset < end_address)): # skip call to self 155 continue 156 157 # Add the export to our database of imported calls. 158 if (func.obj_offset in apis and 159 func_pointer.obj_offset not in calls_imported): 160 iat_addr = func_pointer.obj_offset 161 calls_imported[iat_addr] = (iat_addr, func)
162
163 - def _original_import(self, mod_name, func_name):
164 """Revert a forwarded import to the original module 165 and function name. 166 167 @param mod_name: current module name 168 @param func_name: current function name 169 """ 170 171 if func_name in self.FORWARDED_IMPORTS: 172 return self.FORWARDED_IMPORTS[func_name].split("!") 173 else: 174 return mod_name, func_name
175 176 CALL_RULE = {'mnemonic': 'CALL', 'operands': [ 177 {'type': 'MEM', 'target': "$target", 'address': '$address'}]} 178 JMP_RULE = {'mnemonic': 'JMP', 'operands': [ 179 {'type': 'MEM', 'target': "$target", 'address': '$address'}]} 180
181 - def call_scan(self, addr_space, base_address, size_to_read):
182 """Locate calls in a block of code. 183 184 Disassemble a block of data and yield possible calls to imported 185 functions. We're looking for instructions such as these: 186 187 x86: 188 CALL DWORD [0x1000400] 189 JMP DWORD [0x1000400] 190 191 x64: 192 CALL QWORD [RIP+0x989d] 193 194 On x86, the 0x1000400 address is an entry in the IAT or call table. It 195 stores a DWORD which is the location of the API function being called. 196 197 On x64, the 0x989d is a relative offset from the current instruction 198 (RIP). 199 200 So we simply disassemble the entire code section of the executable 201 looking for calls, then we collect all the targets of the calls. 202 203 @param addr_space: an AS to scan with 204 @param base_address: memory base address 205 @param data: buffer of data found at base_address 206 207 """ 208 func_obj = self.profile.Function(vm=addr_space, offset=base_address) 209 end_address = base_address + size_to_read 210 211 for instruction in func_obj.disassemble(2**32): 212 if instruction.address > end_address: 213 break 214 215 context = {} 216 if (instruction.match_rule(self.CALL_RULE, context) or 217 instruction.match_rule(self.JMP_RULE, context)): 218 target = context.get("$target") 219 if target: 220 yield (instruction.address, 221 context.get("$address"), 222 self.profile.Function(vm=addr_space, offset=target))
223
224 - def find_process_imports(self, task):
225 task_space = task.get_process_address_space() 226 all_mods = list(task.get_load_modules()) 227 228 # Exported function of all other modules in the address space. 229 apis = self._enum_apis(all_mods) 230 231 # PEB is paged or no DLLs loaded 232 if not all_mods: 233 self.session.logging.error("Cannot load DLLs in process AS") 234 return 235 236 # Its OK to blindly take the 0th element because the executable is 237 # always the first module to load. 238 base_address = int(all_mods[0].DllBase) 239 size_to_read = int(all_mods[0].SizeOfImage) 240 241 calls_imported = {} 242 for address, iat, destination in self.call_scan( 243 task_space, base_address, size_to_read): 244 self.session.report_progress("Resolving import %s->%s" % ( 245 address, iat)) 246 calls_imported[iat] = (address, destination) 247 248 # Scan the IAT for additional functions. 249 self._iat_scan(task_space, calls_imported, apis, 250 base_address, base_address + size_to_read) 251 252 for iat, (_, func_pointer) in sorted(calls_imported.iteritems()): 253 tmp = apis.get(func_pointer.obj_offset) 254 if tmp: 255 module, func_pointer, func_name = tmp 256 yield iat, func_pointer, module, func_name
257
258 - def find_kernel_import(self):
259 # If the user has not specified the base, we just use the kernel's 260 # image. 261 base_address = self.base 262 if base_address is None: 263 base_address = self.session.GetParameter("kernel_base") 264 265 # Get the size from the module list if its not supplied 266 size_to_read = self.size 267 if not size_to_read: 268 modlist = self.session.plugins.modules() 269 for module in modlist.lsmod(): 270 if module.DllBase == base_address: 271 size_to_read = module.SizeOfImage 272 break 273 274 if not size_to_read: 275 raise plugin.PluginError("You must specify a size to read.") 276 277 all_mods = list(modlist.lsmod()) 278 apis = self._enum_apis(all_mods) 279 280 calls_imported = {} 281 for address, iat, destination in self.call_scan( 282 self.kernel_address_space, base_address, size_to_read): 283 calls_imported[iat] = (address, destination) 284 self.session.report_progress( 285 "Found %s imports" % len(calls_imported)) 286 287 # Scan the IAT for additional functions. 288 self._iat_scan(self.kernel_address_space, calls_imported, apis, 289 base_address, size_to_read) 290 291 for iat, (address, func_pointer) in sorted(calls_imported.items()): 292 module, func_pointer, func_name = apis.get(func_pointer.v(), ( 293 obj.NoneObject("Unknown"), 294 obj.NoneObject("Unknown"), 295 obj.NoneObject("Unknown"))) 296 297 yield iat, func_pointer, module, func_name
298
299 - def render(self, renderer):
300 table_header = [("IAT", 'iat', "[addrpad]"), 301 ("Call", 'call', "[addrpad]"), 302 ("Module", 'moduole', "20"), 303 ("Function", 'function', ""), 304 ] 305 306 if self.kernel: 307 renderer.format("Kernel Imports\n") 308 309 renderer.table_header(table_header) 310 for iat, func, mod, func_name in self.find_kernel_import(): 311 mod_name, func_name = self._original_import( 312 mod.BaseDllName, func_name) 313 314 renderer.table_row(iat, func, mod_name, func_name) 315 else: 316 for task in self.filter_processes(): 317 renderer.section() 318 renderer.format("Process {0} PID {1}\n", task.ImageFileName, 319 task.UniqueProcessId) 320 renderer.table_header(table_header) 321 322 for iat, func, mod, func_name in self.find_process_imports( 323 task): 324 mod_name, func_name = self._original_import( 325 mod.BaseDllName, func_name) 326 renderer.table_row(iat, func, mod_name, func_name)
327
328 329 -class TestImpScan(testlib.SimpleTestCase):
330 """Test the impscan module.""" 331 332 PARAMETERS = dict(commandline="impscan %(pids)s")
333