AppCacheBuilder.cpp [plain text]
#include "AppCacheBuilder.h"
#include <mach/mach_time.h>
#include <sys/stat.h>
#include <CoreFoundation/CFArray.h>
#include <CoreFoundation/CFError.h>
#include <CoreFoundation/CFNumber.h>
#include <CoreFoundation/CFPropertyList.h>
#include <CoreFoundation/CFString.h>
#include <CommonCrypto/CommonHMAC.h>
#include <CommonCrypto/CommonDigest.h>
#include <CommonCrypto/CommonDigestSPI.h>
AppCacheBuilder::AppCacheBuilder(const DyldSharedCache::CreateOptions& options,
const Options& appCacheOptions,
const dyld3::closure::FileSystem& fileSystem)
: CacheBuilder(options, fileSystem), appCacheOptions(appCacheOptions)
{
_is64 = true;
}
AppCacheBuilder::~AppCacheBuilder() {
if (prelinkInfoDict) {
CFRelease(prelinkInfoDict);
}
if (_fullAllocatedBuffer) {
vm_deallocate(mach_task_self(), _fullAllocatedBuffer, _allocatedBufferSize);
}
}
void AppCacheBuilder::makeSortedDylibs(const std::vector<InputDylib>& dylibs)
{
for (const InputDylib& file : dylibs) {
if ( file.dylib.loadedFileInfo.fileContent == nullptr ) {
codelessKexts.push_back(file);
} else {
AppCacheDylibInfo& dylibInfo = sortedDylibs.emplace_back();
dylibInfo.input = &file.dylib;
dylibInfo.dylibID = file.dylibID;
dylibInfo.dependencies = file.dylibDeps;
dylibInfo.infoPlist = file.infoPlist;
dylibInfo.errors = file.errors;
dylibInfo.bundlePath = file.bundlePath;
dylibInfo.stripMode = file.stripMode;
}
}
std::sort(sortedDylibs.begin(), sortedDylibs.end(), [&](const DylibInfo& a, const DylibInfo& b) {
bool isStaticExecutableA = a.input->mappedFile.mh->isStaticExecutable();
bool isStaticExecutableB = b.input->mappedFile.mh->isStaticExecutable();
if (isStaticExecutableA != isStaticExecutableB)
return isStaticExecutableA;
bool splitSegA = a.input->mappedFile.mh->hasSplitSeg();
bool splitSegB = b.input->mappedFile.mh->hasSplitSeg();
if (splitSegA != splitSegB)
return splitSegA;
return a.input->mappedFile.runtimePath < b.input->mappedFile.runtimePath;
});
std::sort(codelessKexts.begin(), codelessKexts.end(), [&](const InputDylib& a, const InputDylib& b) {
return a.dylibID < b.dylibID;
});
}
void AppCacheBuilder::forEachCacheDylib(void (^callback)(const dyld3::MachOAnalyzer* ma,
const std::string& dylibID,
DylibStripMode stripMode,
const std::vector<std::string>& dependencies,
Diagnostics& dylibDiag,
bool& stop)) const {
bool stop = false;
for (const AppCacheDylibInfo& dylib : sortedDylibs) {
for (const SegmentMappingInfo& loc : dylib.cacheLocation) {
if (!strcmp(loc.segName, "__TEXT")) {
callback((const dyld3::MachOAnalyzer*)loc.dstSegment, dylib.dylibID, dylib.stripMode,
dylib.dependencies, *dylib.errors, stop);
break;
}
}
if (stop)
break;
}
}
void AppCacheBuilder::forEachDylibInfo(void (^callback)(const DylibInfo& dylib, Diagnostics& dylibDiag)) {
for (const AppCacheDylibInfo& dylibInfo : sortedDylibs)
callback(dylibInfo, *dylibInfo.errors);
}
const CacheBuilder::DylibInfo* AppCacheBuilder::getKernelStaticExecutableInputFile() const {
for (const auto& dylib : sortedDylibs) {
const dyld3::MachOAnalyzer* ma = dylib.input->mappedFile.mh;
if ( ma->isStaticExecutable() )
return &dylib;
}
return nullptr;
}
const dyld3::MachOAnalyzer* AppCacheBuilder::getKernelStaticExecutableFromCache() const {
assert(appCacheOptions.cacheKind == Options::AppCacheKind::kernel);
__block const dyld3::MachOAnalyzer* kernelMA = nullptr;
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
DylibStripMode stripMode, const std::vector<std::string>& dependencies,
Diagnostics& dylibDiag,
bool& stop) {
if ( ma->isStaticExecutable() ) {
kernelMA = ma;
stop = true;
}
});
assert(kernelMA != nullptr);
return kernelMA;
}
void AppCacheBuilder::forEachRegion(void (^callback)(const Region& region)) const {
callback(cacheHeaderRegion);
callback(readOnlyTextRegion);
if ( readExecuteRegion.sizeInUse != 0 )
callback(readExecuteRegion);
if ( branchStubsRegion.bufferSize != 0 )
callback(branchStubsRegion);
if ( dataConstRegion.sizeInUse != 0 )
callback(dataConstRegion);
if ( branchGOTsRegion.bufferSize != 0 )
callback(branchGOTsRegion);
if ( readWriteRegion.sizeInUse != 0 )
callback(readWriteRegion);
if ( hibernateRegion.sizeInUse != 0 )
callback(hibernateRegion);
for (const Region& region : customDataRegions)
callback(region);
if ( prelinkInfoDict != nullptr )
callback(prelinkInfoRegion);
for (const Region& region : nonSplitSegRegions)
callback(region);
callback(_readOnlyRegion);
}
uint64_t AppCacheBuilder::numRegions() const {
__block uint64_t count = 0;
forEachRegion(^(const Region ®ion) {
++count;
});
return count;
}
uint64_t AppCacheBuilder::fixupsPageSize() const {
bool use4K = false;
use4K |= (_options.archs == &dyld3::GradedArchs::x86_64);
use4K |= (_options.archs == &dyld3::GradedArchs::x86_64h);
return use4K ? 4096 : 16384;
}
uint64_t AppCacheBuilder::numWritablePagesToFixup(uint64_t numBytesToFixup) const {
uint64_t pageSize = fixupsPageSize();
assert((numBytesToFixup % pageSize) == 0);
uint64_t numPagesToFixup = numBytesToFixup / pageSize;
return numPagesToFixup;
}
bool AppCacheBuilder::fixupsArePerKext() const {
if ( appCacheOptions.cacheKind == Options::AppCacheKind::pageableKC )
return true;
bool isX86 = (_options.archs == &dyld3::GradedArchs::x86_64) || (_options.archs == &dyld3::GradedArchs::x86_64h);
return isX86 && (appCacheOptions.cacheKind == Options::AppCacheKind::auxKC);
}
uint64_t AppCacheBuilder::numBranchRelocationTargets() {
bool mayHaveBranchRelocations = false;
mayHaveBranchRelocations |= (_options.archs == &dyld3::GradedArchs::x86_64);
mayHaveBranchRelocations |= (_options.archs == &dyld3::GradedArchs::x86_64h);
if ( !mayHaveBranchRelocations )
return 0;
switch (appCacheOptions.cacheKind) {
case Options::AppCacheKind::none:
case Options::AppCacheKind::kernel:
return 0;
case Options::AppCacheKind::pageableKC:
case Options::AppCacheKind::kernelCollectionLevel2:
case Options::AppCacheKind::auxKC:
break;
}
uint64_t totalTargets = 0;
for (const DylibInfo& dylib : sortedDylibs) {
typedef std::pair<std::string_view, int> Symbol;
struct SymbolHash
{
size_t operator() (const Symbol& symbol) const
{
return std::hash<std::string_view>{}(symbol.first) ^ std::hash<int>{}(symbol.second);
}
};
__block std::unordered_set<Symbol, SymbolHash> seenSymbols;
dylib.input->mappedFile.mh->forEachBind(_diagnostics,
^(uint64_t runtimeOffset, int libOrdinal, uint8_t type,
const char *symbolName, bool weakImport,
bool lazyBind, uint64_t addend, bool &stop) {
if ( type != BIND_TYPE_TEXT_PCREL32 )
return;
seenSymbols.insert({ symbolName, libOrdinal });
}, ^(const char *symbolName) {
});
totalTargets += seenSymbols.size();
}
return totalTargets;
}
void AppCacheBuilder::assignSegmentRegionsAndOffsets()
{
for (DylibInfo& dylib : sortedDylibs) {
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
dylib.cacheLocation.push_back({});
});
}
const dyld3::MachOAnalyzer* kernelMA = nullptr;
if ( appCacheOptions.cacheKind == Options::AppCacheKind::kernel ) {
for (DylibInfo& dylib : sortedDylibs) {
if ( dylib.input->mappedFile.mh->isStaticExecutable() ) {
kernelMA = dylib.input->mappedFile.mh;
break;
}
}
if ( kernelMA == nullptr ) {
_diagnostics.error("Could not find kernel image");
return;
}
cacheBaseAddress = kernelMA->preferredLoadAddress();
}
uint64_t branchTargetsFromKexts = numBranchRelocationTargets();
uint32_t minimumSegmentAlignmentP2 = 14;
if ( (_options.archs == &dyld3::GradedArchs::x86_64) || (_options.archs == &dyld3::GradedArchs::x86_64h) ) {
minimumSegmentAlignmentP2 = 12;
}
auto getMinAlignment = ^(const dyld3::MachOAnalyzer* ma) {
if ( ma == kernelMA )
return minimumSegmentAlignmentP2;
if ( fixupsArePerKext() )
return minimumSegmentAlignmentP2;
if ( _options.archs == &dyld3::GradedArchs::arm64e )
return minimumSegmentAlignmentP2;
return 0U;
};
{
__block uint64_t offsetInRegion = 0;
for (DylibInfo& dylib : sortedDylibs) {
bool canBePacked = dylib.input->mappedFile.mh->hasSplitSeg();
if (!canBePacked)
continue;
__block uint64_t textSegVmAddr = 0;
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
if ( strcmp(segInfo.segName, "__TEXT") == 0 )
textSegVmAddr = segInfo.vmAddr;
if ( segInfo.protections != (VM_PROT_READ) )
return;
if ( (strcmp(segInfo.segName, "__DATA_CONST") == 0)
|| (strcmp(segInfo.segName, "__PPLDATA_CONST") == 0)
|| (strcmp(segInfo.segName, "__LASTDATA_CONST") == 0) )
return;
if ( strcmp(segInfo.segName, "__LINKEDIT") == 0 )
return;
if ( strcmp(segInfo.segName, "__LINKINFO") == 0 )
return;
uint32_t minAlignmentP2 = getMinAlignment(dylib.input->mappedFile.mh);
size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
uint64_t dstCacheSegmentSize = align(segInfo.sizeOfSections, minAlignmentP2);
if ( strcmp(segInfo.segName, "__CTF") == 0 ) {
copySize = 0;
dstCacheSegmentSize = 0;
}
offsetInRegion = align(offsetInRegion, std::max(segInfo.p2align, 4U));
offsetInRegion = align(offsetInRegion, minAlignmentP2);
SegmentMappingInfo loc;
loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
loc.segName = segInfo.segName;
loc.dstSegment = nullptr;
loc.dstCacheUnslidAddress = offsetInRegion; loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
loc.dstCacheSegmentSize = (uint32_t)dstCacheSegmentSize;
loc.dstCacheFileSize = (uint32_t)copySize;
loc.copySegmentSize = (uint32_t)copySize;
loc.srcSegmentIndex = segInfo.segIndex;
loc.parentRegion = &readOnlyTextRegion;
dylib.cacheLocation[segInfo.segIndex] = loc;
offsetInRegion += dstCacheSegmentSize;
});
}
readOnlyTextRegion.bufferSize = align(offsetInRegion, 14);
readOnlyTextRegion.sizeInUse = readOnlyTextRegion.bufferSize;
readOnlyTextRegion.permissions = VM_PROT_READ;
readOnlyTextRegion.name = "__PRELINK_TEXT";
}
{
__block uint64_t offsetInRegion = 0;
for (DylibInfo& dylib : sortedDylibs) {
bool canBePacked = dylib.input->mappedFile.mh->hasSplitSeg();
if (!canBePacked)
continue;
__block uint64_t textSegVmAddr = 0;
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
if ( strcmp(segInfo.segName, "__TEXT") == 0 )
textSegVmAddr = segInfo.vmAddr;
if ( strcmp(segInfo.segName, "__HIB") == 0 )
return;
if ( segInfo.protections != (VM_PROT_READ | VM_PROT_EXECUTE) )
return;
uint32_t minAlignmentP2 = getMinAlignment(dylib.input->mappedFile.mh);
offsetInRegion = align(offsetInRegion, std::max(segInfo.p2align, 4U));
offsetInRegion = align(offsetInRegion, minAlignmentP2);
size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
uint64_t dstCacheSegmentSize = align(segInfo.sizeOfSections, minAlignmentP2);
SegmentMappingInfo loc;
loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
loc.segName = segInfo.segName;
loc.dstSegment = nullptr;
loc.dstCacheUnslidAddress = offsetInRegion; loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
loc.dstCacheSegmentSize = (uint32_t)dstCacheSegmentSize;
loc.dstCacheFileSize = (uint32_t)copySize;
loc.copySegmentSize = (uint32_t)copySize;
loc.srcSegmentIndex = segInfo.segIndex;
loc.parentRegion = &readExecuteRegion;
dylib.cacheLocation[segInfo.segIndex] = loc;
offsetInRegion += loc.dstCacheSegmentSize;
});
}
readExecuteRegion.bufferSize = align(offsetInRegion, 14);
readExecuteRegion.sizeInUse = readExecuteRegion.bufferSize;
readExecuteRegion.permissions = VM_PROT_READ | VM_PROT_EXECUTE;
readExecuteRegion.name = "__TEXT_EXEC";
}
if ( branchTargetsFromKexts != 0 ) {
branchStubsRegion.bufferSize = align(branchTargetsFromKexts * 6, 14);
branchStubsRegion.sizeInUse = branchStubsRegion.bufferSize;
branchStubsRegion.permissions = VM_PROT_READ | VM_PROT_EXECUTE;
branchStubsRegion.name = "__BRANCH_STUBS";
}
{
__block uint64_t offsetInRegion = 0;
for (DylibInfo& dylib : sortedDylibs) {
if (!dylib.input->mappedFile.mh->hasSplitSeg())
continue;
__block uint64_t textSegVmAddr = 0;
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
if ( strcmp(segInfo.segName, "__TEXT") == 0 )
textSegVmAddr = segInfo.vmAddr;
if ( (segInfo.protections & VM_PROT_EXECUTE) != 0 )
return;
if ( (strcmp(segInfo.segName, "__DATA_CONST") != 0)
&& (strcmp(segInfo.segName, "__PPLDATA_CONST") != 0)
&& (strcmp(segInfo.segName, "__LASTDATA_CONST") != 0) )
return;
uint32_t minAlignmentP2 = getMinAlignment(dylib.input->mappedFile.mh);
offsetInRegion = align(offsetInRegion, segInfo.p2align);
offsetInRegion = align(offsetInRegion, minAlignmentP2);
size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
uint64_t dstCacheSegmentSize = align(segInfo.sizeOfSections, minAlignmentP2);
SegmentMappingInfo loc;
loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
loc.segName = segInfo.segName;
loc.dstSegment = nullptr;
loc.dstCacheUnslidAddress = offsetInRegion; loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
loc.dstCacheSegmentSize = (uint32_t)dstCacheSegmentSize;
loc.dstCacheFileSize = (uint32_t)copySize;
loc.copySegmentSize = (uint32_t)copySize;
loc.srcSegmentIndex = segInfo.segIndex;
loc.parentRegion = &dataConstRegion;
dylib.cacheLocation[segInfo.segIndex] = loc;
offsetInRegion += loc.dstCacheSegmentSize;
});
}
dataConstRegion.bufferSize = align(offsetInRegion, 14);
dataConstRegion.sizeInUse = dataConstRegion.bufferSize;
dataConstRegion.permissions = VM_PROT_READ;
dataConstRegion.name = "__DATA_CONST";
}
if ( branchTargetsFromKexts != 0 ) {
branchGOTsRegion.bufferSize = align(branchTargetsFromKexts * 8, 14);
branchGOTsRegion.sizeInUse = branchGOTsRegion.bufferSize;
branchGOTsRegion.permissions = VM_PROT_READ | VM_PROT_WRITE;
branchGOTsRegion.name = "__BRANCH_GOTS";
}
{
__block uint64_t offsetInRegion = 0;
for (DylibInfo& dylib : sortedDylibs) {
if (!dylib.input->mappedFile.mh->hasSplitSeg())
continue;
__block uint64_t textSegVmAddr = 0;
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
if ( strcmp(segInfo.segName, "__TEXT") == 0 )
textSegVmAddr = segInfo.vmAddr;
if ( strcmp(segInfo.segName, "__HIB") == 0 )
return;
if ( (strcmp(segInfo.segName, "__DATA_CONST") == 0)
|| (strcmp(segInfo.segName, "__PPLDATA_CONST") == 0)
|| (strcmp(segInfo.segName, "__LASTDATA_CONST") == 0) )
return;
if ( segInfo.protections != (VM_PROT_READ | VM_PROT_WRITE) )
return;
uint32_t minAlignmentP2 = getMinAlignment(dylib.input->mappedFile.mh);
offsetInRegion = align(offsetInRegion, segInfo.p2align);
offsetInRegion = align(offsetInRegion, minAlignmentP2);
size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
uint64_t dstCacheSegmentSize = align(segInfo.sizeOfSections, minAlignmentP2);
SegmentMappingInfo loc;
loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
loc.segName = segInfo.segName;
loc.dstSegment = nullptr;
loc.dstCacheUnslidAddress = offsetInRegion; loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
loc.dstCacheSegmentSize = (uint32_t)dstCacheSegmentSize;
loc.dstCacheFileSize = (uint32_t)copySize;
loc.copySegmentSize = (uint32_t)copySize;
loc.srcSegmentIndex = segInfo.segIndex;
loc.parentRegion = &readWriteRegion;
dylib.cacheLocation[segInfo.segIndex] = loc;
offsetInRegion += loc.dstCacheSegmentSize;
});
}
readWriteRegion.bufferSize = align(offsetInRegion, 14);
readWriteRegion.sizeInUse = readWriteRegion.bufferSize;
readWriteRegion.permissions = VM_PROT_READ | VM_PROT_WRITE;
readWriteRegion.name = "__DATA";
}
{
__block uint64_t offsetInRegion = 0;
for (DylibInfo& dylib : sortedDylibs) {
if ( !dylib.input->mappedFile.mh->isStaticExecutable() )
continue;
__block uint64_t textSegVmAddr = 0;
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
if ( strcmp(segInfo.segName, "__TEXT") == 0 )
textSegVmAddr = segInfo.vmAddr;
if ( strcmp(segInfo.segName, "__HIB") != 0 )
return;
size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
SegmentMappingInfo loc;
loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
loc.segName = segInfo.segName;
loc.dstSegment = nullptr;
loc.dstCacheUnslidAddress = offsetInRegion; loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
loc.dstCacheSegmentSize = (uint32_t)segInfo.vmSize;
loc.dstCacheFileSize = (uint32_t)copySize;
loc.copySegmentSize = (uint32_t)copySize;
loc.srcSegmentIndex = segInfo.segIndex;
loc.parentRegion = &hibernateRegion;
dylib.cacheLocation[segInfo.segIndex] = loc;
offsetInRegion += loc.dstCacheSegmentSize;
hibernateAddress = segInfo.vmAddr;
});
break;
}
hibernateRegion.bufferSize = align(offsetInRegion, 14);
hibernateRegion.sizeInUse = hibernateRegion.bufferSize;
hibernateRegion.permissions = VM_PROT_READ | VM_PROT_WRITE | VM_PROT_EXECUTE;
hibernateRegion.name = "__HIB";
}
{
for (DylibInfo& dylib : sortedDylibs) {
bool canBePacked = dylib.input->mappedFile.mh->hasSplitSeg();
if (canBePacked)
continue;
__block uint64_t textSegVmAddr = 0;
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
if ( strcmp(segInfo.segName, "__TEXT") == 0 )
textSegVmAddr = segInfo.vmAddr;
if ( strcmp(segInfo.segName, "__LINKEDIT") == 0 )
return;
nonSplitSegRegions.emplace_back();
nonSplitSegRegions.back().permissions = segInfo.protections;
nonSplitSegRegions.back().name = "__REGION" + std::to_string(nonSplitSegRegions.size() - 1);
uint64_t offsetInRegion = 0;
SegmentMappingInfo loc;
loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
loc.segName = segInfo.segName;
loc.dstSegment = nullptr;
loc.dstCacheUnslidAddress = offsetInRegion; loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
loc.dstCacheSegmentSize = (uint32_t)segInfo.vmSize;
loc.dstCacheFileSize = (uint32_t)segInfo.fileSize;
loc.copySegmentSize = (uint32_t)segInfo.fileSize;
loc.srcSegmentIndex = segInfo.segIndex;
loc.parentRegion = &nonSplitSegRegions.back();
dylib.cacheLocation[segInfo.segIndex] = loc;
offsetInRegion += loc.dstCacheSegmentSize;
nonSplitSegRegions.back().bufferSize = offsetInRegion;
nonSplitSegRegions.back().sizeInUse = nonSplitSegRegions.back().bufferSize;
});
}
}
if ( !customSegments.empty() ) {
for (CustomSegment& segment: customSegments) {
uint64_t offsetInRegion = 0;
for (CustomSegment::CustomSection& section : segment.sections) {
section.offsetInRegion = offsetInRegion;
offsetInRegion += section.data.size();
}
Region& customRegion = customDataRegions.emplace_back();
segment.parentRegion = &customRegion;
customRegion.bufferSize = align(offsetInRegion, 14);
customRegion.sizeInUse = customRegion.bufferSize;
customRegion.permissions = VM_PROT_READ;
customRegion.name = segment.segmentName;
}
}
{
struct PrelinkInfo {
CFDictionaryRef infoPlist = nullptr;
const dyld3::MachOAnalyzer* ma = nullptr;
std::string_view bundlePath;
std::string_view executablePath;
};
std::vector<PrelinkInfo> infos;
for (AppCacheDylibInfo& dylib : sortedDylibs) {
if (dylib.infoPlist == nullptr)
continue;
infos.push_back({ dylib.infoPlist, dylib.input->mappedFile.mh, dylib.bundlePath, dylib.input->loadedFileInfo.path });
}
for (InputDylib& dylib : codelessKexts) {
infos.push_back({ dylib.infoPlist, nullptr, dylib.bundlePath, "" });
}
CFMutableArrayRef bundlesArrayRef = CFArrayCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeArrayCallBacks);
for (PrelinkInfo& info : infos) {
CFDictionaryRef infoPlist = info.infoPlist;
CFMutableDictionaryRef dictCopyRef = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, infoPlist);
CFStringRef bundlePath = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, info.bundlePath.data(),
kCFStringEncodingASCII, kCFAllocatorNull);
CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkBundlePath"), bundlePath);
CFRelease(bundlePath);
const uint64_t largeAddress = 0x7FFFFFFFFFFFFFFF;
CFNumberRef loadAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &largeAddress);
CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkExecutableLoadAddr"), loadAddrRef);
CFRelease(loadAddrRef);
if ( info.executablePath != "" ) {
const char* relativePath = info.executablePath.data();
if ( strncmp(relativePath, info.bundlePath.data(), info.bundlePath.size()) == 0 ) {
relativePath = relativePath + info.bundlePath.size();
if ( relativePath[0] == '/' )
++relativePath;
} else if ( const char* lastSlash = strrchr(relativePath, '/') )
relativePath = lastSlash+1;
CFStringRef executablePath = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, relativePath,
kCFStringEncodingASCII, kCFAllocatorNull);
CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkExecutableRelativePath"), executablePath);
CFRelease(executablePath);
}
__block uint64_t textSegFileSize = 0;
if ( info.ma != nullptr ) {
info.ma->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
if ( strcmp(segInfo.segName, "__TEXT") == 0 )
textSegFileSize = segInfo.fileSize;
});
}
if (textSegFileSize != 0) {
CFNumberRef fileSizeRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &textSegFileSize);
CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkExecutableSize"), fileSizeRef);
CFRelease(fileSizeRef);
}
CFNumberRef sourceAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &largeAddress);
CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkExecutableSourceAddr"), sourceAddrRef);
CFRelease(sourceAddrRef);
dyld3::MachOAnalyzer::FoundSymbol foundInfo;
if ( (info.ma != nullptr) ) {
__block bool found = false;
found = info.ma->findExportedSymbol(_diagnostics, "_kmod_info", true, foundInfo, nullptr);
if ( !found ) {
info.ma->forEachLocalSymbol(_diagnostics, ^(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 ) {
found = true;
stop = true;
}
});
}
if ( found ) {
CFNumberRef kmodInfoAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &largeAddress);
CFDictionarySetValue(dictCopyRef, CFSTR("_PrelinkKmodInfo"), kmodInfoAddrRef);
CFRelease(kmodInfoAddrRef);
}
}
CFArrayAppendValue(bundlesArrayRef, dictCopyRef);
CFRelease(dictCopyRef);
}
prelinkInfoDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
if ( extraPrelinkInfo != nullptr ) {
CFDictionaryApplierFunction applier = [](const void *key, const void *value, void *context) {
CFMutableDictionaryRef parentDict = (CFMutableDictionaryRef)context;
CFDictionaryAddValue(parentDict, key, value);
};
CFDictionaryApplyFunction(extraPrelinkInfo, applier, (void*)prelinkInfoDict);
}
if ( bundlesArrayRef != nullptr ) {
CFDictionaryAddValue(prelinkInfoDict, CFSTR("_PrelinkInfoDictionary"), bundlesArrayRef);
CFRelease(bundlesArrayRef);
}
{
uuid_t uuid = {};
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (const uint8_t*)&uuid, sizeof(uuid));
CFDictionaryAddValue(prelinkInfoDict, CFSTR("_PrelinkKCID"), dataRef);
CFRelease(dataRef);
}
if ( existingKernelCollection != nullptr ) {
uuid_t uuid = {};
bool foundUUID = existingKernelCollection->getUuid(uuid);
if ( !foundUUID ) {
_diagnostics.error("Could not find UUID in base kernel collection");
return;
}
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (const uint8_t*)&uuid, sizeof(uuid));
CFDictionaryAddValue(prelinkInfoDict, CFSTR("_BootKCID"), dataRef);
CFRelease(dataRef);
}
if ( pageableKernelCollection != nullptr ) {
uuid_t uuid = {};
bool foundUUID = pageableKernelCollection->getUuid(uuid);
if ( !foundUUID ) {
_diagnostics.error("Could not find UUID in pageable kernel collection");
return;
}
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, (const uint8_t*)&uuid, sizeof(uuid));
CFDictionaryAddValue(prelinkInfoDict, CFSTR("_PageableKCID"), dataRef);
CFRelease(dataRef);
}
CFErrorRef errorRef = nullptr;
CFDataRef xmlData = CFPropertyListCreateData(kCFAllocatorDefault, prelinkInfoDict,
kCFPropertyListXMLFormat_v1_0, 0, &errorRef);
if (errorRef != nullptr) {
CFStringRef errorString = CFErrorCopyDescription(errorRef);
_diagnostics.error("Could not serialise plist because :%s",
CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
CFRelease(xmlData);
CFRelease(errorRef);
return;
} else {
CFIndex xmlDataLength = CFDataGetLength(xmlData);
CFRelease(xmlData);
prelinkInfoRegion.bufferSize = align(xmlDataLength, 14);
prelinkInfoRegion.sizeInUse = prelinkInfoRegion.bufferSize;
prelinkInfoRegion.permissions = VM_PROT_READ | VM_PROT_WRITE;
prelinkInfoRegion.name = "__PRELINK_INFO";
}
}
_nonLinkEditReadOnlySize = 0;
__block uint64_t offsetInRegion = 0;
for (DylibInfo& dylib : sortedDylibs) {
__block uint64_t textSegVmAddr = 0;
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
if ( strcmp(segInfo.segName, "__TEXT") == 0 )
textSegVmAddr = segInfo.vmAddr;
if ( segInfo.protections != VM_PROT_READ )
return;
if ( strcmp(segInfo.segName, "__LINKINFO") != 0 )
return;
offsetInRegion = align(offsetInRegion, std::max((int)segInfo.p2align, (int)12));
size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
SegmentMappingInfo loc;
loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
loc.segName = segInfo.segName;
loc.dstSegment = nullptr;
loc.dstCacheUnslidAddress = offsetInRegion; loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12);
loc.dstCacheFileSize = (uint32_t)copySize;
loc.copySegmentSize = (uint32_t)copySize;
loc.srcSegmentIndex = segInfo.segIndex;
loc.parentRegion = &_readOnlyRegion;
dylib.cacheLocation[segInfo.segIndex] = loc;
offsetInRegion += loc.dstCacheSegmentSize;
});
}
offsetInRegion = align(offsetInRegion, 14);
_nonLinkEditReadOnlySize = offsetInRegion;
for (DylibInfo& dylib : sortedDylibs) {
__block uint64_t textSegVmAddr = 0;
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
if ( strcmp(segInfo.segName, "__TEXT") == 0 )
textSegVmAddr = segInfo.vmAddr;
if ( segInfo.protections != VM_PROT_READ )
return;
if ( strcmp(segInfo.segName, "__LINKEDIT") != 0 )
return;
offsetInRegion = align(offsetInRegion, std::max((int)segInfo.p2align, (int)12));
size_t copySize = std::min((size_t)segInfo.fileSize, (size_t)segInfo.sizeOfSections);
SegmentMappingInfo loc;
loc.srcSegment = (uint8_t*)dylib.input->mappedFile.mh + segInfo.vmAddr - textSegVmAddr;
loc.segName = segInfo.segName;
loc.dstSegment = nullptr;
loc.dstCacheUnslidAddress = offsetInRegion; loc.dstCacheFileOffset = (uint32_t)offsetInRegion;
loc.dstCacheSegmentSize = (uint32_t)align(segInfo.sizeOfSections, 12);
loc.dstCacheFileSize = (uint32_t)copySize;
loc.copySegmentSize = (uint32_t)copySize;
loc.srcSegmentIndex = segInfo.segIndex;
loc.parentRegion = &_readOnlyRegion;
dylib.cacheLocation[segInfo.segIndex] = loc;
offsetInRegion += loc.dstCacheSegmentSize;
});
}
_readOnlyRegion.bufferSize = align(offsetInRegion, 14);
_readOnlyRegion.sizeInUse = _readOnlyRegion.bufferSize;
_readOnlyRegion.permissions = VM_PROT_READ;
_readOnlyRegion.name = "__LINKEDIT";
{
__block uint64_t numSegmentsForChainedFixups = 0;
uint64_t numChainedFixupHeaders = 0;
if ( fixupsArePerKext() ) {
for (DylibInfo& dylib : sortedDylibs) {
dylib.input->mappedFile.mh->forEachSegment(^(const dyld3::MachOFile::SegmentInfo& segInfo, bool& stop) {
++numSegmentsForChainedFixups;
});
}
numChainedFixupHeaders = sortedDylibs.size();
numSegmentsForChainedFixups += numRegions();
numChainedFixupHeaders++;
} else {
numSegmentsForChainedFixups = numRegions();
numChainedFixupHeaders = 1;
}
uint64_t numBytesForPageStarts = 0;
if ( dataConstRegion.sizeInUse != 0 )
numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(dataConstRegion.bufferSize));
if ( branchGOTsRegion.bufferSize != 0 )
numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(branchGOTsRegion.bufferSize));
if ( readWriteRegion.sizeInUse != 0 )
numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(readWriteRegion.bufferSize));
if ( hibernateRegion.sizeInUse != 0 )
numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(hibernateRegion.bufferSize));
for (const Region& region : nonSplitSegRegions) {
numBytesForPageStarts += sizeof(dyld_chained_starts_in_segment) + (sizeof(uint16_t) * numWritablePagesToFixup(region.bufferSize));
}
uint64_t numBytesForChainedFixups = 0;
if ( numBytesForPageStarts != 0 ) {
numBytesForChainedFixups = numBytesForPageStarts;
numBytesForChainedFixups += sizeof(dyld_chained_fixups_header) * numChainedFixupHeaders;
numBytesForChainedFixups += sizeof(dyld_chained_starts_in_image) * numChainedFixupHeaders;
numBytesForChainedFixups += sizeof(uint32_t) * numSegmentsForChainedFixups;
}
__block uint64_t numBytesForClassicRelocs = 0;
if ( appCacheOptions.cacheKind == Options::AppCacheKind::kernel ) {
if ( const DylibInfo* dylib = getKernelStaticExecutableInputFile() ) {
if ( dylib->input->mappedFile.mh->usesClassicRelocationsInKernelCollection() ) {
dylib->input->mappedFile.mh->forEachRebase(_diagnostics, false, ^(uint64_t runtimeOffset, bool &stop) {
numBytesForClassicRelocs += sizeof(relocation_info);
});
}
}
}
if ( (numBytesForChainedFixups != 0) || (numBytesForClassicRelocs != 0) ) {
uint64_t numBytes = align(numBytesForChainedFixups, 3) + align(numBytesForClassicRelocs, 3);
fixupsSubRegion.bufferSize = align(numBytes, 14);
fixupsSubRegion.sizeInUse = fixupsSubRegion.bufferSize;
fixupsSubRegion.permissions = VM_PROT_READ;
fixupsSubRegion.name = "__FIXUPS";
}
}
}
void AppCacheBuilder::assignSegmentAddresses() {
for (DylibInfo& dylib : sortedDylibs) {
for (SegmentMappingInfo& loc : dylib.cacheLocation) {
loc.dstSegment = loc.parentRegion->buffer + loc.dstCacheFileOffset;
loc.dstCacheUnslidAddress = loc.parentRegion->unslidLoadAddress + loc.dstCacheFileOffset;
loc.dstCacheFileOffset = (uint32_t)loc.parentRegion->cacheFileOffset + loc.dstCacheFileOffset;
}
}
}
void AppCacheBuilder::copyRawSegments() {
const bool log = false;
CacheBuilder::copyRawSegments();
for (const CustomSegment& segment : customSegments) {
for (const CustomSegment::CustomSection& section : segment.sections) {
uint8_t* dstBuffer = segment.parentRegion->buffer + section.offsetInRegion;
uint64_t dstVMAddr = segment.parentRegion->unslidLoadAddress + section.offsetInRegion;
if (log) fprintf(stderr, "copy %s segment %s %s (0x%08lX bytes) from %p to %p (logical addr 0x%llX)\n",
_options.archs->name(), segment.segmentName.c_str(), section.sectionName.c_str(),
section.data.size(), section.data.data(), dstBuffer, dstVMAddr);
::memcpy(dstBuffer, section.data.data(), section.data.size());
}
}
}
static uint8_t getFixupLevel(AppCacheBuilder::Options::AppCacheKind kind) {
uint8_t currentLevel = (uint8_t)~0U;
switch (kind) {
case AppCacheBuilder::Options::AppCacheKind::none:
assert(0 && "Cache kind should have been set");
break;
case AppCacheBuilder::Options::AppCacheKind::kernel:
currentLevel = 0;
break;
case AppCacheBuilder::Options::AppCacheKind::pageableKC:
currentLevel = 1;
break;
case AppCacheBuilder::Options::AppCacheKind::kernelCollectionLevel2:
assert(0 && "Unimplemented");
break;
case AppCacheBuilder::Options::AppCacheKind::auxKC:
currentLevel = 3;
break;
}
return currentLevel;
}
uint32_t AppCacheBuilder::getCurrentFixupLevel() const {
return getFixupLevel(appCacheOptions.cacheKind);
}
struct VTableBindSymbol {
std::string_view binaryID;
std::string symbolName;
};
struct DylibSymbols {
DylibSymbols() = default;
DylibSymbols(const DylibSymbols&) = delete;
DylibSymbols(DylibSymbols&&) = default;
DylibSymbols(std::map<std::string_view, uint64_t>&& globals,
std::map<std::string_view, uint64_t>&& locals,
std::unique_ptr<std::unordered_set<std::string>> kpiSymbols,
uint32_t dylibLevel, const std::string& dylibName)
: globals(std::move(globals)), locals(std::move(locals)), kpiSymbols(std::move(kpiSymbols)),
dylibLevel(dylibLevel), dylibName(dylibName) { }
DylibSymbols& operator=(const DylibSymbols& other) = delete;
DylibSymbols& operator=(DylibSymbols&& other) = default;
std::map<std::string_view, uint64_t> globals;
std::map<std::string_view, uint64_t> locals;
std::unique_ptr<std::unordered_set<std::string>> kpiSymbols;
uint32_t dylibLevel = 0;
std::string dylibName;
std::unordered_map<const uint8_t*, VTableBindSymbol> resolvedBindLocations;
};
class VTablePatcher {
public:
VTablePatcher(uint32_t numFixupLevels);
bool hasError() const;
void addKernelCollection(const dyld3::MachOAppCache* cacheMA, AppCacheBuilder::Options::AppCacheKind kind,
const uint8_t* basePointer, uint64_t baseAddress);
void addDylib(Diagnostics& diags, const dyld3::MachOAnalyzer* ma, const std::string& dylibID,
const std::vector<std::string>& dependencies, uint8_t cacheLevel);
void findMetaclassDefinitions(std::map<std::string, DylibSymbols>& dylibsToSymbols,
const std::string& kernelID, const dyld3::MachOAnalyzer* kernelMA,
AppCacheBuilder::Options::AppCacheKind cacheKind);
void findExistingFixups(Diagnostics& diags,
const dyld3::MachOAppCache* existingKernelCollection,
const dyld3::MachOAppCache* pageableKernelCollection);
void findBaseKernelVTables(Diagnostics& diags, const dyld3::MachOAppCache* existingKernelCollection,
std::map<std::string, DylibSymbols>& dylibsToSymbols);
void findPageableKernelVTables(Diagnostics& diags, const dyld3::MachOAppCache* existingKernelCollection,
std::map<std::string, DylibSymbols>& dylibsToSymbols);
void findVTables(uint8_t currentLevel, const dyld3::MachOAnalyzer* kernelMA,
std::map<std::string, DylibSymbols>& dylibsToSymbols,
const AppCacheBuilder::ASLR_Tracker& aslrTracker,
const std::map<const uint8_t*, const VTableBindSymbol>& missingBindLocations);
void calculateSymbols();
void patchVTables(Diagnostics& diags,
std::map<const uint8_t*, const VTableBindSymbol>& missingBindLocations,
AppCacheBuilder::ASLR_Tracker& aslrTracker,
uint8_t currentLevel);
private:
void logFunc(const char* format, ...) {
if ( logPatching ) {
va_list list;
va_start(list, format);
vfprintf(stderr, format, list);
va_end(list);
}
};
void logFuncVerbose(const char* format, ...) {
if ( logPatchingVerbose ) {
va_list list;
va_start(list, format);
vfprintf(stderr, format, list);
va_end(list);
}
};
std::string_view extractString(std::string_view str, std::string_view prefix, std::string_view suffix) {
if ( !prefix.empty() ) {
if ( str.find(prefix) != 0 ) {
return std::string_view();
}
str.remove_prefix(prefix.size());
}
if ( !suffix.empty() ) {
size_t pos = str.rfind(suffix);
if ( pos != (str.size() - suffix.size()) ) {
return std::string_view();
}
str.remove_suffix(suffix.size());
}
return str;
};
struct VTable {
struct Entry {
const uint8_t* location = nullptr;
uint64_t targetVMAddr = ~0ULL;
uint32_t targetCacheLevel = ~0;
uint16_t diversity = 0;
bool hasAddrDiv = false;
uint8_t key = 0;
bool hasPointerAuth = false;
};
const dyld3::MachOAnalyzer* ma = nullptr;
const uint8_t* superVTable = nullptr;
const DylibSymbols* dylib = nullptr;
bool fromParentCollection = false;
bool patched = false;
std::string name = "";
std::vector<Entry> entries;
};
struct SymbolLocation {
uint64_t vmAddr = 0;
bool foundSymbol = 0;
bool found() const {
return foundSymbol;
}
};
struct Fixup {
uint64_t targetVMAddr = 0;
uint8_t cacheLevel = 0;
uint16_t diversity = 0;
bool hasAddrDiv = false;
uint8_t key = 0;
bool hasPointerAuth = false;
};
struct VTableDylib {
Diagnostics* diags = nullptr;
const dyld3::MachOAnalyzer* ma = nullptr;
std::string dylibID = "";
std::vector<std::string> dependencies;
uint32_t cacheLevel = ~0U;
};
struct KernelCollection {
const dyld3::MachOAppCache* ma = nullptr;
const uint8_t* basePointer = nullptr;
uint64_t baseAddress = ~0ULL;
std::unordered_map<uint64_t, const char*> symbolNames;
std::map<uint64_t, std::string_view> metaclassDefinitions;
};
SymbolLocation findVTablePatchingSymbol(std::string_view symbolName, const DylibSymbols& dylibSymbols);
std::vector<VTableDylib> dylibs;
std::map<const uint8_t*, VTable> vtables;
std::vector<KernelCollection> collections;
const uint8_t* baseMetaClassVTableLoc = nullptr;
std::map<const uint8_t*, Fixup> existingCollectionFixupLocations;
const uint32_t pointerSize = 8;
const bool logPatching = false;
const bool logPatchingVerbose = false;
const char* vtablePrefix = "__ZTV";
const char* osObjPrefix = "__ZN";
const char* metaclassToken = "10gMetaClassE";
const char* superMetaclassPointerToken = "10superClassE";
const char* metaclassVTablePrefix = "__ZTVN";
const char* metaclassVTableSuffix = "9MetaClassE";
};
VTablePatcher::VTablePatcher(uint32_t numFixupLevels) {
collections.resize(numFixupLevels);
}
bool VTablePatcher::hasError() const {
for (const VTableDylib& dylib : dylibs) {
if ( dylib.diags->hasError() )
return true;
}
return false;
}
void VTablePatcher::addKernelCollection(const dyld3::MachOAppCache* cacheMA, AppCacheBuilder::Options::AppCacheKind kind,
const uint8_t* basePointer, uint64_t baseAddress) {
uint8_t cacheLevel = getFixupLevel(kind);
assert(cacheLevel < collections.size());
assert(collections[cacheLevel].ma == nullptr);
collections[cacheLevel].ma = cacheMA;
collections[cacheLevel].basePointer = basePointer;
collections[cacheLevel].baseAddress = baseAddress;
}
void VTablePatcher::addDylib(Diagnostics &diags, const dyld3::MachOAnalyzer *ma,
const std::string& dylibID, const std::vector<std::string>& dependencies,
uint8_t cacheLevel) {
dylibs.push_back((VTableDylib){ &diags, ma, dylibID, dependencies, cacheLevel });
}
VTablePatcher::SymbolLocation VTablePatcher::findVTablePatchingSymbol(std::string_view symbolName,
const DylibSymbols& dylibSymbols) {
auto globalsIt = dylibSymbols.globals.find(symbolName);
if ( globalsIt != dylibSymbols.globals.end() ) {
return { globalsIt->second, true };
}
auto localsIt = dylibSymbols.locals.find(symbolName);
if ( localsIt != dylibSymbols.locals.end() ) {
return { localsIt->second, true };
}
return { ~0ULL, false };
};
void VTablePatcher::findMetaclassDefinitions(std::map<std::string, DylibSymbols>& dylibsToSymbols,
const std::string& kernelID, const dyld3::MachOAnalyzer* kernelMA,
AppCacheBuilder::Options::AppCacheKind cacheKind) {
for (VTableDylib& dylib : dylibs) {
auto& metaclassDefinitions = collections[dylib.cacheLevel].metaclassDefinitions;
dylib.ma->forEachGlobalSymbol(*dylib.diags, ^(const char *symbolName, uint64_t n_value,
uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
if ( strstr(symbolName, metaclassToken) != nullptr )
metaclassDefinitions[n_value] = symbolName;
});
dylib.ma->forEachLocalSymbol(*dylib.diags, ^(const char *symbolName, uint64_t n_value,
uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
if ( strstr(symbolName, metaclassToken) != nullptr )
metaclassDefinitions[n_value] = symbolName;
});
}
DylibSymbols& kernelDylibSymbols = dylibsToSymbols[kernelID];
SymbolLocation symbolLocation = findVTablePatchingSymbol("__ZTV11OSMetaClass", kernelDylibSymbols);
if ( symbolLocation.found() ) {
baseMetaClassVTableLoc = (uint8_t*)kernelMA + (symbolLocation.vmAddr - kernelMA->preferredLoadAddress());
VTable& vtable = vtables[baseMetaClassVTableLoc];
vtable.ma = kernelMA;
vtable.dylib = &kernelDylibSymbols;
vtable.fromParentCollection = (cacheKind != AppCacheBuilder::Options::AppCacheKind::kernel);
vtable.patched = true;
vtable.name = "__ZTV11OSMetaClass";
}
}
void VTablePatcher::findExistingFixups(Diagnostics& diags,
const dyld3::MachOAppCache* existingKernelCollection,
const dyld3::MachOAppCache* pageableKernelCollection) {
const bool is64 = pointerSize == 8;
if ( existingKernelCollection != nullptr ) {
uint8_t kernelLevel = getFixupLevel(AppCacheBuilder::Options::AppCacheKind::kernel);
uint64_t kernelBaseAddress = collections[kernelLevel].baseAddress;
const uint8_t* kernelBasePointer = collections[kernelLevel].basePointer;
if ( existingKernelCollection->hasChainedFixupsLoadCommand() ) {
existingKernelCollection->withChainStarts(diags, 0, ^(const dyld_chained_starts_in_image* starts) {
existingKernelCollection->forEachFixupInAllChains(diags, starts, false,
^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
uint64_t vmOffset = 0;
bool isRebase = fixupLoc->isRebase(segInfo->pointer_format, kernelBaseAddress, vmOffset);
assert(isRebase);
uint64_t targetVMAddr = kernelBaseAddress + vmOffset;
uint16_t diversity = fixupLoc->kernel64.diversity;
bool hasAddrDiv = fixupLoc->kernel64.addrDiv;
uint8_t key = fixupLoc->kernel64.key;
bool hasPointerAuth = fixupLoc->kernel64.isAuth;
existingCollectionFixupLocations[(const uint8_t*)fixupLoc] = { targetVMAddr, kernelLevel, diversity, hasAddrDiv, key, hasPointerAuth };
});
});
}
existingKernelCollection->forEachRebase(diags, ^(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 - kernelBaseAddress;
const uint8_t* fixupLoc = kernelBasePointer + runtimeOffset;
uint64_t targetVMAddr = 0;
if ( is64 ) {
targetVMAddr = *(uint64_t*)fixupLoc;
} else {
targetVMAddr = *(uint32_t*)fixupLoc;
}
uint16_t diversity = 0;
bool hasAddrDiv = false;
uint8_t key = 0;
bool hasPointerAuth = false;
existingCollectionFixupLocations[(const uint8_t*)fixupLoc] = { targetVMAddr, kernelLevel, diversity, hasAddrDiv, key, hasPointerAuth };
});
}
if ( pageableKernelCollection != nullptr ) {
pageableKernelCollection->forEachDylib(diags, ^(const dyld3::MachOAnalyzer *ma, const char *name, bool &stop) {
if ( !ma->hasChainedFixupsLoadCommand() )
return;
ma->withChainStarts(diags, 0, ^(const dyld_chained_starts_in_image* starts) {
ma->forEachFixupInAllChains(diags, starts, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
uint64_t vmOffset = 0;
bool isRebase = fixupLoc->isRebase(DYLD_CHAINED_PTR_64_KERNEL_CACHE, 0, vmOffset);
assert(isRebase);
uint8_t targetFixupLevel = fixupLoc->kernel64.cacheLevel;
uint64_t targetVMAddr = collections[targetFixupLevel].baseAddress + vmOffset;
uint16_t diversity = fixupLoc->kernel64.diversity;
bool hasAddrDiv = fixupLoc->kernel64.addrDiv;
uint8_t key = fixupLoc->kernel64.key;
bool hasPointerAuth = fixupLoc->kernel64.isAuth;
existingCollectionFixupLocations[(const uint8_t*)fixupLoc] = { targetVMAddr, targetFixupLevel, diversity, hasAddrDiv, key, hasPointerAuth };
});
});
});
}
}
void VTablePatcher::findBaseKernelVTables(Diagnostics& diags, const dyld3::MachOAppCache* existingKernelCollection,
std::map<std::string, DylibSymbols>& dylibsToSymbols)
{
const bool is64 = pointerSize == 8;
uint8_t kernelLevel = getFixupLevel(AppCacheBuilder::Options::AppCacheKind::kernel);
uint64_t kernelBaseAddress = collections[kernelLevel].baseAddress;
const uint8_t* kernelBasePointer = collections[kernelLevel].basePointer;
uint16_t chainedPointerFormat = 0;
if ( existingKernelCollection->hasChainedFixupsLoadCommand() )
chainedPointerFormat = existingKernelCollection->chainedPointerFormat();
std::map<std::string, const std::vector<std::string>*> kextDependencies;
for (VTableDylib& dylib : dylibs) {
if ( dylib.cacheLevel != kernelLevel )
continue;
kextDependencies[dylib.dylibID] = &dylib.dependencies;
}
bool kernelUsesClassicRelocs = existingKernelCollection->usesClassicRelocationsInKernelCollection();
existingKernelCollection->forEachDylib(diags, ^(const dyld3::MachOAnalyzer *ma, const char *dylibID, bool &stop) {
uint64_t loadAddress = ma->preferredLoadAddress();
auto visitBaseKernelCollectionSymbols = ^(const char *symbolName, uint64_t n_value) {
if ( strstr(symbolName, superMetaclassPointerToken) == nullptr )
return;
uint8_t* fixupLoc = (uint8_t*)ma + (n_value - loadAddress);
logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName, dylibID, fixupLoc);
std::string_view className = extractString(symbolName, osObjPrefix, superMetaclassPointerToken);
if ( className.empty() ) {
logFunc("Unsupported vtable superclass name\n");
return;
}
logFunc("Class name: '%s'\n", std::string(className).c_str());
std::string classVTableName = std::string(vtablePrefix) + std::string(className);
logFunc("Class vtable name: '%s'\n", classVTableName.c_str());
uint64_t classVTableVMAddr = 0;
const DylibSymbols& dylibSymbols = dylibsToSymbols[dylibID];
{
std::string namespacedVTableName;
SymbolLocation symbolLocation = findVTablePatchingSymbol(classVTableName, dylibSymbols);
if ( !symbolLocation.found() ) {
namespacedVTableName = std::string(vtablePrefix) + "N" + std::string(className) + "E";
logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName.c_str());
symbolLocation = findVTablePatchingSymbol(namespacedVTableName, dylibSymbols);
}
if ( symbolLocation.found() ) {
classVTableVMAddr = symbolLocation.vmAddr;
} else {
diags.error("Class vtables '%s' or '%s' is not exported from '%s'",
classVTableName.c_str(), namespacedVTableName.c_str(), dylibID);
stop = true;
return;
}
}
logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr);
const uint8_t* classVTableLoc = kernelBasePointer + (classVTableVMAddr - kernelBaseAddress);
uint64_t superMetaclassSymbolAddress = 0;
auto existingKernelCollectionFixupLocIt = existingCollectionFixupLocations.find(fixupLoc);
if ( existingKernelCollectionFixupLocIt != existingCollectionFixupLocations.end() ) {
if ( ma->isKextBundle() || !kernelUsesClassicRelocs ) {
auto* chainedFixupLoc = (dyld3::MachOLoaded::ChainedFixupPointerOnDisk*)fixupLoc;
uint64_t vmOffset = 0;
bool isRebase = chainedFixupLoc->isRebase(chainedPointerFormat, kernelBaseAddress, vmOffset);
assert(isRebase);
superMetaclassSymbolAddress = kernelBaseAddress + vmOffset;
} else {
assert(is64);
superMetaclassSymbolAddress = *(uint64_t*)fixupLoc;
}
}
logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress);
if ( superMetaclassSymbolAddress == 0 ) {
if ( classVTableName == "__ZTV8OSObject" ) {
VTable& vtable = vtables[classVTableLoc];
vtable.ma = ma;
vtable.dylib = &dylibSymbols;
vtable.fromParentCollection = true;
vtable.patched = true;
vtable.name = classVTableName;
return;
}
}
uint8_t superclassFixupLevel = kernelLevel;
auto& metaclassDefinitions = collections[superclassFixupLevel].metaclassDefinitions;
auto metaclassIt = metaclassDefinitions.find(superMetaclassSymbolAddress);
if ( metaclassIt == metaclassDefinitions.end() ) {
diags.error("Cannot find symbol for metaclass pointed to by '%s' in '%s'",
symbolName, dylibID);
stop = true;
return;
}
std::string_view superClassName = extractString(metaclassIt->second, osObjPrefix, metaclassToken);
if ( superClassName.empty() ) {
logFunc("Unsupported vtable superclass name\n");
return;
}
logFunc("Superclass name: '%s'\n", std::string(superClassName).c_str());
std::string superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
const uint8_t* superclassVTableLoc = nullptr;
for (unsigned i = 0; i != 2; ++i) {
if ( i == 1 ) {
superclassVTableName = std::string(vtablePrefix) + + "N" + std::string(superClassName) + "E";
}
logFunc("Superclass vtable name: '%s'\n", superclassVTableName.c_str());
if ( ma->isKextBundle() ) {
auto it = kextDependencies.find(dylibID);
assert(it != kextDependencies.end());
const std::vector<std::string>& dependencies = *it->second;
for (const std::string& dependencyID : dependencies) {
auto depIt = dylibsToSymbols.find(dependencyID);
if (depIt == dylibsToSymbols.end()) {
diags.error("Failed to bind '%s' in '%s' as could not find a kext with '%s' bundle-id",
symbolName, dylibID, dependencyID.c_str());
stop = true;
return;
}
const DylibSymbols& dylibSymbols = depIt->second;
SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
if ( !symbolLocation.found() )
continue;
uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
break;
}
}
if ( superclassVTableLoc == nullptr ) {
auto depIt = dylibsToSymbols.find(dylibID);
if (depIt == dylibsToSymbols.end()) {
diags.error("Failed to bind '%s' in '%s' as could not find a binary with '%s' bundle-id",
symbolName, dylibID, dylibID);
stop = true;
return;
}
const DylibSymbols& dylibSymbols = depIt->second;
SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
if ( symbolLocation.found() ) {
uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
}
}
if ( superclassVTableLoc != nullptr )
break;
}
if ( superclassVTableLoc == nullptr ) {
superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
diags.error("Superclass vtable '%s' is not exported from '%s' or its dependencies",
superclassVTableName.c_str(), dylibID);
stop = true;
return;
}
VTable& vtable = vtables[classVTableLoc];
vtable.superVTable = superclassVTableLoc;
vtable.ma = ma;
vtable.dylib = &dylibSymbols;
vtable.fromParentCollection = true;
vtable.patched = true;
vtable.name = classVTableName;
VTable& supervtable = vtables[superclassVTableLoc];
supervtable.fromParentCollection = true;
supervtable.patched = true;
supervtable.name = superclassVTableName;
};
ma->forEachGlobalSymbol(diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
uint8_t n_sect, uint16_t n_desc, bool &stop) {
visitBaseKernelCollectionSymbols(symbolName, n_value);
});
if ( diags.hasError() ) {
stop = true;
return;
}
ma->forEachLocalSymbol(diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
uint8_t n_sect, uint16_t n_desc, bool &stop) {
visitBaseKernelCollectionSymbols(symbolName, n_value);
});
if ( diags.hasError() ) {
stop = true;
return;
}
});
}
void VTablePatcher::findPageableKernelVTables(Diagnostics& diags, const dyld3::MachOAppCache* pageableKernelCollection,
std::map<std::string, DylibSymbols>& dylibsToSymbols)
{
uint8_t collectionLevel = getFixupLevel(AppCacheBuilder::Options::AppCacheKind::pageableKC);
uint64_t collectionBaseAddress = collections[collectionLevel].baseAddress;
const uint8_t* collectionBasePointer = collections[collectionLevel].basePointer;
std::map<std::string, const std::vector<std::string>*> kextDependencies;
for (VTableDylib& dylib : dylibs) {
if ( dylib.cacheLevel != collectionLevel )
continue;
kextDependencies[dylib.dylibID] = &dylib.dependencies;
}
pageableKernelCollection->forEachDylib(diags, ^(const dyld3::MachOAnalyzer *ma, const char *dylibID, bool &stop) {
uint64_t loadAddress = ma->preferredLoadAddress();
auto visitPageableKernelCollectionSymbols = ^(const char *symbolName, uint64_t n_value) {
if ( strstr(symbolName, superMetaclassPointerToken) == nullptr )
return;
uint8_t* fixupLoc = (uint8_t*)ma + (n_value - loadAddress);
logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName, dylibID, fixupLoc);
std::string_view className = extractString(symbolName, osObjPrefix, superMetaclassPointerToken);
if ( className.empty() ) {
logFunc("Unsupported vtable superclass name\n");
return;
}
logFunc("Class name: '%s'\n", std::string(className).c_str());
std::string classVTableName = std::string(vtablePrefix) + std::string(className);
logFunc("Class vtable name: '%s'\n", classVTableName.c_str());
uint64_t classVTableVMAddr = 0;
const DylibSymbols& dylibSymbols = dylibsToSymbols[dylibID];
{
std::string namespacedVTableName;
SymbolLocation symbolLocation = findVTablePatchingSymbol(classVTableName, dylibSymbols);
if ( !symbolLocation.found() ) {
namespacedVTableName = std::string(vtablePrefix) + "N" + std::string(className) + "E";
logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName.c_str());
symbolLocation = findVTablePatchingSymbol(namespacedVTableName, dylibSymbols);
}
if ( symbolLocation.found() ) {
classVTableVMAddr = symbolLocation.vmAddr;
} else {
diags.error("Class vtables '%s' or '%s' is not exported from '%s'",
classVTableName.c_str(), namespacedVTableName.c_str(), dylibID);
stop = true;
return;
}
}
logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr);
const uint8_t* classVTableLoc = collectionBasePointer + (classVTableVMAddr - collectionBaseAddress);
uint8_t superclassFixupLevel = (uint8_t)~0U;
uint64_t superMetaclassSymbolAddress = 0;
auto existingKernelCollectionFixupLocIt = existingCollectionFixupLocations.find(fixupLoc);
if ( existingKernelCollectionFixupLocIt != existingCollectionFixupLocations.end() ) {
auto* chainedFixupLoc = (dyld3::MachOLoaded::ChainedFixupPointerOnDisk*)fixupLoc;
uint64_t vmOffset = 0;
bool isRebase = chainedFixupLoc->isRebase(DYLD_CHAINED_PTR_64_KERNEL_CACHE, 0, vmOffset);
assert(isRebase);
superclassFixupLevel = chainedFixupLoc->kernel64.cacheLevel;
superMetaclassSymbolAddress = collections[superclassFixupLevel].baseAddress + vmOffset;
}
logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress);
if ( superMetaclassSymbolAddress == 0 ) {
if ( classVTableName == "__ZTV8OSObject" ) {
VTable& vtable = vtables[classVTableLoc];
vtable.ma = ma;
vtable.dylib = &dylibSymbols;
vtable.fromParentCollection = true;
vtable.patched = true;
vtable.name = classVTableName;
return;
}
}
auto& metaclassDefinitions = collections[superclassFixupLevel].metaclassDefinitions;
auto metaclassIt = metaclassDefinitions.find(superMetaclassSymbolAddress);
if ( metaclassIt == metaclassDefinitions.end() ) {
diags.error("Cannot find symbol for metaclass pointed to by '%s' in '%s'",
symbolName, dylibID);
stop = true;
return;
}
std::string_view superClassName = extractString(metaclassIt->second, osObjPrefix, metaclassToken);
if ( superClassName.empty() ) {
logFunc("Unsupported vtable superclass name\n");
return;
}
logFunc("Superclass name: '%s'\n", std::string(superClassName).c_str());
std::string superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
const uint8_t* superclassVTableLoc = nullptr;
for (unsigned i = 0; i != 2; ++i) {
if ( i == 1 ) {
superclassVTableName = std::string(vtablePrefix) + + "N" + std::string(superClassName) + "E";
}
logFunc("Superclass vtable name: '%s'\n", superclassVTableName.c_str());
if ( ma->isKextBundle() ) {
auto it = kextDependencies.find(dylibID);
assert(it != kextDependencies.end());
const std::vector<std::string>& dependencies = *it->second;
for (const std::string& dependencyID : dependencies) {
auto depIt = dylibsToSymbols.find(dependencyID);
if (depIt == dylibsToSymbols.end()) {
diags.error("Failed to bind '%s' in '%s' as could not find a kext with '%s' bundle-id",
symbolName, dylibID, dependencyID.c_str());
stop = true;
return;
}
const DylibSymbols& dylibSymbols = depIt->second;
SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
if ( !symbolLocation.found() )
continue;
uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
break;
}
}
if ( superclassVTableLoc == nullptr ) {
auto depIt = dylibsToSymbols.find(dylibID);
if (depIt == dylibsToSymbols.end()) {
diags.error("Failed to bind '%s' in '%s' as could not find a binary with '%s' bundle-id",
symbolName, dylibID, dylibID);
stop = true;
return;
}
const DylibSymbols& dylibSymbols = depIt->second;
SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
if ( symbolLocation.found() ) {
uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
}
}
if ( superclassVTableLoc != nullptr )
break;
}
if ( superclassVTableLoc == nullptr ) {
superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
diags.error("Superclass vtable '%s' is not exported from '%s' or its dependencies",
superclassVTableName.c_str(), dylibID);
stop = true;
return;
}
VTable& vtable = vtables[classVTableLoc];
vtable.superVTable = superclassVTableLoc;
vtable.ma = ma;
vtable.dylib = &dylibSymbols;
vtable.fromParentCollection = true;
vtable.patched = true;
vtable.name = classVTableName;
VTable& supervtable = vtables[superclassVTableLoc];
supervtable.fromParentCollection = true;
supervtable.patched = true;
supervtable.name = superclassVTableName;
};
ma->forEachGlobalSymbol(diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
uint8_t n_sect, uint16_t n_desc, bool &stop) {
visitPageableKernelCollectionSymbols(symbolName, n_value);
});
if ( diags.hasError() ) {
stop = true;
return;
}
ma->forEachLocalSymbol(diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
uint8_t n_sect, uint16_t n_desc, bool &stop) {
visitPageableKernelCollectionSymbols(symbolName, n_value);
});
if ( diags.hasError() ) {
stop = true;
return;
}
});
}
void VTablePatcher::findVTables(uint8_t currentLevel, const dyld3::MachOAnalyzer* kernelMA,
std::map<std::string, DylibSymbols>& dylibsToSymbols,
const AppCacheBuilder::ASLR_Tracker& aslrTracker,
const std::map<const uint8_t*, const VTableBindSymbol>& missingBindLocations)
{
const bool is64 = pointerSize == 8;
uint64_t collectionBaseAddress = collections[currentLevel].baseAddress;
const uint8_t* collectionBasePointer = collections[currentLevel].basePointer;
for (VTableDylib& dylib : dylibs) {
if ( dylib.cacheLevel != currentLevel )
continue;
const dyld3::MachOAnalyzer* ma = dylib.ma;
const std::string& dylibID = dylib.dylibID;
Diagnostics& dylibDiags = *dylib.diags;
const std::vector<std::string>& dependencies = dylib.dependencies;
uint64_t loadAddress = ma->preferredLoadAddress();
bool alreadyPatched = (ma == kernelMA);
auto visitSymbols = ^(const char *symbolName, uint64_t n_value) {
if ( strstr(symbolName, superMetaclassPointerToken) == nullptr )
return;
uint8_t* fixupLoc = (uint8_t*)ma + (n_value - loadAddress);
logFunc("Found superclass pointer with name '%s' in '%s' at %p\n", symbolName, dylibID.c_str(), fixupLoc);
std::string_view className = extractString(symbolName, osObjPrefix, superMetaclassPointerToken);
if ( className.empty() ) {
logFunc("Unsupported vtable superclass name\n");
return;
}
logFunc("Class name: '%s'\n", std::string(className).c_str());
std::string classVTableName = std::string(vtablePrefix) + std::string(className);
logFunc("Class vtable name: '%s'\n", classVTableName.c_str());
uint64_t classVTableVMAddr = 0;
const DylibSymbols& dylibSymbols = dylibsToSymbols[dylibID];
{
std::string namespacedVTableName;
SymbolLocation symbolLocation = findVTablePatchingSymbol(classVTableName, dylibSymbols);
if ( !symbolLocation.found() ) {
namespacedVTableName = std::string(vtablePrefix) + "N" + std::string(className) + "E";
logFunc("Class namespaced vtable name: '%s'\n", namespacedVTableName.c_str());
symbolLocation = findVTablePatchingSymbol(namespacedVTableName, dylibSymbols);
}
if ( symbolLocation.found() ) {
classVTableVMAddr = symbolLocation.vmAddr;
} else {
dylibDiags.error("Class vtables '%s' or '%s' is not an exported symbol",
classVTableName.c_str(), namespacedVTableName.c_str());
return;
}
}
logFunc("Class vtable vmAddr: '0x%llx'\n", classVTableVMAddr);
const uint8_t* classVTableLoc = (uint8_t*)ma + (classVTableVMAddr - loadAddress);
uint64_t superMetaclassSymbolAddress = 0;
{
uint32_t vmAddr32 = 0;
uint64_t vmAddr64 = 0;
if ( aslrTracker.hasRebaseTarget32(fixupLoc, &vmAddr32) ) {
superMetaclassSymbolAddress = vmAddr32;
} else if ( aslrTracker.hasRebaseTarget64(fixupLoc, &vmAddr64) ) {
superMetaclassSymbolAddress = vmAddr64;
} else {
assert(is64);
superMetaclassSymbolAddress = *(uint64_t*)fixupLoc;
}
uint8_t highByte = 0;
if ( aslrTracker.hasHigh8(fixupLoc, &highByte) ) {
uint64_t tbi = (uint64_t)highByte << 56;
superMetaclassSymbolAddress |= tbi;
}
}
logFunc("Super MetaClass's symbol address: '0x%llx'\n", superMetaclassSymbolAddress);
if ( superMetaclassSymbolAddress == 0 ) {
if ( classVTableName == "__ZTV8OSObject" ) {
VTable& vtable = vtables[classVTableLoc];
vtable.ma = ma;
vtable.dylib = &dylibSymbols;
vtable.fromParentCollection = false;
vtable.patched = true;
vtable.name = classVTableName;
return;
}
}
uint8_t superclassFixupLevel = currentLevel;
aslrTracker.has(fixupLoc, &superclassFixupLevel);
auto& metaclassDefinitions = collections[superclassFixupLevel].metaclassDefinitions;
auto metaclassIt = metaclassDefinitions.find(superMetaclassSymbolAddress);
if ( metaclassIt == metaclassDefinitions.end() ) {
auto bindIt = missingBindLocations.find(fixupLoc);
if ( bindIt != missingBindLocations.end() ) {
dylibDiags.error("Cannot find symbol for metaclass pointed to by '%s'. "
"Expected symbol '%s' to be defined in another kext",
symbolName, bindIt->second.symbolName.c_str());
} else {
dylibDiags.error("Cannot find symbol for metaclass pointed to by '%s'",
symbolName);
}
return;
}
std::string_view superClassName = extractString(metaclassIt->second, osObjPrefix, metaclassToken);
if ( superClassName.empty() ) {
logFunc("Unsupported vtable superclass name\n");
return;
}
logFunc("Superclass name: '%s'\n", std::string(superClassName).c_str());
std::string superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
const uint8_t* superclassVTableLoc = nullptr;
bool superVTableIsInParentCollection = false;
for (unsigned i = 0; i != 2; ++i) {
if ( i == 1 ) {
superclassVTableName = std::string(vtablePrefix) + + "N" + std::string(superClassName) + "E";
}
logFunc("Superclass vtable name: '%s'\n", superclassVTableName.c_str());
{
for (const std::string& dependencyID : dependencies) {
auto depIt = dylibsToSymbols.find(dependencyID);
if (depIt == dylibsToSymbols.end()) {
dylibDiags.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
symbolName, dependencyID.c_str());
return;
}
const DylibSymbols& dylibSymbols = depIt->second;
SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
if ( !symbolLocation.found() )
continue;
uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
logFunc("Superclass vtable vmAddr: '0x%llx'\n", superclassVTableVMAddr);
superclassVTableLoc = collections[dylibSymbols.dylibLevel].basePointer + (superclassVTableVMAddr - collections[dylibSymbols.dylibLevel].baseAddress);
superVTableIsInParentCollection = dylibSymbols.dylibLevel != currentLevel;
break;
}
if ( superclassVTableLoc == nullptr ) {
SymbolLocation symbolLocation = findVTablePatchingSymbol(superclassVTableName, dylibSymbols);
if ( symbolLocation.found() ) {
uint64_t superclassVTableVMAddr = symbolLocation.vmAddr;
superclassVTableLoc = (uint8_t*)collectionBasePointer + (superclassVTableVMAddr - collectionBaseAddress);
superVTableIsInParentCollection = false;
}
}
}
if ( superclassVTableLoc != nullptr )
break;
}
if ( superclassVTableLoc == nullptr ) {
superclassVTableName = std::string(vtablePrefix) + std::string(superClassName);
dylibDiags.error("Superclass vtable '%s' is not exported from kext or its dependencies",
superclassVTableName.c_str());
return;
}
VTable& vtable = vtables[classVTableLoc];
vtable.superVTable = superclassVTableLoc;
vtable.ma = ma;
vtable.dylib = &dylibSymbols;
vtable.fromParentCollection = false;
vtable.patched |= alreadyPatched;
vtable.name = classVTableName;
VTable& supervtable = vtables[superclassVTableLoc];
supervtable.fromParentCollection = superVTableIsInParentCollection;
supervtable.patched |= alreadyPatched;
supervtable.name = superclassVTableName;
std::string metaclassVTableName = std::string(metaclassVTablePrefix) + std::string(className) + metaclassVTableSuffix;
logFunc("Metaclass vtable name: '%s'\n", metaclassVTableName.c_str());
{
SymbolLocation symbolLocation = findVTablePatchingSymbol(metaclassVTableName, dylibSymbols);
if ( symbolLocation.found() ) {
uint64_t metaclassVTableVMAddr = symbolLocation.vmAddr;
logFunc("Metaclass vtable vmAddr: '0x%llx'\n", metaclassVTableVMAddr);
uint8_t* metaclassVTableLoc = (uint8_t*)ma + (metaclassVTableVMAddr - loadAddress);
VTable& vtable = vtables[metaclassVTableLoc];
vtable.superVTable = baseMetaClassVTableLoc;
vtable.ma = ma;
vtable.dylib = &dylibSymbols;
vtable.fromParentCollection = false;
vtable.patched |= alreadyPatched;
vtable.name = metaclassVTableName;
}
}
};
ma->forEachGlobalSymbol(dylibDiags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
uint8_t n_sect, uint16_t n_desc, bool &stop) {
visitSymbols(symbolName, n_value);
});
ma->forEachLocalSymbol(dylibDiags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
uint8_t n_sect, uint16_t n_desc, bool &stop) {
visitSymbols(symbolName, n_value);
});
}
}
void VTablePatcher::calculateSymbols() {
for (VTableDylib& dylib : dylibs) {
auto& symbolNames = collections[dylib.cacheLevel].symbolNames;
dylib.ma->forEachGlobalSymbol(*dylib.diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
uint8_t n_sect, uint16_t n_desc, bool &stop) {
symbolNames[n_value] = symbolName;
});
dylib.ma->forEachLocalSymbol(*dylib.diags, ^(const char *symbolName, uint64_t n_value, uint8_t n_type,
uint8_t n_sect, uint16_t n_desc, bool &stop) {
symbolNames[n_value] = symbolName;
});
}
}
void VTablePatcher::patchVTables(Diagnostics& diags,
std::map<const uint8_t*, const VTableBindSymbol>& missingBindLocations,
AppCacheBuilder::ASLR_Tracker& aslrTracker,
uint8_t currentLevel)
{
const bool is64 = pointerSize == 8;
if ( (baseMetaClassVTableLoc == nullptr) && !vtables.empty() ) {
diags.error("Could not find OSMetaClass vtable in kernel binary");
return;
}
calculateSymbols();
auto calculateVTableEntries = ^(const uint8_t* vtableLoc, VTable& vtable) {
assert(vtable.patched);
logFunc("Calculating vtable: '%s'\n", vtable.name.c_str());
const uint8_t* relocLoc = vtableLoc + (2 * pointerSize);
if ( vtable.fromParentCollection ) {
auto it = existingCollectionFixupLocations.find(relocLoc);
while ( it != existingCollectionFixupLocations.end() ) {
const Fixup& fixup = it->second;
uint64_t targetVMAddr = fixup.targetVMAddr;
uint16_t diversity = fixup.diversity;
bool hasAddrDiv = fixup.hasAddrDiv;
uint8_t key = fixup.key;
bool hasPointerAuth = fixup.hasPointerAuth;
uint32_t cacheLevel = fixup.cacheLevel;
vtable.entries.push_back({ relocLoc, targetVMAddr, cacheLevel, diversity, hasAddrDiv, key, hasPointerAuth });
relocLoc += pointerSize;
it = existingCollectionFixupLocations.find(relocLoc);
}
} else {
while ( aslrTracker.has((void*)relocLoc) ||
(missingBindLocations.find(relocLoc) != missingBindLocations.end()) ) {
uint16_t diversity = 0;
bool hasAddrDiv = false;
uint8_t key = 0;
bool hasPointerAuth = false;
uint8_t cacheLevel = currentLevel;
if ( aslrTracker.has((void*)relocLoc, &cacheLevel) ) {
hasPointerAuth = aslrTracker.hasAuthData((void*)relocLoc, &diversity, &hasAddrDiv, &key);
}
uint64_t targetVMAddr = 0;
{
uint32_t vmAddr32 = 0;
uint64_t vmAddr64 = 0;
if ( aslrTracker.hasRebaseTarget32((void*)relocLoc, &vmAddr32) ) {
targetVMAddr = vmAddr32;
} else if ( aslrTracker.hasRebaseTarget64((void*)relocLoc, &vmAddr64) ) {
targetVMAddr = vmAddr64;
} else {
assert(is64);
targetVMAddr = *(uint64_t*)relocLoc;
}
uint8_t highByte = 0;
if ( aslrTracker.hasHigh8((void*)relocLoc, &highByte) ) {
uint64_t tbi = (uint64_t)highByte << 56;
targetVMAddr |= tbi;
}
}
vtable.entries.push_back({ relocLoc, targetVMAddr, cacheLevel, diversity, hasAddrDiv, key, hasPointerAuth });
relocLoc += pointerSize;
}
}
logFunc("Found %d vtable items: '%s'\n", vtable.entries.size(), vtable.name.c_str());
};
std::unordered_map<const dyld3::MachOAnalyzer*, Diagnostics*> diagsMap;
for (VTableDylib& dylib : dylibs)
diagsMap[dylib.ma] = dylib.diags;
uint32_t numPatchedVTables = 0;
for (auto& vtableEntry : vtables) {
if ( vtableEntry.second.patched ) {
calculateVTableEntries(vtableEntry.first, vtableEntry.second);
++numPatchedVTables;
}
}
while ( numPatchedVTables != vtables.size() ) {
typedef std::pair<const uint8_t*, VTable*> VTableEntry;
std::vector<VTableEntry> toBePatched;
for (auto& vtableEntry : vtables) {
if ( vtableEntry.second.patched )
continue;
auto superIt = vtables.find(vtableEntry.second.superVTable);
assert(superIt != vtables.end());
if ( !superIt->second.patched )
continue;
logFunc("Found unpatched vtable: '%s' with patched superclass '%s'\n",
vtableEntry.second.name.c_str(), superIt->second.name.c_str());
toBePatched.push_back({ vtableEntry.first, &vtableEntry.second });
}
if ( toBePatched.empty() ) {
for (const auto& vtableEntry : vtables) {
if ( vtableEntry.second.patched )
continue;
auto superIt = vtables.find(vtableEntry.second.superVTable);
assert(superIt != vtables.end());
diags.error("Found unpatched vtable: '%s' with unpatched superclass '%s'\n",
vtableEntry.second.name.c_str(), superIt->second.name.c_str());
}
break;
}
for (VTableEntry& vtableEntry : toBePatched) {
VTable& vtable = *vtableEntry.second;
vtable.patched = true;
++numPatchedVTables;
auto superIt = vtables.find(vtable.superVTable);
logFunc("Processing unpatched vtable: '%s' with patched superclass '%s'\n",
vtable.name.c_str(), superIt->second.name.c_str());
calculateVTableEntries(vtableEntry.first, vtable);
const VTable& supervtable = superIt->second;
if ( vtable.entries.size() < supervtable.entries.size() ) {
auto diagIt = diagsMap.find(vtable.ma);
Diagnostics* diag = (diagIt != diagsMap.end()) ? diagIt->second : &diags;
diag->error("Malformed vtable. Super class '%s' has %lu entries vs subclass '%s' with %lu entries",
supervtable.name.c_str(), supervtable.entries.size(),
vtable.name.c_str(), vtable.entries.size());
return;
}
const std::unordered_map<const uint8_t*, VTableBindSymbol>& resolvedBindLocations = vtable.dylib->resolvedBindLocations;
for (uint64_t entryIndex = 0; entryIndex != supervtable.entries.size(); ++entryIndex) {
logFuncVerbose("Processing entry %lld: super[0x%llx] vs subclass[0x%llx]\n", entryIndex,
*(uint64_t*)supervtable.entries[entryIndex].location,
*(uint64_t*)vtable.entries[entryIndex].location);
VTable::Entry& vtableEntry = vtable.entries[entryIndex];
const VTable::Entry& superVTableEntry = supervtable.entries[entryIndex];
const uint8_t* patchLoc = vtableEntry.location;
uint64_t targetVMAddr = superVTableEntry.targetVMAddr;
auto resolvedBindIt = resolvedBindLocations.find(patchLoc);
auto unresolvedBindIt = missingBindLocations.find(patchLoc);
if ( (resolvedBindIt == resolvedBindLocations.end()) && (unresolvedBindIt == missingBindLocations.end()) )
continue;
const char* childSymbolName = nullptr;
const char* parentSymbolName = nullptr;
if ( resolvedBindIt != resolvedBindLocations.end() ) {
childSymbolName = resolvedBindIt->second.symbolName.c_str();
} else {
assert(unresolvedBindIt != missingBindLocations.end());
childSymbolName = unresolvedBindIt->second.symbolName.c_str();
}
auto& symbolNames = collections[superVTableEntry.targetCacheLevel].symbolNames;
auto parentNameIt = symbolNames.find(superVTableEntry.targetVMAddr);
if ( parentNameIt != symbolNames.end() )
parentSymbolName = parentNameIt->second;
if ( childSymbolName == nullptr ) {
continue;
}
if ( parentSymbolName == nullptr ) {
continue;
}
logFuncVerbose("Processing entry %lld: super[%s] vs subclass[%s]\n", entryIndex,
parentSymbolName, childSymbolName);
if ( !strcmp(childSymbolName, "___cxa_pure_virtual") ) {
continue;
}
if ( !strcmp(childSymbolName, parentSymbolName) && (unresolvedBindIt == missingBindLocations.end()) ) {
continue;
}
#if 0
require_action(!kxld_sym_name_is_padslot(parent_entry->patched.name),
finish, rval = KERN_FAILURE;
kxld_log(kKxldLogPatching, kKxldLogErr,
kKxldLogParentOutOfDate,
kxld_demangle(super_vtable->name, &demangled_name1,
&demangled_length1),
kxld_demangle(vtable->name, &demangled_name2,
&demangled_length2)));
#endif
logFunc("Patching entry '%s' in '%s' to point to '%s' in superclass '%s'\n",
childSymbolName, vtable.name.c_str(), parentSymbolName, supervtable.name.c_str());
if ( is64 ) {
*((uint64_t*)patchLoc) = targetVMAddr;
} else {
*((uint32_t*)patchLoc) = (uint32_t)targetVMAddr;
}
aslrTracker.add((void*)patchLoc, superVTableEntry.targetCacheLevel);
if ( superVTableEntry.hasPointerAuth )
aslrTracker.setAuthData((void*)patchLoc, superVTableEntry.diversity,
superVTableEntry.hasAddrDiv, superVTableEntry.key);
vtableEntry.targetVMAddr = superVTableEntry.targetVMAddr;
vtableEntry.targetCacheLevel = superVTableEntry.targetCacheLevel;
vtableEntry.diversity = superVTableEntry.diversity;
vtableEntry.hasAddrDiv = superVTableEntry.hasAddrDiv;
vtableEntry.key = superVTableEntry.key;
vtableEntry.hasPointerAuth = superVTableEntry.hasPointerAuth;
missingBindLocations.erase(patchLoc);
}
}
}
}
typedef std::pair<uint8_t, uint64_t> CacheOffset;
struct DylibSymbolLocation {
const DylibSymbols* dylibSymbols;
uint64_t symbolVMAddr;
bool isKPI;
};
struct DylibFixups {
void processFixups(const std::map<std::string, DylibSymbols>& dylibsToSymbols,
const std::unordered_map<std::string_view, std::vector<DylibSymbolLocation>>& symbolMap,
const std::string& kernelID, const CacheBuilder::ASLR_Tracker& aslrTracker);
const dyld3::MachOAnalyzer* ma = nullptr;
DylibSymbols& dylibSymbols;
Diagnostics& dylibDiag;
const std::vector<std::string>& dependencies;
struct AuthData {
uint16_t diversity;
bool addrDiv;
uint8_t key;
};
struct BranchStubData {
CacheOffset targetCacheOffset;
const void* fixupLoc;
uint64_t fixupVMOffset;
};
std::unordered_map<const uint8_t*, VTableBindSymbol> missingBindLocations;
std::unordered_map<void*, uint8_t> fixupLocs;
std::unordered_map<void*, uint8_t> fixupHigh8s;
std::unordered_map<void*, AuthData> fixupAuths;
std::vector<BranchStubData> branchStubs;
};
void DylibFixups::processFixups(const std::map<std::string, DylibSymbols>& dylibsToSymbols,
const std::unordered_map<std::string_view, std::vector<DylibSymbolLocation>>& symbolMap,
const std::string& kernelID, const CacheBuilder::ASLR_Tracker& aslrTracker) {
auto& resolvedBindLocations = dylibSymbols.resolvedBindLocations;
const std::string& dylibID = dylibSymbols.dylibName;
const bool _is64 = true;
const bool isThirdPartyKext = (dylibID.find("com.apple") != 0);
const char* missingWeakImportSymbolName = "_gOSKextUnresolved";
struct SymbolDefinition {
uint64_t symbolVMAddr;
uint32_t kernelCollectionLevel;
};
auto findDependencyWithSymbol = [&symbolMap, &isThirdPartyKext](const char* symbolName,
const std::vector<std::string>& dependencies) {
auto symbolMapIt = symbolMap.find(symbolName);
if ( symbolMapIt == symbolMap.end() )
return (SymbolDefinition){ ~0ULL, 0 };
const std::vector<DylibSymbolLocation>& dylibSymbols = symbolMapIt->second;
for (const std::string& dependency : dependencies) {
for (const DylibSymbolLocation& dylibSymbol : dylibSymbols) {
if ( dependency == dylibSymbol.dylibSymbols->dylibName ) {
const bool isAppleKext = (dependency.find("com.apple") == 0);
if ( isThirdPartyKext && isAppleKext && !dylibSymbol.isKPI )
continue;
return (SymbolDefinition){ dylibSymbol.symbolVMAddr, dylibSymbol.dylibSymbols->dylibLevel };
}
}
}
return (SymbolDefinition){ ~0ULL, 0 };
};
if (ma->hasChainedFixups()) {
struct BindTarget {
const VTableBindSymbol bindSymbol;
uint64_t vmAddr;
uint32_t dylibLevel;
bool isMissingWeakImport;
bool isMissingSymbol;
};
__block std::vector<BindTarget> bindTargets;
__block bool foundMissingWeakImport = false;
ma->forEachChainedFixupTarget(dylibDiag, ^(int libOrdinal, const char* symbolName, uint64_t addend,
bool weakImport, bool& stop) {
if ( (libOrdinal != BIND_SPECIAL_DYLIB_FLAT_LOOKUP) && (libOrdinal != BIND_SPECIAL_DYLIB_WEAK_LOOKUP) ) {
dylibDiag.error("All chained binds should be flat namespace or weak lookups");
stop = true;
return;
}
if ( addend != 0 ) {
dylibDiag.error("Chained bind addends are not supported right now");
stop = true;
return;
}
VTableBindSymbol bindSymbol = { dylibID, symbolName };
bool isMissingSymbol = false;
for (const std::string& dependencyID : dependencies) {
auto depIt = dylibsToSymbols.find(dependencyID);
if (depIt == dylibsToSymbols.end()) {
dylibDiag.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
symbolName, dependencyID.c_str());
stop = true;
return;
}
const DylibSymbols& dylibSymbols = depIt->second;
auto exportIt = dylibSymbols.globals.find(symbolName);
if ( exportIt == dylibSymbols.globals.end() )
continue;
isMissingSymbol = false;
bindTargets.push_back({ bindSymbol, exportIt->second, dylibSymbols.dylibLevel, false, isMissingSymbol });
return;
}
if ( libOrdinal == BIND_SPECIAL_DYLIB_WEAK_LOOKUP ) {
auto dylibIt = dylibsToSymbols.find(dylibID);
if (dylibIt == dylibsToSymbols.end()) {
dylibDiag.error("Failed to bind weak '%s' as could not find a define in self",
symbolName);
stop = true;
return;
}
const DylibSymbols& dylibSymbols = dylibIt->second;
auto exportIt = dylibSymbols.globals.find(symbolName);
if ( exportIt != dylibSymbols.globals.end() ) {
isMissingSymbol = false;
bindTargets.push_back({ bindSymbol, exportIt->second, dylibSymbols.dylibLevel, false, isMissingSymbol });
return;
}
}
if ( weakImport ) {
auto kernelSymbolsIt = dylibsToSymbols.find(kernelID);
assert(kernelSymbolsIt != dylibsToSymbols.end());
const DylibSymbols& kernelSymbols = kernelSymbolsIt->second;
auto exportIt = kernelSymbols.globals.find(missingWeakImportSymbolName);
if (exportIt != kernelSymbols.globals.end()) {
foundMissingWeakImport = true;
isMissingSymbol = false;
bindTargets.push_back({ bindSymbol, exportIt->second, kernelSymbols.dylibLevel, true, isMissingSymbol });
return;
}
dylibDiag.error("Weak bind symbol '%s' not found in kernel", missingWeakImportSymbolName);
return;
}
isMissingSymbol = true;
bindTargets.push_back({ bindSymbol, 0, 0, false, isMissingSymbol });
});
if ( dylibDiag.hasError() )
return;
if( foundMissingWeakImport ) {
auto kernelSymbolsIt = dylibsToSymbols.find(kernelID);
assert(kernelSymbolsIt != dylibsToSymbols.end());
const DylibSymbols& kernelSymbols = kernelSymbolsIt->second;
auto exportIt = kernelSymbols.globals.find(missingWeakImportSymbolName);
assert(exportIt != kernelSymbols.globals.end());
bool foundUseOfMagicSymbol = false;
for (const BindTarget& bindTarget : bindTargets) {
if ( bindTarget.isMissingWeakImport || bindTarget.isMissingSymbol )
continue;
if ( (bindTarget.dylibLevel != 0) && (bindTarget.vmAddr != exportIt->second) )
continue;
foundUseOfMagicSymbol = true;
break;
}
if ( !foundUseOfMagicSymbol ) {
dylibDiag.error("Has weak references but does not test for them. "
"Test for weak references with OSKextSymbolIsResolved().");
return;
}
}
ma->withChainStarts(dylibDiag, 0, ^(const dyld_chained_starts_in_image* starts) {
ma->forEachFixupInAllChains(dylibDiag, starts, false, ^(dyld3::MachOLoaded::ChainedFixupPointerOnDisk* fixupLoc, const dyld_chained_starts_in_segment* segInfo, bool& stop) {
switch (segInfo->pointer_format) {
case DYLD_CHAINED_PTR_64_OFFSET:
if ( fixupLoc->generic64.bind.bind ) {
uint64_t bindOrdinal = fixupLoc->generic64.bind.ordinal;
if ( bindOrdinal >= bindTargets.size() ) {
dylibDiag.error("Bind ordinal %lld out of range %lu", bindOrdinal, bindTargets.size());
stop = true;
return;
}
const BindTarget& bindTarget = bindTargets[bindOrdinal];
if ( bindTarget.isMissingSymbol ) {
fixupLoc->raw64 = 0;
missingBindLocations[(const uint8_t*)fixupLoc] = bindTarget.bindSymbol;
} else {
fixupLoc->raw64 = bindTarget.vmAddr;
fixupLocs[fixupLoc] = bindTarget.dylibLevel;
resolvedBindLocations[(const uint8_t*)fixupLoc] = bindTarget.bindSymbol;
}
}
else {
uint64_t targetVMAddr = fixupLoc->generic64.rebase.target;
uint64_t sideTableAddr = 0;
if ( aslrTracker.hasRebaseTarget64(fixupLoc, &sideTableAddr) )
targetVMAddr = sideTableAddr;
if ( fixupLoc->generic64.rebase.high8 )
fixupHigh8s[fixupLoc] = fixupLoc->generic64.rebase.high8;
fixupLoc->raw64 = targetVMAddr;
}
break;
case DYLD_CHAINED_PTR_ARM64E_KERNEL:
if ( fixupLoc->arm64e.bind.bind ) {
uint64_t bindOrdinal = fixupLoc->arm64e.bind.ordinal;
if ( bindOrdinal >= bindTargets.size() ) {
dylibDiag.error("Bind ordinal %lld out of range %lu", bindOrdinal, bindTargets.size());
stop = true;
return;
}
const BindTarget& bindTarget = bindTargets[bindOrdinal];
uint64_t targetVMAddr = bindTarget.vmAddr;
if ( fixupLoc->arm64e.authBind.auth ) {
fixupAuths[fixupLoc] = {
(uint16_t)fixupLoc->arm64e.authBind.diversity,
(bool)fixupLoc->arm64e.authBind.addrDiv,
(uint8_t)fixupLoc->arm64e.authBind.key
};
}
else {
targetVMAddr += fixupLoc->arm64e.bind.addend;
}
if ( bindTarget.isMissingSymbol ) {
fixupLoc->raw64 = 0;
missingBindLocations[(const uint8_t*)fixupLoc] = bindTarget.bindSymbol;
} else {
fixupLoc->raw64 = targetVMAddr;
fixupLocs[fixupLoc] = bindTarget.dylibLevel;
resolvedBindLocations[(const uint8_t*)fixupLoc] = bindTarget.bindSymbol;
}
}
else {
if ( fixupLoc->arm64e.rebase.auth ) {
fixupAuths[fixupLoc] = {
(uint16_t)fixupLoc->arm64e.authRebase.diversity,
(bool)fixupLoc->arm64e.authRebase.addrDiv,
(uint8_t)fixupLoc->arm64e.authRebase.key
};
uint64_t targetVMAddr = fixupLoc->arm64e.authRebase.target;
fixupLoc->raw64 = targetVMAddr;
}
else {
uint64_t targetVMAddr = fixupLoc->arm64e.rebase.target;
uint64_t sideTableAddr;
if ( aslrTracker.hasRebaseTarget64(fixupLoc, &sideTableAddr) )
targetVMAddr = sideTableAddr;
if ( fixupLoc->arm64e.rebase.high8 )
fixupHigh8s[fixupLoc] = fixupLoc->arm64e.rebase.high8;
fixupLoc->raw64 = targetVMAddr;
}
}
break;
default:
fprintf(stderr, "unknown pointer type %d\n", segInfo->pointer_format);
break;
}
});
});
return;
}
__block bool foundUseOfMagicSymbol = false;
__block bool foundMissingWeakImport = false;
const uint64_t loadAddress = ma->preferredLoadAddress();
ma->forEachBind(dylibDiag, ^(uint64_t runtimeOffset, int libOrdinal, uint8_t bindType,
const char *symbolName, bool weakImport, bool lazyBind, uint64_t addend, bool &stop) {
bool foundSymbol = false;
VTableBindSymbol bindSymbol = { dylibID, symbolName };
if (SymbolDefinition symbolDef = findDependencyWithSymbol(symbolName, dependencies); symbolDef.symbolVMAddr != ~0ULL) {
uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
if ( bindType == BIND_TYPE_POINTER ) {
if ( _is64 )
*((uint64_t*)fixupLoc) = symbolDef.symbolVMAddr;
else
*((uint32_t*)fixupLoc) = (uint32_t)symbolDef.symbolVMAddr;
fixupLocs[fixupLoc] = symbolDef.kernelCollectionLevel;
resolvedBindLocations[(const uint8_t*)fixupLoc] = bindSymbol;
} else if ( bindType == BIND_TYPE_TEXT_PCREL32 ) {
uint64_t targetAddress = 0;
if ( dylibSymbols.dylibLevel != symbolDef.kernelCollectionLevel ) {
CacheOffset targetCacheOffset = { symbolDef.kernelCollectionLevel, symbolDef.symbolVMAddr };
branchStubs.emplace_back((BranchStubData){
.targetCacheOffset = targetCacheOffset,
.fixupLoc = fixupLoc,
.fixupVMOffset = runtimeOffset
});
} else {
targetAddress = symbolDef.symbolVMAddr;
uint64_t diffValue = targetAddress - (loadAddress + runtimeOffset + 4);
*((uint32_t*)fixupLoc) = (uint32_t)diffValue;
}
} else {
dylibDiag.error("Unexpected bind type: %d", bindType);
stop = true;
return;
}
foundSymbol = true;
}
if ( foundSymbol && !foundUseOfMagicSymbol ) {
foundUseOfMagicSymbol = (strcmp(symbolName, missingWeakImportSymbolName) == 0);
}
if (!foundSymbol) {
for (const std::string& dependencyID : dependencies) {
auto depIt = dylibsToSymbols.find(dependencyID);
if (depIt == dylibsToSymbols.end()) {
dylibDiag.error("Failed to bind '%s' as could not find a kext with '%s' bundle-id",
symbolName, dependencyID.c_str());
stop = true;
return;
}
const DylibSymbols& dylibSymbols = depIt->second;
auto exportIt = dylibSymbols.globals.find(symbolName);
if ( exportIt == dylibSymbols.globals.end() )
continue;
findDependencyWithSymbol(symbolName, dependencies);
break;
}
}
if ( !foundSymbol && (libOrdinal == BIND_SPECIAL_DYLIB_WEAK_LOOKUP) ) {
auto dylibIt = dylibsToSymbols.find(dylibID);
if (dylibIt == dylibsToSymbols.end()) {
dylibDiag.error("Failed to bind weak '%s' as could not find a define in self",
symbolName);
stop = true;
return;
}
const DylibSymbols& dylibSymbols = dylibIt->second;
auto exportIt = dylibSymbols.globals.find(symbolName);
if ( exportIt != dylibSymbols.globals.end() ) {
uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
if ( bindType == BIND_TYPE_POINTER ) {
if ( _is64 )
*((uint64_t*)fixupLoc) = exportIt->second;
else
*((uint32_t*)fixupLoc) = (uint32_t)exportIt->second;
fixupLocs[fixupLoc] = dylibSymbols.dylibLevel;
resolvedBindLocations[(const uint8_t*)fixupLoc] = bindSymbol;
} else if ( bindType == BIND_TYPE_TEXT_PCREL32 ) {
dylibDiag.error("Unexpected weak bind type: %d", bindType);
stop = true;
return;
} else {
dylibDiag.error("Unexpected bind type: %d", bindType);
stop = true;
return;
}
foundSymbol = true;
}
}
if ( !foundSymbol && weakImport ) {
if ( bindType != BIND_TYPE_POINTER ) {
dylibDiag.error("Unexpected bind type: %d", bindType);
stop = true;
return;
}
auto kernelSymbolsIt = dylibsToSymbols.find(kernelID);
assert(kernelSymbolsIt != dylibsToSymbols.end());
const DylibSymbols& kernelSymbols = kernelSymbolsIt->second;
auto exportIt = kernelSymbols.globals.find(missingWeakImportSymbolName);
if (exportIt != kernelSymbols.globals.end()) {
foundMissingWeakImport = true;
uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
if ( _is64 )
*((uint64_t*)fixupLoc) = exportIt->second;
else
*((uint32_t*)fixupLoc) = (uint32_t)exportIt->second;
fixupLocs[fixupLoc] = kernelSymbols.dylibLevel;
return;
}
dylibDiag.error("Weak bind symbol '%s' not found in kernel", missingWeakImportSymbolName);
return;
}
if ( !foundSymbol ) {
const uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
missingBindLocations[fixupLoc] = bindSymbol;
}
}, ^(const char *symbolName) {
dylibDiag.error("Strong binds are not supported right now");
});
if ( foundMissingWeakImport && !foundUseOfMagicSymbol ) {
dylibDiag.error("Has weak references but does not test for them. "
"Test for weak references with OSKextSymbolIsResolved().");
return;
}
ma->forEachRebase(dylibDiag, false, ^(uint64_t runtimeOffset, bool &stop) {
uint8_t* fixupLoc = (uint8_t*)ma+runtimeOffset;
fixupLocs[fixupLoc] = (uint8_t)~0U;
});
}
struct AutoReleaseTypeRef {
AutoReleaseTypeRef() = default;
~AutoReleaseTypeRef() {
if ( ref != nullptr ) {
CFRelease(ref);
}
}
void setRef(CFTypeRef typeRef) {
assert(ref == nullptr);
ref = typeRef;
}
CFTypeRef ref = nullptr;
};
static std::unique_ptr<std::unordered_set<std::string>> getKPI(Diagnostics& diags, const dyld3::MachOAnalyzer* ma,
std::string_view dylibID) {
bool isAppleKext = (dylibID.find("com.apple") == 0);
if ( !isAppleKext )
return {};
__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 = ma->findSectionContent("__LINKINFO", "__symbolsets", symbolSetsSize);
if ( symbolSetsContent == nullptr )
return {};
AutoReleaseTypeRef dataRefReleaser;
AutoReleaseTypeRef plistRefReleaser;
std::unordered_set<std::string> symbols;
CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)symbolSetsContent, symbolSetsSize, kCFAllocatorNull);
if ( dataRef == nullptr ) {
diags.error("Could not create data ref for kpi");
return {};
}
dataRefReleaser.setRef(dataRef);
CFErrorRef errorRef = nullptr;
CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, dataRef, kCFPropertyListImmutable, nullptr, &errorRef);
if (errorRef != nullptr) {
CFStringRef errorString = CFErrorCopyDescription(errorRef);
diags.error("Could not load plist because :%s", CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
CFRelease(errorRef);
return {};
}
if ( plistRef == nullptr ) {
diags.error("Could not create plist ref for kpi");
return {};
}
plistRefReleaser.setRef(plistRef);
if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) {
diags.error("kpi plist should be a dictionary");
return {};
}
CFDictionaryRef symbolSetsDictRef = (CFDictionaryRef)plistRef;
CFStringRef bundleIDRef = (CFStringRef)CFDictionaryGetValue(symbolSetsDictRef, CFSTR("CFBundleIdentifier"));
if ( (bundleIDRef == nullptr) || (CFGetTypeID(bundleIDRef) != CFStringGetTypeID()) ) {
diags.error("kpi bundle ID should be a string");
return {};
}
const char* bundleID = getString(diags, bundleIDRef);
if ( bundleID == nullptr )
return {};
if ( dylibID != bundleID ) {
diags.error("kpi bundle ID doesn't match kext");
return {};
}
CFArrayRef symbolsArrayRef = (CFArrayRef)CFDictionaryGetValue(symbolSetsDictRef, CFSTR("Symbols"));
if ( symbolsArrayRef != nullptr ) {
if ( CFGetTypeID(symbolsArrayRef) != CFArrayGetTypeID() ) {
diags.error("Symbols value should be an array");
return {};
}
for (CFIndex symbolSetIndex = 0; symbolSetIndex != CFArrayGetCount(symbolsArrayRef); ++symbolSetIndex) {
CFStringRef symbolNameRef = (CFStringRef)CFArrayGetValueAtIndex(symbolsArrayRef, symbolSetIndex);
if ( (symbolNameRef == nullptr) || (CFGetTypeID(symbolNameRef) != CFStringGetTypeID()) ) {
diags.error("Symbol name should be a string");
return {};
}
const char* symbolName = getString(diags, symbolNameRef);
if ( symbolName == nullptr )
return {};
symbols.insert(symbolName);
}
}
return std::make_unique<std::unordered_set<std::string>>(std::move(symbols));
}
void AppCacheBuilder::processFixups()
{
auto dylibsToSymbolsOwner = std::make_unique<std::map<std::string, DylibSymbols>>();
std::map<std::string, DylibSymbols>& dylibsToSymbols = *dylibsToSymbolsOwner.get();
auto vtablePatcherOwner = std::make_unique<VTablePatcher>(numFixupLevels);
VTablePatcher& vtablePatcher = *vtablePatcherOwner.get();
const uint32_t kernelLevel = 0;
uint8_t currentLevel = getCurrentFixupLevel();
std::map<const uint8_t*, const VTableBindSymbol> missingBindLocations;
__block std::string kernelID;
__block const dyld3::MachOAnalyzer* kernelMA = nullptr;
if ( appCacheOptions.cacheKind == Options::AppCacheKind::kernel ) {
kernelMA = getKernelStaticExecutableFromCache();
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
DylibStripMode stripMode, const std::vector<std::string> &dependencies,
Diagnostics& dylibDiag,
bool &stop) {
if ( ma == kernelMA ) {
kernelID = dylibID;
stop = true;
}
});
assert(!kernelID.empty());
} else {
assert(existingKernelCollection != nullptr);
existingKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *name, bool &stop) {
if ( ma->isStaticExecutable() ) {
kernelMA = ma;
kernelID = name;
}
});
if ( kernelMA == nullptr ) {
_diagnostics.error("Could not find kernel in kernel collection");
return;
}
}
auto getGlobals = [](Diagnostics& diags, const dyld3::MachOAnalyzer *ma) -> std::map<std::string_view, uint64_t> {
std::map<std::string_view, uint64_t> exports;
__block std::map<std::string_view, uint64_t>& exportsRef = exports;
ma->forEachGlobalSymbol(diags, ^(const char *symbolName, uint64_t n_value,
uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
exportsRef[symbolName] = n_value;
});
return exports;
};
auto getLocals = [](Diagnostics& diags, const dyld3::MachOAnalyzer *ma) -> std::map<std::string_view, uint64_t> {
std::map<std::string_view, uint64_t> exports;
__block std::map<std::string_view, uint64_t>& exportsRef = exports;
ma->forEachLocalSymbol(diags, ^(const char *symbolName, uint64_t n_value,
uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool &stop) {
exportsRef[symbolName] = n_value;
});
return exports;
};
dylibsToSymbols[kernelID] = {
getGlobals(_diagnostics, kernelMA),
getLocals(_diagnostics, kernelMA),
nullptr,
kernelLevel,
std::string(kernelID)
};
for (const InputDylib& dylib : codelessKexts) {
dylibsToSymbols[dylib.dylibID] = { };
}
if ( existingKernelCollection != nullptr ) {
existingKernelCollection->forEachPrelinkInfoLibrary(_diagnostics,
^(const char *bundleName, const char* relativePath,
const std::vector<const char *> &deps) {
dylibsToSymbols[bundleName] = { };
});
}
if ( pageableKernelCollection != nullptr ) {
pageableKernelCollection->forEachPrelinkInfoLibrary(_diagnostics,
^(const char *bundleName, const char* relativePath,
const std::vector<const char *> &deps) {
dylibsToSymbols[bundleName] = { };
});
}
AutoReleaseTypeRef dataRefReleaser;
AutoReleaseTypeRef plistRefReleaser;
__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 ) {
const DylibSymbols& kernelSymbols = dylibsToSymbols[kernelID];
CFDataRef dataRef = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, (const uint8_t*)symbolSetsContent, symbolSetsSize, kCFAllocatorNull);
if ( dataRef == nullptr ) {
_diagnostics.error("Could not create data ref for symbol sets");
return;
}
dataRefReleaser.setRef(dataRef);
CFErrorRef errorRef = nullptr;
CFPropertyListRef plistRef = CFPropertyListCreateWithData(kCFAllocatorDefault, dataRef, kCFPropertyListImmutable, nullptr, &errorRef);
if (errorRef != nullptr) {
CFStringRef errorString = CFErrorCopyDescription(errorRef);
_diagnostics.error("Could not load plist because :%s",
CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
CFRelease(errorRef);
return;
}
if ( plistRef == nullptr ) {
_diagnostics.error("Could not create plist ref for symbol sets");
return;
}
plistRefReleaser.setRef(plistRef);
if ( CFGetTypeID(plistRef) != CFDictionaryGetTypeID() ) {
_diagnostics.error("Symbol set plist should be a dictionary");
return;
}
CFDictionaryRef symbolSetsDictRef = (CFDictionaryRef)plistRef;
CFArrayRef symbolSetArrayRef = (CFArrayRef)CFDictionaryGetValue(symbolSetsDictRef, CFSTR("SymbolsSets"));
if ( symbolSetArrayRef != nullptr ) {
if ( CFGetTypeID(symbolSetArrayRef) != CFArrayGetTypeID() ) {
_diagnostics.error("SymbolsSets value should be an array");
return;
}
for (CFIndex symbolSetIndex = 0; symbolSetIndex != CFArrayGetCount(symbolSetArrayRef); ++symbolSetIndex) {
CFDictionaryRef symbolSetDictRef = (CFDictionaryRef)CFArrayGetValueAtIndex(symbolSetArrayRef, symbolSetIndex);
if ( CFGetTypeID(symbolSetDictRef) != CFDictionaryGetTypeID() ) {
_diagnostics.error("Symbol set element should be a dictionary");
return;
}
CFStringRef bundleIDRef = (CFStringRef)CFDictionaryGetValue(symbolSetDictRef, CFSTR("CFBundleIdentifier"));
if ( (bundleIDRef == nullptr) || (CFGetTypeID(bundleIDRef) != CFStringGetTypeID()) ) {
_diagnostics.error("Symbol set bundle ID should be a string");
return;
}
CFArrayRef symbolsArrayRef = (CFArrayRef)CFDictionaryGetValue(symbolSetDictRef, CFSTR("Symbols"));
if ( (symbolsArrayRef == nullptr) || (CFGetTypeID(symbolsArrayRef) != CFArrayGetTypeID()) ) {
_diagnostics.error("Symbol set symbols should be an array");
return;
}
std::map<std::string_view, uint64_t> symbolSetGlobals;
std::map<std::string_view, uint64_t> symbolSetLocals;
for (CFIndex symbolIndex = 0; symbolIndex != CFArrayGetCount(symbolsArrayRef); ++symbolIndex) {
CFDictionaryRef symbolDictRef = (CFDictionaryRef)CFArrayGetValueAtIndex(symbolsArrayRef, symbolIndex);
if ( CFGetTypeID(symbolDictRef) != CFDictionaryGetTypeID() ) {
_diagnostics.error("Symbols array element should be a dictionary");
return;
}
CFStringRef symbolPrefixRef = (CFStringRef)CFDictionaryGetValue(symbolDictRef, CFSTR("SymbolPrefix"));
if ( symbolPrefixRef != nullptr ) {
if ( CFGetTypeID(symbolPrefixRef) != CFStringGetTypeID() ) {
_diagnostics.error("Symbol prefix should be a string");
return;
}
const char* symbolPrefix = getString(_diagnostics, symbolPrefixRef);
if ( symbolPrefix == nullptr )
return;
size_t symbolPrefixLen = strlen(symbolPrefix);
for (std::pair<std::string_view, uint64_t> kernelGlobal : kernelSymbols.globals) {
if ( strncmp(kernelGlobal.first.data(), symbolPrefix, symbolPrefixLen) == 0 ) {
symbolSetGlobals[kernelGlobal.first] = kernelGlobal.second;
}
}
for (std::pair<std::string_view, uint64_t> kernelLocal : kernelSymbols.locals) {
if ( strncmp(kernelLocal.first.data(), symbolPrefix, symbolPrefixLen) == 0 ) {
symbolSetLocals[kernelLocal.first] = kernelLocal.second;
}
}
continue;
}
CFStringRef symbolNameRef = (CFStringRef)CFDictionaryGetValue(symbolDictRef, CFSTR("SymbolName"));
if ( (symbolNameRef == nullptr) || (CFGetTypeID(symbolNameRef) != CFStringGetTypeID()) ) {
_diagnostics.error("Symbol name should be a string");
return;
}
CFStringRef aliasTargetRef = (CFStringRef)CFDictionaryGetValue(symbolDictRef, CFSTR("AliasTarget"));
if ( aliasTargetRef == nullptr ) {
const char* symbolName = getString(_diagnostics, symbolNameRef);
if ( symbolName == nullptr )
return;
auto globalIt = kernelSymbols.globals.find(symbolName);
if (globalIt != kernelSymbols.globals.end()) {
symbolSetGlobals[symbolName] = globalIt->second;
}
auto localIt = kernelSymbols.locals.find(symbolName);
if (localIt != kernelSymbols.locals.end()) {
symbolSetLocals[symbolName] = localIt->second;
}
} else {
if ( CFGetTypeID(aliasTargetRef) != CFStringGetTypeID() ) {
_diagnostics.error("Alias should be a string");
return;
}
const char* symbolName = getString(_diagnostics, symbolNameRef);
if ( symbolName == nullptr )
return;
const char* aliasTargetName = getString(_diagnostics, aliasTargetRef);
if ( aliasTargetName == nullptr )
return;
auto globalIt = kernelSymbols.globals.find(aliasTargetName);
if (globalIt != kernelSymbols.globals.end()) {
symbolSetGlobals[symbolName] = globalIt->second;
} else {
_diagnostics.error("Alias '%s' not found in kernel", aliasTargetName);
return;
}
auto localIt = kernelSymbols.locals.find(aliasTargetName);
if (localIt != kernelSymbols.locals.end()) {
symbolSetLocals[symbolName] = localIt->second;
} else {
}
}
}
const char* dylibID = getString(_diagnostics, bundleIDRef);
if ( dylibID == nullptr )
return;
auto metaclassHackIt = symbolSetGlobals.find("__ZN15OSMetaClassBase8DispatchE5IORPC");
if ( metaclassHackIt != symbolSetGlobals.end() )
symbolSetGlobals["__ZN15OSMetaClassBase25_RESERVEDOSMetaClassBase3Ev"] = metaclassHackIt->second;
dylibsToSymbols[dylibID] = {
std::move(symbolSetGlobals),
std::move(symbolSetLocals),
nullptr,
kernelLevel,
dylibID
};
}
}
}
auto processBinary = ^(Diagnostics& dylibDiags, const dyld3::MachOAnalyzer *ma,
const std::string& dylibID, uint32_t dylibLevel) {
uint32_t unusedExportTrieOffset = 0;
uint32_t unusedExportTrieSize = 0;
if (ma->hasExportTrie(unusedExportTrieOffset, unusedExportTrieSize))
assert(false);
if ( ma == kernelMA )
return;
dylibsToSymbols[dylibID] = {
getGlobals(dylibDiags, ma),
getLocals(dylibDiags, ma),
getKPI(dylibDiags, ma, dylibID),
dylibLevel,
dylibID };
};
{
struct DylibData {
const dyld3::MachOAnalyzer* ma = nullptr;
Diagnostics& dylibDiag;
const std::string& dylibID;
};
__block std::vector<DylibData> dylibDatas;
dylibDatas.reserve(sortedDylibs.size());
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID, DylibStripMode stripMode,
const std::vector<std::string> &dependencies, Diagnostics &dylibDiag, bool &stop) {
if ( ma == kernelMA )
return;
dylibsToSymbols[dylibID] = { };
dylibDatas.emplace_back((DylibData){ ma, dylibDiag, dylibID });
});
dispatch_apply(dylibDatas.size(), DISPATCH_APPLY_AUTO, ^(size_t index) {
DylibData& dylibData = dylibDatas[index];
processBinary(dylibData.dylibDiag, dylibData.ma, dylibData.dylibID, currentLevel);
});
}
if ( existingKernelCollection != nullptr ) {
uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::kernel);
existingKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *name, bool &stop) {
processBinary(_diagnostics, ma, name, fixupLevel);
});
}
if ( pageableKernelCollection != nullptr ) {
uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::pageableKC);
pageableKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *name, bool &stop) {
processBinary(_diagnostics, ma, name, fixupLevel);
});
}
struct CacheOffsetHash
{
size_t operator() (const CacheOffset& cacheOffset) const
{
return std::hash<uint32_t>{}(cacheOffset.first) ^ std::hash<uint64_t>{}(cacheOffset.second);
}
};
std::unordered_map<CacheOffset, uint64_t, CacheOffsetHash> branchStubs;
branchStubsRegion.sizeInUse = 0;
branchGOTsRegion.sizeInUse = 0;
{
auto symbolMapOwner = std::make_unique<std::unordered_map<std::string_view, std::vector<DylibSymbolLocation>>>();
__block auto& symbolMap = *symbolMapOwner.get();
for (const auto& dylibNameAndSymbols : dylibsToSymbols) {
const DylibSymbols& dylibSymbols = dylibNameAndSymbols.second;
for (const auto& symbolNameAndAddress : dylibSymbols.globals) {
bool isKPI = true;
if ( dylibSymbols.dylibName == "com.apple.kpi.private" ) {
isKPI = false;
} else if ( dylibSymbols.kpiSymbols ) {
const std::unordered_set<std::string>* kpiSymbols = dylibSymbols.kpiSymbols.get();
if ( kpiSymbols->count(symbolNameAndAddress.first.data()) == 0 )
isKPI = false;
}
symbolMap[symbolNameAndAddress.first].push_back({ &dylibSymbols, symbolNameAndAddress.second, isKPI });
}
}
auto dylibFixupsOwner = std::make_unique<std::vector<DylibFixups>>();
__block auto& dylibFixups = *dylibFixupsOwner.get();
dylibFixups.reserve(sortedDylibs.size());
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID, DylibStripMode stripMode,
const std::vector<std::string> &dependencies, Diagnostics &dylibDiag, bool &stop) {
auto dylibSymbolsIt = dylibsToSymbols.find(dylibID);
assert(dylibSymbolsIt != dylibsToSymbols.end());
dylibFixups.emplace_back((DylibFixups){
.ma = ma,
.dylibSymbols = dylibSymbolsIt->second,
.dylibDiag = dylibDiag,
.dependencies = dependencies
});
});
dispatch_apply(dylibFixups.size(), DISPATCH_APPLY_AUTO, ^(size_t index) {
DylibFixups& dylibFixup = dylibFixups[index];
dylibFixup.processFixups(dylibsToSymbols, symbolMap, kernelID, _aslrTracker);
});
for (DylibFixups& dylibFixup : dylibFixups) {
if ( dylibFixup.dylibDiag.hasError() ) {
if ( !_diagnostics.hasError() ) {
_diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
}
return;
}
if ( !dylibFixup.missingBindLocations.empty() ) {
missingBindLocations.insert(dylibFixup.missingBindLocations.begin(),
dylibFixup.missingBindLocations.end());
}
if ( !dylibFixup.fixupLocs.empty() ) {
for (auto fixupLocAndLevel : dylibFixup.fixupLocs) {
_aslrTracker.add(fixupLocAndLevel.first, fixupLocAndLevel.second);
}
}
if ( !dylibFixup.fixupHigh8s.empty() ) {
for (auto fixupLocAndHigh8 : dylibFixup.fixupHigh8s) {
_aslrTracker.setHigh8(fixupLocAndHigh8.first, fixupLocAndHigh8.second);
}
}
if ( !dylibFixup.fixupAuths.empty() ) {
for (auto fixupLocAndAuth : dylibFixup.fixupAuths) {
_aslrTracker.setAuthData(fixupLocAndAuth.first, fixupLocAndAuth.second.diversity,
fixupLocAndAuth.second.addrDiv, fixupLocAndAuth.second.key);
}
}
const uint64_t loadAddress = dylibFixup.ma->preferredLoadAddress();
for (const DylibFixups::BranchStubData& branchData : dylibFixup.branchStubs) {
uint64_t targetAddress = 0;
const CacheOffset& targetCacheOffset = branchData.targetCacheOffset;
auto itAndInserted = branchStubs.insert({ targetCacheOffset, 0 });
if ( itAndInserted.second ) {
if ( branchStubsRegion.sizeInUse == branchStubsRegion.bufferSize ) {
_diagnostics.error("Overflow in branch stubs region");
return;
}
if ( branchGOTsRegion.sizeInUse == branchGOTsRegion.bufferSize ) {
_diagnostics.error("Overflow in branch GOTs region");
return;
}
uint64_t stubAddress = branchStubsRegion.unslidLoadAddress + branchStubsRegion.sizeInUse;
uint8_t* stubBuffer = branchStubsRegion.buffer + branchStubsRegion.sizeInUse;
uint64_t gotAddress = branchGOTsRegion.unslidLoadAddress + branchGOTsRegion.sizeInUse;
uint8_t* gotBuffer = branchGOTsRegion.buffer + branchGOTsRegion.sizeInUse;
uint64_t diffValue = gotAddress - (stubAddress + 6);
stubBuffer[0] = 0xFF;
stubBuffer[1] = 0x25;
memcpy(&stubBuffer[2], &diffValue, sizeof(uint32_t));
uint8_t symbolCacheLevel = targetCacheOffset.first;
uint64_t symbolVMAddr = targetCacheOffset.second;
if ( _is64 )
*((uint64_t*)gotBuffer) = symbolVMAddr;
else
*((uint32_t*)gotBuffer) = (uint32_t)symbolVMAddr;
_aslrTracker.add(gotBuffer, symbolCacheLevel);
branchStubsRegion.sizeInUse += 6;
branchGOTsRegion.sizeInUse += 8;
targetAddress = stubAddress;
itAndInserted.first->second = targetAddress;
} else {
targetAddress = itAndInserted.first->second;
}
uint64_t diffValue = targetAddress - (loadAddress + branchData.fixupVMOffset + 4);
*((uint32_t*)branchData.fixupLoc) = (uint32_t)diffValue;
}
}
}
if ( existingKernelCollection != nullptr ) {
__block uint64_t baseAddress = ~0ULL;
existingKernelCollection->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
baseAddress = std::min(baseAddress, info.vmAddr);
});
uint64_t basePointerOffset = existingKernelCollection->preferredLoadAddress() - baseAddress;
const uint8_t* basePointer = (uint8_t*)existingKernelCollection - basePointerOffset;
vtablePatcher.addKernelCollection(existingKernelCollection, Options::AppCacheKind::kernel,
basePointer, baseAddress);
}
if ( pageableKernelCollection != nullptr ) {
__block uint64_t baseAddress = ~0ULL;
pageableKernelCollection->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
baseAddress = std::min(baseAddress, info.vmAddr);
});
uint64_t basePointerOffset = pageableKernelCollection->preferredLoadAddress() - baseAddress;
const uint8_t* basePointer = (uint8_t*)pageableKernelCollection - basePointerOffset;
vtablePatcher.addKernelCollection(pageableKernelCollection, Options::AppCacheKind::pageableKC,
basePointer, baseAddress);
}
vtablePatcher.addKernelCollection((const dyld3::MachOAppCache*)cacheHeader.header, appCacheOptions.cacheKind,
(const uint8_t*)_fullAllocatedBuffer, cacheBaseAddress);
{
if ( existingKernelCollection != nullptr ) {
uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::kernel);
__block std::map<std::string, std::vector<std::string>> kextDependencies;
kextDependencies[kernelID] = {};
existingKernelCollection->forEachPrelinkInfoLibrary(_diagnostics,
^(const char *bundleName, const char* relativePath,
const std::vector<const char *> &deps) {
std::vector<std::string>& dependencies = kextDependencies[bundleName];
dependencies.insert(dependencies.end(), deps.begin(), deps.end());
});
existingKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *dylibID, bool &stop) {
auto depsIt = kextDependencies.find(dylibID);
assert(depsIt != kextDependencies.end());
vtablePatcher.addDylib(_diagnostics, ma, dylibID, depsIt->second, fixupLevel);
});
}
if ( pageableKernelCollection != nullptr ) {
uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::pageableKC);
__block std::map<std::string, std::vector<std::string>> kextDependencies;
pageableKernelCollection->forEachPrelinkInfoLibrary(_diagnostics,
^(const char *bundleName, const char* relativePath,
const std::vector<const char *> &deps) {
std::vector<std::string>& dependencies = kextDependencies[bundleName];
dependencies.insert(dependencies.end(), deps.begin(), deps.end());
});
pageableKernelCollection->forEachDylib(_diagnostics, ^(const dyld3::MachOAnalyzer *ma, const char *dylibID, bool &stop) {
auto depsIt = kextDependencies.find(dylibID);
assert(depsIt != kextDependencies.end());
vtablePatcher.addDylib(_diagnostics, ma, dylibID, depsIt->second, fixupLevel);
});
}
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID, DylibStripMode stripMode,
const std::vector<std::string> &dependencies, Diagnostics& dylibDiag, bool &stop) {
vtablePatcher.addDylib(dylibDiag, ma, dylibID, dependencies, currentLevel);
});
}
vtablePatcher.findMetaclassDefinitions(dylibsToSymbols, kernelID, kernelMA, appCacheOptions.cacheKind);
vtablePatcher.findExistingFixups(_diagnostics, existingKernelCollection, pageableKernelCollection);
if ( _diagnostics.hasError() )
return;
if ( existingKernelCollection != nullptr ) {
vtablePatcher.findBaseKernelVTables(_diagnostics, existingKernelCollection, dylibsToSymbols);
if ( _diagnostics.hasError() )
return;
}
if ( pageableKernelCollection != nullptr ) {
vtablePatcher.findPageableKernelVTables(_diagnostics, pageableKernelCollection, dylibsToSymbols);
if ( _diagnostics.hasError() )
return;
}
vtablePatcher.findVTables(currentLevel, kernelMA, dylibsToSymbols, _aslrTracker, missingBindLocations);
if ( vtablePatcher.hasError() ) {
_diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
return;
}
vtablePatcher.patchVTables(_diagnostics, missingBindLocations, _aslrTracker, currentLevel);
if ( _diagnostics.hasError() )
return;
if ( vtablePatcher.hasError() ) {
_diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
return;
}
vtablePatcherOwner.reset();
for (const auto& missingLocationAndBind : missingBindLocations) {
const uint8_t* missingBindLoc = missingLocationAndBind.first;
const VTableBindSymbol& missingBind = missingLocationAndBind.second;
__block bool reportedError = false;
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID, DylibStripMode stripMode,
const std::vector<std::string> &dependencies, Diagnostics& dylibDiag, bool &stopDylib) {
intptr_t slide = ma->getSlide();
ma->forEachSection(^(const dyld3::MachOAnalyzer::SectionInfo §Info,
bool malformedSectionRange, bool &stopSection) {
const uint8_t* content = (uint8_t*)(sectInfo.sectAddr + slide);
const uint8_t* start = (uint8_t*)content;
const uint8_t* end = start + sectInfo.sectSize;
if ( (missingBindLoc >= start) && (missingBindLoc < end) ) {
std::string segmentName = sectInfo.segInfo.segName;
std::string sectionName = sectInfo.sectName;
uint64_t sectionOffset = (missingBindLoc - start);
dylibDiag.error("Failed to bind '%s' in '%s' (at offset 0x%llx in %s, %s) as "
"could not find a kext which exports this symbol",
missingBind.symbolName.c_str(), missingBind.binaryID.data(),
sectionOffset, segmentName.c_str(), sectionName.c_str());
reportedError = true;
stopSection = true;
stopDylib = true;
}
});
});
if ( !reportedError ) {
_diagnostics.error("Failed to bind '%s' in '%s' as could not find a kext which exports this symbol",
missingBind.symbolName.c_str(), missingBind.binaryID.data());
}
}
if ( !missingBindLocations.empty() && _diagnostics.noError() ) {
_diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
}
}
namespace {
class ByteBuffer {
public:
ByteBuffer(uint8_t* storage, uintptr_t allocCount) {
buffer.setInitialStorage(storage, allocCount);
}
uint8_t* makeSpace(size_t bytesNeeded) {
for (size_t i = 0; i != bytesNeeded; ++i)
buffer.default_constuct_back();
uint8_t* data = buffer.begin();
dyld3::Array<uint8_t> newBuffer(buffer.end(), buffer.freeCount(), 0);
buffer = newBuffer;
return data;
};
const uint8_t* begin() const {
return buffer.begin();
}
const uint8_t* end() const {
return buffer.end();
}
private:
dyld3::Array<uint8_t> buffer;
};
}
void AppCacheBuilder::writeFixups()
{
if ( fixupsSubRegion.sizeInUse == 0 )
return;
__block ByteBuffer byteBuffer(fixupsSubRegion.buffer, fixupsSubRegion.bufferSize);
const uint8_t* classicRelocsBufferStart = nullptr;
const uint8_t* classicRelocsBufferEnd = nullptr;
CacheHeader64& header = cacheHeader;
if ( header.dynSymbolTable != nullptr ) {
classicRelocsBufferStart = byteBuffer.begin();
dyld3::MachOAnalyzer* cacheMA = (dyld3::MachOAnalyzer*)header.header;
__block uint64_t localRelocBaseAddress = 0;
cacheMA->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
if ( info.protections & VM_PROT_WRITE ) {
localRelocBaseAddress = info.vmAddr;
stop = true;
}
});
const std::vector<void*> allRebaseTargets = _aslrTracker.getRebaseTargets();
const dyld3::MachOAnalyzer* kernelMA = getKernelStaticExecutableFromCache();
kernelMA->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
std::vector<void*> segmentRebaseTargets;
uint64_t segmentVMOffset = info.vmAddr - cacheBaseAddress;
const uint8_t* segmentStartAddr = (const uint8_t*)(_fullAllocatedBuffer + segmentVMOffset);
const uint8_t* segmentEndAddr = (const uint8_t*)(segmentStartAddr + info.vmSize);
for (void* target : allRebaseTargets) {
if ( (target >= segmentStartAddr) && (target < segmentEndAddr) ) {
segmentRebaseTargets.push_back(target);
}
}
std::sort(segmentRebaseTargets.begin(), segmentRebaseTargets.end());
for (void* target : segmentRebaseTargets) {
uint64_t targetSegmentOffset = (uint64_t)target - (uint64_t)segmentStartAddr;
uint64_t offsetFromBaseAddress = (info.vmAddr + targetSegmentOffset) - localRelocBaseAddress;
relocation_info* reloc = (relocation_info*)byteBuffer.makeSpace(sizeof(relocation_info));
reloc->r_address = (uint32_t)offsetFromBaseAddress;
reloc->r_symbolnum = 0;
reloc->r_pcrel = false;
reloc->r_length = 0;
reloc->r_extern = 0;
reloc->r_type = 0;
uint32_t vmAddr32 = 0;
uint64_t vmAddr64 = 0;
if ( _aslrTracker.hasRebaseTarget32(target, &vmAddr32) ) {
reloc->r_length = 2;
*(uint32_t*)target = vmAddr32;
} else if ( _aslrTracker.hasRebaseTarget64(target, &vmAddr64) ) {
reloc->r_length = 3;
*(uint64_t*)target = vmAddr64;
}
}
for (void* target : segmentRebaseTargets)
_aslrTracker.remove(target);
});
classicRelocsBufferEnd = byteBuffer.begin();
}
assert(_is64);
const uint8_t currentLevel = getCurrentFixupLevel();
BLOCK_ACCCESSIBLE_ARRAY(uint64_t, levelBaseAddresses, 4);
for (unsigned i = 0; i != numFixupLevels; ++i)
levelBaseAddresses[i] = 0;
levelBaseAddresses[currentLevel] = cacheBaseAddress;
if ( appCacheOptions.cacheKind != Options::AppCacheKind::kernel ) {
assert(existingKernelCollection != nullptr);
__block uint64_t baseAddress = ~0ULL;
existingKernelCollection->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
baseAddress = std::min(baseAddress, info.vmAddr);
});
levelBaseAddresses[0] = baseAddress;
}
if ( pageableKernelCollection != nullptr ) {
__block uint64_t baseAddress = ~0ULL;
pageableKernelCollection->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo& info, bool& stop) {
baseAddress = std::min(baseAddress, info.vmAddr);
});
uint8_t fixupLevel = getFixupLevel(Options::AppCacheKind::pageableKC);
levelBaseAddresses[fixupLevel] = baseAddress;
}
struct SegmentFixups {
uint8_t* segmentBuffer = nullptr;
uint64_t segmentIndex = 0;
uint64_t unslidLoadAddress = 0;
uint64_t sizeInUse = 0;
dyld_chained_starts_in_segment* starts = nullptr;
uint64_t startsByteSize = 0;
uint64_t numPagesToFixup = 0;
};
auto buildChainedFixups = ^(uint64_t baseAddress, uint64_t segmentCount, std::vector<SegmentFixups>& startsInSegments) {
const uint8_t* chainedFixupsBufferStart = nullptr;
const uint8_t* chainedFixupsBufferEnd = nullptr;
chainedFixupsBufferStart = byteBuffer.begin();
dyld_chained_fixups_header* fixupsHeader = (dyld_chained_fixups_header*)byteBuffer.makeSpace(sizeof(dyld_chained_fixups_header));
dyld_chained_starts_in_image* startsInImage = (dyld_chained_starts_in_image*)byteBuffer.makeSpace(sizeof(dyld_chained_starts_in_image) + (segmentCount * sizeof(uint32_t)));
const uint8_t* endOfStarts = nullptr;
for (SegmentFixups& segmentFixups : startsInSegments) {
uint64_t startsInSegmentByteSize = sizeof(dyld_chained_starts_in_segment) + (segmentFixups.numPagesToFixup * sizeof(uint16_t));
dyld_chained_starts_in_segment* startsInSegment = (dyld_chained_starts_in_segment*)byteBuffer.makeSpace(startsInSegmentByteSize);
endOfStarts = (const uint8_t*)startsInSegment + startsInSegmentByteSize;
segmentFixups.starts = startsInSegment;
segmentFixups.startsByteSize = startsInSegmentByteSize;
}
startsInImage->seg_count = (uint32_t)segmentCount;
for (uint32_t segmentIndex = 0; segmentIndex != segmentCount; ++segmentIndex) {
startsInImage->seg_info_offset[segmentIndex] = 0;
}
for (const SegmentFixups& segmentFixups : startsInSegments) {
dyld_chained_starts_in_segment* startsInSegment = segmentFixups.starts;
uint64_t segmentIndex = segmentFixups.segmentIndex;
assert(segmentIndex < segmentCount);
assert(startsInImage->seg_info_offset[segmentIndex] == 0);
startsInImage->seg_info_offset[segmentIndex] = (uint32_t)((uint8_t*)startsInSegment - (uint8_t*)startsInImage);
}
const unsigned chainedPointerStride = dyld3::MachOAnalyzer::ChainedFixupPointerOnDisk::strideSize(chainedPointerFormat);
for (const SegmentFixups& segmentFixups : startsInSegments) {
dyld_chained_starts_in_segment* startsInSegment = segmentFixups.starts;
startsInSegment->size = (uint32_t)segmentFixups.startsByteSize;
startsInSegment->page_size = fixupsPageSize();
startsInSegment->pointer_format = chainedPointerFormat;
startsInSegment->segment_offset = segmentFixups.unslidLoadAddress - baseAddress;
startsInSegment->max_valid_pointer = 0; startsInSegment->page_count = (segmentFixups.sizeInUse + startsInSegment->page_size - 1) / startsInSegment->page_size;
for (uint64_t pageIndex = 0; pageIndex != startsInSegment->page_count; ++pageIndex) {
startsInSegment->page_start[pageIndex] = DYLD_CHAINED_PTR_START_NONE;
uint8_t* lastLoc = nullptr;
for (uint64_t pageOffset = 0; pageOffset != startsInSegment->page_size; pageOffset += 1) {
uint8_t* fixupLoc = segmentFixups.segmentBuffer + (pageIndex * startsInSegment->page_size) + pageOffset;
uint8_t fixupLevel = currentLevel;
if ( !_aslrTracker.has(fixupLoc, &fixupLevel) )
continue;
assert((pageOffset % chainedPointerStride) == 0);
if ( lastLoc ) {
assert(_is64);
dyld_chained_ptr_64_kernel_cache_rebase* lastLocBits = (dyld_chained_ptr_64_kernel_cache_rebase*)lastLoc;
assert(lastLocBits->next == 0);
uint64_t next = (fixupLoc - lastLoc) / chainedPointerStride;
lastLocBits->next = next;
assert(lastLocBits->next == next && "next location truncated");
} else {
startsInSegment->page_start[pageIndex] = pageOffset;
}
lastLoc = fixupLoc;
uint64_t targetVMAddr = *(uint64_t*)fixupLoc;
uint8_t highByte = 0;
if ( _aslrTracker.hasHigh8(fixupLoc, &highByte) ) {
uint64_t tbi = (uint64_t)highByte << 56;
targetVMAddr |= tbi;
}
assert(fixupLevel < numFixupLevels);
uint64_t targetVMOffset = targetVMAddr - levelBaseAddresses[fixupLevel];
dyld_chained_ptr_64_kernel_cache_rebase* locBits = (dyld_chained_ptr_64_kernel_cache_rebase*)fixupLoc;
uint16_t diversity;
bool hasAddrDiv;
uint8_t key;
if ( _aslrTracker.hasAuthData(fixupLoc, &diversity, &hasAddrDiv, &key) ) {
locBits->target = targetVMOffset;
locBits->cacheLevel = fixupLevel;
locBits->diversity = diversity;
locBits->addrDiv = hasAddrDiv;
locBits->key = key;
locBits->next = 0;
locBits->isAuth = 1;
assert(locBits->target == targetVMOffset && "target truncated");
}
else {
locBits->target = targetVMOffset;
locBits->cacheLevel = fixupLevel;
locBits->diversity = 0;
locBits->addrDiv = 0;
locBits->key = 0;
locBits->next = 0;
locBits->isAuth = 0;
assert(locBits->target == targetVMOffset && "target truncated");
}
}
}
}
chainedFixupsBufferEnd = byteBuffer.begin();
fixupsHeader->fixups_version = 0;
fixupsHeader->starts_offset = (uint32_t)((uint8_t*)startsInImage - (uint8_t*)fixupsHeader);
fixupsHeader->imports_offset = (uint32_t)((uint8_t*)chainedFixupsBufferEnd - (uint8_t*)fixupsHeader);
fixupsHeader->symbols_offset = fixupsHeader->imports_offset;
fixupsHeader->imports_count = 0;
fixupsHeader->imports_format = DYLD_CHAINED_IMPORT; fixupsHeader->symbols_format = 0;
return std::make_pair(chainedFixupsBufferStart, chainedFixupsBufferEnd);
};
if ( fixupsArePerKext() ) {
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
DylibStripMode stripMode, const std::vector<std::string> &dependencies,
Diagnostics& dylibDiag, bool &stop) {
uint64_t loadAddress = ma->preferredLoadAddress();
__block uint64_t numSegments = 0;
__block std::vector<SegmentFixups> segmentFixups;
ma->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
bool segmentCanHaveFixups = false;
if ( appCacheOptions.cacheKind == Options::AppCacheKind::pageableKC ) {
segmentCanHaveFixups = (info.protections & VM_PROT_WRITE) != 0;
} else {
segmentCanHaveFixups = (strcmp(info.segName, "__LINKEDIT") != 0);
}
if ( segmentCanHaveFixups) {
SegmentFixups segmentToFixup;
segmentToFixup.segmentBuffer = (uint8_t*)ma + (info.vmAddr - loadAddress);
segmentToFixup.segmentIndex = info.segIndex;
segmentToFixup.unslidLoadAddress = info.vmAddr;
segmentToFixup.sizeInUse = info.vmSize;
segmentToFixup.starts = nullptr;
segmentToFixup.startsByteSize = 0;
segmentToFixup.numPagesToFixup = numWritablePagesToFixup(info.vmSize);
segmentFixups.push_back(segmentToFixup);
}
++numSegments;
});
std::pair<const uint8_t*, const uint8_t*> chainedFixupsRange = buildChainedFixups(loadAddress,
numSegments, segmentFixups);
const uint8_t* chainedFixupsBufferStart = chainedFixupsRange.first;
const uint8_t* chainedFixupsBufferEnd = chainedFixupsRange.second;
if ( chainedFixupsBufferStart != chainedFixupsBufferEnd ) {
uint64_t fixupsOffset = (uint64_t)chainedFixupsBufferStart - (uint64_t)fixupsSubRegion.buffer;
uint64_t fixupsSize = (uint64_t)chainedFixupsBufferEnd - (uint64_t)chainedFixupsBufferStart;
assert(_is64);
typedef Pointer64<LittleEndian> P;
uint32_t freeSpace = ma->loadCommandsFreeSpace();
assert(freeSpace >= sizeof(macho_linkedit_data_command<P>));
uint8_t* endOfLoadCommands = (uint8_t*)ma + sizeof(macho_header<P>) + ma->sizeofcmds;
macho_header<P>* mh = (macho_header<P>*)ma;
mh->set_sizeofcmds(mh->sizeofcmds() + sizeof(macho_linkedit_data_command<P>));
mh->set_ncmds(mh->ncmds() + 1);
macho_linkedit_data_command<P>* cmd = (macho_linkedit_data_command<P>*)endOfLoadCommands;
cmd->set_cmd(LC_DYLD_CHAINED_FIXUPS);
cmd->set_cmdsize(sizeof(linkedit_data_command));
cmd->set_dataoff((uint32_t)(_readOnlyRegion.cacheFileOffset + _readOnlyRegion.sizeInUse + fixupsOffset));
cmd->set_datasize((uint32_t)fixupsSize);
}
});
uint64_t segmentCount = numRegions();
__block std::vector<SegmentFixups> segmentFixups;
if ( branchGOTsRegion.sizeInUse != 0 ) {
SegmentFixups segmentToFixup;
segmentToFixup.segmentBuffer = branchGOTsRegion.buffer;
segmentToFixup.segmentIndex = branchGOTsRegion.index;
segmentToFixup.unslidLoadAddress = branchGOTsRegion.unslidLoadAddress;
segmentToFixup.sizeInUse = branchGOTsRegion.sizeInUse;
segmentToFixup.starts = nullptr;
segmentToFixup.startsByteSize = 0;
segmentToFixup.numPagesToFixup = numWritablePagesToFixup(branchGOTsRegion.bufferSize);
segmentFixups.push_back(segmentToFixup);
}
std::pair<const uint8_t*, const uint8_t*> chainedFixupsRange = buildChainedFixups(cacheHeaderRegion.unslidLoadAddress,
segmentCount, segmentFixups);
const uint8_t* chainedFixupsBufferStart = chainedFixupsRange.first;
const uint8_t* chainedFixupsBufferEnd = chainedFixupsRange.second;
if ( chainedFixupsBufferStart != chainedFixupsBufferEnd ) {
uint64_t fixupsOffset = (uint64_t)chainedFixupsBufferStart - (uint64_t)fixupsSubRegion.buffer;
uint64_t fixupsSize = (uint64_t)chainedFixupsBufferEnd - (uint64_t)chainedFixupsBufferStart;
header.chainedFixups->dataoff = (uint32_t)_readOnlyRegion.cacheFileOffset + (uint32_t)_readOnlyRegion.sizeInUse + (uint32_t)fixupsOffset;;
header.chainedFixups->datasize = (uint32_t)fixupsSize;
}
} else {
uint64_t segmentCount = numRegions();
__block std::vector<SegmentFixups> segmentFixups;
auto addSegmentStarts = ^(const Region& region) {
SegmentFixups segmentToFixup;
segmentToFixup.segmentBuffer = region.buffer;
segmentToFixup.segmentIndex = region.index;
segmentToFixup.unslidLoadAddress = region.unslidLoadAddress;
segmentToFixup.sizeInUse = region.sizeInUse;
segmentToFixup.starts = nullptr;
segmentToFixup.startsByteSize = 0;
segmentToFixup.numPagesToFixup = numWritablePagesToFixup(region.bufferSize);
segmentFixups.push_back(segmentToFixup);
};
if ( dataConstRegion.sizeInUse != 0 )
addSegmentStarts(dataConstRegion);
if ( branchGOTsRegion.sizeInUse != 0 )
addSegmentStarts(branchGOTsRegion);
if ( readWriteRegion.sizeInUse != 0 )
addSegmentStarts(readWriteRegion);
if ( hibernateRegion.sizeInUse != 0 )
addSegmentStarts(hibernateRegion);
for (const Region& region : nonSplitSegRegions) {
addSegmentStarts(region);
}
std::pair<const uint8_t*, const uint8_t*> chainedFixupsRange = buildChainedFixups(cacheHeaderRegion.unslidLoadAddress,
segmentCount, segmentFixups);
const uint8_t* chainedFixupsBufferStart = chainedFixupsRange.first;
const uint8_t* chainedFixupsBufferEnd = chainedFixupsRange.second;
if ( chainedFixupsBufferStart != chainedFixupsBufferEnd ) {
uint64_t fixupsOffset = (uint64_t)chainedFixupsBufferStart - (uint64_t)fixupsSubRegion.buffer;
uint64_t fixupsSize = (uint64_t)chainedFixupsBufferEnd - (uint64_t)chainedFixupsBufferStart;
header.chainedFixups->dataoff = (uint32_t)_readOnlyRegion.cacheFileOffset + (uint32_t)_readOnlyRegion.sizeInUse + (uint32_t)fixupsOffset;;
header.chainedFixups->datasize = (uint32_t)fixupsSize;
}
}
if ( classicRelocsBufferStart != classicRelocsBufferEnd ) {
uint64_t fixupsOffset = (uint64_t)classicRelocsBufferStart - (uint64_t)fixupsSubRegion.buffer;
uint64_t fixupsSize = (uint64_t)classicRelocsBufferEnd - (uint64_t)classicRelocsBufferStart;
header.dynSymbolTable->locreloff = (uint32_t)_readOnlyRegion.cacheFileOffset + (uint32_t)_readOnlyRegion.sizeInUse + (uint32_t)fixupsOffset;
header.dynSymbolTable->nlocrel = (uint32_t)fixupsSize / sizeof(fixupsSize);
}
uint64_t fixupsSpace = (uint64_t)byteBuffer.end() - (uint64_t)fixupsSubRegion.buffer;
uint8_t* linkeditEnd = _readOnlyRegion.buffer + _readOnlyRegion.sizeInUse;
memcpy(linkeditEnd, fixupsSubRegion.buffer, fixupsSpace);
uint8_t* fixupsEnd = linkeditEnd + fixupsSpace;
_readOnlyRegion.sizeInUse += align(fixupsSpace, _is64 ? 3 : 2);
_readOnlyRegion.sizeInUse = align(_readOnlyRegion.sizeInUse, 14);
_readOnlyRegion.bufferSize = _readOnlyRegion.sizeInUse;
uint8_t* alignedBufferEnd = _readOnlyRegion.buffer + _readOnlyRegion.sizeInUse;
if ( fixupsEnd != alignedBufferEnd ){
memset(fixupsEnd, 0, alignedBufferEnd - fixupsEnd);
}
#if 0
dyld3::MachOAnalyzer* cacheMA = (dyld3::MachOAnalyzer*)header.header;
uint64_t cachePreferredLoadAddress = cacheMA->preferredLoadAddress();
cacheMA->forEachRebase(_diagnostics, false, ^(uint64_t runtimeOffset, bool &stop) {
printf("Rebase: 0x%llx = 0x%llx\n", runtimeOffset, runtimeOffset + cachePreferredLoadAddress);
});
#endif
}
void AppCacheBuilder::allocateBuffer()
{
bool dataRegionFirstInVMOrder = false;
bool hibernateRegionFirstInVMOrder = false;
switch (appCacheOptions.cacheKind) {
case Options::AppCacheKind::none:
assert(0 && "Cache kind should have been set");
break;
case Options::AppCacheKind::kernel:
if ( hibernateAddress != 0 )
hibernateRegionFirstInVMOrder = true;
break;
case Options::AppCacheKind::pageableKC:
break;
case Options::AppCacheKind::kernelCollectionLevel2:
assert(0 && "Unimplemented");
break;
case Options::AppCacheKind::auxKC:
dataRegionFirstInVMOrder = true;
break;
}
__block uint64_t numRegionFileBytes = 0;
__block uint64_t numRegionVMBytes = 0;
std::vector<std::pair<Region*, uint64_t>> regions;
std::vector<std::pair<Region*, uint64_t>> regionsVMOrder;
std::map<const Region*, uint32_t> sectionsToAddToRegions;
if ( hibernateRegionFirstInVMOrder ) {
regionsVMOrder.push_back({ &hibernateRegion, numRegionVMBytes });
uint64_t paddedSize = cacheBaseAddress - hibernateAddress;
if ( hibernateRegion.bufferSize > paddedSize ) {
_diagnostics.error("Could not lay out __HIB segment");
return;
}
numRegionVMBytes = paddedSize;
cacheBaseAddress = hibernateAddress;
sectionsToAddToRegions[&hibernateRegion] = 1;
} else if ( dataRegionFirstInVMOrder ) {
if ( prelinkInfoDict != nullptr ) {
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &prelinkInfoRegion, numRegionVMBytes });
numRegionVMBytes += prelinkInfoRegion.bufferSize;
}
if ( readWriteRegion.sizeInUse != 0 ) {
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &readWriteRegion, numRegionVMBytes });
numRegionVMBytes += readWriteRegion.bufferSize;
}
}
numRegionVMBytes = align(numRegionVMBytes, 14);
regions.push_back({ &cacheHeaderRegion, 0 });
regionsVMOrder.push_back({ &cacheHeaderRegion, numRegionVMBytes });
{
readOnlyTextRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += readOnlyTextRegion.bufferSize;
regions.push_back({ &readOnlyTextRegion, 0 });
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &readOnlyTextRegion, numRegionVMBytes });
numRegionVMBytes += readOnlyTextRegion.bufferSize;
sectionsToAddToRegions[&readOnlyTextRegion] = 1;
}
if ( readExecuteRegion.sizeInUse != 0 ) {
readExecuteRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += readExecuteRegion.bufferSize;
regions.push_back({ &readExecuteRegion, 0 });
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &readExecuteRegion, numRegionVMBytes });
numRegionVMBytes += readExecuteRegion.bufferSize;
}
if ( branchStubsRegion.bufferSize != 0 ) {
branchStubsRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += branchStubsRegion.bufferSize;
regions.push_back({ &branchStubsRegion, 0 });
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &branchStubsRegion, numRegionVMBytes });
numRegionVMBytes += branchStubsRegion.bufferSize;
}
if ( dataConstRegion.sizeInUse != 0 ) {
dataConstRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += dataConstRegion.bufferSize;
regions.push_back({ &dataConstRegion, 0 });
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &dataConstRegion, numRegionVMBytes });
numRegionVMBytes += dataConstRegion.bufferSize;
}
if ( branchGOTsRegion.bufferSize != 0 ) {
branchGOTsRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += branchGOTsRegion.bufferSize;
regions.push_back({ &branchGOTsRegion, 0 });
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &branchGOTsRegion, numRegionVMBytes });
numRegionVMBytes += branchGOTsRegion.bufferSize;
}
numRegionFileBytes = align(numRegionFileBytes, 14);
for (CustomSegment& customSegment : customSegments) {
Region& region = *customSegment.parentRegion;
region.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += region.bufferSize;
regions.push_back({ ®ion, 0 });
assert( (numRegionVMBytes % 4096) == 0);
regionsVMOrder.push_back({ ®ion, numRegionVMBytes });
numRegionVMBytes += region.bufferSize;
uint32_t sectionsToAdd = 0;
if ( customSegment.sections.size() > 1 ) {
sectionsToAdd = (uint32_t)customSegment.sections.size();
} else if ( !customSegment.sections.front().sectionName.empty() ) {
sectionsToAdd = 1;
}
sectionsToAddToRegions[®ion] = sectionsToAdd;
}
numRegionVMBytes = align(numRegionVMBytes, 14);
numRegionFileBytes = align(numRegionFileBytes, 14);
if ( prelinkInfoDict != nullptr )
{
prelinkInfoRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += prelinkInfoRegion.bufferSize;
regions.push_back({ &prelinkInfoRegion, 0 });
if ( !dataRegionFirstInVMOrder ) {
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &prelinkInfoRegion, numRegionVMBytes });
numRegionVMBytes += prelinkInfoRegion.bufferSize;
}
sectionsToAddToRegions[&prelinkInfoRegion] = 1;
}
numRegionFileBytes = align(numRegionFileBytes, 14);
if ( readWriteRegion.sizeInUse != 0 ) {
readWriteRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += readWriteRegion.bufferSize;
regions.push_back({ &readWriteRegion, 0 });
if ( !dataRegionFirstInVMOrder ) {
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &readWriteRegion, numRegionVMBytes });
numRegionVMBytes += readWriteRegion.bufferSize;
}
}
numRegionFileBytes = align(numRegionFileBytes, 14);
if ( hibernateRegion.sizeInUse != 0 ) {
hibernateRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += hibernateRegion.bufferSize;
regions.push_back({ &hibernateRegion, 0 });
}
numRegionFileBytes = align(numRegionFileBytes, 14);
for (Region& region : nonSplitSegRegions) {
region.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += region.bufferSize;
regions.push_back({ ®ion, 0 });
assert( (numRegionVMBytes % 4096) == 0);
regionsVMOrder.push_back({ ®ion, numRegionVMBytes });
numRegionVMBytes += region.bufferSize;
}
numRegionVMBytes = align(numRegionVMBytes, 14);
numRegionFileBytes = align(numRegionFileBytes, 14);
_readOnlyRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += _readOnlyRegion.bufferSize;
regions.push_back({ &_readOnlyRegion, 0 });
numRegionVMBytes = align(numRegionVMBytes, 14);
regionsVMOrder.push_back({ &_readOnlyRegion, numRegionVMBytes });
numRegionVMBytes += _readOnlyRegion.bufferSize;
numRegionFileBytes = align(numRegionFileBytes, 14);
if ( fixupsSubRegion.sizeInUse != 0 ) {
fixupsSubRegion.cacheFileOffset = numRegionFileBytes;
numRegionFileBytes += fixupsSubRegion.bufferSize;
regionsVMOrder.push_back({ &fixupsSubRegion, numRegionVMBytes });
numRegionVMBytes += fixupsSubRegion.bufferSize;
}
const thread_command* unixThread = nullptr;
if (const DylibInfo* dylib = getKernelStaticExecutableInputFile()) {
unixThread = dylib->input->mappedFile.mh->unixThreadLoadCommand();
}
if (_is64) {
const uint64_t cacheHeaderSize = sizeof(mach_header_64);
uint64_t cacheLoadCommandsSize = 0;
uint64_t cacheNumLoadCommands = 0;
++cacheNumLoadCommands;
uint64_t uuidOffset = cacheHeaderSize + cacheLoadCommandsSize;
cacheLoadCommandsSize += sizeof(uuid_command);
++cacheNumLoadCommands;
uint64_t buildVersionOffset = cacheHeaderSize + cacheLoadCommandsSize;
cacheLoadCommandsSize += sizeof(build_version_command);
uint64_t unixThreadOffset = 0;
if ( unixThread != nullptr ) {
++cacheNumLoadCommands;
unixThreadOffset = cacheHeaderSize + cacheLoadCommandsSize;
cacheLoadCommandsSize += unixThread->cmdsize;
}
uint64_t symbolTableOffset = 0;
uint64_t dynSymbolTableOffset = 0;
if (const DylibInfo* dylib = getKernelStaticExecutableInputFile()) {
if ( dylib->input->mappedFile.mh->usesClassicRelocationsInKernelCollection() ) {
++cacheNumLoadCommands;
symbolTableOffset = cacheHeaderSize + cacheLoadCommandsSize;
cacheLoadCommandsSize += sizeof(symtab_command);
++cacheNumLoadCommands;
dynSymbolTableOffset = cacheHeaderSize + cacheLoadCommandsSize;
cacheLoadCommandsSize += sizeof(dysymtab_command);
}
}
uint64_t chainedFixupsOffset = 0;
if ( fixupsSubRegion.bufferSize != 0 ) {
++cacheNumLoadCommands;
chainedFixupsOffset = cacheHeaderSize + cacheLoadCommandsSize;
cacheLoadCommandsSize += sizeof(linkedit_data_command);
}
for (auto& regionAndOffset : regions) {
++cacheNumLoadCommands;
regionAndOffset.second = cacheHeaderSize + cacheLoadCommandsSize;
cacheLoadCommandsSize += sizeof(segment_command_64);
auto sectionIt = sectionsToAddToRegions.find(regionAndOffset.first);
if ( sectionIt != sectionsToAddToRegions.end() ) {
uint32_t numSections = sectionIt->second;
cacheLoadCommandsSize += sizeof(section_64) * numSections;
}
}
std::vector<std::pair<const DylibInfo*, uint64_t>> dylibs;
for (const auto& dylib : sortedDylibs) {
++cacheNumLoadCommands;
const char* dylibID = dylib.dylibID.c_str();
dylibs.push_back({ &dylib, cacheHeaderSize + cacheLoadCommandsSize });
uint64_t size = align(sizeof(fileset_entry_command) + strlen(dylibID) + 1, 3);
cacheLoadCommandsSize += size;
}
uint64_t cacheHeaderRegionSize = cacheHeaderSize + cacheLoadCommandsSize;
cacheHeaderRegionSize = align(cacheHeaderRegionSize, 14);
assert(numRegionFileBytes <= numRegionVMBytes);
_allocatedBufferSize = cacheHeaderRegionSize + numRegionVMBytes;
uint64_t cacheLimit = 1 << 30;
if ( (appCacheOptions.cacheKind == Options::AppCacheKind::auxKC) && (_options.archs == &dyld3::GradedArchs::arm64e) )
cacheLimit = 64 * (1 << 20);
if ( _allocatedBufferSize >= cacheLimit ) {
_diagnostics.error("kernel collection size exceeds maximum size of %lld vs actual size of %lld",
cacheLimit, _allocatedBufferSize);
return;
}
if ( vm_allocate(mach_task_self(), &_fullAllocatedBuffer, _allocatedBufferSize, VM_FLAGS_ANYWHERE) != 0 ) {
_diagnostics.error("could not allocate buffer");
return;
}
{
bool seenCacheHeader = false;
for (const auto& regionAndVMOffset : regionsVMOrder) {
Region* region = regionAndVMOffset.first;
uint64_t vmOffset = regionAndVMOffset.second;
region->unslidLoadAddress = cacheBaseAddress + vmOffset;
if ( seenCacheHeader ) {
region->unslidLoadAddress += cacheHeaderRegionSize;
} else {
seenCacheHeader = (region == &cacheHeaderRegion);
}
region->buffer = (uint8_t*)_fullAllocatedBuffer + (region->unslidLoadAddress - cacheBaseAddress);
}
}
cacheHeaderRegion.bufferSize = cacheHeaderRegionSize;
cacheHeaderRegion.sizeInUse = cacheHeaderRegion.bufferSize;
cacheHeaderRegion.cacheFileOffset = 0;
cacheHeaderRegion.permissions = VM_PROT_READ;
cacheHeaderRegion.name = "__TEXT";
#if 0
for (const auto& regionAndVMOffset : regionsVMOrder) {
printf("0x%llx : %s\n", regionAndVMOffset.first->unslidLoadAddress, regionAndVMOffset.first->name.c_str());
}
#endif
CacheHeader64& header = cacheHeader;
header.header = (mach_header_64*)cacheHeaderRegion.buffer;
header.numLoadCommands = cacheNumLoadCommands;
header.loadCommandsSize = cacheLoadCommandsSize;
header.uuid = (uuid_command*)(cacheHeaderRegion.buffer + uuidOffset);
header.buildVersion = (build_version_command*)(cacheHeaderRegion.buffer + buildVersionOffset);
if ( unixThread != nullptr ) {
header.unixThread = (thread_command*)(cacheHeaderRegion.buffer + unixThreadOffset);
memcpy(header.unixThread, unixThread, unixThread->cmdsize);
}
if ( symbolTableOffset != 0 ) {
header.symbolTable = (symtab_command*)(cacheHeaderRegion.buffer + symbolTableOffset);
}
if ( dynSymbolTableOffset != 0 ) {
header.dynSymbolTable = (dysymtab_command*)(cacheHeaderRegion.buffer + dynSymbolTableOffset);
}
if ( chainedFixupsOffset != 0 ) {
header.chainedFixups = (linkedit_data_command*)(cacheHeaderRegion.buffer + chainedFixupsOffset);
}
for (auto& regionAndOffset : regions) {
assert(regionAndOffset.first->permissions != 0);
segment_command_64* loadCommand = (segment_command_64*)(cacheHeaderRegion.buffer + regionAndOffset.second);
header.segments.push_back({ loadCommand, regionAndOffset.first });
}
for (const auto& dylibAndOffset : dylibs) {
fileset_entry_command* loadCommand = (fileset_entry_command*)(cacheHeaderRegion.buffer + dylibAndOffset.second);
header.dylibs.push_back({ loadCommand, dylibAndOffset.first });
}
readOnlyTextRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
readExecuteRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
branchStubsRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
dataConstRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
branchGOTsRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
readWriteRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
hibernateRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
for (Region& region : customDataRegions) {
region.cacheFileOffset += cacheHeaderRegion.sizeInUse;
}
for (Region& region : nonSplitSegRegions) {
region.cacheFileOffset += cacheHeaderRegion.sizeInUse;
}
prelinkInfoRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
_readOnlyRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
fixupsSubRegion.cacheFileOffset += cacheHeaderRegion.sizeInUse;
} else {
assert(false);
}
}
void AppCacheBuilder::generateCacheHeader() {
if ( !_is64 )
assert(0 && "Unimplemented");
{
typedef Pointer64<LittleEndian> P;
CacheHeader64& header = cacheHeader;
macho_header<P>* mh = (macho_header<P>*)header.header;
mh->set_magic(MH_MAGIC_64);
mh->set_cputype(_options.archs->_orderedCpuTypes[0].type);
mh->set_cpusubtype(_options.archs->_orderedCpuTypes[0].subtype);
mh->set_filetype(MH_FILESET);
mh->set_ncmds((uint32_t)header.numLoadCommands);
mh->set_sizeofcmds((uint32_t)header.loadCommandsSize);
mh->set_flags(0);
mh->set_reserved(0);
{
macho_uuid_command<P>* cmd = (macho_uuid_command<P>*)header.uuid;
cmd->set_cmd(LC_UUID);
cmd->set_cmdsize(sizeof(uuid_command));
cmd->set_uuid((uuid_t){});
}
{
macho_build_version_command<P>* cmd = (macho_build_version_command<P>*)header.buildVersion;
cmd->set_cmd(LC_BUILD_VERSION);
cmd->set_cmdsize(sizeof(build_version_command));
cmd->set_platform((uint32_t)_options.platform);
cmd->set_minos(0);
cmd->set_sdk(0);
cmd->set_ntools(0);
}
if ( header.unixThread != nullptr ) {
const DylibInfo* dylib = getKernelStaticExecutableInputFile();
const dyld3::MachOAnalyzer* ma = dylib->input->mappedFile.mh;
ma->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
uint64_t startAddress = dylib->input->mappedFile.mh->entryAddrFromThreadCmd(header.unixThread);
if ( (startAddress < info.vmAddr) || (startAddress >= (info.vmAddr + info.vmSize)) )
return;
uint64_t segSlide = dylib->cacheLocation[info.segIndex].dstCacheUnslidAddress - info.vmAddr;
startAddress += segSlide;
macho_thread_command<P>* cmd = (macho_thread_command<P>*)header.unixThread;
cmd->set_thread_register(ma->entryAddrRegisterIndexForThreadCmd(), startAddress);
stop = true;
});
}
if ( header.symbolTable != nullptr ) {
macho_symtab_command<P>* cmd = (macho_symtab_command<P>*)header.symbolTable;
cmd->set_cmd(LC_SYMTAB);
cmd->set_cmdsize(sizeof(symtab_command));
cmd->set_symoff(0);
cmd->set_nsyms(0);
cmd->set_stroff(0);
cmd->set_strsize(0);
}
if ( header.dynSymbolTable != nullptr ) {
macho_dysymtab_command<P>* cmd = (macho_dysymtab_command<P>*)header.dynSymbolTable;
cmd->set_cmd(LC_DYSYMTAB);
cmd->set_cmdsize(sizeof(dysymtab_command));
cmd->set_ilocalsym(0);
cmd->set_nlocalsym(0);
cmd->set_iextdefsym(0);
cmd->set_nextdefsym(0);
cmd->set_iundefsym(0);
cmd->set_nundefsym(0);
cmd->set_tocoff(0);
cmd->set_ntoc(0);
cmd->set_modtaboff(0);
cmd->set_nmodtab(0);
cmd->set_extrefsymoff(0);
cmd->set_nextrefsyms(0);
cmd->set_indirectsymoff(0);
cmd->set_nindirectsyms(0);
cmd->set_extreloff(0);
cmd->set_nextrel(0);
cmd->set_locreloff(0);
cmd->set_nlocrel(0);
}
if ( header.chainedFixups != nullptr ) {
macho_linkedit_data_command<P>* cmd = (macho_linkedit_data_command<P>*)header.chainedFixups;
cmd->set_cmd(LC_DYLD_CHAINED_FIXUPS);
cmd->set_cmdsize(sizeof(linkedit_data_command));
cmd->set_dataoff(0);
cmd->set_datasize(0);
}
uint64_t segmentIndex = 0;
for (CacheHeader64::SegmentCommandAndRegion& cmdAndInfo : header.segments) {
macho_segment_command<P>* cmd = (macho_segment_command<P>*)cmdAndInfo.first;
Region* region = cmdAndInfo.second;
region->index = segmentIndex;
++segmentIndex;
assert(region->permissions != 0);
const char* name = region->name.c_str();
cmd->set_cmd(LC_SEGMENT_64);
cmd->set_cmdsize(sizeof(segment_command_64));
cmd->set_segname(name);
cmd->set_vmaddr(region->unslidLoadAddress);
cmd->set_vmsize(region->sizeInUse);
cmd->set_fileoff(region->cacheFileOffset);
cmd->set_filesize(region->sizeInUse);
cmd->set_maxprot(region->permissions);
cmd->set_initprot(region->permissions);
cmd->set_nsects(0);
cmd->set_flags(0);
if ( region == &readOnlyTextRegion ) {
cmd->set_cmdsize(cmd->cmdsize() + sizeof(section_64));
cmd->set_nsects(1);
macho_section<P>* section = (macho_section<P>*)((uint64_t)cmd + sizeof(*cmd));
section->set_sectname("__text");
section->set_segname(name);
section->set_addr(region->unslidLoadAddress);
section->set_size(region->sizeInUse);
section->set_offset((uint32_t)region->cacheFileOffset);
section->set_align(0);
section->set_reloff(0);
section->set_nreloc(0);
section->set_flags(S_REGULAR | S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS);
section->set_reserved1(0);
section->set_reserved2(0);
} else if ( region == &prelinkInfoRegion ) {
cmd->set_cmdsize(cmd->cmdsize() + sizeof(section_64));
cmd->set_nsects(1);
macho_section<P>* section = (macho_section<P>*)((uint64_t)cmd + sizeof(*cmd));
section->set_sectname("__info");
section->set_segname(name);
section->set_addr(region->unslidLoadAddress);
section->set_size(region->sizeInUse);
section->set_offset((uint32_t)region->cacheFileOffset);
section->set_align(0);
section->set_reloff(0);
section->set_nreloc(0);
section->set_flags(S_REGULAR);
section->set_reserved1(0);
section->set_reserved2(0);
} else if ( region == &hibernateRegion ) {
cmd->set_cmdsize(cmd->cmdsize() + sizeof(section_64));
cmd->set_nsects(1);
macho_section<P>* section = (macho_section<P>*)((uint64_t)cmd + sizeof(*cmd));
section->set_sectname("__text");
section->set_segname(name);
section->set_addr(region->unslidLoadAddress);
section->set_size(region->sizeInUse);
section->set_offset((uint32_t)region->cacheFileOffset);
section->set_align(0);
section->set_reloff(0);
section->set_nreloc(0);
section->set_flags(S_REGULAR | S_ATTR_SOME_INSTRUCTIONS);
section->set_reserved1(0);
section->set_reserved2(0);
} else {
for (CustomSegment &customSegment : customSegments) {
if ( region != customSegment.parentRegion )
continue;
uint32_t sectionsToAdd = 0;
if ( customSegment.sections.size() > 1 ) {
sectionsToAdd = (uint32_t)customSegment.sections.size();
} else if ( !customSegment.sections.front().sectionName.empty() ) {
sectionsToAdd = 1;
} else {
continue;
}
cmd->set_cmdsize(cmd->cmdsize() + (sizeof(section_64) * sectionsToAdd));
cmd->set_nsects(sectionsToAdd);
uint8_t* bufferPos = (uint8_t*)cmd + sizeof(*cmd);
for (const CustomSegment::CustomSection& customSection : customSegment.sections) {
macho_section<P>* section = (macho_section<P>*)bufferPos;
section->set_sectname(customSection.sectionName.c_str());
section->set_segname(name);
section->set_addr(region->unslidLoadAddress + customSection.offsetInRegion);
section->set_size(customSection.data.size());
section->set_offset((uint32_t)(region->cacheFileOffset + customSection.offsetInRegion));
section->set_align(0);
section->set_reloff(0);
section->set_nreloc(0);
section->set_flags(S_REGULAR);
section->set_reserved1(0);
section->set_reserved2(0);
bufferPos += sizeof(section_64);
}
}
}
}
for (CacheHeader64::DylibCommandAndInfo& cmdAndInfo : header.dylibs) {
macho_fileset_entry_command<P>* cmd = (macho_fileset_entry_command<P>*)cmdAndInfo.first;
const DylibInfo* dylib = cmdAndInfo.second;
const char* dylibID = dylib->dylibID.c_str();
uint64_t size = align(sizeof(fileset_entry_command) + strlen(dylibID) + 1, 3);
cmd->set_cmd(LC_FILESET_ENTRY);
cmd->set_cmdsize((uint32_t)size);
cmd->set_vmaddr(dylib->cacheLocation[0].dstCacheUnslidAddress);
cmd->set_fileoff(dylib->cacheLocation[0].dstCacheFileOffset);
cmd->set_entry_id(dylibID);
}
}
}
void AppCacheBuilder::generatePrelinkInfo() {
if ( prelinkInfoDict == nullptr ) {
bool needsPrelink = true;
if ( appCacheOptions.cacheKind == Options::AppCacheKind::kernel ) {
if ( sortedDylibs.size() == 1 )
needsPrelink = false;
}
if ( needsPrelink ) {
_diagnostics.error("Expected prelink info dictionary");
}
return;
}
CFMutableArrayRef arrayRef = (CFMutableArrayRef)CFDictionaryGetValue(prelinkInfoDict,
CFSTR("_PrelinkInfoDictionary"));
if ( arrayRef == nullptr ) {
_diagnostics.error("Expected prelink info dictionary array");
return;
}
typedef std::pair<const dyld3::MachOAnalyzer*, Diagnostics*> DylibAndDiag;
__block std::unordered_map<std::string_view, DylibAndDiag> dylibs;
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
DylibStripMode stripMode, const std::vector<std::string>& dependencies,
Diagnostics& dylibDiag, bool& stop) {
dylibs[dylibID] = { ma, &dylibDiag };
});
for (const InputDylib& dylib : codelessKexts) {
dylibs[dylib.dylibID] = { nullptr, 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();
};
bool badKext = false;
CFIndex arrayCount = CFArrayGetCount(arrayRef);
for (CFIndex i = 0; i != arrayCount; ++i) {
CFMutableDictionaryRef dictRef = (CFMutableDictionaryRef)CFArrayGetValueAtIndex(arrayRef, i);
CFStringRef bundleIDRef = (CFStringRef)CFDictionaryGetValue(dictRef, CFSTR("CFBundleIdentifier"));
if ( bundleIDRef == nullptr ) {
_diagnostics.error("Cannot get bundle ID for dylib");
return;
}
const char* bundleIDStr = getString(_diagnostics, bundleIDRef);
if ( _diagnostics.hasError() )
return;
auto dylibIt = dylibs.find(bundleIDStr);
if ( dylibIt == dylibs.end() ) {
_diagnostics.error("Cannot get dylib for bundle ID %s", bundleIDStr);
return;
}
const dyld3::MachOAnalyzer *ma = dylibIt->second.first;
Diagnostics* dylibDiag = dylibIt->second.second;
if ( ma == nullptr )
continue;
uint64_t loadAddress = ma->preferredLoadAddress();
CFNumberRef loadAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &loadAddress);
CFDictionarySetValue(dictRef, CFSTR("_PrelinkExecutableLoadAddr"), loadAddrRef);
CFRelease(loadAddrRef);
CFNumberRef sourceAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &loadAddress);
CFDictionarySetValue(dictRef, CFSTR("_PrelinkExecutableSourceAddr"), sourceAddrRef);
CFRelease(sourceAddrRef);
__block uint64_t kmodInfoAddress = 0;
__block bool found = false;
{
dyld3::MachOAnalyzer::FoundSymbol foundInfo;
found = ma->findExportedSymbol(_diagnostics, "_kmod_info", true, foundInfo, nullptr);
if ( found ) {
kmodInfoAddress = loadAddress + foundInfo.value;
}
}
if ( !found ) {
ma->forEachLocalSymbol(_diagnostics, ^(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 ) {
kmodInfoAddress = n_value;
found = true;
stop = true;
}
});
}
if ( found ) {
CFNumberRef kmodInfoAddrRef = CFNumberCreate(kCFAllocatorDefault, kCFNumberLongLongType, &kmodInfoAddress);
CFDictionarySetValue(dictRef, CFSTR("_PrelinkKmodInfo"), kmodInfoAddrRef);
CFRelease(kmodInfoAddrRef);
assert(_is64);
uint64_t kmodInfoVMOffset = kmodInfoAddress - loadAddress;
dyld3::MachOAppCache::KModInfo64_v1* kmodInfo = (dyld3::MachOAppCache::KModInfo64_v1*)((uint8_t*)ma + kmodInfoVMOffset);
if ( kmodInfo->info_version != 1 ) {
dylibDiag->error("unsupported kmod_info version of %d", kmodInfo->info_version);
badKext = true;
continue;
}
__block uint64_t textSegmnentVMAddr = 0;
__block uint64_t textSegmnentVMSize = 0;
ma->forEachSegment(^(const dyld3::MachOAnalyzer::SegmentInfo &info, bool &stop) {
if ( !strcmp(info.segName, "__TEXT") ) {
textSegmnentVMAddr = info.vmAddr;
textSegmnentVMSize = info.vmSize;
stop = true;
}
});
kmodInfo->address = textSegmnentVMAddr;
kmodInfo->size = textSegmnentVMSize;
}
}
CFErrorRef errorRef = nullptr;
CFDataRef xmlData = CFPropertyListCreateData(kCFAllocatorDefault, prelinkInfoDict,
kCFPropertyListXMLFormat_v1_0, 0, &errorRef);
if (errorRef != nullptr) {
CFStringRef errorString = CFErrorCopyDescription(errorRef);
_diagnostics.error("Could not serialise plist because :%s",
CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
CFRelease(xmlData);
CFRelease(errorRef);
return;
} else {
CFIndex xmlDataLength = CFDataGetLength(xmlData);
if ( xmlDataLength > prelinkInfoRegion.bufferSize ) {
_diagnostics.error("Overflow in prelink info segment. 0x%llx vs 0x%llx",
(uint64_t)xmlDataLength, prelinkInfoRegion.bufferSize);
CFRelease(xmlData);
return;
}
memcpy(prelinkInfoRegion.buffer, CFDataGetBytePtr(xmlData), xmlDataLength);
CFRelease(xmlData);
}
if ( badKext && _diagnostics.noError() ) {
_diagnostics.error("One or more binaries has an error which prevented linking. See other errors.");
}
}
bool AppCacheBuilder::addCustomSection(const std::string& segmentName,
CustomSegment::CustomSection section) {
for (CustomSegment& segment: customSegments) {
if ( segment.segmentName != segmentName )
continue;
if ( section.sectionName.empty() ) {
_diagnostics.error("Cannot add empty section name with segment '%s' as other sections exist on that segment",
segmentName.c_str());
return false;
}
for (const CustomSegment::CustomSection& existingSection : segment.sections) {
if ( existingSection.sectionName.empty() ) {
_diagnostics.error("Cannot add section named '%s' with segment '%s' as segment has existing nameless section",
segmentName.c_str(), section.sectionName.c_str());
return false;
}
if ( existingSection.sectionName == section.sectionName ) {
_diagnostics.error("Cannot add section named '%s' with segment '%s' as section already exists",
segmentName.c_str(), section.sectionName.c_str());
return false;
}
}
segment.sections.push_back(section);
return true;
}
CustomSegment segment;
segment.segmentName = segmentName;
segment.sections.push_back(section);
customSegments.push_back(segment);
return true;
}
void AppCacheBuilder::setExistingKernelCollection(const dyld3::MachOAppCache* appCacheMA) {
existingKernelCollection = appCacheMA;
}
void AppCacheBuilder::setExistingPageableKernelCollection(const dyld3::MachOAppCache* appCacheMA) {
pageableKernelCollection = appCacheMA;
}
void AppCacheBuilder::setExtraPrelinkInfo(CFDictionaryRef dictionary) {
extraPrelinkInfo = dictionary;
}
inline uint32_t absolutetime_to_milliseconds(uint64_t abstime)
{
return (uint32_t)(abstime/1000/1000);
}
void AppCacheBuilder::buildAppCache(const std::vector<InputDylib>& dylibs)
{
uint64_t t1 = mach_absolute_time();
makeSortedDylibs(dylibs);
if ( (_options.archs == &dyld3::GradedArchs::x86_64) || (_options.archs == &dyld3::GradedArchs::x86_64h) ) {
chainedPointerFormat = DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE;
} else {
chainedPointerFormat = DYLD_CHAINED_PTR_64_KERNEL_CACHE;
}
if ( sortedDylibs.empty() ) {
if ( codelessKexts.empty() ) {
_diagnostics.error("No binaries or codeless kexts were provided");
} else {
_diagnostics.error("Cannot build collection without binaries as only %lx codeless kexts provided",
codelessKexts.size());
}
return;
}
assignSegmentRegionsAndOffsets();
if ( _diagnostics.hasError() )
return;
allocateBuffer();
if ( _diagnostics.hasError() )
return;
assignSegmentAddresses();
generateCacheHeader();
uint64_t t2 = mach_absolute_time();
copyRawSegments();
uint64_t t3 = mach_absolute_time();
if ( appCacheOptions.cacheKind == Options::AppCacheKind::auxKC ) {
__block const Region* firstDataRegion = nullptr;
__block const Region* lastDataRegion = nullptr;
forEachRegion(^(const Region ®ion) {
if ( (firstDataRegion == nullptr) || (region.buffer < firstDataRegion->buffer) )
firstDataRegion = ®ion;
if ( (lastDataRegion == nullptr) || (region.buffer > lastDataRegion->buffer) )
lastDataRegion = ®ion;
});
if ( firstDataRegion != nullptr ) {
uint64_t size = (lastDataRegion->buffer - firstDataRegion->buffer) + lastDataRegion->bufferSize;
_aslrTracker.setDataRegion(firstDataRegion->buffer, size);
}
} else {
const Region* firstDataRegion = nullptr;
const Region* lastDataRegion = nullptr;
if ( hibernateRegion.sizeInUse != 0 ) {
firstDataRegion = &hibernateRegion;
lastDataRegion = &hibernateRegion;
}
if ( dataConstRegion.sizeInUse != 0 ) {
if ( firstDataRegion == nullptr )
firstDataRegion = &dataConstRegion;
if ( (lastDataRegion == nullptr) || (dataConstRegion.buffer > lastDataRegion->buffer) )
lastDataRegion = &dataConstRegion;
}
if ( branchGOTsRegion.bufferSize != 0 ) {
if ( firstDataRegion == nullptr )
firstDataRegion = &branchGOTsRegion;
if ( (lastDataRegion == nullptr) || (branchGOTsRegion.buffer > lastDataRegion->buffer) )
lastDataRegion = &branchGOTsRegion;
}
if ( readWriteRegion.sizeInUse != 0 ) {
if ( (firstDataRegion == nullptr) || (readWriteRegion.buffer < firstDataRegion->buffer) )
firstDataRegion = &readWriteRegion;
if ( (lastDataRegion == nullptr) || (readWriteRegion.buffer > lastDataRegion->buffer) )
lastDataRegion = &readWriteRegion;
}
for (const Region& region : nonSplitSegRegions) {
if ( readWriteRegion.sizeInUse != 0 ) {
assert(region.buffer >= readWriteRegion.buffer);
}
if ( firstDataRegion == nullptr )
firstDataRegion = ®ion;
if ( (lastDataRegion == nullptr) || (region.buffer > lastDataRegion->buffer) )
lastDataRegion = ®ion;
}
if ( firstDataRegion != nullptr ) {
uint64_t size = (lastDataRegion->buffer - firstDataRegion->buffer) + lastDataRegion->bufferSize;
_aslrTracker.setDataRegion(firstDataRegion->buffer, size);
}
}
adjustAllImagesForNewSegmentLocations(cacheBaseAddress, _aslrTracker, nullptr, nullptr);
if ( _diagnostics.hasError() )
return;
generatePrelinkInfo();
if ( _diagnostics.hasError() )
return;
uint64_t t4 = mach_absolute_time();
processFixups();
if ( _diagnostics.hasError() )
return;
uint64_t t5 = mach_absolute_time();
uint64_t t6 = mach_absolute_time();
{
__block std::vector<std::pair<const mach_header*, const char*>> images;
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
DylibStripMode stripMode, const std::vector<std::string>& dependencies,
Diagnostics& dylibDiag, bool& stop) {
images.push_back({ ma, dylibID.c_str() });
});
const char* const neverStubEliminateSymbols[] = {
nullptr
};
uint64_t cacheUnslidAddr = cacheBaseAddress;
int64_t cacheSlide = (long)_fullAllocatedBuffer - cacheUnslidAddr;
optimizeAwayStubs(images, cacheSlide, cacheUnslidAddr,
nullptr, neverStubEliminateSymbols);
}
fipsSign();
uint64_t t7 = mach_absolute_time();
{
__block std::vector<std::tuple<const mach_header*, const char*, DylibStripMode>> images;
__block std::set<const mach_header*> imagesToStrip;
__block const dyld3::MachOAnalyzer* kernelMA = nullptr;
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
DylibStripMode stripMode, const std::vector<std::string>& dependencies,
Diagnostics& dylibDiag, bool& stop) {
if ( stripMode == DylibStripMode::stripNone ) {
switch (appCacheOptions.cacheKind) {
case Options::AppCacheKind::none:
assert("Unhandled kind");
break;
case Options::AppCacheKind::kernel:
switch (appCacheOptions.stripMode) {
case Options::StripMode::none:
break;
case Options::StripMode::all:
stripMode = CacheBuilder::DylibStripMode::stripAll;
break;
case Options::StripMode::allExceptKernel:
if ( kernelMA == nullptr ) {
kernelMA = getKernelStaticExecutableFromCache();
}
if ( ma != kernelMA )
stripMode = CacheBuilder::DylibStripMode::stripAll;
break;
}
break;
case Options::AppCacheKind::pageableKC:
assert("Unhandled kind");
break;
case Options::AppCacheKind::kernelCollectionLevel2:
assert("Unhandled kind");
break;
case Options::AppCacheKind::auxKC:
assert("Unhandled kind");
break;
}
}
images.push_back({ ma, dylibID.c_str(), stripMode });
});
optimizeLinkedit(nullptr, images);
if ( !_is64 )
assert(0 && "Unimplemented");
{
typedef Pointer64<LittleEndian> P;
CacheHeader64& header = cacheHeader;
for (CacheHeader64::SegmentCommandAndRegion& cmdAndRegion : header.segments) {
if (cmdAndRegion.second != &_readOnlyRegion)
continue;
cmdAndRegion.first->vmsize = _readOnlyRegion.sizeInUse;
cmdAndRegion.first->filesize = _readOnlyRegion.sizeInUse;
break;
}
}
}
uint64_t t8 = mach_absolute_time();
uint64_t t9 = mach_absolute_time();
writeFixups();
{
if ( !_is64 )
assert(0 && "Unimplemented");
{
typedef Pointer64<LittleEndian> P;
CacheHeader64& header = cacheHeader;
for (CacheHeader64::SegmentCommandAndRegion& cmdAndRegion : header.segments) {
if (cmdAndRegion.second != &_readOnlyRegion)
continue;
cmdAndRegion.first->vmsize = _readOnlyRegion.sizeInUse;
cmdAndRegion.first->filesize = _readOnlyRegion.sizeInUse;
break;
}
}
}
uint64_t t10 = mach_absolute_time();
generateUUID();
if ( _diagnostics.hasError() )
return;
uint64_t t11 = mach_absolute_time();
if ( _options.verbose ) {
fprintf(stderr, "time to layout cache: %ums\n", absolutetime_to_milliseconds(t2-t1));
fprintf(stderr, "time to copy cached dylibs into buffer: %ums\n", absolutetime_to_milliseconds(t3-t2));
fprintf(stderr, "time to adjust segments for new split locations: %ums\n", absolutetime_to_milliseconds(t4-t3));
fprintf(stderr, "time to bind all images: %ums\n", absolutetime_to_milliseconds(t5-t4));
fprintf(stderr, "time to optimize Objective-C: %ums\n", absolutetime_to_milliseconds(t6-t5));
fprintf(stderr, "time to do stub elimination: %ums\n", absolutetime_to_milliseconds(t7-t6));
fprintf(stderr, "time to optimize LINKEDITs: %ums\n", absolutetime_to_milliseconds(t8-t7));
fprintf(stderr, "time to compute slide info: %ums\n", absolutetime_to_milliseconds(t10-t9));
fprintf(stderr, "time to compute UUID and codesign cache file: %ums\n", absolutetime_to_milliseconds(t11-t10));
}
}
void AppCacheBuilder::fipsSign()
{
if ( appCacheOptions.cacheKind != Options::AppCacheKind::kernel )
return;
__block const dyld3::MachOAnalyzer* kextMA = nullptr;
forEachCacheDylib(^(const dyld3::MachOAnalyzer *ma, const std::string &dylibID,
DylibStripMode stripMode, const std::vector<std::string>& dependencies,
Diagnostics& dylibDiag, bool& stop) {
if ( dylibID == "com.apple.kec.corecrypto" ) {
kextMA = ma;
stop = true;
}
});
if ( kextMA == nullptr ) {
_diagnostics.warning("Could not find com.apple.kec.corecrypto, skipping FIPS sealing");
return;
}
uint64_t hashStoreSize;
const void* hashStoreLocation = kextMA->findSectionContent("__TEXT", "__fips_hmacs", hashStoreSize);
if ( hashStoreLocation == nullptr ) {
_diagnostics.warning("Could not find __TEXT/__fips_hmacs section in com.apple.kec.corecrypto, skipping FIPS sealing");
return;
}
if ( hashStoreSize != 32 ) {
_diagnostics.warning("__TEXT/__fips_hmacs section in com.apple.kec.corecrypto is not 32 bytes in size, skipping FIPS sealing");
return;
}
uint64_t textSize;
const void* textLocation = kextMA->findSectionContent("__TEXT", "__text", textSize);
if ( textLocation == nullptr ) {
textLocation = kextMA->findSectionContent("__TEXT_EXEC", "__text", textSize);
}
if ( textLocation == nullptr ) {
_diagnostics.warning("Could not find __TEXT/__text section in com.apple.kec.corecrypto, skipping FIPS sealing");
return;
}
unsigned char hmac_key = 0;
CCHmac(kCCHmacAlgSHA256, &hmac_key, 1, textLocation, textSize, (void*)hashStoreLocation); }
void AppCacheBuilder::generateUUID() {
uint8_t* uuidLoc = cacheHeader.uuid->uuid;
assert(uuid_is_null(uuidLoc));
CCDigestRef digestRef = CCDigestCreate(kCCDigestSHA256);
forEachRegion(^(const Region ®ion) {
if ( _diagnostics.hasError() )
return;
if ( region.sizeInUse == 0 )
return;
int result = CCDigestUpdate(digestRef, region.buffer, region.sizeInUse);
if ( result != 0 ) {
_diagnostics.error("Could not generate UUID: %d", result);
return;
}
});
if ( !_diagnostics.hasError() ) {
uint8_t buffer[CCDigestGetOutputSize(kCCDigestSHA256)];
int result = CCDigestFinal(digestRef, buffer);
memcpy(cacheHeader.uuid->uuid, buffer, sizeof(cacheHeader.uuid->uuid));
if ( result != 0 ) {
_diagnostics.error("Could not finalize UUID: %d", result);
}
}
CCDigestDestroy(digestRef);
if ( _diagnostics.hasError() )
return;
if ( prelinkInfoDict != nullptr ) {
CFDataRef dataRef = CFDataCreate(kCFAllocatorDefault, &cacheHeader.uuid->uuid[0], sizeof(cacheHeader.uuid->uuid));
CFDictionarySetValue(prelinkInfoDict, CFSTR("_PrelinkKCID"), dataRef);
CFRelease(dataRef);
CFErrorRef errorRef = nullptr;
CFDataRef xmlData = CFPropertyListCreateData(kCFAllocatorDefault, prelinkInfoDict,
kCFPropertyListXMLFormat_v1_0, 0, &errorRef);
if (errorRef != nullptr) {
CFStringRef errorString = CFErrorCopyDescription(errorRef);
_diagnostics.error("Could not serialise plist because :%s",
CFStringGetCStringPtr(errorString, kCFStringEncodingASCII));
CFRelease(xmlData);
CFRelease(errorRef);
return;
} else {
CFIndex xmlDataLength = CFDataGetLength(xmlData);
if ( xmlDataLength > prelinkInfoRegion.bufferSize ) {
_diagnostics.error("Overflow in prelink info segment. 0x%llx vs 0x%llx",
(uint64_t)xmlDataLength, prelinkInfoRegion.bufferSize);
CFRelease(xmlData);
return;
}
memcpy(prelinkInfoRegion.buffer, CFDataGetBytePtr(xmlData), xmlDataLength);
CFRelease(xmlData);
}
}
}
void AppCacheBuilder::writeFile(const std::string& path)
{
std::string pathTemplate = path + "-XXXXXX";
size_t templateLen = strlen(pathTemplate.c_str())+2;
BLOCK_ACCCESSIBLE_ARRAY(char, pathTemplateSpace, templateLen);
strlcpy(pathTemplateSpace, pathTemplate.c_str(), templateLen);
int fd = mkstemp(pathTemplateSpace);
if ( fd == -1 ) {
_diagnostics.error("could not open file %s", pathTemplateSpace);
return;
}
uint64_t cacheFileSize = 0;
cacheFileSize = _readOnlyRegion.cacheFileOffset + _readOnlyRegion.sizeInUse;
::ftruncate(fd, cacheFileSize);
uint64_t writtenSize = pwrite(fd, (const uint8_t*)_fullAllocatedBuffer, cacheFileSize, 0);
if (writtenSize == cacheFileSize) {
::fchmod(fd, S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH); if ( ::rename(pathTemplateSpace, path.c_str()) == 0) {
::close(fd);
return; }
} else {
_diagnostics.error("could not write whole file. %lld bytes out of %lld were written",
writtenSize, cacheFileSize);
return;
}
::close(fd);
::unlink(pathTemplateSpace);
}
void AppCacheBuilder::writeBuffer(uint8_t*& buffer, uint64_t& bufferSize) const {
bufferSize = _readOnlyRegion.cacheFileOffset + _readOnlyRegion.sizeInUse;
buffer = (uint8_t*)malloc(bufferSize);
forEachRegion(^(const Region ®ion) {
if ( region.sizeInUse == 0 )
return;
memcpy(buffer + region.cacheFileOffset, (const uint8_t*)region.buffer, region.sizeInUse);
});
}