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