static int __fat_iterator_init( struct __fat_iterator * iter, const void * file_data, const void * file_end, int macho_only) { int result = -1; size_t length = file_end - file_data; uint32_t magic; if (length < sizeof(magic)) { goto finish; } iter->file_start = (void *)file_data; iter->file_end = (void *)file_end; magic = MAGIC32(file_data); if (ISFAT(magic)) { void * arches_end; if (length < sizeof(struct fat_header)) { goto finish; } iter->fat_header = (struct fat_header *)file_data; iter->fat_arches = (struct fat_arch *)((char *)iter->fat_header + sizeof(struct fat_header)); iter->num_arches = OSSwapBigToHostInt32( iter->fat_header->nfat_arch); arches_end = (void *)iter->fat_arches + (iter->num_arches * sizeof(struct fat_arch)); if (arches_end > iter->file_end) { goto finish; } iter->iterable = 1; } else if (ISMACHO(magic)) { if (length < sizeof(struct mach_header)) { goto finish; } iter->iterable = 1; iter->num_arches = 1; iter->arch_index = 0; } else if (macho_only) { goto finish; } result = 0; finish: return result; }
void * macho_find_section_numbered( const void * file_start, const void * file_end, uint8_t sect_num) { _sect_scan sect_data; bzero(§_data, sizeof(sect_data)); sect_data.sect_num = sect_num; if (ISMACHO64(MAGIC32(file_start))) { sect_data.sixtyfourbit = 1; } if (macho_seek_result_found == macho_scan_load_commands( file_start, file_end, &__macho_sect_in_lc, §_data)) { return sect_data.sect_info; } return NULL; }
int fat_iterator_find_fat_arch( fat_iterator iter, cpu_type_t cputype, cpu_subtype_t cpusubtype, struct fat_arch * fat_arch_out) { int result = 0; uint32_t magic; uint32_t nfat_arch; struct fat_arch * fat_arches; struct fat_arch * fat_arches_copy = NULL; // must free struct fat_arch * found_arch; magic = MAGIC32(iter->file_start); if (iter->fat_header) { uint32_t fat_arches_size; uint32_t index; nfat_arch = iter->num_arches; fat_arches_size = nfat_arch * sizeof(struct fat_arch); fat_arches_copy = (struct fat_arch *)(malloc(fat_arches_size)); if (!fat_arches_copy) { goto finish; } fat_arches = fat_arches_copy; memcpy(fat_arches, iter->fat_arches, fat_arches_size); /* NXFindBestFatArch() requires all the fat info to be in host * byte order. */ for (index = 0; index < nfat_arch; index++) { fat_arches[index].cputype = OSSwapBigToHostInt32(fat_arches[index].cputype); fat_arches[index].cpusubtype = OSSwapBigToHostInt32(fat_arches[index].cpusubtype); fat_arches[index].offset = OSSwapBigToHostInt32(fat_arches[index].offset); fat_arches[index].size = OSSwapBigToHostInt32(fat_arches[index].size); fat_arches[index].align = OSSwapBigToHostInt32(fat_arches[index].align); } } else { struct fat_arch fake_fat_arches; uint8_t swap; struct mach_header * mach_hdr; nfat_arch = 1; bzero(&fake_fat_arches, sizeof(fake_fat_arches)); fat_arches = &fake_fat_arches; swap = ISSWAPPEDMACHO(magic); mach_hdr = (struct mach_header *)iter->file_start; fat_arches[0].cputype = CondSwapInt32(swap, mach_hdr->cputype); fat_arches[0].cpusubtype = CondSwapInt32(swap, mach_hdr->cpusubtype); fat_arches[0].offset = 0; fat_arches[0].size = iter->file_end - iter->file_start; fat_arches[0].align = 1; // not used anyhow } found_arch = NXFindBestFatArch(cputype, cpusubtype, fat_arches, nfat_arch); if (found_arch) { result = 1; if (fat_arch_out) { memcpy(fat_arch_out, found_arch, sizeof(*fat_arch_out)); } } finish: if (fat_arches_copy) { free(fat_arches_copy); } return result; }
macho_seek_result macho_scan_load_commands( const void * file_start, const void * file_end, macho_lc_callback lc_callback, void * user_data) { macho_seek_result result = macho_seek_result_not_found; struct mach_header * mach_header = (struct mach_header *)file_start; uint8_t swap = 0; uint32_t cmdsize_mult = CMDSIZE_MULT_32; uint32_t num_cmds; uint32_t sizeofcmds; char * cmds_end; uint32_t cmd_index; struct load_command * load_commands; struct load_command * seek_lc; switch (MAGIC32(file_start)) { case MH_MAGIC_64: cmdsize_mult = CMDSIZE_MULT_64; break; case MH_CIGAM_64: cmdsize_mult = CMDSIZE_MULT_64; swap = 1; break; case MH_CIGAM: swap = 1; break; case MH_MAGIC: break; default: result = macho_seek_result_error; goto finish; break; } if (cmdsize_mult == CMDSIZE_MULT_64) { load_commands = (struct load_command *) (file_start + sizeof(struct mach_header_64)); } else { load_commands = (struct load_command *) (file_start + sizeof(struct mach_header)); } if (file_start >= file_end || (((void *)load_commands) > file_end)) { result = macho_seek_result_error; goto finish; } num_cmds = CondSwapInt32(swap, mach_header->ncmds); sizeofcmds = CondSwapInt32(swap, mach_header->sizeofcmds); cmds_end = (char *)load_commands + sizeofcmds; if (cmds_end > (char *)file_end) { result = macho_seek_result_error; goto finish; } seek_lc = load_commands; for (cmd_index = 0; cmd_index < num_cmds; cmd_index++) { uint32_t cmd_size; char * lc_end; cmd_size = CondSwapInt32(swap, seek_lc->cmdsize); lc_end = (char *)seek_lc + cmd_size; if ((cmd_size % cmdsize_mult != 0) || (lc_end > cmds_end)) { result = macho_seek_result_error; goto finish; } result = lc_callback(seek_lc, file_end, swap, user_data); switch (result) { case macho_seek_result_not_found: /* Not found, keep scanning. */ break; case macho_seek_result_stop: /* Definitely found that it isn't there. */ result = macho_seek_result_not_found; goto finish; break; case macho_seek_result_found: /* Found it! */ goto finish; break; default: /* Error, fall through default case. */ result = macho_seek_result_error; goto finish; break; } seek_lc = (struct load_command *)((char *)seek_lc + cmd_size); } finish: return result; }
macho_seek_result macho_find_symbol( const void * file_start, const void * file_end, const char * name, uint8_t * nlist_type, const void ** symbol_address) { macho_seek_result result = macho_seek_result_not_found; macho_seek_result symtab_result = macho_seek_result_not_found; uint8_t swap = 0; char sixtyfourbit = 0; struct symtab_command * symtab = NULL; struct nlist * syms_address; struct nlist_64 * syms_address_64; const void * string_list; char * symbol_name; unsigned int symtab_offset; unsigned int str_offset; unsigned int num_syms; unsigned int syms_bytes; unsigned int sym_index; if (symbol_address) { *symbol_address = 0; } symtab_result = macho_find_symtab(file_start, file_end, &symtab); if (symtab_result != macho_seek_result_found) { goto finish; } if (ISSWAPPEDMACHO(MAGIC32(file_start))) { swap = 1; } if (ISMACHO64(MAGIC32(file_start))) { sixtyfourbit = 1; } symtab_offset = CondSwapInt32(swap, symtab->symoff); str_offset = CondSwapInt32(swap, symtab->stroff); num_syms = CondSwapInt32(swap, symtab->nsyms); syms_address = (struct nlist *)(file_start + symtab_offset); syms_address_64 = (struct nlist_64 *)(file_start + symtab_offset); string_list = file_start + str_offset; if (sixtyfourbit) { syms_bytes = num_syms * sizeof(struct nlist_64); } else { syms_bytes = num_syms * sizeof(struct nlist); } if ((char *)syms_address + syms_bytes > (char *)file_end) { result = macho_seek_result_error; goto finish; } for (sym_index = 0; sym_index < num_syms; sym_index++) { struct nlist * seekptr; struct nlist_64 * seekptr_64; uint32_t string_index; uint8_t n_type; uint8_t n_sect; uint64_t n_value; if (sixtyfourbit) { seekptr_64 = &syms_address_64[sym_index]; string_index = CondSwapInt32(swap, seekptr_64->n_un.n_strx); n_type = seekptr_64->n_type; n_sect = seekptr_64->n_sect; n_value = CondSwapInt64(swap, seekptr_64->n_value); } else { seekptr = &syms_address[sym_index]; string_index = CondSwapInt32(swap, seekptr->n_un.n_strx); n_type = seekptr->n_type; n_sect = seekptr->n_sect; n_value = (uint64_t)CondSwapInt32(swap, seekptr->n_value); } if (string_index == 0 || n_type & N_STAB) { continue; } symbol_name = (char *)(string_list + string_index); if (strcmp(name, symbol_name) == 0) { if (nlist_type) { *nlist_type = n_type; } switch (n_type & N_TYPE) { case N_SECT: { void * v_sect_info = macho_find_section_numbered( file_start, file_end, n_sect); if (!v_sect_info) { break; // out of the switch } if (symbol_address) { if (sixtyfourbit) { struct section_64 * sect_info_64 = (struct section_64 *)v_sect_info; // this isn't right for 64bit? compare below size_t reloffset = (n_value - CondSwapInt64(swap, sect_info_64->addr)); *symbol_address = file_start; *symbol_address += CondSwapInt32(swap, sect_info_64->offset); *symbol_address += reloffset; } else { struct section * sect_info = (struct section *)v_sect_info; size_t reloffset = (n_value - CondSwapInt32(swap, sect_info->addr)); *symbol_address = file_start; *symbol_address += CondSwapInt32(swap, sect_info->offset); *symbol_address += reloffset; } } result = macho_seek_result_found; goto finish; } break; case N_UNDF: result = macho_seek_result_found_no_value; goto finish; break; case N_ABS: result = macho_seek_result_found_no_value; goto finish; break; /* We don't chase indirect symbols as they can be external. */ case N_INDR: result = macho_seek_result_found_no_value; goto finish; break; default: goto finish; break; } } } finish: return result; }
ExitStatus printKextInfo(KctoolArgs * toolArgs) { ExitStatus result = EX_SOFTWARE; CFArrayRef kextPlistArray = NULL; CFIndex i, count; if (CFArrayGetTypeID() == CFGetTypeID(toolArgs->kernelcacheInfoPlist)) { kextPlistArray = (CFArrayRef)toolArgs->kernelcacheInfoPlist; } else if (CFDictionaryGetTypeID() == CFGetTypeID(toolArgs->kernelcacheInfoPlist)){ kextPlistArray = (CFArrayRef)CFDictionaryGetValue(toolArgs->kernelcacheInfoPlist, CFSTR("_PrelinkInfoDictionary")); } else { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Unrecognized kernelcache plist data."); goto finish; } count = CFArrayGetCount(kextPlistArray); for (i = 0; i < count; i++) { CFDictionaryRef kextInfoDict = (CFDictionaryRef)CFArrayGetValueAtIndex(kextPlistArray, i); CFStringRef thisKextID = CFDictionaryGetValue(kextInfoDict, kCFBundleIdentifierKey); if (thisKextID && CFEqual(thisKextID, toolArgs->kextID)) { uint64_t kextAddr = 0; uint64_t kextSize = 0; u_long kextOffset = 0; const UInt8 * kextMachO = NULL; // do not free void * section = NULL; // do not free if (!getKextAddressAndSize(kextInfoDict, &kextAddr, &kextSize)) { goto finish; } if (ISMACHO64(MAGIC32(toolArgs->kernelcacheImageBytes))) { section = (void *)macho_get_section_by_name_64( (struct mach_header_64 *)toolArgs->kernelcacheImageBytes, kPrelinkTextSegment, kPrelinkTextSection); } else { section = (void *)macho_get_section_by_name( (struct mach_header *)toolArgs->kernelcacheImageBytes, kPrelinkTextSegment, kPrelinkTextSection); } if (!section) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Cannot find %s,%s in kernelcache.", kPrelinkTextSegment, kPrelinkTextSection); goto finish; } if (ISMACHO64(MAGIC32(toolArgs->kernelcacheImageBytes))) { kextOffset = ((struct section_64 *)section)->offset + (u_long)(kextAddr - ((struct section_64 *)section)->addr); } else { kextOffset = ((struct section *)section)->offset + (u_long)(kextAddr - ((struct section *)section)->addr); } kextMachO = toolArgs->kernelcacheImageBytes + kextOffset; /* Find the requested section's file offset and size */ if (ISMACHO64(MAGIC32(toolArgs->kernelcacheImageBytes))) { section = (void *)macho_get_section_by_name_64( (struct mach_header_64 *)kextMachO, toolArgs->segmentName, toolArgs->sectionName); } else { /* macho_get_section_by_name doesn't work as the kexts don't have a __TEXT segment. * They just have a single segment named "" with all the sections dumped under it. */ section = (void *)getSectionByName( kextMachO, toolArgs->segmentName, toolArgs->sectionName); } if (!section) { OSKextLogCFString(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, CFSTR("Cannot find %s,%s in kext %@\n"), toolArgs->segmentName, toolArgs->sectionName, toolArgs->kextID); goto finish; } if (ISMACHO64(MAGIC32(toolArgs->kernelcacheImageBytes))) { printf("%#llx %#lx %#llx\n", ((struct section_64 *)section)->addr, kextOffset + ((struct section_64 *)section)->offset, ((struct section_64 *)section)->size); } else { printf("%#x %#lx %#x\n", ((struct section *)section)->addr, kextOffset + ((struct section *)section)->offset, ((struct section *)section)->size); } result = EX_OK; break; } } finish: return result; }
int main(int argc, char * const argv[]) { ExitStatus result = EX_SOFTWARE; KctoolArgs toolArgs; int kernelcache_fd = -1; // must close() void * fat_header = NULL; // must unmapFatHeaderPage() struct fat_arch * fat_arch = NULL; CFDataRef rawKernelcache = NULL; // must release CFDataRef kernelcacheImage = NULL; // must release void * prelinkInfoSect = NULL; const char * prelinkInfoBytes = NULL; CFPropertyListRef prelinkInfoPlist = NULL; // must release bzero(&toolArgs, sizeof(toolArgs)); /***** * Find out what the program was invoked as. */ progname = rindex(argv[0], '/'); if (progname) { progname++; // go past the '/' } else { progname = (char *)argv[0]; } /* Set the OSKext log callback right away. */ OSKextSetLogOutputFunction(&tool_log); /***** * Process args & check for permission to load. */ result = readArgs(&argc, &argv, &toolArgs); if (result != EX_OK) { if (result == kKctoolExitHelp) { result = EX_OK; } goto finish; } kernelcache_fd = open(toolArgs.kernelcachePath, O_RDONLY); if (kernelcache_fd == -1) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't open %s: %s.", toolArgs.kernelcachePath, strerror(errno)); result = EX_OSERR; goto finish; } fat_header = mapAndSwapFatHeaderPage(kernelcache_fd); if (!fat_header) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't map %s: %s.", toolArgs.kernelcachePath, strerror(errno)); result = EX_OSERR; goto finish; } fat_arch = getFirstFatArch(fat_header); if (fat_arch && !toolArgs.archInfo) { toolArgs.archInfo = NXGetArchInfoFromCpuType(fat_arch->cputype, fat_arch->cpusubtype); } rawKernelcache = readMachOSliceForArch(toolArgs.kernelcachePath, toolArgs.archInfo, /* checkArch */ FALSE); if (!rawKernelcache) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't read arch %s from %s.", toolArgs.archInfo->name, toolArgs.kernelcachePath); goto finish; } if (MAGIC32(CFDataGetBytePtr(rawKernelcache)) == OSSwapHostToBigInt32('comp')) { kernelcacheImage = uncompressPrelinkedSlice(rawKernelcache); if (!kernelcacheImage) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't uncompress kernelcache slice."); goto finish; } } else { kernelcacheImage = CFRetain(rawKernelcache); } toolArgs.kernelcacheImageBytes = CFDataGetBytePtr(kernelcacheImage); if (ISMACHO64(MAGIC32(toolArgs.kernelcacheImageBytes))) { prelinkInfoSect = (void *)macho_get_section_by_name_64( (struct mach_header_64 *)toolArgs.kernelcacheImageBytes, "__PRELINK_INFO", "__info"); } else { prelinkInfoSect = (void *)macho_get_section_by_name( (struct mach_header *)toolArgs.kernelcacheImageBytes, "__PRELINK_INFO", "__info"); } if (!prelinkInfoSect) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't find prelink info section."); goto finish; } if (ISMACHO64(MAGIC32(toolArgs.kernelcacheImageBytes))) { prelinkInfoBytes = ((char *)toolArgs.kernelcacheImageBytes) + ((struct section_64 *)prelinkInfoSect)->offset; } else { prelinkInfoBytes = ((char *)toolArgs.kernelcacheImageBytes) + ((struct section *)prelinkInfoSect)->offset; } toolArgs.kernelcacheInfoPlist = (CFPropertyListRef)IOCFUnserialize(prelinkInfoBytes, kCFAllocatorDefault, /* options */ 0, /* errorString */ NULL); if (!toolArgs.kernelcacheInfoPlist) { OSKextLog(/* kext */ NULL, kOSKextLogErrorLevel | kOSKextLogGeneralFlag, "Can't unserialize prelink info."); goto finish; } result = printKextInfo(&toolArgs); if (result != EX_OK) { goto finish; } result = EX_OK; finish: SAFE_RELEASE(toolArgs.kernelcacheInfoPlist); SAFE_RELEASE(kernelcacheImage); SAFE_RELEASE(rawKernelcache); if (fat_header) { unmapFatHeaderPage(fat_header); } if (kernelcache_fd != -1) { close(kernelcache_fd); } return result; }