1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 import struct
31
32 from rekall import testlib
33
34 from rekall.plugins.windows import common
35 from rekall.plugins.overlays.windows import pe_vtypes
36 from rekall_lib import utils
37
38
40 """Raised when unable to decode an instruction."""
41
42
44 """A Hook heuristic detects possible hooks.
45
46 This heuristic emulates some common CPU instructions to try and detect
47 control flow jumps within the first few instructions of a function.
48
49 These are essentially guesses based on the most common hook types. Be aware
50 that these are pretty easy to defeat which will cause the hook to be missed.
51
52 See rekall/src/hooks/amd64.asm and rekall/src/hooks/i386.asm For the test
53 cases which illustrate the type of hooks that we will detect.
54 """
55
59
61
62 self.regs = {}
63 self.stack = []
64 self.memory = {}
65
67 if operand["type"] == "REG":
68 self.regs[operand["reg"]] = value
69
70 elif operand["type"] == "IMM":
71 self.memory[operand["address"]] = value
72
73 elif operand["type"] == "MEM":
74 self.memory[self._get_mem_operand_target(operand)] = value
75
76 else:
77 raise DecodingError("Operand not supported")
78
80 """Read the operand.
81
82 We support the following forms:
83
84 - Immediate (IMM): JMP 0x123456
85 - Absolute Memory Address (MEM): JMP [0x123456]
86 - Register (REG): JMP [EAX]
87 """
88
89 if operand["type"] == 'REG':
90 return self.regs.get(operand["reg"], 0)
91
92
93 elif operand["type"] == 'IMM':
94 return operand["address"]
95
96
97 elif operand["type"] == "MEM":
98 return self._GetMemoryAddress(
99 self._get_mem_operand_target(operand), operand["size"])
100
101 else:
102 raise DecodingError("Operand not supported")
103
105 reg_base = operand["base"]
106 if reg_base == "RIP":
107 return operand["address"]
108 else:
109
110 return (self.regs.get(reg_base, 0) +
111 operand["disp"] +
112 self.regs.get(operand["index"], 0) * operand["scale"])
113
115 try:
116
117 return self.memory[offset]
118 except KeyError:
119 data = self.address_space.read(offset, size)
120 format_string = {1: "b", 2: "H", 4: "I", 8: "Q"}[size]
121
122 return struct.unpack(format_string, data)[0]
123
125 """Copies the address from the second operand to the first."""
126 operand = instruction.operands[1]
127 if operand["type"] == 'MEM':
128 self.WriteToOperand(instruction.operands[0],
129 self._get_mem_operand_target(operand))
130 else:
131 raise DecodingError("Invalid LEA source.")
132
137
139 try:
140 value = self.stack.pop(-1)
141 except IndexError:
142 value = 0
143
144 self.WriteToOperand(instruction.operands[0], value)
145
147 if self.stack:
148 return self.stack.pop(-1)
149
154
159
164
166 """We dont do anything with the comparison since we dont test for it."""
167 _ = instruction
168
170 """We dont do anything with the comparison since we dont test for it."""
171 _ = instruction
172
173 - def _Operate(self, instruction, operator):
179
181 return self._Operate(instruction, lambda x, y: x ^ y)
182
184 return self._Operate(instruction, lambda x, y: x + y)
185
187 return self._Operate(instruction, lambda x, y: x - y)
188
190 return self._Operate(instruction, lambda x, y: x & y)
191
193 return self._Operate(instruction, lambda x, y: x | y)
194
196 return self._Operate(instruction, lambda x, y: x << (y % 0xFF))
197
199 return self._Operate(instruction, lambda x, y: x >> (y % 0xFF))
200
201 - def Inspect(self, function, instructions=10):
202 """The main entry point to the Hook processor.
203
204 We emulate the function instructions and try to determine the jump
205 destination.
206
207 Args:
208 function: A basic.Function() instance.
209 """
210 self.Reset()
211 self.address_space = function.obj_vm
212
213 for instruction in function.disassemble(instructions=instructions):
214 if instruction.is_return():
215
216 return self.process_ret(instruction)
217
218 elif instruction.mnemonic == "call":
219 return self.ReadFromOperand(instruction.operands[0])
220
221
222 elif instruction.is_branch():
223 return self.ReadFromOperand(instruction.operands[0])
224
225 else:
226 try:
227 handler = getattr(self, "process_%s" % instruction.mnemonic)
228 except AttributeError:
229 continue
230
231
232 try:
233 handler(instruction)
234 except Exception:
235 self.session.logging.error(
236 "Unable to handle instruction %s", instruction.op_str)
237 return
238
239
241 """Checks a pe file mapped into memory for hooks."""
242
243 name = "check_pehooks"
244
245 __args = [
246 dict(name="image_base", default=0,
247 positional=True, type="SymbolAddress",
248 help="The base address of the pe image in memory."),
249
250 dict(name="type", default="all",
251 choices=["all", "iat", "inline", "eat"],
252 type="Choice", help="Type of hook to display."),
253
254 dict(name="thorough", default=False, type="Boolean",
255 help="By default we take some optimization. This flags forces "
256 "thorough but slower checks."),
257 ]
258
259 table_header = [
260 dict(name="Type", width=10),
261 dict(name="source", width=20),
262 dict(name="target", width=20),
263 dict(name="source_func", width=60),
264 dict(name="target_func"),
265 ]
266
268 """Determines if the address should be reported.
269
270 This assesses the destination address for suspiciousness. For example if
271 the address resides in a VAD region which is not mapped by a dll then it
272 might be suspicious.
273 """
274 destination_names = self.session.address_resolver.format_address(
275 address)
276
277
278
279
280 destination = hex(address)
281 for destination in destination_names:
282 if not destination.startswith("vad_"):
283 return False
284
285 return destination
286
288 """Detect Import Address Table hooks.
289
290 An IAT hook is where malware changes the IAT entry for a dll after its
291 loaded so that when it is called from within the DLL, flow control is
292 directed to the malware instead.
293
294 We determine the IAT entry is hooked if the address is outside the dll
295 which is imported.
296 """
297 pe = pe_vtypes.PE(image_base=self.plugin_args.image_base,
298 session=self.session)
299
300
301 imports = [
302 (dll, func_name) for dll, func_name, _ in pe.ImportDirectory()]
303
304 resolver = self.session.address_resolver
305
306 for idx, (dll, func_address, _) in enumerate(pe.IAT()):
307
308 try:
309 target_dll, target_func_name = imports[idx]
310 target_dll = self.session.address_resolver.NormalizeModuleName(
311 target_dll)
312 except IndexError:
313
314
315
316 target_dll = dll
317 target_func_name = ""
318
319 self.session.report_progress(
320 "Checking function %s!%s", target_dll, target_func_name)
321
322
323 module = resolver.GetContainingModule(func_address)
324 if module and target_dll == module.name:
325 continue
326
327
328 if not len(target_func_name):
329 target_func_name = "(%s)" % idx
330
331 function_name = "%s!%s" % (target_dll, target_func_name)
332
333
334 yield function_name, func_address
335
345
347 """Detect Export Address Table hooks.
348
349 An EAT hook is where malware changes the EAT entry for a dll after its
350 loaded so that a new DLL wants to link against it, the new DLL will use
351 the malware's function instead of the exporting DLL's function.
352
353 We determine the EAT entry is hooked if the address lies outside the
354 exporting dll.
355 """
356 address_space = self.session.GetParameter("default_address_space")
357 pe = pe_vtypes.PE(image_base=self.plugin_args.image_base,
358 session=self.session,
359 address_space=address_space)
360 start = self.plugin_args.image_base
361 end = self.plugin_args.image_base + size
362
363
364 if not size:
365 for _, _, virtual_address, section_size in pe.Sections():
366
367 section_end = (self.plugin_args.image_base +
368 virtual_address + section_size)
369 if section_end > end:
370 end = section_end
371
372 resolver = self.session.address_resolver
373
374 for dll, func, name, hint in pe.ExportDirectory():
375 self.session.report_progress("Checking export %s!%s", dll, name)
376
377
378 if address_space.read(func.v(), 10) == "\x00" * 10:
379 continue
380
381
382 if start < func.v() < end:
383 continue
384
385 function_name = "%s:%s (%s)" % (
386 resolver.NormalizeModuleName(dll), name, hint)
387
388 yield function_name, func
389
399
401 """A Generator of hooked exported functions from this PE file.
402
403 Yields:
404 A tuple of (function, name, jump_destination)
405 """
406
407 pe = pe_vtypes.PE(image_base=self.plugin_args.image_base,
408 address_space=self.session.GetParameter(
409 "default_address_space"),
410 session=self.session)
411 pfn_db = self.session.profile.get_constant_object("MmPfnDatabase")
412 heuristic = HookHeuristic(session=self.session)
413
414 ok_pages = set()
415
416 for _, function, name, _ in pe.ExportDirectory():
417
418 function_address = function.deref().obj_offset
419
420 self.session.report_progress(
421 "Checking function %#x (%s)", function, name)
422
423
424
425
426
427
428 if not self.plugin_args.thorough:
429
430
431 phys_address = function.obj_vm.vtop(function_address)
432
433
434 if phys_address == None:
435 continue
436
437 phys_page = phys_address >> 12
438
439
440 if phys_page in ok_pages:
441 continue
442
443
444 pfn_obj = pfn_db[phys_page]
445
446
447
448 if pfn_obj.IsPrototype:
449 ok_pages.add(phys_page)
450 continue
451
452
453 destination = heuristic.Inspect(function, instructions=3) or ""
454
455
456 if destination:
457 yield function, name, destination
458
460 for function, _, destination in self.detect_inline_hooks():
461 hook_detected = False
462
463
464 destination_name = self.reported_access(destination)
465
466
467
468
469 if destination_name:
470 hook_detected = True
471
472
473 if self.plugin_args.verbosity < 10 and not hook_detected:
474 continue
475
476
477 highlight = ""
478 if hook_detected and self.plugin_args.verbosity > 1:
479 highlight = "important"
480
481 yield dict(Type="Inline",
482 source=utils.FormattedAddress(
483 self.session.address_resolver,
484 function.deref(), max_count=1),
485 target=utils.FormattedAddress(
486 self.session.address_resolver,
487 destination, max_count=1),
488 source_func=function.deref(),
489 target_func=self.session.profile.Function(
490 destination),
491 highlight=highlight)
492
505
506
507 -class EATHooks(common.WinProcessFilter):
508 """Detect EAT hooks in process and kernel memory"""
509
510 name = "hooks_eat"
511
512 table_header = [
513 dict(name="divider", type="Divider"),
514 dict(name="_EPROCESS", hidden=True),
515 dict(name="Type", hidden=True),
516 dict(name="source", width=20),
517 dict(name="target", width=20),
518 dict(name="target_func"),
519 ]
520
521 checker_method = CheckPEHooks.collect_eat_hooks
522
528
536
548
549
556
557
563
564
571
572
574 """Detect API hooks in process and kernel memory"""
575
576 name = "hooks_inline"
577 checker_method = CheckPEHooks.collect_inline_hooks
578 table_header = [
579 dict(name="divider", type="Divider"),
580 dict(name="_EPROCESS", hidden=True),
581 dict(name="source", width=20),
582 dict(name="target", width=20),
583 dict(name="Type", hidden=True),
584 dict(name="source_func", width=60),
585 dict(name="target_func"),
586 ]
587
588
595