dyld_app_cache_util.cpp [plain text]
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <list>
#include <map>
#include <vector>
#include <variant>
#include <CoreFoundation/CFBundle.h>
#include <CoreFoundation/CFPropertyList.h>
#include <CoreFoundation/CFStream.h>
#include "kernel_collection_builder.h"
#include "ClosureFileSystemPhysical.h"
#include "FileUtils.h"
#include "JSONWriter.h"
#include "MachOAppCache.h"
using namespace dyld3::json;
using dyld3::closure::FileSystemPhysical;
using dyld3::closure::LoadedFileInfo;
using dyld3::GradedArchs;
using dyld3::MachOAnalyzer;
using dyld3::MachOAppCache;
using dyld3::Platform;
using dyld3::json::Node;
__attribute__((noreturn))
static void exit_usage(const char* missingOption = nullptr) {
if ( missingOption != nullptr ) {
fprintf(stderr, "Missing option '%s'\n", missingOption);
fprintf(stderr, "\n");
}
fprintf(stderr, "Usage: dyld_app_cache_util [-layout] [-entrypoint] [-fixups] [-symbols] [-kmod] [-uuid] [-fips] -arch arch -platform platform -app-cache app-cache-path\n");
fprintf(stderr, " -layout print the layout of an existing app cache\n");
fprintf(stderr, " -entrypoint print the entrypoint of an existing app cache\n");
fprintf(stderr, " -fixups print the fixups of an existing app cache\n");
fprintf(stderr, " -symbols print the symbols of an existing app cache\n");
fprintf(stderr, " -kmod print the kmod_info of an existing app cache\n");
fprintf(stderr, " -uuid print the UUID of an existing app cache\n");
fprintf(stderr, " -fips print the FIPS section of an existing app cache\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: dyld_app_cache_util -validate file-path -arch arch -platform platform\n");
fprintf(stderr, " -validate the path to check is valid for inserting in to an app cache\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: dyld_app_cache_util -list-bundles directory-path\n");
fprintf(stderr, " -list-bundles the directory to index for bundles\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: dyld_app_cache_util -create-kernel-collection kernel-collection -kernel kernel-path [-extensions path-to-extensions] [-bundle-id bundle-id]*\n");
fprintf(stderr, " -create-kernel-collection create a kernel collection and write to the given path\n");
fprintf(stderr, " -kernel path to the kernel static executable\n");
fprintf(stderr, " -extensions path to the kernel extensions directory\n");
fprintf(stderr, " -bundle-id zero or more bundle-ids to link in to the kernel collection\n");
fprintf(stderr, " -sectcreate segment name, section name, and payload file path for more data to embed in the kernel\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: dyld_app_cache_util -create-pageable-kernel-collection aux-kernel-collection -kernel-collection kernel-collection-path [-extensions path-to-extensions] [-bundle-id bundle-id]*\n");
fprintf(stderr, " -create-pageable-kernel-collection create a pageable kernel collection and write to the given path\n");
fprintf(stderr, " -kernel-collection path to the kernel collection collection\n");
fprintf(stderr, " -extensions path to the kernel extensions directory\n");
fprintf(stderr, " -bundle-id zero or more bundle-ids to link in to the kernel collection\n");
fprintf(stderr, "\n");
fprintf(stderr, "Usage: dyld_app_cache_util -create-aux-kernel-collection aux-kernel-collection -kernel-collection kernel-collection-path [-extensions path-to-extensions] [-bundle-id bundle-id]*\n");
fprintf(stderr, " -create-aux-kernel-collection create an aux kernel collection and write to the given path\n");
fprintf(stderr, " -kernel-collection path to the kernel collection\n");
fprintf(stderr, " -pageable-collection path to the pageable collection\n");
fprintf(stderr, " -extensions path to the kernel extensions directory\n");
fprintf(stderr, " -bundle-id zero or more bundle-ids to link in to the kernel collection\n");
fprintf(stderr, "\n");
fprintf(stderr, "Common options:\n");
fprintf(stderr, " -arch xxx the arch to use to create the app cache\n");
fprintf(stderr, " -platform xxx the platform to use to create the app cache\n");
exit(1);
}
struct DumpOptions {
bool printLayout = false;
bool printEntryPoint = false;
bool printFixups = false;
bool printSymbols = false;
bool printUUID = false;
bool printKModInfo = false;
bool printFIPS = false;
};
struct ValidateOptions {
const char* filePath = nullptr;
};
struct ListBundlesOptions {
const char* directoryPath = nullptr;
};
struct SectionData {
const char* segmentName = nullptr;
const char* sectionName = nullptr;
const char* payloadFilePath = nullptr;
};
struct CreateKernelCollectionOptions {
const char* outputCachePath = nullptr;
const char* kernelPath = nullptr;
const char* kernelCollectionPath = nullptr;
const char* pageableCollectionPath = nullptr;
const char* extensionsPath = nullptr;
const char* volumeRoot = "";
std::vector<const char*> bundleIDs;
bool verbose = false;
bool printJSONErrors = false;
CollectionKind collectionKind = unknownKC;
StripMode stripMode = unknownStripMode;
std::vector<SectionData> sections;
const char* prelinkInfoExtraData = nullptr;
};
typedef std::variant<std::monostate, DumpOptions, ValidateOptions, ListBundlesOptions, CreateKernelCollectionOptions> OptionsVariants;
struct CommonOptions {
const char* appCachePath = nullptr;
std::vector<const char*> archs;
const char* platform = nullptr;
};
CommonOptions gOpts;
template<typename T>
static T& exitOrGetState(OptionsVariants& options, const char* argv) {
if (std::holds_alternative<std::monostate>(options)) {
return options.emplace<T>();
}
if (std::holds_alternative<T>(options))
return std::get<T>(options);
exit_usage();
}
static bool parseArgs(int argc, const char* argv[], OptionsVariants& options) {
for (int i = 1; i < argc; ++i) {
const char* arg = argv[i];
if (arg[0] != '-') {
fprintf(stderr, "unknown option: %s\n", arg);
exit_usage();
}
if (strcmp(arg, "-app-cache") == 0) {
if (gOpts.appCachePath != nullptr)
exit_usage();
gOpts.appCachePath = argv[++i];
continue;
}
if (strcmp(arg, "-arch") == 0) {
gOpts.archs.push_back(argv[++i]);
continue;
}
if (strcmp(arg, "-platform") == 0) {
if (gOpts.platform != nullptr)
exit_usage();
gOpts.platform = argv[++i];
continue;
}
if (strcmp(arg, "-layout") == 0) {
exitOrGetState<DumpOptions>(options, arg).printLayout = true;
continue;
}
if (strcmp(arg, "-entrypoint") == 0) {
exitOrGetState<DumpOptions>(options, arg).printEntryPoint = true;
continue;
}
if (strcmp(arg, "-fixups") == 0) {
exitOrGetState<DumpOptions>(options, arg).printFixups = true;
continue;
}
if (strcmp(arg, "-symbols") == 0) {
exitOrGetState<DumpOptions>(options, arg).printSymbols = true;
continue;
}
if (strcmp(arg, "-uuid") == 0) {
exitOrGetState<DumpOptions>(options, arg).printUUID = true;
continue;
}
if (strcmp(arg, "-kmod") == 0) {
exitOrGetState<DumpOptions>(options, arg).printKModInfo = true;
continue;
}
if (strcmp(arg, "-fips") == 0) {
exitOrGetState<DumpOptions>(options, arg).printFIPS = true;
continue;
}
if (strcmp(arg, "-validate") == 0) {
exitOrGetState<ValidateOptions>(options, arg).filePath = argv[++i];
continue;
}
if (strcmp(arg, "-list-bundles") == 0) {
exitOrGetState<ListBundlesOptions>(options, arg).directoryPath = argv[++i];
continue;
}
if (strcmp(arg, "-create-kernel-collection") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).outputCachePath = argv[++i];
exitOrGetState<CreateKernelCollectionOptions>(options, arg).collectionKind = baseKC;
continue;
}
if (strcmp(arg, "-kernel") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).kernelPath = argv[++i];
continue;
}
if (strcmp(arg, "-create-pageable-kernel-collection") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).outputCachePath = argv[++i];
exitOrGetState<CreateKernelCollectionOptions>(options, arg).collectionKind = pageableKC;
continue;
}
if (strcmp(arg, "-create-aux-kernel-collection") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).outputCachePath = argv[++i];
exitOrGetState<CreateKernelCollectionOptions>(options, arg).collectionKind = auxKC;
continue;
}
if (strcmp(arg, "-kernel-collection") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).kernelCollectionPath = argv[++i];
continue;
}
if (strcmp(arg, "-pageable-collection") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).pageableCollectionPath = argv[++i];
continue;
}
if (strcmp(arg, "-extensions") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).extensionsPath = argv[++i];
continue;
}
if (strcmp(arg, "-volume-root") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).volumeRoot = argv[++i];
continue;
}
if (strcmp(arg, "-bundle-id") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).bundleIDs.push_back(argv[++i]);
continue;
}
if (strcmp(arg, "-verbose") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).verbose = true;
continue;
}
if (strcmp(arg, "-json-errors") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).printJSONErrors = true;
continue;
}
if (strcmp(arg, "-strip-all") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).stripMode = stripAll;
continue;
}
if (strcmp(arg, "-strip-all-kexts") == 0) {
exitOrGetState<CreateKernelCollectionOptions>(options, arg).stripMode = stripAllKexts;
continue;
}
if (strcmp(arg, "-sectcreate") == 0) {
const char* segmentName = argv[++i];
const char* sectionName = argv[++i];
const char* payloadFilePath = argv[++i];
SectionData sectData = { segmentName, sectionName, payloadFilePath };
exitOrGetState<CreateKernelCollectionOptions>(options, arg).sections.push_back(sectData);
continue;
}
if (strcmp(arg, "-prelink-info-extra") == 0) {
const char* payloadFilePath = argv[++i];
exitOrGetState<CreateKernelCollectionOptions>(options, arg).prelinkInfoExtraData = payloadFilePath;
continue;
}
fprintf(stderr, "unknown option: %s\n", arg);
exit_usage();
}
return true;
}
static Platform stringToPlatform(const std::string& str) {
if (str == "unknown")
return Platform::unknown;
if (str == "macOS")
return Platform::macOS;
if (str == "iOS")
return Platform::iOS;
if (str == "tvOS")
return Platform::tvOS;
if (str == "watchOS")
return Platform::watchOS;
if (str == "bridgeOS")
return Platform::bridgeOS;
if (str == "iOSMac")
return Platform::iOSMac;
if (str == "UIKitForMac")
return Platform::iOSMac;
if (str == "iOS_simulator")
return Platform::iOS_simulator;
if (str == "tvOS_simulator")
return Platform::tvOS_simulator;
if (str == "watchOS_simulator")
return Platform::watchOS_simulator;
return Platform::unknown;
}
static int dumpAppCache(const DumpOptions& options) {
if (gOpts.archs.size() != 1)
exit_usage("-arch");
if (gOpts.platform == nullptr)
exit_usage("-platform");
if (gOpts.appCachePath == nullptr)
exit_usage();
FileSystemPhysical fileSystem;
if (!fileSystem.fileExists(gOpts.appCachePath)) {
fprintf(stderr, "App-cache path does not exist: %s\n", gOpts.appCachePath);
return 1;
}
const GradedArchs& archs = GradedArchs::forName(gOpts.archs[0]);
Platform platform = Platform::unknown;
bool isKernelCollection = false;
if (!strcmp(gOpts.platform, "kernel")) {
isKernelCollection = true;
} else {
platform = stringToPlatform(gOpts.platform);
if (platform == Platform::unknown) {
fprintf(stderr, "Could not create app cache because: unknown platform '%s'\n", gOpts.platform);
return 1;
}
}
__block Diagnostics diag;
char appCacheRealPath[MAXPATHLEN];
LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, gOpts.appCachePath, archs, platform, appCacheRealPath);
if (diag.hasError()) {
fprintf(stderr, "Could not load app cache because: %s\n", diag.errorMessage().c_str());
return 1;
}
MachOAppCache* appCacheMA = (MachOAppCache*)loadedFileInfo.fileContent;
if (appCacheMA == nullptr) {
fprintf(stderr, "Could not load app cache: %s\n", gOpts.appCachePath);
return 1;
}
if (options.printLayout) {
__block Node topNode;
__block Node segmentsNode;
__block bool hasError = false;
appCacheMA->forEachSegment(^(const dyld3::MachOFile::SegmentInfo &info, bool &stop) {
Node segmentNode;
segmentNode.map["name"] = makeNode(info.segName);
segmentNode.map["vmAddr"] = makeNode(hex(info.vmAddr));
segmentNode.map["vmSize"] = makeNode(hex(info.vmSize));
segmentNode.map["vmEnd"] = makeNode(hex(info.vmAddr + info.vmSize));
switch (info.protections) {
case VM_PROT_READ:
segmentNode.map["permissions"] = makeNode("r--");
break;
case VM_PROT_WRITE:
segmentNode.map["permissions"] = makeNode("-w-");
break;
case VM_PROT_EXECUTE:
segmentNode.map["permissions"] = makeNode("--x");
break;
case VM_PROT_READ | VM_PROT_WRITE:
segmentNode.map["permissions"] = makeNode("rw-");
break;
case VM_PROT_READ | VM_PROT_EXECUTE:
segmentNode.map["permissions"] = makeNode("r-x");
break;
case VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE:
segmentNode.map["permissions"] = makeNode("rwx");
break;
default:
fprintf(stderr, "Unknown permissions on segment '%s'\n", info.segName);
hasError = true;
stop = true;
}
__block Node sectionsNode;
appCacheMA->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo §Info, bool malformedSectionRange, bool &stop) {
if ( strncmp(sectInfo.segInfo.segName, info.segName, 16) != 0 )
return;
Node sectionNode;
sectionNode.map["name"] = makeNode(sectInfo.sectName);
sectionNode.map["vmAddr"] = makeNode(hex(sectInfo.sectAddr));
sectionNode.map["vmSize"] = makeNode(hex(sectInfo.sectSize));
sectionNode.map["vmEnd"] = makeNode(hex(sectInfo.sectAddr + sectInfo.sectSize));
sectionsNode.array.push_back(sectionNode);
});
if ( !sectionsNode.array.empty() ) {
segmentNode.map["sections"] = sectionsNode;
}
segmentsNode.array.push_back(segmentNode);
});
if (hasError)
return 1;
topNode.map["cache-segments"] = segmentsNode;
__block std::unordered_map<std::string, std::string> relativePaths;
appCacheMA->forEachPrelinkInfoLibrary(diag, ^(const char *bundleName, const char* relativePath,
const std::vector<const char *> &deps) {
if ( relativePath != nullptr )
relativePaths[bundleName] = relativePath;
});
__block Node dylibsNode;
appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
__block Node segmentsNode;
ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo &info, bool &stop) {
Node segmentNode;
segmentNode.map["name"] = makeNode(info.segName);
segmentNode.map["vmAddr"] = makeNode(hex(info.vmAddr));
segmentNode.map["vmSize"] = makeNode(hex(info.vmSize));
segmentNode.map["vmEnd"] = makeNode(hex(info.vmAddr + info.vmSize));
switch (info.protections) {
case VM_PROT_READ:
segmentNode.map["permissions"] = makeNode("r--");
break;
case VM_PROT_WRITE:
segmentNode.map["permissions"] = makeNode("-w-");
break;
case VM_PROT_EXECUTE:
segmentNode.map["permissions"] = makeNode("--x");
break;
case VM_PROT_READ | VM_PROT_WRITE:
segmentNode.map["permissions"] = makeNode("rw-");
break;
case VM_PROT_READ | VM_PROT_EXECUTE:
segmentNode.map["permissions"] = makeNode("r-x");
break;
default:
fprintf(stderr, "Unknown permissions on segment '%s'\n", info.segName);
hasError = true;
stop = true;
}
__block Node sectionsNode;
ma->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo §Info, bool malformedSectionRange, bool &stop) {
if ( strncmp(sectInfo.segInfo.segName, info.segName, 16) != 0 )
return;
Node sectionNode;
sectionNode.map["name"] = makeNode(sectInfo.sectName);
sectionNode.map["vmAddr"] = makeNode(hex(sectInfo.sectAddr));
sectionNode.map["vmSize"] = makeNode(hex(sectInfo.sectSize));
sectionNode.map["vmEnd"] = makeNode(hex(sectInfo.sectAddr + sectInfo.sectSize));
sectionsNode.array.push_back(sectionNode);
});
if ( !sectionsNode.array.empty() ) {
segmentNode.map["sections"] = sectionsNode;
}
segmentsNode.array.push_back(segmentNode);
});
Node dylibNode;
dylibNode.map["name"] = makeNode(name);
dylibNode.map["segments"] = segmentsNode;
auto relativePathIt = relativePaths.find(name);
if ( relativePathIt != relativePaths.end() )
dylibNode.map["relativePath"] = makeNode(relativePathIt->second);
dylibsNode.array.push_back(dylibNode);
});
topNode.map["dylibs"] = dylibsNode;
printJSON(topNode, 0, std::cout);
}
if (options.printEntryPoint) {
__block Node topNode;
uint64_t entryOffset;
bool usesCRT;
Node entryPointNode;
if ( appCacheMA->getEntry(entryOffset, usesCRT) ) {
entryPointNode.value = hex(appCacheMA->preferredLoadAddress() + entryOffset);
}
topNode.map["entrypoint"] = entryPointNode;
printJSON(topNode, 0, std::cout);
}
if (options.printFixups) {
__block Node topNode;
__block uint64_t baseAddress = ~0ULL;
appCacheMA->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
baseAddress = std::min(baseAddress, info.vmAddr);
});
uint64_t cacheBaseAddress = baseAddress;
uint64_t textSegVMAddr = appCacheMA->preferredLoadAddress();
auto getFixupsNode = [cacheBaseAddress, textSegVMAddr](const dyld3::MachOAnalyzer* ma) {
__block Node fixupsNode;
if (!ma->hasChainedFixups()) {
return makeNode("none");
}
__block std::set<uint64_t> seenFixupVMOffsets;
__block Diagnostics diag;
ma->withChainStarts(diag, 0, ^(const dyld_chained_starts_in_image* starts) {
ma->forEachFixupInAllChains(diag, starts, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
uint64_t vmOffset = (uint8_t*)fixupLoc - (uint8_t*)ma;
seenFixupVMOffsets.insert(vmOffset);
vmOffset += (textSegVMAddr - cacheBaseAddress);
fixupsNode.map[hex(vmOffset)] = makeNode("fixup");
switch (segInfo->pointer_format) {
case DYLD_CHAINED_PTR_64_KERNEL_CACHE:
case DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE: {
uint64_t targetVMOffset = fixupLoc->kernel64.target;
uint64_t targetVMAddr = targetVMOffset + cacheBaseAddress;
std::string level = "kc(" + decimal(fixupLoc->kernel64.cacheLevel) + ")";
std::string fixup = level + " + " + hex(targetVMAddr);
if (fixupLoc->kernel64.isAuth) {
fixup += " auth(";
fixup += fixupLoc->kernel64.keyName();
fixup += " ";
fixup += fixupLoc->kernel64.addrDiv ? "addr" : "!addr";
fixup += " ";
fixup += decimal(fixupLoc->kernel64.diversity);
fixup += ")";
}
fixupsNode.map[hex(vmOffset)] = makeNode(fixup);
break;
}
default:
diag.error("unknown pointer type %d", segInfo->pointer_format);
break;
}
});
});
diag.assertNoError();
ma->forEachRebase(diag, ^(const char *opcodeName, const dyld3::MachOAnalyzer::LinkEditInfo &leInfo,
const dyld3::MachOAnalyzer::SegmentInfo *segments,
bool segIndexSet, uint32_t pointerSize, uint8_t segmentIndex,
uint64_t segmentOffset, dyld3::MachOAnalyzer::Rebase kind, bool &stop) {
uint64_t rebaseVmAddr = segments[segmentIndex].vmAddr + segmentOffset;
uint64_t runtimeOffset = rebaseVmAddr - textSegVMAddr;
const uint8_t* fixupLoc = (const uint8_t*)ma + runtimeOffset;
runtimeOffset += (textSegVMAddr - cacheBaseAddress);
std::string fixup = "kc(0) + ";
switch ( kind ) {
case dyld3::MachOAnalyzer::Rebase::unknown:
fixup += " : unhandled";
break;
case dyld3::MachOAnalyzer::Rebase::pointer32: {
uint32_t value = *(uint32_t*)(fixupLoc);
fixup += hex(value) + " : pointer32";
break;
}
case dyld3::MachOAnalyzer::Rebase::pointer64: {
uint64_t value = *(uint64_t*)(fixupLoc);
fixup += hex(value) + " : pointer64";
break;
}
case dyld3::MachOAnalyzer::Rebase::textPCrel32:
fixup += " : pcrel32";
break;
case dyld3::MachOAnalyzer::Rebase::textAbsolute32:
fixup += " : absolute32";
break;
}
fixupsNode.map[hex(runtimeOffset)] = makeNode(fixup);
});
diag.assertNoError();
return fixupsNode;
};
topNode.map["fixups"] = getFixupsNode(appCacheMA);
__block Node dylibsNode;
appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
Node dylibNode;
dylibNode.map["name"] = makeNode(name);
dylibNode.map["fixups"] = getFixupsNode(ma);
dylibsNode.array.push_back(dylibNode);
});
topNode.map["dylibs"] = dylibsNode;
printJSON(topNode, 0, std::cout);
}
if (options.printSymbols) {
__block Node topNode;
__block Node dylibsNode;
appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
__block Node globalSymbolsNode;
ma->forEachGlobalSymbol(diag, ^(const char *symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
Node symbolNode;
symbolNode.map["name"] = makeNode(symbolName);
symbolNode.map["vmAddr"] = makeNode(hex(n_value));
globalSymbolsNode.array.push_back(symbolNode);
});
__block Node localSymbolsNode;
ma->forEachLocalSymbol(diag, ^(const char *symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
Node symbolNode;
symbolNode.map["name"] = makeNode(symbolName);
symbolNode.map["vmAddr"] = makeNode(hex(n_value));
localSymbolsNode.array.push_back(symbolNode);
});
if (globalSymbolsNode.array.empty())
globalSymbolsNode = makeNode("none");
if (localSymbolsNode.array.empty())
localSymbolsNode = makeNode("none");
Node dylibNode;
dylibNode.map["name"] = makeNode(name);
dylibNode.map["global-symbols"] = globalSymbolsNode;
dylibNode.map["local-symbols"] = localSymbolsNode;
dylibsNode.array.push_back(dylibNode);
});
topNode.map["dylibs"] = dylibsNode;
printJSON(topNode, 0, std::cout);
}
if (options.printUUID) {
__block Node topNode;
Node uuidNode;
uuid_t uuid = {};
if ( appCacheMA->getUuid(uuid) ) {
uuid_string_t uuidString;
uuid_unparse_upper(uuid, uuidString);
uuidNode.value = uuidString;
}
topNode.map["uuid"] = uuidNode;
auto getPlistUUID = ^(const char* jsonNodeName, CFStringRef keyName) {
uuid_t uuid = {};
const uint8_t* prelinkInfoBuffer = nullptr;
uint64_t prelinkInfoBufferSize = 0;
prelinkInfoBuffer = (const uint8_t*)appCacheMA->findSectionContent("__PRELINK_INFO", "__info", prelinkInfoBufferSize);
if ( prelinkInfoBuffer != nullptr ) {
CFReadStreamRef readStreamRef = CFReadStreamCreateWithBytesNoCopy(kCFAllocatorDefault, prelinkInfoBuffer, prelinkInfoBufferSize, kCFAllocatorNull);
if ( !CFReadStreamOpen(readStreamRef) ) {
fprintf(stderr, "Could not open plist stream\n");
exit(1);
}
CFErrorRef errorRef = nullptr;
CFPropertyListRef plistRef = CFPropertyListCreateWithStream(kCFAllocatorDefault, readStreamRef, prelinkInfoBufferSize, kCFPropertyListImmutable, nullptr, &errorRef);
if ( errorRef != nullptr ) {
CFStringRef stringRef = CFErrorCopyFailureReason(errorRef);
fprintf(stderr, "Could not read plist because: %s\n", CFStringGetCStringPtr(stringRef, kCFStringEncodingASCII));
CFRelease(stringRef);
exit(1);
}
assert(CFGetTypeID(plistRef) == CFDictionaryGetTypeID());
CFDataRef uuidDataRef = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)plistRef, keyName);
if ( uuidDataRef != nullptr ) {
CFDataGetBytes(uuidDataRef, CFRangeMake(0, CFDataGetLength(uuidDataRef)), uuid);
Node uuidNode;
uuid_string_t uuidString;
uuid_unparse_upper(uuid, uuidString);
uuidNode.value = uuidString;
topNode.map[jsonNodeName] = uuidNode;
}
CFRelease(plistRef);
CFRelease(readStreamRef);
}
};
getPlistUUID("prelink-info-uuid", CFSTR("_PrelinkKCID"));
getPlistUUID("prelink-info-base-uuid", CFSTR("_BootKCID"));
getPlistUUID("prelink-info-pageable-uuid", CFSTR("_PageableKCID"));
printJSON(topNode, 0, std::cout);
}
if (options.printKModInfo) {
__block Node topNode;
appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
Node dylibNode;
dylibNode.map["name"] = makeNode(name);
__block uint64_t kmodInfoVMOffset = 0;
__block bool found = false;
{
dyld3::MachOAnalyzer::FoundSymbol foundInfo;
found = ma->findExportedSymbol(diag, "_kmod_info", true, foundInfo, nullptr);
if ( found ) {
kmodInfoVMOffset = foundInfo.value;
}
}
if ( !found ) {
ma->forEachLocalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type,
uint8_t n_sect, uint16_t n_desc, bool& stop) {
if ( strcmp(aSymbolName, "_kmod_info") == 0 ) {
kmodInfoVMOffset = n_value - ma->preferredLoadAddress();
found = true;
stop = true;
}
});
}
if ( found ) {
dyld3::MachOAppCache::KModInfo64_v1* kmodInfo = (dyld3::MachOAppCache::KModInfo64_v1*)((uint8_t*)ma + kmodInfoVMOffset);
Node kmodInfoNode;
kmodInfoNode.map["info-version"] = makeNode(decimal(kmodInfo->info_version));
kmodInfoNode.map["name"] = makeNode((const char*)&kmodInfo->name[0]);
kmodInfoNode.map["version"] = makeNode((const char*)&kmodInfo->version[0]);
kmodInfoNode.map["address"] = makeNode(hex(kmodInfo->address));
kmodInfoNode.map["size"] = makeNode(hex(kmodInfo->size));
dylibNode.map["kmod_info"] = kmodInfoNode;
} else {
dylibNode.map["kmod_info"] = makeNode("none");
}
topNode.array.push_back(dylibNode);
});
printJSON(topNode, 0, std::cout);
}
if (options.printFIPS) {
__block Node topNode;
appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
if ( strcmp(name, "com.apple.kec.corecrypto") != 0 )
return;
uint64_t hashStoreSize;
const void* hashStoreLocation = ma->findSectionContent("__TEXT", "__fips_hmacs", hashStoreSize);
assert(hashStoreLocation != nullptr);
const uint8_t* hashBuffer = (const uint8_t*)hashStoreLocation;
std::string hashString;
for (int i = 0; i < hashStoreSize; ++i) {
uint8_t byte = hashBuffer[i];
uint8_t nibbleL = byte & 0x0F;
uint8_t nibbleH = byte >> 4;
if ( nibbleH < 10 ) {
hashString += '0' + nibbleH;
} else {
hashString += 'a' + (nibbleH-10);
}
if ( nibbleL < 10 ) {
hashString += '0' + nibbleL;
} else {
hashString += 'a' + (nibbleL-10);
}
}
stop = true;
topNode.map["fips"] = makeNode(hashString);
});
printJSON(topNode, 0, std::cout);
}
return 0;
}
static int validateFile(const ValidateOptions& options) {
if (gOpts.archs.size() != 1)
exit_usage("-arch");
if (gOpts.platform == nullptr)
exit_usage("-platform");
if (options.filePath == nullptr)
exit_usage();
const GradedArchs& archs = GradedArchs::forName(gOpts.archs[0]);
Platform platform = Platform::unknown;
if (strcmp(gOpts.platform, "kernel")) {
fprintf(stderr, "Could not create app cache because: unknown platform '%s'\n", gOpts.platform);
return 1;
}
__block Diagnostics diag;
std::string file = options.filePath;
{
FileSystemPhysical fileSystem;
char fileRealPath[MAXPATHLEN];
LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, file.c_str(), archs, platform, fileRealPath);
if (diag.hasError()) {
fprintf(stderr, "Could not load file '%s' because: %s\n", file.c_str(), diag.errorMessage().c_str());
diag.clearError();
return 1;
}
MachOAnalyzer* ma = (MachOAnalyzer*)loadedFileInfo.fileContent;
if (ma == nullptr) {
fprintf(stderr, "Could not load file: %s\n", file.c_str());
return 1;
}
auto errorHandler = ^(const char* msg) {
diag.warning("File '%s' cannot be placed in kernel collection because: %s", file.c_str(), msg);
};
if (ma->canBePlacedInKernelCollection(file.c_str(), errorHandler)) {
return 0;
} else {
fileSystem.unloadFile(loadedFileInfo);
}
}
{
if (diag.warnings().empty()) {
fprintf(stderr, "File '%s' was not valid for app-cache\n", file.c_str());
} else {
for (const std::string& msg : diag.warnings()) {
fprintf(stderr, " %s\n", msg.c_str());
}
}
return 1;
}
return 0;
}
static void forEachBundle(const char* bundlesDirectoryPath,
void (^callback)(CFBundleRef bundleRef, const char* bundleName)) {
CFStringRef sourcePath = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, bundlesDirectoryPath,
kCFStringEncodingASCII, kCFAllocatorNull);
CFURLRef sourceURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, sourcePath,
kCFURLPOSIXPathStyle, true);
CFArrayRef bundles = CFBundleCreateBundlesFromDirectory(kCFAllocatorDefault, sourceURL, nullptr);
for (CFIndex i = 0, e = CFArrayGetCount(bundles); i != e; ++i) {
CFBundleRef bundleRef = (CFBundleRef)CFArrayGetValueAtIndex(bundles, i);
CFStringRef bundleID = CFBundleGetIdentifier(bundleRef);
if (!bundleID)
continue;
const char* bundleName = CFStringGetCStringPtr(bundleID, kCFStringEncodingASCII);
callback(bundleRef, bundleName);
}
CFRelease(sourcePath);
CFRelease(sourceURL);
CFRelease(bundles);
}
static int listBundles(const ListBundlesOptions& options) {
if (options.directoryPath == nullptr)
exit_usage();
forEachBundle(options.directoryPath, ^(CFBundleRef bundleRef, const char* bundleName){
printf("Bundle: %s\n", bundleName);
});
return 0;
}
static CFDataRef
createKernelCollectionForArch(const CreateKernelCollectionOptions& options, const char* arch,
Diagnostics& diag) {
const GradedArchs& archs = GradedArchs::forName(arch);
Platform platform = Platform::unknown;
KernelCollectionBuilder* kcb = nullptr;
{
CFStringRef archStringRef = CFStringCreateWithCString(kCFAllocatorDefault, arch, kCFStringEncodingASCII);
BuildOptions_v1 buildOptions = { 1, options.collectionKind, options.stripMode, archStringRef, options.verbose };
kcb = createKernelCollectionBuilder(&buildOptions);
CFRelease(archStringRef);
}
FileSystemPhysical fileSystem;
LoadedFileInfo kernelCollectionLoadedFileInfo;
auto loadKernelCollection = ^(const char* kernelCollectionPath, CollectionKind collectionKind) {
if (!fileSystem.fileExists(kernelCollectionPath)) {
fprintf(stderr, "kernel collection path does not exist: %s\n", options.kernelPath);
return false;
}
LoadedFileInfo info;
char realerPath[MAXPATHLEN];
bool loadedFile = fileSystem.loadFile(kernelCollectionPath, info, realerPath, ^(const char *format, ...) {
va_list list;
va_start(list, format);
diag.error(format, list);
va_end(list);
});
if ( !loadedFile )
return false;
CFStringRef pathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, kernelCollectionPath, kCFStringEncodingASCII);
CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)info.fileContent, info.fileContentLen, kCFAllocatorNull);
if ( !addCollectionFile(kcb, pathStringRef, dataRef, collectionKind) ) {
diag.error("Could not load kernel collection file");
return false;
}
CFRelease(dataRef);
CFRelease(pathStringRef);
return true;
};
switch (options.collectionKind) {
case unknownKC:
fprintf(stderr, "Invalid kernel collection kind\n");
exit(1);
case baseKC: {
if (!fileSystem.fileExists(options.kernelPath)) {
fprintf(stderr, "Kernel path does not exist: %s\n", options.kernelPath);
return {};
}
LoadedFileInfo info;
char realerPath[MAXPATHLEN];
bool loadedFile = fileSystem.loadFile(options.kernelPath, info, realerPath, ^(const char *format, ...) {
va_list list;
va_start(list, format);
diag.error(format, list);
va_end(list);
});
if ( !loadedFile )
return {};
CFStringRef pathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, options.kernelPath, kCFStringEncodingASCII);
CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)info.fileContent, info.fileContentLen, kCFAllocatorNull);
if ( !addKernelFile(kcb, pathStringRef, dataRef) ) {
uint64_t errorCount = 0;
const char* const* errors = getErrors(kcb, &errorCount);
for (uint64_t i = 0; i != errorCount; ++i)
diag.error("Could not load kernel file because: '%s'", errors[i]);
return {};
}
CFRelease(dataRef);
CFRelease(pathStringRef);
break;
}
case pageableKC:
if ( !loadKernelCollection(options.kernelCollectionPath, baseKC) )
return {};
break;
case auxKC:
if ( !loadKernelCollection(options.kernelCollectionPath, baseKC) )
return {};
if ( options.pageableCollectionPath != nullptr ) {
if ( !loadKernelCollection(options.pageableCollectionPath, pageableKC) )
return {};
}
break;
}
if ( !options.bundleIDs.empty() ) {
struct BundleData {
std::string executablePath;
std::string bundlePath;
std::vector<std::string> dependencies;
CFDictionaryRef infoPlist = nullptr;
};
__block std::map<std::string, BundleData> foundBundles;
__block std::list<std::pair<std::string, bool>> kextDirectoriesToProcess;
kextDirectoriesToProcess.push_back({ options.extensionsPath, true });
while ( !kextDirectoriesToProcess.empty() ) {
std::string kextDir = kextDirectoriesToProcess.front().first;
bool lookForPlugins = kextDirectoriesToProcess.front().second;
kextDirectoriesToProcess.pop_front();
__block bool foundError = false;
forEachBundle(kextDir.c_str(), ^(CFBundleRef bundleRef, const char* bundleName) {
if (foundError)
return;
if (lookForPlugins) {
CFURLRef pluginsRelativeURL = CFBundleCopyBuiltInPlugInsURL(bundleRef);
if ( pluginsRelativeURL != nullptr ) {
CFURLRef pluginsAbsoluteURL = CFURLCopyAbsoluteURL(pluginsRelativeURL);
CFStringRef pluginString = CFURLCopyFileSystemPath(pluginsAbsoluteURL, kCFURLPOSIXPathStyle);
const char* pluginPath = CFStringGetCStringPtr(pluginString, kCFStringEncodingASCII);
kextDirectoriesToProcess.push_back({ pluginPath, false });
CFRelease(pluginString);
CFRelease(pluginsAbsoluteURL);
CFRelease(pluginsRelativeURL);
}
}
#if 0
if ( foundBundles.count(bundleName) == 0 )
return;
#endif
BundleData bundleData;
bundleData.infoPlist = CFBundleGetInfoDictionary(bundleRef);
CFURLRef bundleExecutableRelativeURL = CFBundleCopyExecutableURL(bundleRef);
if ( bundleExecutableRelativeURL == nullptr ) {
foundBundles[bundleName] = bundleData;
return;
}
CFURLRef bundleExecutableAbsoluteURL = CFURLCopyAbsoluteURL(bundleExecutableRelativeURL);
CFStringRef bundleExecutableString = CFURLCopyFileSystemPath(bundleExecutableAbsoluteURL, kCFURLPOSIXPathStyle);
const char* bundleExecutablePath = CFStringGetCStringPtr(bundleExecutableString, kCFStringEncodingASCII);
std::string archBundleLibraries = std::string("OSBundleLibraries") + "_" + arch;
CFStringRef archBundleLibrariesStringRef = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, archBundleLibraries.c_str(),
kCFStringEncodingASCII, kCFAllocatorNull);
CFTypeRef depsRef = CFBundleGetValueForInfoDictionaryKey(bundleRef, archBundleLibrariesStringRef);
if ( depsRef == nullptr ) {
depsRef = CFBundleGetValueForInfoDictionaryKey(bundleRef, CFSTR("OSBundleLibraries"));
}
if (depsRef != nullptr) {
if (CFGetTypeID(depsRef) != CFDictionaryGetTypeID()) {
fprintf(stderr, "Bad bundle '%s' (\"OSBundleLibraries\" is not a dictionary)\n", bundleName);
foundError = true;
return;
}
CFDictionaryRef dictRef = (CFDictionaryRef)depsRef;
CFDictionaryApplyFunction(dictRef, [](const void *key, const void *value, void *context) {
BundleData* bundleData = (BundleData*)context;
CFStringRef keyRef = (CFStringRef)key;
bundleData->dependencies.push_back(CFStringGetCStringPtr(keyRef, kCFStringEncodingASCII));
}, &bundleData);
}
if ( !bundleData.dependencies.empty() ) {
for (const std::string& dep : bundleData.dependencies) {
if (dep == "com.apple.kernel") {
fprintf(stderr, "Rejecting bundle '%s' as it is trying to link directly to the kernel\n",
bundleName);
foundError = true;
return;
}
}
}
bundleData.executablePath = bundleExecutablePath;
CFURLRef bundleURLRef = CFBundleCopyBundleURL(bundleRef);
CFStringRef bundleURLString = CFURLCopyFileSystemPath(bundleURLRef, kCFURLPOSIXPathStyle);
const char* bundleURLPath = CFStringGetCStringPtr(bundleURLString, kCFStringEncodingASCII);
if (strncmp(bundleURLPath, options.extensionsPath, strlen(options.extensionsPath)) != 0) {
fprintf(stderr, "Bundle path '%s' is not within extensions directory '%s'\n",
bundleURLPath, options.extensionsPath);
}
bundleData.bundlePath = bundleURLPath + strlen(options.volumeRoot);
foundBundles[bundleName] = bundleData;
CFRelease(bundleExecutableString);
CFRelease(bundleExecutableAbsoluteURL);
CFRelease(bundleExecutableRelativeURL);
});
if (foundError)
return {};
}
__block std::set<std::string> existingBundles;
auto addSymbolSetsBundleIDs = ^(const dyld3::MachOAnalyzer* kernelMA) {
assert(kernelMA != nullptr);
__block std::list<std::string> nonASCIIStrings;
auto getString = ^(Diagnostics& diags, CFStringRef symbolNameRef) {
const char* symbolName = CFStringGetCStringPtr(symbolNameRef, kCFStringEncodingUTF8);
if ( symbolName != nullptr )
return symbolName;
CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(symbolNameRef), kCFStringEncodingUTF8);
char buffer[len + 1];
if ( !CFStringGetCString(symbolNameRef, buffer, len, kCFStringEncodingUTF8) ) {
diags.error("Could not convert string to ASCII");
return (const char*)nullptr;
}
buffer[len] = '\0';
nonASCIIStrings.push_back(buffer);
return nonASCIIStrings.back().c_str();
};
uint64_t symbolSetsSize = 0;
const void* symbolSetsContent = kernelMA->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize);
if ( symbolSetsContent != nullptr ) {
struct AutoReleaseTypeRef {
AutoReleaseTypeRef() = default;
~AutoReleaseTypeRef() {
if ( ref != nullptr ) {
CFRelease(ref);
}
}
void setRef(CFTypeRef typeRef) {
assert(ref == nullptr);
ref = typeRef;
}
CFTypeRef ref = nullptr;
};
AutoReleaseTypeRef dataRefReleaser;
AutoReleaseTypeRef plistRefReleaser;
CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)symbolSetsContent, symbolSetsSize, kCFAllocatorNull);
if ( dataRef == nullptr ) {
diag.error("Could not create data ref for symbol sets");
return false;
}
dataRefReleaser.setRef(dataRef);
CFErrorRef errorRef = nullptr;
CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, dataRef, kCFPropertyListImmutable, nullptr, &errorRef);
if (errorRef != nullptr) {
CFStringRef errorString = CFErrorCopyDescription(errorRef);
diag.error("Could not load plist because :%s",
CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
CFRelease(errorRef);
return false;
}
if ( plistRef == nullptr ) {
diag.error("Could not create plist ref for symbol sets");
return false;
}
plistRefReleaser.setRef(plistRef);
if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) {
diag.error("Symbol set plist should be a dictionary");
return false;
}
CFDictionaryRef symbolSetsDictRef = (CFDictionaryRef)plistRef;
CFArrayRef symbolSetArrayRef = (CFArrayRef)CFDictionaryGetValue(symbolSetsDictRef, CFSTR("SymbolsSets"));
if ( symbolSetArrayRef != nullptr ) {
if ( CFGetTypeID(symbolSetArrayRef) != CFArrayGetTypeID() ) {
diag.error("SymbolsSets value should be an array");
return false;
}
for (CFIndex symbolSetIndex = 0; symbolSetIndex != CFArrayGetCount(symbolSetArrayRef); ++symbolSetIndex) {
CFDictionaryRef symbolSetDictRef = (CFDictionaryRef)CFArrayGetValueAtIndex(symbolSetArrayRef, symbolSetIndex);
if ( CFGetTypeID(symbolSetDictRef) != CFDictionaryGetTypeID() ) {
diag.error("Symbol set element should be a dictionary");
return false;
}
CFStringRef bundleIDRef = (CFStringRef)CFDictionaryGetValue(symbolSetDictRef, CFSTR("CFBundleIdentifier"));
if ( (bundleIDRef == nullptr) || (CFGetTypeID(bundleIDRef) != CFStringGetTypeID()) ) {
diag.error("Symbol set bundle ID should be a string");
return false;
}
const char* dylibID = getString(diag, bundleIDRef);
if ( dylibID == nullptr )
return false;
existingBundles.insert(dylibID);
}
}
}
return true;
};
auto addExistingBundleIDs = ^(const char* path, bool isBaseKC) {
char fileRealPath[MAXPATHLEN];
auto kernelCollectionLoadedFileInfo = MachOAnalyzer::load(diag, fileSystem, path, archs, platform, fileRealPath);
if ( diag.hasError() ) {
fprintf(stderr, "Could not load file '%s' because: %s\n", path, diag.errorMessage().c_str());
return false;
}
const MachOAppCache* appCacheMA = (const MachOAppCache*)kernelCollectionLoadedFileInfo.fileContent;
if (appCacheMA == nullptr) {
fprintf(stderr, "Could not load file: %s\n", path);
return false;
}
if ( !appCacheMA->isFileSet() ) {
fprintf(stderr, "kernel collection is not a cache file: %s\n", path);
return false;
}
__block const dyld3::MachOAnalyzer* kernelMA = nullptr;
appCacheMA->forEachDylib(diag, ^(const MachOAnalyzer *ma, const char *name, bool &stop) {
if ( ma->isStaticExecutable() ) {
kernelMA = ma;
}
existingBundles.insert(name);
});
if ( isBaseKC ) {
if ( !addSymbolSetsBundleIDs(kernelMA) )
return false;
}
fileSystem.unloadFile(kernelCollectionLoadedFileInfo);
return true;
};
if ( options.collectionKind == baseKC ) {
char fileRealPath[MAXPATHLEN];
auto kernelLoadedFileInfo = MachOAnalyzer::load(diag, fileSystem, options.kernelPath, archs, platform, fileRealPath);
if ( diag.hasError() ) {
fprintf(stderr, "Could not load file '%s' because: %s\n", options.kernelPath, diag.errorMessage().c_str());
return {};
}
const MachOAppCache* kernelMA = (const MachOAppCache*)kernelLoadedFileInfo.fileContent;
if (kernelMA == nullptr) {
fprintf(stderr, "Could not load file: %s\n", options.kernelPath);
return {};
}
if ( !kernelMA->isStaticExecutable() ) {
fprintf(stderr, "kernel is not a static executable: %s\n", options.kernelPath);
return {};
}
if ( !addSymbolSetsBundleIDs(kernelMA) )
return {};
fileSystem.unloadFile(kernelLoadedFileInfo);
}
if ( (options.collectionKind == auxKC) || (options.collectionKind == pageableKC) ) {
if ( !addExistingBundleIDs(options.kernelCollectionPath, true) )
return {};
}
if ( options.pageableCollectionPath != nullptr ) {
if ( !addExistingBundleIDs(options.pageableCollectionPath, false) )
return {};
}
std::set<std::string> processedBundleIDs;
std::list<const char*> bundleIDsToLoad;
bundleIDsToLoad.insert(bundleIDsToLoad.end(), options.bundleIDs.begin(), options.bundleIDs.end());
while (!bundleIDsToLoad.empty()) {
std::string bundleID = bundleIDsToLoad.front();
bundleIDsToLoad.pop_front();
std::string stripModeString;
if ( const char* colonPos = strchr(bundleID.c_str(), ':') ) {
stripModeString = colonPos + 1;
bundleID.erase(colonPos - bundleID.data());
}
if (!processedBundleIDs.insert(bundleID).second)
continue;
auto it = foundBundles.find(bundleID);
if (it == foundBundles.end()) {
fprintf(stderr, "[WARNING]: Could not find bundle with ID '%s'\n", bundleID.c_str());
continue;
}
BundleData& bundleData = it->second;
LoadedFileInfo info;
bool isCodeless = bundleData.executablePath.empty();
if ( !isCodeless ) {
char realerPath[MAXPATHLEN];
bool loadedFile = fileSystem.loadFile(bundleData.executablePath.c_str(), info, realerPath,
^(const char *format, ...) {
va_list list;
va_start(list, format);
diag.error(format, list);
va_end(list);
});
if ( !loadedFile )
return {};
}
std::vector<const char*> deps;
for (const std::string& dependency : bundleData.dependencies)
deps.push_back(dependency.c_str());
CFStringRef kextPathStringRef = nullptr;
CFDataRef kextDataRef = nullptr;
if ( !isCodeless) {
kextPathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, bundleData.executablePath.c_str(), kCFStringEncodingASCII);
kextDataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)info.fileContent, info.fileContentLen, kCFAllocatorNull);
}
CFMutableArrayRef kextDepsArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, bundleData.dependencies.size(), &kCFTypeArrayCallBacks);
for (const std::string& dependency : bundleData.dependencies) {
CFStringRef depStringRef = CFStringCreateWithCString(kCFAllocatorDefault, dependency.c_str(), kCFStringEncodingASCII);
CFArrayAppendValue(kextDepsArrayRef, depStringRef);
CFRelease(depStringRef);
}
CFStringRef kextBundleIDStringRef = CFStringCreateWithCString(kCFAllocatorDefault, bundleID.c_str(), kCFStringEncodingASCII);
CFStringRef kextBundlePathStringRef = CFStringCreateWithCString(kCFAllocatorDefault, bundleData.bundlePath.c_str(), kCFStringEncodingASCII);
BinaryStripMode stripMode = binaryStripNone;
if ( !stripModeString.empty() ) {
if ( stripModeString == "locals" ) {
stripMode = binaryStripLocals;
} else if ( stripModeString == "exports" ) {
stripMode = binaryStripExports;
} else if ( stripModeString == "all" ) {
stripMode = binaryStripAll;
} else {
diag.error("Unknown strip mode: '%s'", stripModeString.c_str());
return {};
}
}
KextFileData_v1 fileData = { 1, kextPathStringRef, kextDataRef,
kextDepsArrayRef, kextBundleIDStringRef, kextBundlePathStringRef,
bundleData.infoPlist, stripMode };
if ( !addKextDataFile(kcb, &fileData) ) {
uint64_t errorCount = 0;
const char* const* errors = getErrors(kcb, &errorCount);
for (uint64_t i = 0; i != errorCount; ++i)
diag.error("Could not load kext file because: '%s'", errors[i]);
return {};
}
for (const std::string& dependency : bundleData.dependencies) {
if ( existingBundles.find(dependency) == existingBundles.end() )
bundleIDsToLoad.push_back(dependency.c_str());
}
}
#if 0
const std::map<std::string, BundleData>* foundBundlesPtr = &foundBundles;
for (AppCacheBuilder::InputDylib& file : loadedFiles) {
file.dylibDeps.erase(std::remove_if(file.dylibDeps.begin(), file.dylibDeps.end(),
[&](const std::string& depName) {
auto it = foundBundlesPtr->find(depName);
assert(it != foundBundlesPtr->end());
return it->second.executablePath.empty();
}),file.dylibDeps.end());
}
#endif
}
#if 0
for (AppCacheBuilder::InputDylib& file : loadedFiles) {
char fileRealPath[MAXPATHLEN];
const char* path = file.dylib.loadedFileInfo.path;
LoadedFileInfo loadedFileInfo = MachOAnalyzer::load(diag, fileSystem, path, archs, platform, fileRealPath);
if ( diag.hasError() ) {
fprintf(stderr, "Could not load file '%s' because: %s\n", path, diag.errorMessage().c_str());
return {};
}
MachOAnalyzer* ma = (MachOAnalyzer*)loadedFileInfo.fileContent;
if (ma == nullptr) {
fprintf(stderr, "Could not load file: %s\n", path);
return {};
}
auto errorHandler = ^(const char* msg) {
diag.error("Binary located at '%s' cannot be placed in kernel collection because: %s", path, msg);
};
if (ma->canBePlacedInKernelCollection(path, errorHandler)) {
DyldSharedCache::MappedMachO mappedFile(path, ma, loadedFileInfo.sliceLen, false, false,
loadedFileInfo.sliceOffset, loadedFileInfo.mtime,
loadedFileInfo.inode);
CacheBuilder::LoadedMachO loadedMachO = { mappedFile, loadedFileInfo, nullptr };
file.dylib = loadedMachO;
} else {
fileSystem.unloadFile(loadedFileInfo);
}
if ( diag.hasError() ) {
fprintf(stderr, "%s\n", diag.errorMessage().c_str());
return {};
}
}
#endif
#if 0
if (loadedFiles.empty()) {
fprintf(stderr, "Could not find any valid files to create kernel collection\n");
if (!diag.warnings().empty()) {
fprintf(stderr, "Failed to use the following files:\n");
for (const std::string& msg : diag.warnings()) {
fprintf(stderr, " %s\n", msg.c_str());
}
}
return {};
}
if (options.verbose) {
for (const AppCacheBuilder::InputDylib& loadedFile : loadedFiles)
fprintf(stderr, "Building cache with file: %s\n", loadedFile.dylib.loadedFileInfo.path);
}
#endif
for (const SectionData& sectData : options.sections) {
CFStringRef segmentName = CFStringCreateWithCString(kCFAllocatorDefault, sectData.segmentName, kCFStringEncodingASCII);
CFStringRef sectionName = nullptr;
if ( sectData.sectionName != nullptr )
sectionName = CFStringCreateWithCString(kCFAllocatorDefault, sectData.sectionName, kCFStringEncodingASCII);
CFDataRef sectionData = nullptr;
{
struct stat stat_buf;
int fd = ::open(sectData.payloadFilePath, O_RDONLY, 0);
if (fd == -1) {
diag.error("can't open file '%s', errno=%d\n", sectData.payloadFilePath, errno);
return {};
}
if (fstat(fd, &stat_buf) == -1) {
diag.error("can't stat open file '%s', errno=%d\n", sectData.payloadFilePath, errno);
::close(fd);
return {};
}
const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buffer == MAP_FAILED) {
diag.error("mmap() for file at %s failed, errno=%d\n", sectData.payloadFilePath, errno);
::close(fd);
return {};
}
::close(fd);
sectionData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)buffer, stat_buf.st_size, kCFAllocatorNull);
}
if ( !addSegmentData(kcb, segmentName, sectionName, sectionData) ) {
uint64_t errorCount = 0;
const char* const* errors = getErrors(kcb, &errorCount);
for (uint64_t i = 0; i != errorCount; ++i)
diag.error("Could not load section data file because: '%s'", errors[i]);
return {};
}
}
if ( options.prelinkInfoExtraData != nullptr ) {
struct stat stat_buf;
int fd = ::open(options.prelinkInfoExtraData, O_RDONLY, 0);
if (fd == -1) {
diag.error("can't open file '%s', errno=%d\n", options.prelinkInfoExtraData, errno);
return {};
}
if (fstat(fd, &stat_buf) == -1) {
diag.error("can't stat open file '%s', errno=%d\n", options.prelinkInfoExtraData, errno);
::close(fd);
return {};
}
const void* buffer = mmap(NULL, (size_t)stat_buf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buffer == MAP_FAILED) {
diag.error("mmap() for file at %s failed, errno=%d\n", options.prelinkInfoExtraData, errno);
::close(fd);
return {};
}
::close(fd);
CFDataRef prelinkInfoData = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)buffer, stat_buf.st_size, kCFAllocatorNull);
CFErrorRef errorRef = nullptr;
CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, prelinkInfoData, kCFPropertyListImmutable, nullptr, &errorRef);
if (errorRef != nullptr) {
CFStringRef errorString = CFErrorCopyDescription(errorRef);
diag.error("Could not load prelink info plist because :%s",
CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
CFRelease(errorRef);
return {};
}
if ( plistRef == nullptr ) {
diag.error("Could not create plist ref for prelink info");
return {};
}
if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) {
diag.error("Prelink info plist should be a dictionary");
return {};
}
if ( !addPrelinkInfo(kcb, (CFDictionaryRef)plistRef) ) {
uint64_t errorCount = 0;
const char* const* errors = getErrors(kcb, &errorCount);
for (uint64_t i = 0; i != errorCount; ++i)
diag.error("Could not prelink data file because: '%s'", errors[i]);
return {};
}
}
bool success = runKernelCollectionBuilder(kcb);
uint64_t errorCount = 0;
const char* const* errors = getErrors(kcb, &errorCount);
if ( errors != nullptr ) {
if ( !options.printJSONErrors ) {
for (uint64_t i = 0; i != errorCount; ++i) {
fprintf(stderr, "Could not build kernel collection because '%s'\n", errors[i]);
}
}
CFDictionaryRef errorDictRef = getKextErrors(kcb);
if ( errorDictRef != nullptr ) {
Node rootNode;
CFDictionaryApplyFunction(errorDictRef, [](const void *key, const void *value, void *context) {
Node* rootNode = (Node*)context;
CFStringRef keyRef = (CFStringRef)key;
CFArrayRef valueRef = (CFArrayRef)value;
Node bundleNode;
bundleNode.map["id"] = Node(CFStringGetCStringPtr(keyRef, kCFStringEncodingASCII));
Node errorsNode;
CFArrayApplyFunction(valueRef, CFRangeMake(0, CFArrayGetCount(valueRef)), [](const void *value, void *context) {
Node* errorsNode = (Node*)context;
CFStringRef valueRef = (CFStringRef)value;
errorsNode->array.push_back(Node(CFStringGetCStringPtr(valueRef, kCFStringEncodingASCII)));
}, &errorsNode);
bundleNode.map["errors"] = errorsNode;
rootNode->array.push_back(bundleNode);
}, &rootNode);
std::sort(rootNode.array.begin(), rootNode.array.end(),
[](const Node& a, const Node&b) {
return a.map.find("id")->second.value < b.map.find("id")->second.value;
});
printJSON(rootNode);
} else {
Node rootNode;
for (uint64_t i = 0; i != errorCount; ++i) {
rootNode.array.push_back(Node(errors[i]));
}
printJSON(rootNode);
}
return {};
}
if ( !success )
return {};
uint64_t fileResultCount = 0;
const auto* fileResults = getCollectionFileResults(kcb, &fileResultCount);
if ( fileResults == nullptr ) {
fprintf(stderr, "Could not get file results\n");
return {};
}
if ( fileResultCount != 1 ) {
fprintf(stderr, "Unsupported file result count: %lld\n", fileResultCount);
return {};
}
CFDataRef dataRef = fileResults[0]->data;
CFRetain(dataRef);
destroyKernelCollectionBuilder(kcb);
return dataRef;
}
static int createKernelCollection(const CreateKernelCollectionOptions& options) {
if (gOpts.archs.empty()) {
exit_usage("-arch");
} else {
std::set<std::string_view> archs(gOpts.archs.begin(), gOpts.archs.end());
if (archs.size() != gOpts.archs.size()) {
fprintf(stderr, "Duplicate -arch specified\n");
exit(1);
}
}
if (options.outputCachePath == nullptr)
exit_usage();
switch (options.stripMode) {
case unknownStripMode:
case stripNone:
break;
case stripAll:
case stripAllKexts:
if ( options.collectionKind != baseKC ) {
fprintf(stderr, "Cannot use -strip-all-kexts with auxKC. Use strip-all instead\n");
exit(1);
}
break;
}
switch (options.collectionKind) {
case unknownKC:
fprintf(stderr, "Invalid kernel collection kind\n");
exit(1);
case baseKC:
if (options.kernelPath == nullptr)
exit_usage("-kernel");
break;
case pageableKC:
case auxKC:
if (options.kernelCollectionPath == nullptr)
exit_usage("-kernel-collection");
break;
}
if ( !options.bundleIDs.empty() ) {
if (options.extensionsPath == nullptr)
exit_usage("-extensions");
}
if ( options.extensionsPath != nullptr ) {
if ( strncmp(options.extensionsPath, options.volumeRoot, strlen(options.volumeRoot)) != 0 ) {
fprintf(stderr, "Volume root '%s' is not a prefix of extensions path '%s'\n",
options.volumeRoot, options.extensionsPath);
}
}
std::vector<CFDataRef> buffers;
for (const char* arch : gOpts.archs) {
Diagnostics diag;
CFDataRef bufferRef = createKernelCollectionForArch(options, arch, diag);
if ( diag.hasError() ) {
fprintf(stderr, "%s\n", diag.errorMessage().c_str());
return 1;
}
if ( bufferRef == nullptr ) {
if ( options.printJSONErrors )
return 0;
return 1;
}
buffers.push_back(bufferRef);
}
if (buffers.size() == 1) {
CFDataRef bufferRef = buffers.front();
if ( !safeSave(CFDataGetBytePtr(bufferRef), CFDataGetLength(bufferRef), options.outputCachePath) ) {
fprintf(stderr, "Could not write app cache\n");
return 1;
}
CFRelease(bufferRef);
} else {
std::vector<uint8_t> fatBuffer;
fatBuffer.resize(0x4000, 0);
fat_header* header = (fat_header*)&fatBuffer.front();
header->magic = OSSwapHostToBigInt32(FAT_MAGIC);
header->nfat_arch = OSSwapHostToBigInt32((uint32_t)buffers.size());
for (uint32_t i = 0; i != buffers.size(); ++i) {
CFDataRef bufferRef = buffers[i];
mach_header* mh = (mach_header*)CFDataGetBytePtr(bufferRef);
uint32_t offsetInBuffer = (uint32_t)fatBuffer.size();
fat_arch* archBuffer = (fat_arch*)(&fatBuffer.front() + sizeof(fat_header));
archBuffer[i].cputype = OSSwapHostToBigInt32(mh->cputype);
archBuffer[i].cpusubtype = OSSwapHostToBigInt32(mh->cpusubtype);
archBuffer[i].offset = OSSwapHostToBigInt32(offsetInBuffer);
archBuffer[i].size = OSSwapHostToBigInt32((uint32_t)CFDataGetLength(bufferRef));
archBuffer[i].align = OSSwapHostToBigInt32(14);
auto align = [](uint64_t addr, uint8_t p2) {
uint64_t mask = (1 << p2);
return (addr + mask - 1) & (-mask);
};
uint32_t alignedSize = (uint32_t)align((uint32_t)CFDataGetLength(bufferRef), 14);
fatBuffer.resize(fatBuffer.size() + alignedSize, 0);
memcpy(&fatBuffer.front() + offsetInBuffer, CFDataGetBytePtr(bufferRef), CFDataGetLength(bufferRef));
}
if ( !safeSave(&fatBuffer.front(), fatBuffer.size(), options.outputCachePath) ) {
fprintf(stderr, "Could not write app cache\n");
return 1;
}
}
return 0;
}
int main(int argc, const char* argv[]) {
OptionsVariants options;
if (!parseArgs(argc, argv, options))
return 1;
if (std::holds_alternative<DumpOptions>(options)) {
return dumpAppCache(std::get<DumpOptions>(options));
}
if (std::holds_alternative<ValidateOptions>(options)) {
return validateFile(std::get<ValidateOptions>(options));
}
if (std::holds_alternative<ListBundlesOptions>(options)) {
return listBundles(std::get<ListBundlesOptions>(options));
}
if (std::holds_alternative<CreateKernelCollectionOptions>(options)) {
return createKernelCollection(std::get<CreateKernelCollectionOptions>(options));
}
assert(std::holds_alternative<std::monostate>(options));
exit_usage();
}