1#include <CoreFoundation/CoreFoundation.h> 2#include <System/libkern/OSKextLibPrivate.h> 3#include <System/libkern/prelink.h> 4#include <IOKit/kext/OSKext.h> 5#include <IOKit/kext/OSKextPrivate.h> 6#include <IOKit/IOCFUnserialize.h> 7#include <IOKit/kext/macho_util.h> 8 9#include <architecture/byte_order.h> 10#include <errno.h> 11#include <libc.h> 12#include <mach-o/fat.h> 13#include <sys/mman.h> 14 15#include "kctool_main.h" 16#include "compression.h" 17 18/******************************************************************************* 19* Program Globals 20*******************************************************************************/ 21const char * progname = "(unknown)"; 22 23/******************************************************************************* 24*******************************************************************************/ 25int main(int argc, char * const argv[]) 26{ 27 ExitStatus result = EX_SOFTWARE; 28 KctoolArgs toolArgs; 29 int kernelcache_fd = -1; // must close() 30 void * fat_header = NULL; // must unmapFatHeaderPage() 31 struct fat_arch * fat_arch = NULL; 32 CFDataRef rawKernelcache = NULL; // must release 33 CFDataRef kernelcacheImage = NULL; // must release 34 35 void * prelinkInfoSect = NULL; 36 37 const char * prelinkInfoBytes = NULL; 38 CFPropertyListRef prelinkInfoPlist = NULL; // must release 39 40 bzero(&toolArgs, sizeof(toolArgs)); 41 42 /***** 43 * Find out what the program was invoked as. 44 */ 45 progname = rindex(argv[0], '/'); 46 if (progname) { 47 progname++; // go past the '/' 48 } else { 49 progname = (char *)argv[0]; 50 } 51 52 /* Set the OSKext log callback right away. 53 */ 54 OSKextSetLogOutputFunction(&tool_log); 55 56 /***** 57 * Process args & check for permission to load. 58 */ 59 result = readArgs(&argc, &argv, &toolArgs); 60 if (result != EX_OK) { 61 if (result == kKctoolExitHelp) { 62 result = EX_OK; 63 } 64 goto finish; 65 } 66 67 kernelcache_fd = open(toolArgs.kernelcachePath, O_RDONLY); 68 if (kernelcache_fd == -1) { 69 OSKextLog(/* kext */ NULL, 70 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 71 "Can't open %s: %s.", toolArgs.kernelcachePath, strerror(errno)); 72 result = EX_OSERR; 73 goto finish; 74 } 75 fat_header = mapAndSwapFatHeaderPage(kernelcache_fd); 76 if (!fat_header) { 77 OSKextLog(/* kext */ NULL, 78 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 79 "Can't map %s: %s.", toolArgs.kernelcachePath, strerror(errno)); 80 result = EX_OSERR; 81 goto finish; 82 } 83 84 fat_arch = getFirstFatArch(fat_header); 85 if (fat_arch && !toolArgs.archInfo) { 86 toolArgs.archInfo = NXGetArchInfoFromCpuType(fat_arch->cputype, fat_arch->cpusubtype); 87 } 88 89 rawKernelcache = readMachOSliceForArch(toolArgs.kernelcachePath, toolArgs.archInfo, 90 /* checkArch */ FALSE); 91 if (!rawKernelcache) { 92 OSKextLog(/* kext */ NULL, 93 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 94 "Can't read arch %s from %s.", toolArgs.archInfo->name, toolArgs.kernelcachePath); 95 goto finish; 96 } 97 98 if (MAGIC32(CFDataGetBytePtr(rawKernelcache)) == OSSwapHostToBigInt32('comp')) { 99 kernelcacheImage = uncompressPrelinkedSlice(rawKernelcache); 100 if (!kernelcacheImage) { 101 OSKextLog(/* kext */ NULL, 102 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 103 "Can't uncompress kernelcache slice."); 104 goto finish; 105 } 106 } else { 107 kernelcacheImage = CFRetain(rawKernelcache); 108 } 109 110 toolArgs.kernelcacheImageBytes = CFDataGetBytePtr(kernelcacheImage); 111 112 if (ISMACHO64(MAGIC32(toolArgs.kernelcacheImageBytes))) { 113 prelinkInfoSect = (void *)macho_get_section_by_name_64( 114 (struct mach_header_64 *)toolArgs.kernelcacheImageBytes, 115 "__PRELINK_INFO", "__info"); 116 117 } else { 118 prelinkInfoSect = (void *)macho_get_section_by_name( 119 (struct mach_header *)toolArgs.kernelcacheImageBytes, 120 "__PRELINK_INFO", "__info"); 121 } 122 123 if (!prelinkInfoSect) { 124 OSKextLog(/* kext */ NULL, 125 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 126 "Can't find prelink info section."); 127 goto finish; 128 } 129 130 if (ISMACHO64(MAGIC32(toolArgs.kernelcacheImageBytes))) { 131 prelinkInfoBytes = ((char *)toolArgs.kernelcacheImageBytes) + 132 ((struct section_64 *)prelinkInfoSect)->offset; 133 } else { 134 prelinkInfoBytes = ((char *)toolArgs.kernelcacheImageBytes) + 135 ((struct section *)prelinkInfoSect)->offset; 136 } 137 138 toolArgs.kernelcacheInfoPlist = (CFPropertyListRef)IOCFUnserialize(prelinkInfoBytes, 139 kCFAllocatorDefault, /* options */ 0, /* errorString */ NULL); 140 if (!toolArgs.kernelcacheInfoPlist) { 141 OSKextLog(/* kext */ NULL, 142 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 143 "Can't unserialize prelink info."); 144 goto finish; 145 } 146 147 result = printKextInfo(&toolArgs); 148 if (result != EX_OK) { 149 goto finish; 150 } 151 152 result = EX_OK; 153 154finish: 155 156 SAFE_RELEASE(toolArgs.kernelcacheInfoPlist); 157 SAFE_RELEASE(kernelcacheImage); 158 SAFE_RELEASE(rawKernelcache); 159 160 if (fat_header) { 161 unmapFatHeaderPage(fat_header); 162 } 163 164 if (kernelcache_fd != -1) { 165 close(kernelcache_fd); 166 } 167 return result; 168} 169 170/******************************************************************************* 171*******************************************************************************/ 172ExitStatus readArgs( 173 int * argc, 174 char * const ** argv, 175 KctoolArgs * toolArgs) 176{ 177 ExitStatus result = EX_USAGE; 178 ExitStatus scratchResult = EX_USAGE; 179 int optchar = 0; 180 int longindex = -1; 181 182 bzero(toolArgs, sizeof(*toolArgs)); 183 184 /***** 185 * Process command line arguments. 186 */ 187 while ((optchar = getopt_long_only(*argc, *argv, 188 kOptChars, sOptInfo, &longindex)) != -1) { 189 190 switch (optchar) { 191 192 case kOptArch: 193 toolArgs->archInfo = NXGetArchInfoFromName(optarg); 194 if (!toolArgs->archInfo) { 195 OSKextLog(/* kext */ NULL, 196 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 197 "Unknown architecture %s.", optarg); 198 goto finish; 199 } 200 break; 201 202 case kOptHelp: 203 usage(kUsageLevelFull); 204 result = kKctoolExitHelp; 205 goto finish; 206 207 default: 208 OSKextLog(/* kext */ NULL, 209 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 210 "unrecognized option %s", (*argv)[optind-1]); 211 goto finish; 212 break; 213 214 } 215 216 /* Reset longindex, because getopt_long_only() is stupid and doesn't. 217 */ 218 longindex = -1; 219 } 220 221 /* Update the argc & argv seen by main() so that boot<>root calls 222 * handle remaining args. 223 */ 224 *argc -= optind; 225 *argv += optind; 226 227 if (*argc != 4) { 228 OSKextLog(/* kext */ NULL, 229 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 230 "incorrect number of arguments"); 231 goto finish; 232 } 233 234 /***** 235 * Record remaining args from the command line. 236 */ 237 toolArgs->kernelcachePath = (*argv)[0]; 238 toolArgs->kextID = CFStringCreateWithCString(kCFAllocatorDefault, (*argv)[1], kCFStringEncodingUTF8); 239 if (!toolArgs->kextID) { 240 OSKextLogMemError(); 241 result = EX_OSERR; 242 goto finish; 243 } 244 toolArgs->segmentName = (*argv)[2]; 245 toolArgs->sectionName = (*argv)[3]; 246 247 result = EX_OK; 248 249finish: 250 if (result == EX_USAGE) { 251 usage(kUsageLevelBrief); 252 } 253 254 return result; 255} 256 257/******************************************************************************* 258 *******************************************************************************/ 259static struct section * 260getSectionByName(const UInt8 *file, const char *segname, const char *sectname) 261{ 262 struct mach_header *machHeader; 263 struct load_command *cmdHeader; 264 struct segment_command *segmentHeader; 265 struct section *sectionHeader; 266 u_long offset; 267 u_int i, j; 268 269 machHeader = (struct mach_header *) file; 270 if (machHeader->magic != MH_MAGIC) return NULL; 271 272 offset = sizeof(*machHeader); 273 for (i = 0; i < machHeader->ncmds; ++i, offset+=cmdHeader->cmdsize) { 274 cmdHeader = (struct load_command *) (file + offset); 275 if (cmdHeader->cmd != LC_SEGMENT) continue; 276 277 segmentHeader = (struct segment_command *)cmdHeader; 278 sectionHeader = (struct section *) (file + offset + sizeof(*segmentHeader)); 279 for (j = 0; j < segmentHeader->nsects; ++j, ++sectionHeader) { 280 if (!strncmp(sectionHeader->segname, segname, sizeof(sectionHeader->segname)) && 281 !strncmp(sectionHeader->sectname, sectname, sizeof(sectionHeader->sectname))) 282 { 283 return sectionHeader; 284 } 285 } 286 } 287 288 return NULL; 289} 290 291/******************************************************************************* 292*******************************************************************************/ 293ExitStatus printKextInfo(KctoolArgs * toolArgs) 294{ 295 ExitStatus result = EX_SOFTWARE; 296 CFArrayRef kextPlistArray = NULL; 297 CFIndex i, count; 298 299 if (CFArrayGetTypeID() == CFGetTypeID(toolArgs->kernelcacheInfoPlist)) { 300 kextPlistArray = (CFArrayRef)toolArgs->kernelcacheInfoPlist; 301 } else if (CFDictionaryGetTypeID() == CFGetTypeID(toolArgs->kernelcacheInfoPlist)){ 302 kextPlistArray = (CFArrayRef)CFDictionaryGetValue(toolArgs->kernelcacheInfoPlist, 303 CFSTR("_PrelinkInfoDictionary")); 304 } else { 305 OSKextLog(/* kext */ NULL, 306 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 307 "Unrecognized kernelcache plist data."); 308 goto finish; 309 } 310 311 count = CFArrayGetCount(kextPlistArray); 312 for (i = 0; i < count; i++) { 313 CFDictionaryRef kextInfoDict = (CFDictionaryRef)CFArrayGetValueAtIndex(kextPlistArray, i); 314 CFStringRef thisKextID = CFDictionaryGetValue(kextInfoDict, kCFBundleIdentifierKey); 315 316 if (thisKextID && CFEqual(thisKextID, toolArgs->kextID)) { 317 uint64_t kextAddr = 0; 318 uint64_t kextSize = 0; 319 u_long kextOffset = 0; 320 const UInt8 * kextMachO = NULL; // do not free 321 void * section = NULL; // do not free 322 323 if (!getKextAddressAndSize(kextInfoDict, &kextAddr, &kextSize)) { 324 goto finish; 325 } 326 327 if (ISMACHO64(MAGIC32(toolArgs->kernelcacheImageBytes))) { 328 section = (void *)macho_get_section_by_name_64( 329 (struct mach_header_64 *)toolArgs->kernelcacheImageBytes, 330 kPrelinkTextSegment, kPrelinkTextSection); 331 332 } else { 333 section = (void *)macho_get_section_by_name( 334 (struct mach_header *)toolArgs->kernelcacheImageBytes, 335 kPrelinkTextSegment, kPrelinkTextSection); 336 } 337 338 if (!section) { 339 OSKextLog(/* kext */ NULL, 340 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 341 "Cannot find %s,%s in kernelcache.", 342 kPrelinkTextSegment, kPrelinkTextSection); 343 goto finish; 344 } 345 346 if (ISMACHO64(MAGIC32(toolArgs->kernelcacheImageBytes))) { 347 kextOffset = ((struct section_64 *)section)->offset + (u_long)(kextAddr - ((struct section_64 *)section)->addr); 348 } else { 349 kextOffset = ((struct section *)section)->offset + (u_long)(kextAddr - ((struct section *)section)->addr); 350 } 351 kextMachO = toolArgs->kernelcacheImageBytes + kextOffset; 352 353 /* Find the requested section's file offset and size */ 354 355 if (ISMACHO64(MAGIC32(toolArgs->kernelcacheImageBytes))) { 356 section = (void *)macho_get_section_by_name_64( 357 (struct mach_header_64 *)kextMachO, 358 toolArgs->segmentName, toolArgs->sectionName); 359 360 } else { 361 /* macho_get_section_by_name doesn't work as the kexts don't have a __TEXT segment. 362 * They just have a single segment named "" with all the sections dumped under it. 363 */ 364 section = (void *)getSectionByName( 365 kextMachO, 366 toolArgs->segmentName, toolArgs->sectionName); 367 } 368 369 if (!section) { 370 OSKextLogCFString(/* kext */ NULL, 371 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 372 CFSTR("Cannot find %s,%s in kext %@\n"), 373 toolArgs->segmentName, toolArgs->sectionName, toolArgs->kextID); 374 goto finish; 375 } 376 377 if (ISMACHO64(MAGIC32(toolArgs->kernelcacheImageBytes))) { 378 printf("%#llx %#lx %#llx\n", 379 ((struct section_64 *)section)->addr, 380 kextOffset + ((struct section_64 *)section)->offset, 381 ((struct section_64 *)section)->size); 382 } else { 383 printf("%#x %#lx %#x\n", 384 ((struct section *)section)->addr, 385 kextOffset + ((struct section *)section)->offset, 386 ((struct section *)section)->size); 387 } 388 389 result = EX_OK; 390 break; 391 } 392 } 393 394finish: 395 return result; 396} 397 398/******************************************************************************* 399*******************************************************************************/ 400Boolean getKextAddressAndSize(CFDictionaryRef infoDict, uint64_t *addr, uint64_t *size) 401{ 402 Boolean result = FALSE; 403 CFNumberRef scratchNum = NULL; // do not release 404 405 scratchNum = CFDictionaryGetValue(infoDict, CFSTR(kPrelinkExecutableSourceKey)); 406 if (!scratchNum) { 407 OSKextLog(/* kext */ NULL, 408 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 409 "Cannot find kext load address"); 410 goto finish; 411 } 412 413 if (!CFNumberGetValue(scratchNum, kCFNumberSInt64Type, addr)) { 414 OSKextLog(/* kext */ NULL, 415 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 416 "Cannot convert kext load address"); 417 goto finish; 418 } 419 420 scratchNum = CFDictionaryGetValue(infoDict, CFSTR(kPrelinkExecutableSizeKey)); 421 if (!scratchNum) { 422 OSKextLog(/* kext */ NULL, 423 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 424 "Cannot find kext size\n"); 425 goto finish; 426 } 427 428 if (!CFNumberGetValue(scratchNum, kCFNumberSInt64Type, size)) { 429 OSKextLog(/* kext */ NULL, 430 kOSKextLogErrorLevel | kOSKextLogGeneralFlag, 431 "Cannot convert kext size\n"); 432 goto finish; 433 } 434 435 result = TRUE; 436 437finish: 438 return result; 439} 440 441/******************************************************************************* 442* usage() 443*******************************************************************************/ 444void usage(UsageLevel usageLevel) 445{ 446 fprintf(stderr, 447 "usage: %1$s [-arch archname] [--] kernelcache bundle-id segment section\n" 448 "usage: %1$s -help\n" 449 "\n", 450 progname); 451 452 if (usageLevel == kUsageLevelBrief) { 453 fprintf(stderr, "use %s -%s for an explanation of each option\n", 454 progname, kOptNameHelp); 455 } 456 457 if (usageLevel == kUsageLevelBrief) { 458 return; 459 } 460 461 fprintf(stderr, "-%s <archname>:\n" 462 " list info for architecture <archname>\n", 463 kOptNameArch); 464 fprintf(stderr, "\n"); 465 466 fprintf(stderr, "-%s (-%c): print this message and exit\n", 467 kOptNameHelp, kOptHelp); 468 469 return; 470} 471