1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 """This module guesses the current profile using various heuristics."""
21
22 __author__ = "Michael Cohen <scudette@gmail.com>"
23
24
25 import re
26
27 from rekall import addrspace
28 from rekall import cache
29 from rekall import config
30 from rekall import kb
31 from rekall import obj
32 from rekall import scan
33 from rekall_lib import registry
34 from rekall_lib import utils
35
36 from rekall.plugins.addrspaces import amd64
37 from rekall.plugins.addrspaces import intel
38 from rekall.plugins.darwin import common as darwin_common
39 from rekall.plugins.linux import common as linux_common
40 from rekall.plugins.windows import common as win_common
41 from rekall.plugins.overlays.windows import pe_vtypes
42
43
45 """A baseclass to implement autodetection methods."""
46
47 __metaclass__ = registry.MetaclassRegistry
48 name = None
49
50 order = 100
51
54
56 """Return a list of offsets we care about."""
57 return []
58
60 """Returns a list of keywords which will be searched.
61
62 Each time the keyword is matched, this instance will be called to
63 attempt detection.
64 """
65 return []
66
67 find_dtb_impl = None
68
75
110
112 """Gets called for each hit.
113
114 If a profile matches, return it, otherwise None.
115 """
116
117
118
119 config.DeclareOption("autodetect", group="Autodetection Overrides",
120 type="ChoiceArray", required=True,
121 choices=utils.JITIterator(DetectionMethod),
122 default=utils.JITIterator(DetectionMethod),
123 help="Autodetection method.")
124
125 config.DeclareOption("autodetect_threshold", default=1.0,
126 group="Autodetection Overrides",
127 help="Worst acceptable match for profile autodetection." +
128 " (Default 1.0)",
129 type="Float")
130
131 config.DeclareOption("autodetect_build_local", default="basic",
132 group="Autodetection Overrides",
133 choices=["full", "basic", "none"],
134 help="Attempts to fetch and build profile locally.",
135 type="Choices")
136
137 config.DeclareOption("autodetect_scan_length", default=2**64,
138 group="Autodetection Overrides",
139 help="How much of physical memory to scan before failing",
140 type="IntParser")
141
142
144 """Apply the windows index to detect the profile."""
145
146 find_dtb_impl = win_common.WinFindDTB
147
148 name = "nt_index"
149
155
157 """We trigger when we see some common windows processes.
158
159 Since all windows processes also map the kernel we can detect it.
160 """
161 return ["cmd.exe\x00\x00", "System\x00\x00", "csrss.exe\x00\x00",
162 "svchost.exe\x00\x00", "lsass.exe\x00\x00",
163 "winlogon.exe\x00\x00"]
164
167
169 """Verify this address space.
170
171 Checks that the _KUSER_SHARED_DATA makes sense. This structure is always
172 at a known offset since it must be shared with user space apps.
173 """
174 kuser_shared = self.eprocess_index._KUSER_SHARED_DATA(
175 offset=0xFFFFF78000000000, vm=test_as)
176
177
178 if (kuser_shared.NtMajorVersion in [5, 6, 10] and
179 kuser_shared.NtMinorVersion in [0, 1, 2, 3]):
180 return True
181
183 """Verify this address space.
184
185 Checks that the _KUSER_SHARED_DATA makes sense. This structure is always
186 at a known offset since it must be shared with user space apps.
187 """
188 kuser_shared = self.eprocess_index._KUSER_SHARED_DATA(
189 offset=0xffdf0000, vm=test_as)
190
191
192 if (kuser_shared.NtMajorVersion in [5, 6, 10] and
193 kuser_shared.NtMinorVersion in [0, 1, 2, 3]):
194 return True
195
197 """Checks the possible filename hit for a valid DTB address."""
198 for dtb_rel_offset, arch in self.eprocess_index.filename_to_dtb:
199
200 if arch == "AMD64":
201 possible_dtb = self.eprocess_index.Object(
202 "unsigned long", offset=filename_offset - dtb_rel_offset,
203 vm=address_space).v()
204
205
206
207 if not possible_dtb or possible_dtb & 0xFFF:
208 continue
209
210 test_as = amd64.AMD64PagedMemory(
211 session=self.session, base=address_space, dtb=possible_dtb)
212 if self.VerifyAMD64DTB(test_as):
213 yield test_as
214
215 elif arch == "I386":
216 possible_dtb = self.eprocess_index.Object(
217 "unsigned long", offset=filename_offset - dtb_rel_offset,
218 vm=address_space).v()
219
220
221
222 if not possible_dtb or possible_dtb & 0x1F:
223 continue
224
225
226 test_as = intel.IA32PagedMemoryPae(
227 session=self.session, base=address_space, dtb=possible_dtb)
228 if self.VerifyI386DTB(test_as):
229 yield test_as
230
242
244
245
246
247 if filename_offset == 0:
248 if (self.session.HasParameter("dtb") and
249 self.session.HasParameter("kernel_base")):
250 test_as = amd64.AMD64PagedMemory(
251 session=self.session, base=address_space,
252 dtb=self.session.GetParameter("dtb"))
253
254 if self.VerifyAMD64DTB(test_as):
255 return self._match_profile_for_kernel_base(
256 self.session.GetParameter("kernel_base"),
257 test_as)
258
259 return
260
261
262 for test_as in self.DetectWindowsDTB(filename_offset, address_space):
263
264
265 scanner = scan.MultiStringScanner(
266 address_space=test_as, needles=[
267 "This program cannot be run in DOS mode",
268 ])
269
270 if self.session.HasParameter("kernel_base"):
271 kernel_base = self.session.GetParameter("kernel_base")
272 return self._match_profile_for_kernel_base(
273 kernel_base, test_as)
274
275 for offset, _ in scanner.scan(
276 offset=0xF80000000000, maxlen=0x10000000000):
277 kernel_base = offset & 0xFFFFFFFFFFFFFF000
278 profile_obj = self._match_profile_for_kernel_base(
279 kernel_base, test_as)
280
281 if profile_obj:
282 return profile_obj
283
284
286
287 name = "pe"
288 order = 50
289
293
297
314
315
390
391
393 name = "windows_kernel_file"
394 order = 50
395
398
399 KERNEL_PATHS = [r"C:\Windows\SysNative\ntoskrnl.exe",
400 r"C:\Windows\System32\ntoskrnl.exe"]
401
427
428
430 """A kernel detector that uses live symbols to do exact matching.
431
432 LinuxIndexDetector uses kallsyms (or any other source of live symbols) to
433 match a kernel exactly by finding known-unique symbols.
434 """
435
436 name = "linux_index"
437
438
439 find_dtb_impl = linux_common.LinuxFindDTB
440
444
447
449 if offset != 0:
450 return
451
452 self.session.logging.debug(
453 "LinuxIndexDetector:DetectFromHit(%x) = %s", offset, hit)
454
455 kaslr_reader = linux_common.KAllSyms(self.session)
456
457
458
459 symbol_dict = {}
460 for offset, symbol, _, module in kaslr_reader.ObtainSymbols():
461
462 if not module:
463 symbol_dict[symbol] = offset
464
465 if not symbol_dict:
466 return
467
468 matching_profiles = self.index.LookupProfile(symbol_dict)
469 if len(matching_profiles) > 1:
470 self.session.logging.info(
471 "LinuxIndexDetector found %d matching profiles: %s",
472 len(matching_profiles),
473 ', '.join([p[0] for p in matching_profiles]))
474 return
475 elif len(matching_profiles) == 1:
476 profile_id = matching_profiles[0][0]
477 self.session.logging.info(
478 "LinuxIndexDetector found profile %s with %d/%d matches.",
479 profile_id,
480 matching_profiles[0][1],
481 len(self.index.traits[profile_id]))
482
483 profile = self.session.LoadProfile(profile_id)
484 if profile:
485
486 kallsyms_proc_banner = symbol_dict["linux_proc_banner"]
487 profile_proc_banner = profile.get_constant("linux_proc_banner",
488 is_address=False)
489 kernel_slide = kallsyms_proc_banner - profile_proc_banner
490 self.session.logging.info("Found slide 0x%x", kernel_slide)
491 self.session.SetCache("kernel_slide", kernel_slide)
492
493 verified_profile = self.VerifyProfile(profile)
494 if verified_profile:
495 return verified_profile
496 else:
497 self.session.SetCache("kernel_slide", None)
498
499
500 self.session.logging.warn("LinuxIndexDetector found no matches.")
501 self._LimitScanLength()
502
505
506
553
554
556 """Detect the Darwin version using the index.
557
558 To work around KASLR, we have an index of known symbols' offsets relative to
559 the Catfish string, along with the data we expect to find at those
560 offsets. Profile similarity is the percentage of these symbols that match as
561 expected.
562
563 Ideally, we'd like a 100% match, but in case we don't have the exact
564 profile, we'll make do with anything higher than 0% that can resolve the
565 DTB.
566 """
567 name = "osx"
568
569 find_dtb_impl = darwin_common.DarwinFindDTB
570
574
576
577
578 return ["Catfish \x00\x00"]
579
593
594
596 """A ParameterHook for default_address_space.
597
598 This will only be called if default_address_space is not set. We load the
599 kernel address space, or load it if needed.
600 """
601 name = "default_address_space"
602
603 volatile = False
604
613
614
616 """If the profile is not specified, we guess it."""
617 name = "profile_obj"
618
619 volatile = False
620
622 try:
623 self.session.SetCache("execution_phase", "ProfileAutodetect")
624 return self._ScanProfiles()
625 finally:
626 self.session.SetCache("execution_phase", None)
627
629 address_space = self.session.physical_address_space
630 best_profile = None
631 best_match = 0
632
633 methods = []
634 needles = []
635 needle_lookup = {}
636
637 method_names = self.session.GetParameter("autodetect")
638
639 self.session.logging.debug(
640 "Will detect profile using these Detectors: %s" % ",".join(
641 method_names))
642
643 if not method_names:
644 raise RuntimeError("No autodetection methods specified. "
645 "Use the --autodetect parameter.")
646
647 for method_name in method_names:
648 for method in DetectionMethod.classes_by_name[method_name]:
649 methods.append(method(session=self.session))
650
651 methods.sort(key=lambda x: x.order)
652 for method in methods:
653 for keyword in method.Keywords():
654 needles.append(keyword)
655 needle_lookup.setdefault(keyword, []).append(method)
656
657 for offset in method.Offsets():
658 self.session.logging.debug("Trying method %s, offset %d",
659 method.name, offset)
660 profile = method.DetectFromHit(None, offset, address_space)
661 if profile:
662 self.session.logging.info(
663 "Detection method %s yielded profile %s",
664 method.name, profile)
665 return profile
666
667
668 autodetect_scan_length = self.session.GetParameter(
669 "autodetect_scan_length", 10*1024*1024*1024)
670
671
672 scanner = scan.MultiStringScanner(
673 profile=obj.NoneObject(),
674 address_space=address_space, needles=needles,
675 session=self.session)
676 scanner.progress_message = "Autodetecting profile: %(offset)#08x"
677 for offset, hit in scanner.scan(maxlen=autodetect_scan_length):
678 self.session.render_progress(
679 "guess_profile: autodetection hit @ %x - %s", offset, hit)
680
681 for method in needle_lookup[hit]:
682 profile = method.DetectFromHit(hit, offset, address_space)
683 if profile:
684 self.session.logging.debug(
685 "Detection method %s worked at offset %#x",
686 method.name, offset)
687 return profile
688
689 if best_match == 1.0:
690
691 break
692
693 threshold = self.session.GetParameter("autodetect_threshold")
694 if best_match == 0:
695 self.session.logging.error(
696 "No profiles match this image. Try specifying manually.")
697
698 return obj.NoneObject("No profile detected")
699
700 elif best_match < threshold:
701 self.session.logging.error(
702 "Best match for profile is %s with %.0f%%, which is too low " +
703 "for given threshold of %.0f%%. Try lowering " +
704 "--autodetect-threshold.",
705 best_profile.name,
706 best_match * 100,
707 threshold * 100)
708
709 return obj.NoneObject("No profile detected")
710
711 else:
712 self.session.logging.info(
713 "Profile %s matched with %.0f%% confidence.",
714 best_profile.name,
715 best_match * 100)
716
717 return best_profile
718
771