1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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
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
140
141 start_addr = min(calls_imported.keys())
142 iat_size = min(max(calls_imported.keys()) - start_addr, 2000)
143
144
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)):
155 continue
156
157
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
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
225 task_space = task.get_process_address_space()
226 all_mods = list(task.get_load_modules())
227
228
229 apis = self._enum_apis(all_mods)
230
231
232 if not all_mods:
233 self.session.logging.error("Cannot load DLLs in process AS")
234 return
235
236
237
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
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
259
260
261 base_address = self.base
262 if base_address is None:
263 base_address = self.session.GetParameter("kernel_base")
264
265
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
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
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
333