1from __future__ import absolute_import, division, print_function 2 3from builtins import object 4from builtins import hex 5from builtins import map 6from builtins import filter 7 8from collections import namedtuple 9import os 10import io 11import core 12from uuid import UUID 13 14from core.cvalue import ( 15 unsigned, 16 signed, 17 addressof 18) 19from core.caching import ( 20 cache_dynamically, 21 LazyTarget, 22) 23from core.io import SBProcessRawIO 24from macho import MachOSegment, MemMachO, VisualMachoMap 25 26from xnu import ( 27 IterateLinkedList, 28 lldb_alias, 29 lldb_command, 30 lldb_run_command, 31 lldb_type_summary, 32 kern, 33 Cast, 34 header, 35 GetLongestMatchOption, 36 debuglog, 37 dsymForUUID, 38 addDSYM, 39 loadDSYM, 40 ArgumentError, 41 ArgumentStringToInt, 42 GetObjectAtIndexFromArray, 43 ResolveFSPath, 44 uuid_regex 45) 46 47import kmemory 48import macho 49import lldb 50 51 52# 53# Summary of information available about a kext. 54# 55# uuid - UUID of the object 56# vmaddr - VA of the text segment 57# name - Name of the kext 58# address - Kext address 59# segments - Mach-O segments (if available) 60# summary - OSKextLoadedSummary 61# kmod - kmod_info_t 62KextSummary = namedtuple( 63 'KextSummary', 64 'uuid vmaddr name address segments summary kmod' 65) 66 67 68# Segment helpers 69 70 71def text_segment(segments): 72 """ Return TEXT segment if present in the list of first one. 73 segments: List of MachOSegment. 74 """ 75 76 text_segments = { 77 s.name: s 78 for s in segments 79 if s.name in ('__TEXT_EXEC', '__TEXT') 80 } 81 82 # Pick text segment based on our prefered order. 83 for name in ['__TEXT_EXEC', '__TEXT']: 84 if name in text_segments: 85 return text_segments[name] 86 87 return segments[0] 88 89 90def seg_contains(segments, addr): 91 """ Returns generator of all segments that contains given address. """ 92 93 return ( 94 s for s in segments 95 if s.vmaddr <= addr < (s.vmaddr + s.vmsize) 96 ) 97 98 99def sec_contains(sections, addr): 100 """ Returns generator of all sections that contains given address. """ 101 102 return ( 103 s for s in sections 104 if s.addr <= addr < (s.addr + s.size) 105 ) 106 107def sbsec_contains(target, sbsections, addr): 108 """ Returns generator of all SBSections that contains given address. """ 109 110 return ( 111 s for s in sbsections 112 if s.GetLoadAddress(target) <= addr < s.GetLoadAddress(target) + s.GetByteSize() 113 ) 114 115 116# Summary helpers 117 118def LoadMachO(address, size): 119 """ Parses Mach-O headers in given VA range. 120 121 return: MemMachO instance. 122 """ 123 124 process = LazyTarget.GetProcess() 125 procio = SBProcessRawIO(process, address, size) 126 bufio = io.BufferedRandom(procio) 127 return macho.MemMachO(bufio) 128 129 130def IterateKextSummaries(target): 131 """ Generator walking over all kext summaries. """ 132 133 hdr = target.chkFindFirstGlobalVariable('gLoadedKextSummaries').Dereference() 134 arr = hdr.GetValueForExpressionPath('.summaries[0]') 135 total = hdr.xGetIntegerByName('numSummaries') 136 137 for kext in (core.value(e.AddressOf()) for e in arr.xIterSiblings(0, total)): 138 # Load Mach-O segments/sections. 139 mobj = LoadMachO(unsigned(kext.address), unsigned(kext.size)) 140 141 # Construct kext summary. 142 yield KextSummary( 143 uuid=GetUUIDSummary(kext.uuid), 144 vmaddr=text_segment(mobj.segments).vmaddr, 145 name=str(kext.name), 146 address=unsigned(kext.address), 147 segments=mobj.segments, 148 summary=kext, 149 kmod=GetKmodWithAddr(unsigned(kext.address)) 150 ) 151 152 153@cache_dynamically 154def GetAllKextSummaries(target=None): 155 """ Return all kext summaries. (cached) """ 156 157 return list(IterateKextSummaries(target)) 158 159 160def FindKextSummary(kmod_addr): 161 """ Returns summary for given kmod_info_t. """ 162 163 for mod in GetAllKextSummaries(): 164 if mod.address == kmod_addr or mod.vmaddr == kmod_addr: 165 return mod 166 167 return None 168 169 170# Keep this around until DiskImages2 migrate over to new methods above. 171def GetKextLoadInformation(addr=0, show_progress=False): 172 """ Original wrapper kept for backwards compatibility. """ 173 if addr: 174 return [FindKextSummary(addr)] 175 else: 176 return GetAllKextSummaries() 177 178 179@lldb_command('showkextmacho') 180def ShowKextMachO(cmd_args=[]): 181 """ Show visual Mach-O layout. 182 183 Syntax: (lldb) showkextmacho <name of a kext> 184 """ 185 if len(cmd_args) != 1: 186 raise ArgumentError("kext name is missing") 187 188 for kext in GetAllKextSummaries(): 189 190 # Skip not matching kexts. 191 if kext.name.find(cmd_args[0]) == -1: 192 continue 193 194 # Load Mach-O segments/sections. 195 mobj = LoadMachO(unsigned(kext.kmod.address), unsigned(kext.kmod.size)) 196 197 p = VisualMachoMap(kext.name) 198 p.printMachoMap(mobj) 199 print(" \n") 200 201 202_UNKNOWN_UUID = "........-....-....-....-............" 203 204 205@lldb_type_summary(['uuid_t']) 206@header("") 207def GetUUIDSummary(uuid): 208 """ returns a UUID string in form CA50DA4C-CA10-3246-B8DC-93542489AA26 209 210 uuid - Address of a memory where UUID is stored. 211 """ 212 213 err = lldb.SBError() 214 addr = unsigned(addressof(uuid)) 215 data = LazyTarget.GetProcess().ReadMemory(addr, 16, err) 216 217 if not err.Success(): 218 return _UNKNOWN_UUID 219 220 return str(UUID(bytes=data)).upper() 221 222 223@lldb_type_summary(['kmod_info_t *']) 224@header(( 225 "{0: <20s} {1: <20s} {2: <20s} {3: >3s} {4: >5s} {5: <20s} {6: <20s} " 226 "{7: >20s} {8: <30s}" 227).format('kmod_info', 'address', 'size', 'id', 'refs', 'TEXT exec', 'size', 228 'version', 'name')) 229def GetKextSummary(kmod): 230 """ returns a string representation of kext information """ 231 232 format_string = ( 233 "{mod: <#020x} {mod.address: <#020x} {mod.size: <#020x} " 234 "{mod.id: >3d} {mod.reference_count: >5d} {seg.vmaddr: <#020x} " 235 "{seg.vmsize: <#020x} {mod.version: >20s} {mod.name: <30s}" 236 ) 237 238 # Try to obtain text segment from kext summary 239 summary = FindKextSummary(unsigned(kmod.address)) 240 if summary: 241 seg = text_segment(summary.segments) 242 else: 243 # Fake text segment for pseudo kexts. 244 seg = MachOSegment('__TEXT', kmod.address, kmod.size, 0, kmod.size, []) 245 246 return format_string.format(mod=kmod, seg=seg) 247 248 249def GetKmodWithAddr(addr): 250 """ Go through kmod list and find one with begin_addr as addr. 251 returns: None if not found else a cvalue of type kmod. 252 """ 253 254 for kmod in IterateLinkedList(kern.globals.kmod, 'next'): 255 if addr == unsigned(kmod.address): 256 return kmod 257 258 return None 259 260 261@lldb_command('showkmodaddr') 262def ShowKmodAddr(cmd_args=[]): 263 """ Given an address, print the offset and name for the kmod containing it 264 Syntax: (lldb) showkmodaddr <addr> 265 """ 266 if len(cmd_args) < 1: 267 raise ArgumentError("Insufficient arguments") 268 269 addr = ArgumentStringToInt(cmd_args[0]) 270 271 # Find first summary/segment pair that covers given address. 272 sumseg = ( 273 (m, next(seg_contains(m.segments, addr), None)) 274 for m in GetAllKextSummaries() 275 ) 276 277 print(GetKextSummary.header) 278 for ksum, segment in (t for t in sumseg if t[1] is not None): 279 summary = GetKextSummary(ksum.kmod) 280 print(summary + " segment: {} offset = {:#0x}".format( 281 segment.name, (addr - segment.vmaddr))) 282 283 return True 284 285 286def GetOSKextVersion(version_num): 287 """ returns a string of format 1.2.3x from the version_num 288 params: version_num - int 289 return: str 290 """ 291 if version_num == -1: 292 return "invalid" 293 294 (MAJ_MULT, MIN_MULT) = (1000000000000, 100000000) 295 (REV_MULT, STAGE_MULT) = (10000, 1000) 296 297 version = version_num 298 299 vers_major = version // MAJ_MULT 300 version = version - (vers_major * MAJ_MULT) 301 302 vers_minor = version // MIN_MULT 303 version = version - (vers_minor * MIN_MULT) 304 305 vers_revision = version // REV_MULT 306 version = version - (vers_revision * REV_MULT) 307 308 vers_stage = version // STAGE_MULT 309 version = version - (vers_stage * STAGE_MULT) 310 311 vers_stage_level = version 312 313 out_str = "%d.%d" % (vers_major, vers_minor) 314 if vers_revision > 0: 315 out_str += ".%d" % vers_revision 316 if vers_stage == 1: 317 out_str += "d%d" % vers_stage_level 318 if vers_stage == 3: 319 out_str += "a%d" % vers_stage_level 320 if vers_stage == 5: 321 out_str += "b%d" % vers_stage_level 322 if vers_stage == 6: 323 out_str += "fc%d" % vers_stage_level 324 325 return out_str 326 327 328def FindKmodNameForAddr(addr): 329 """ Given an address, return the name of the kext containing that address. 330 """ 331 332 names = ( 333 mod.kmod.name 334 for mod in GetAllKextSummaries() 335 if (any(seg_contains(mod.segments, unsigned(addr)))) 336 ) 337 338 return next(names, None) 339 340 341@lldb_command('showallkmods') 342def ShowAllKexts(cmd_args=None): 343 """ Display a summary listing of all loaded kexts (alias: showallkmods) """ 344 345 print("{: <36s} ".format("UUID") + GetKextSummary.header) 346 347 for kmod in IterateLinkedList(kern.globals.kmod, 'next'): 348 sum = FindKextSummary(unsigned(kmod.address)) 349 350 if sum: 351 _ksummary = GetKextSummary(sum.kmod) 352 uuid = sum.uuid 353 else: 354 _ksummary = GetKextSummary(kmod) 355 uuid = _UNKNOWN_UUID 356 357 print(uuid + " " + _ksummary) 358 359 360@lldb_command('showallknownkmods') 361def ShowAllKnownKexts(cmd_args=None): 362 """ Display a summary listing of all kexts known in the system. 363 This is particularly useful to find if some kext was unloaded 364 before this crash'ed state. 365 """ 366 kext_ptr = kern.globals.sKextsByID 367 kext_count = unsigned(kext_ptr.count) 368 369 print("%d kexts in sKextsByID:" % kext_count) 370 print("{0: <20s} {1: <20s} {2: >5s} {3: >20s} {4: <30s}".format('OSKEXT *', 'load_addr', 'id', 'version', 'name')) 371 format_string = "{0: <#020x} {1: <20s} {2: >5s} {3: >20s} {4: <30s}" 372 373 for kext_dict in (GetObjectAtIndexFromArray(kext_ptr.dictionary, i) 374 for i in range(kext_count)): 375 376 kext_name = str(kext_dict.key.string) 377 osk = Cast(kext_dict.value, 'OSKext *') 378 379 load_addr = "------" 380 id = "--" 381 382 if int(osk.flags.loaded): 383 load_addr = "{0: <#020x}".format(osk.kmod_info) 384 id = "{0: >5d}".format(osk.loadTag) 385 386 version_num = signed(osk.version) 387 version = GetOSKextVersion(version_num) 388 print(format_string.format(osk, load_addr, id, version, kext_name)) 389 390 391def FetchDSYM(kinfo): 392 """ Obtains and adds dSYM based on kext summary. """ 393 394 # No op for built-in modules. 395 kernel_uuid = str(kern.globals.kernel_uuid_string) 396 if kernel_uuid == kinfo.uuid: 397 print("(built-in)") 398 return 399 400 # Obtain and load binary from dSYM. 401 print("Fetching dSYM for %s" % kinfo.uuid) 402 info = dsymForUUID(kinfo.uuid) 403 if info and 'DBGSymbolRichExecutable' in info: 404 print("Adding dSYM (%s) for %s" % (kinfo.uuid, info['DBGSymbolRichExecutable'])) 405 addDSYM(kinfo.uuid, info) 406 loadDSYM(kinfo.uuid, kinfo.vmaddr, kinfo.segments) 407 else: 408 print("Failed to get symbol info for %s" % kinfo.uuid) 409 410 411def AddKextSymsByFile(filename, slide): 412 """ Add kext based on file name and slide. """ 413 sections = None 414 415 filespec = lldb.SBFileSpec(filename, False) 416 print("target modules add \"{:s}\"".format(filename)) 417 print(lldb_run_command("target modules add \"{:s}\"".format(filename))) 418 419 loaded_module = LazyTarget.GetTarget().FindModule(filespec) 420 if loaded_module.IsValid(): 421 uuid_str = loaded_module.GetUUIDString() 422 debuglog("added module {:s} with uuid {:s}".format(filename, uuid_str)) 423 424 if slide is None: 425 for k in GetAllKextSummaries(): 426 debuglog(k.uuid) 427 if k.uuid.lower() == uuid_str.lower(): 428 slide = k.vmaddr 429 sections = k.segments 430 debuglog("found the slide {:#0x} for uuid {:s}".format(k.vmaddr, k.uuid)) 431 if slide is None: 432 raise ArgumentError("Unable to find load address for module described at {:s} ".format(filename)) 433 434 if not sections: 435 cmd_str = "target modules load --file \"{:s}\" --slide {:s}".format(filename, str(slide)) 436 debuglog(cmd_str) 437 else: 438 cmd_str = "target modules load --file \"{:s}\"".format(filename) 439 for s in sections: 440 cmd_str += " {:s} {:#0x} ".format(s.name, s.vmaddr) 441 debuglog(cmd_str) 442 443 lldb.debugger.HandleCommand(cmd_str) 444 445 kern.symbolicator = None 446 return True 447 448 449def AddKextSymsByName(kextname, all=False): 450 """ Add kext based on longest name match""" 451 452 kexts = GetLongestMatchOption(kextname, [x.name for x in GetAllKextSummaries()], True) 453 if not kexts: 454 print("No matching kext found.") 455 return False 456 457 if len(kexts) != 1 and not all: 458 print("Ambiguous match for name: {:s}".format(kextname)) 459 if len(kexts) > 0: 460 print("Options are:\n\t" + "\n\t".join(kexts)) 461 return False 462 463 # Load all matching dSYMs 464 for sum in GetAllKextSummaries(): 465 if sum.name in kexts: 466 debuglog("matched the kext to name {:s} " 467 "and uuid {:s}".format(sum.name, sum.uuid)) 468 FetchDSYM(sum) 469 470 kern.symbolicator = None 471 return True 472 473 474@lldb_command('addkext', 'AF:N:') 475def AddKextSyms(cmd_args=[], cmd_options={}): 476 """ Add kext symbols into lldb. 477 This command finds symbols for a uuid and load the required executable 478 Usage: 479 addkext <uuid> : Load one kext based on uuid. eg. (lldb)addkext 4DD2344C0-4A81-3EAB-BDCF-FEAFED9EB73E 480 addkext -F <abs/path/to/executable> <load_address> : Load kext executable at specified load address 481 addkext -N <name> : Load one kext that matches the name provided. eg. (lldb) addkext -N corecrypto 482 addkext -N <name> -A: Load all kext that matches the name provided. eg. to load all kext with Apple in name do (lldb) addkext -N Apple -A 483 addkext all : Will load all the kext symbols - SLOW 484 """ 485 486 # Load kext by file name. 487 if "-F" in cmd_options: 488 exec_path = cmd_options["-F"] 489 exec_full_path = ResolveFSPath(exec_path) 490 if not os.path.exists(exec_full_path): 491 raise ArgumentError("Unable to resolve {:s}".format(exec_path)) 492 493 if not os.path.isfile(exec_full_path): 494 raise ArgumentError( 495 """Path is {:s} not a filepath. 496 Please check that path points to executable. 497 For ex. path/to/Symbols/IOUSBFamily.kext/Contents/PlugIns/AppleUSBHub.kext/Contents/MacOS/AppleUSBHub. 498 Note: LLDB does not support adding kext based on directory paths like gdb used to.""".format(exec_path)) 499 500 slide_value = None 501 if cmd_args: 502 slide_value = cmd_args[0] 503 debuglog("loading slide value from user input {:s}".format(cmd_args[0])) 504 505 return AddKextSymsByFile(exec_full_path, slide_value) 506 507 # Load kext by name. 508 if "-N" in cmd_options: 509 kext_name = cmd_options["-N"] 510 return AddKextSymsByName(kext_name, "-A" in cmd_options) 511 512 # Load kexts by UUID or "all" 513 if len(cmd_args) < 1: 514 raise ArgumentError("No arguments specified.") 515 516 kernel_uuid = str(kern.globals.kernel_uuid_string).lower() 517 uuid = cmd_args[0].lower() 518 519 load_all_kexts = (uuid == "all") 520 if not load_all_kexts and len(uuid_regex.findall(uuid)) == 0: 521 raise ArgumentError("Unknown argument {:s}".format(uuid)) 522 523 for sum in GetAllKextSummaries(): 524 cur_uuid = sum.uuid.lower() 525 if load_all_kexts or (uuid == cur_uuid): 526 if kernel_uuid != cur_uuid: 527 FetchDSYM(sum) 528 529 kern.symbolicator = None 530 return True 531 532 533@lldb_command('addkextaddr') 534def AddKextAddr(cmd_args=[]): 535 """ Given an address, load the kext which contains that address 536 Syntax: (lldb) addkextaddr <addr> 537 """ 538 if len(cmd_args) < 1: 539 raise ArgumentError("Insufficient arguments") 540 541 addr = ArgumentStringToInt(cmd_args[0]) 542 kernel_uuid = str(kern.globals.kernel_uuid_string).lower() 543 544 match = ( 545 (kinfo, seg_contains(kinfo.segments, addr)) 546 for kinfo in GetAllKextSummaries() 547 if any(seg_contains(kinfo.segments, addr)) 548 ) 549 550 # Load all kexts which contain given address. 551 print(GetKextSummary.header) 552 for kinfo, segs in match: 553 for s in segs: 554 print(GetKextSummary(kinfo.kmod) + " segment: {} offset = {:#0x}".format(s.name, (addr - s.vmaddr))) 555 FetchDSYM(kinfo) 556 557 558class KextMemoryObject(kmemory.MemoryObject): 559 """ Describes an object landing in some kext """ 560 561 MO_KIND = "kext mach-o" 562 563 def __init__(self, kmem, address, kinfo): 564 super(KextMemoryObject, self).__init__(kmem, address) 565 self.kinfo = kinfo 566 self.target = kmem.target 567 568 @property 569 def object_range(self): 570 seg = next(seg_contains(self.kinfo.segments, self.address)) 571 sec = next(sec_contains(seg.sections, self.address), None) 572 if sec: 573 return kmemory.MemoryRange(sec.addr, sec.addr + sec.size) 574 return kmemory.MemoryRange(seg.vmaddr, seg.vmaddr + seg.vmsize) 575 576 def find_mod_seg_sect(self): 577 target = self.target 578 address = self.address 579 580 return next(( 581 (module, segment, next(sbsec_contains(target, segment, address), None)) 582 for module in target.module_iter() 583 for segment in sbsec_contains(target, module.section_iter(), address) 584 ), (None, None, None)) 585 586 def describe(self, verbose=False): 587 from lldb.utils.symbolication import Symbolicator 588 589 addr = self.address 590 kinfo = self.kinfo 591 592 sbmod, sbseg, sbsec = self.find_mod_seg_sect() 593 if sbmod is None: 594 FetchDSYM(kinfo) 595 print() 596 sbmod, sbseg, sbsec = self.find_mod_seg_sect() 597 598 syms = Symbolicator.InitWithSBTarget(self.target).symbolicate(addr) 599 sym = next(iter(syms)) if syms else None 600 601 if not sbseg: 602 # not really an SBSection but we only need to pretty print 'name' 603 # which both have, yay duck typing 604 sbseg = next(seg_contains(kinfo.segments, addr), None) 605 606 fmt = "Kext Symbol Info\n" 607 fmt += " kext : {kinfo.name} ({kinfo.uuid})\n" 608 fmt += " module : {sbmod.file.basename}\n" if sbmod else "" 609 fmt += " section : {sbseg.name} {sbsec.name}\n" if sbsec else \ 610 " segment : {sbseg.name}\n" if sbseg else "" 611 fmt += " symbol : {sym!s}\n" if sym else "" 612 613 print(fmt.format(kinfo=kinfo, sbmod=sbmod, sbseg=sbseg, sbsec=sbsec, sym=sym)) 614 615 616class MainBinaryMemoryObject(kmemory.MemoryObject): 617 """ Describes an object landing in the main kernel binary """ 618 619 MO_KIND = "kernel mach-o" 620 621 def __init__(self, kmem, address, section): 622 super(MainBinaryMemoryObject, self).__init__(kmem, address) 623 self.section = section 624 self.target = kmem.target 625 626 def _subsection(self): 627 return next(sbsec_contains(self.target, self.section, self.address), None) 628 629 @property 630 def object_range(self): 631 target = self.target 632 section = self._subsection() or self.section 633 addr = section.GetLoadAddress(target) 634 size = section.GetByteSize() 635 return kmemory.MemoryRange(addr, addr + size) 636 637 @property 638 def module(self): 639 return self.target.GetModuleAtIndex(0).GetFileSpec().GetFilename() 640 641 @property 642 def uuid(self): 643 return self.target.GetModuleAtIndex(0).GetUUIDString() 644 645 def describe(self, verbose=False): 646 from lldb.utils.symbolication import Symbolicator 647 648 subsec = self._subsection() 649 syms = Symbolicator.InitWithSBTarget(self.target).symbolicate(self.address) 650 sym = next(iter(syms)) if syms else None 651 652 fmt = "Symbol Info\n" 653 fmt += " module : {mo.module}\n" 654 fmt += " uuid : {mo.uuid}\n" 655 fmt += " section : {mo.section.name} {subsec.name}\n" if subsec else "" 656 fmt += " segment : {mo.section.name}\n" if not subsec else "" 657 fmt += " symbol : {sym}\n" if sym else "" 658 659 print(fmt.format(mo=self, subsec=subsec, sym=sym)) 660 661 662@kmemory.whatis_provider 663class KextWhatisProvider(kmemory.WhatisProvider): 664 """ Kext ranges whatis provider """ 665 666 COST = 100 667 668 def claims(self, address): 669 target = self.target 670 mainmod = target.GetModuleAtIndex(0) 671 672 # 673 # TODO: surely the kexts can provide a better range check 674 # 675 676 return any( 677 sbsec_contains(target, mainmod.section_iter(), address) 678 ) or any( 679 any(seg_contains(kinfo.segments, address)) 680 for kinfo in GetAllKextSummaries() 681 ) 682 683 def lookup(self, address): 684 target = self.target 685 mainmod = target.GetModuleAtIndex(0) 686 687 section = next(sbsec_contains(target, mainmod.section_iter(), address), None) 688 689 if section: 690 return MainBinaryMemoryObject(self.kmem, address, section) 691 692 return KextMemoryObject(self.kmem, address, next( 693 kinfo 694 for kinfo in GetAllKextSummaries() 695 if any(seg_contains(kinfo.segments, address)) 696 )) 697 698 699# Aliases for backward compatibility. 700 701lldb_alias('showkmod', 'showkmodaddr') 702lldb_alias('showkext', 'showkmodaddr') 703lldb_alias('showkextaddr', 'showkmodaddr') 704lldb_alias('showallkexts', 'showallkmods') 705