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