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