#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <uuid/uuid.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/uio.h>
#include <sys/param.h>
#include <sys/sysctl.h>
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <rootless.h>
#include <dirent.h>
#include <mach/mach.h>
#include <mach/machine.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#include <mach-o/fat.h>
#include <mach-o/reloc.h>
#include <mach-o/dyld_priv.h>
#include <CommonCrypto/CommonDigest.h>
#if !DYLD_IN_PROCESS
#include <dlfcn.h>
#endif
#include "MachOParser.h"
#include "Logging.h"
#include "CodeSigningTypes.h"
#include "DyldSharedCache.h"
#include "Trie.hpp"
#if DYLD_IN_PROCESS
#include "APIs.h"
#else
#include "StringUtils.h"
#endif
#ifndef EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE
#define EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE 0x02
#endif
#ifndef CPU_SUBTYPE_ARM64_E
#define CPU_SUBTYPE_ARM64_E 2
#endif
#ifndef LC_BUILD_VERSION
#define LC_BUILD_VERSION 0x32
struct build_version_command {
uint32_t cmd;
uint32_t cmdsize;
uint32_t platform;
uint32_t minos;
uint32_t sdk;
uint32_t ntools;
};
struct build_tool_version {
uint32_t tool;
uint32_t version;
};
#define PLATFORM_MACOS 1
#define PLATFORM_IOS 2
#define PLATFORM_TVOS 3
#define PLATFORM_WATCHOS 4
#define PLATFORM_BRIDGEOS 5
#define TOOL_CLANG 1
#define TOOL_SWIFT 2
#define TOOL_LD 3
#endif
namespace dyld3 {
bool FatUtil::isFatFile(const void* fileStart)
{
const fat_header* fileStartAsFat = (fat_header*)fileStart;
return ( fileStartAsFat->magic == OSSwapBigToHostInt32(FAT_MAGIC) );
}
template<typename T>
static bool greaterThanAddOrOverflow(uint32_t addLHS, uint32_t addRHS, T b) {
return (addLHS > b) || (addRHS > (b-addLHS));
}
template<typename T>
static bool greaterThanAddOrOverflow(uint64_t addLHS, uint64_t addRHS, T b) {
return (addLHS > b) || (addRHS > (b-addLHS));
}
void FatUtil::forEachSlice(Diagnostics& diag, const void* fileContent, size_t fileLen, void (^callback)(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, size_t sliceSize, bool& stop))
{
const fat_header* fh = (fat_header*)fileContent;
if ( fh->magic != OSSwapBigToHostInt32(FAT_MAGIC) ) {
diag.error("not a fat file");
return;
}
if ( OSSwapBigToHostInt32(fh->nfat_arch) > ((4096 - sizeof(fat_header)) / sizeof(fat_arch)) ) {
diag.error("fat header too large: %u entries", OSSwapBigToHostInt32(fh->nfat_arch));
}
const fat_arch* const archs = (fat_arch*)(((char*)fh)+sizeof(fat_header));
bool stop = false;
for (uint32_t i=0; i < OSSwapBigToHostInt32(fh->nfat_arch); ++i) {
uint32_t cpuType = OSSwapBigToHostInt32(archs[i].cputype);
uint32_t cpuSubType = OSSwapBigToHostInt32(archs[i].cpusubtype);
uint32_t offset = OSSwapBigToHostInt32(archs[i].offset);
uint32_t len = OSSwapBigToHostInt32(archs[i].size);
if (greaterThanAddOrOverflow(offset, len, fileLen)) {
diag.error("slice %d extends beyond end of file", i);
return;
}
callback(cpuType, cpuSubType, (uint8_t*)fileContent+offset, len, stop);
if ( stop )
break;
}
}
#if !DYLD_IN_PROCESS
bool FatUtil::isFatFileWithSlice(Diagnostics& diag, const void* fileContent, size_t fileLen, const std::string& archName, size_t& sliceOffset, size_t& sliceLen, bool& missingSlice)
{
missingSlice = false;
if ( !isFatFile(fileContent) )
return false;
__block bool found = false;
forEachSlice(diag, fileContent, fileLen, ^(uint32_t sliceCpuType, uint32_t sliceCpuSubType, const void* sliceStart, size_t sliceSize, bool& stop) {
std::string sliceArchName = MachOParser::archName(sliceCpuType, sliceCpuSubType);
if ( sliceArchName == archName ) {
sliceOffset = (char*)sliceStart - (char*)fileContent;
sliceLen = sliceSize;
found = true;
stop = true;
}
});
if ( diag.hasError() )
return false;
if ( !found )
missingSlice = true;
if ( !found && (archName == "x86_64h") )
return isFatFileWithSlice(diag, fileContent, fileLen, "x86_64", sliceOffset, sliceLen, missingSlice);
return found;
}
#endif
MachOParser::MachOParser(const mach_header* mh, bool dyldCacheIsRaw)
{
#if DYLD_IN_PROCESS
_data = (long)mh;
#else
if (mh == nullptr)
return;
_data = (long)mh;
if ( (mh->flags & 0x80000000) == 0 ) {
_data |= 1;
}
else {
if ( dyldCacheIsRaw )
_data |= 2;
}
#endif
}
const mach_header* MachOParser::header() const
{
return (mach_header*)(_data & -4);
}
bool MachOParser::isRaw() const
{
return (_data & 1);
}
bool MachOParser::inRawCache() const
{
return (_data & 2);
}
uint32_t MachOParser::fileType() const
{
return header()->filetype;
}
bool MachOParser::inDyldCache() const
{
return (header()->flags & 0x80000000);
}
bool MachOParser::hasThreadLocalVariables() const
{
return (header()->flags & MH_HAS_TLV_DESCRIPTORS);
}
Platform MachOParser::platform() const
{
Platform platform;
uint32_t minOS;
uint32_t sdk;
if ( getPlatformAndVersion(&platform, &minOS, &sdk) )
return platform;
switch ( header()->cputype ) {
case CPU_TYPE_X86_64:
case CPU_TYPE_I386:
return Platform::macOS;
case CPU_TYPE_ARM64:
case CPU_TYPE_ARM:
return Platform::iOS;
}
return Platform::macOS;
}
#if !DYLD_IN_PROCESS
const MachOParser::ArchInfo MachOParser::_s_archInfos[] = {
{ "x86_64", CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_ALL },
{ "x86_64h", CPU_TYPE_X86_64, CPU_SUBTYPE_X86_64_H },
{ "i386", CPU_TYPE_I386, CPU_SUBTYPE_I386_ALL },
{ "arm64", CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_ALL },
{ "arm64e", CPU_TYPE_ARM64, CPU_SUBTYPE_ARM64_E },
{ "armv7k", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7K },
{ "armv7s", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7S },
{ "armv7", CPU_TYPE_ARM, CPU_SUBTYPE_ARM_V7 }
};
bool MachOParser::isValidMachO(Diagnostics& diag, const std::string& archName, Platform platform, const void* fileContent, size_t fileLength, const std::string& pathOpened, bool ignoreMainExecutables)
{
const mach_header* mh = (const mach_header*)fileContent;
if ( (mh->magic != MH_MAGIC) && (mh->magic != MH_MAGIC_64) ) {
diag.warning("could not use '%s' because it is not a mach-o file", pathOpened.c_str());
return false;
}
if (!archName.empty() && !isArch(mh, archName)) {
if ( (archName != "x86_64h") || !isArch(mh, "x86_64") ) {
diag.warning("could not use '%s' because it does not contain required architecture %s", pathOpened.c_str(), archName.c_str());
return false;
}
}
switch ( mh->filetype ) {
case MH_EXECUTE:
if ( ignoreMainExecutables )
return false;
break;
case MH_DYLIB:
case MH_BUNDLE:
break;
default:
diag.warning("could not use '%s' because it is not a dylib, bundle, or executable", pathOpened.c_str());
return false;
}
if ( mh->flags & 0x80000000 ) {
diag.warning("could not use '%s' because the high bit of mach_header flags is reserved for images in dyld cache", pathOpened.c_str());
return false;
}
MachOParser parser(mh);
if ( !parser.validLoadCommands(diag, fileLength) )
return false;
if ( parser.platform() != platform ) {
diag.warning("could not use '%s' because it was built for a different platform", pathOpened.c_str());
return false;
}
if ( (mh->filetype == MH_EXECUTE) && !parser.isDynamicExecutable() ) {
diag.warning("could not use '%s' because it is a static executable", pathOpened.c_str());
return false;
}
if ( !parser.validEmbeddedPaths(diag) )
return false;
if ( !parser.validSegments(diag, fileLength) )
return false;
if ( !parser.validLinkeditLayout(diag) )
return false;
return true;
}
bool MachOParser::validLoadCommands(Diagnostics& diag, size_t fileLen)
{
if ( header()->sizeofcmds + sizeof(mach_header_64) > fileLen ) {
diag.warning("load commands exceed length of file");
return false;
}
Diagnostics walkDiag;
LinkEditInfo lePointers;
getLinkEditLoadCommands(walkDiag, lePointers);
if ( walkDiag.hasError() ) {
diag.warning("%s", walkDiag.errorMessage().c_str());
return false;
}
__block bool overflowText = false;
forEachSegment(^(const char* segName, uint32_t segFileOffset, uint32_t segFileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) {
if ( strcmp(segName, "__TEXT") == 0 ) {
if ( header()->sizeofcmds + sizeof(mach_header_64) > segFileSize ) {
diag.warning("load commands exceed length of __TEXT segment");
overflowText = true;
}
stop = true;
}
});
if ( overflowText )
return false;
return true;
}
bool MachOParser::validEmbeddedPaths(Diagnostics& diag)
{
__block int index = 1;
__block bool allGood = true;
__block bool foundInstallName = false;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
const dylib_command* dylibCmd;
const rpath_command* rpathCmd;
switch ( cmd->cmd ) {
case LC_ID_DYLIB:
foundInstallName = true;
case LC_LOAD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB:
dylibCmd = (dylib_command*)cmd;
if ( dylibCmd->dylib.name.offset > cmd->cmdsize ) {
diag.warning("load command #%d name offset (%u) outside its size (%u)", index, dylibCmd->dylib.name.offset, cmd->cmdsize);
stop = true;
allGood = false;
}
else {
bool foundEnd = false;
const char* start = (char*)dylibCmd + dylibCmd->dylib.name.offset;
const char* end = (char*)dylibCmd + cmd->cmdsize;
for (const char* s=start; s < end; ++s) {
if ( *s == '\0' ) {
foundEnd = true;
break;
}
}
if ( !foundEnd ) {
diag.warning("load command #%d string extends beyond end of load command", index);
stop = true;
allGood = false;
}
}
break;
case LC_RPATH:
rpathCmd = (rpath_command*)cmd;
if ( rpathCmd->path.offset > cmd->cmdsize ) {
diag.warning("load command #%d path offset (%u) outside its size (%u)", index, rpathCmd->path.offset, cmd->cmdsize);
stop = true;
allGood = false;
}
else {
bool foundEnd = false;
const char* start = (char*)rpathCmd + rpathCmd->path.offset;
const char* end = (char*)rpathCmd + cmd->cmdsize;
for (const char* s=start; s < end; ++s) {
if ( *s == '\0' ) {
foundEnd = true;
break;
}
}
if ( !foundEnd ) {
diag.warning("load command #%d string extends beyond end of load command", index);
stop = true;
allGood = false;
}
}
break;
}
++index;
});
if ( header()->filetype == MH_DYLIB ) {
if ( !foundInstallName ) {
diag.warning("MH_DYLIB is missing LC_ID_DYLIB");
allGood = false;
}
}
else {
if ( foundInstallName ) {
diag.warning("LC_ID_DYLIB found in non-MH_DYLIB");
allGood = false;
}
}
return allGood;
}
bool MachOParser::validSegments(Diagnostics& diag, size_t fileLen)
{
__block bool badSegmentLoadCommand = false;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_SEGMENT_64 ) {
const segment_command_64* seg = (segment_command_64*)cmd;
int32_t sectionsSpace = cmd->cmdsize - sizeof(segment_command_64);
if ( sectionsSpace < 0 ) {
diag.warning("load command size too small for LC_SEGMENT_64");
badSegmentLoadCommand = true;
stop = true;
}
else if ( (sectionsSpace % sizeof(section_64)) != 0 ) {
diag.warning("segment load command size 0x%X will not fit whole number of sections", cmd->cmdsize);
badSegmentLoadCommand = true;
stop = true;
}
else if ( sectionsSpace != (seg->nsects * sizeof(section_64)) ) {
diag.warning("load command size 0x%X does not match nsects %d", cmd->cmdsize, seg->nsects);
badSegmentLoadCommand = true;
stop = true;
} else if (greaterThanAddOrOverflow(seg->fileoff, seg->filesize, fileLen)) {
diag.warning("segment load command content extends beyond end of file");
badSegmentLoadCommand = true;
stop = true;
} else if ( (seg->filesize > seg->vmsize) && ((seg->vmsize != 0) || ((seg->flags & SG_NORELOC) == 0)) ) {
diag.warning("segment filesize exceeds vmsize");
badSegmentLoadCommand = true;
stop = true;
}
}
else if ( cmd->cmd == LC_SEGMENT ) {
const segment_command* seg = (segment_command*)cmd;
int32_t sectionsSpace = cmd->cmdsize - sizeof(segment_command);
if ( sectionsSpace < 0 ) {
diag.warning("load command size too small for LC_SEGMENT");
badSegmentLoadCommand = true;
stop = true;
}
else if ( (sectionsSpace % sizeof(section)) != 0 ) {
diag.warning("segment load command size 0x%X will not fit whole number of sections", cmd->cmdsize);
badSegmentLoadCommand = true;
stop = true;
}
else if ( sectionsSpace != (seg->nsects * sizeof(section)) ) {
diag.warning("load command size 0x%X does not match nsects %d", cmd->cmdsize, seg->nsects);
badSegmentLoadCommand = true;
stop = true;
} else if ( (seg->filesize > seg->vmsize) && ((seg->vmsize != 0) || ((seg->flags & SG_NORELOC) == 0)) ) {
diag.warning("segment filesize exceeds vmsize");
badSegmentLoadCommand = true;
stop = true;
}
}
});
if ( badSegmentLoadCommand )
return false;
__block bool badPermissions = false;
__block bool badSize = false;
__block bool hasTEXT = false;
__block bool hasLINKEDIT = false;
forEachSegment(^(const char* segName, uint32_t segFileOffset, uint32_t segFileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) {
if ( strcmp(segName, "__TEXT") == 0 ) {
if ( protections != (VM_PROT_READ|VM_PROT_EXECUTE) ) {
diag.warning("__TEXT segment permissions is not 'r-x'");
badPermissions = true;
stop = true;
}
hasTEXT = true;
}
else if ( strcmp(segName, "__LINKEDIT") == 0 ) {
if ( protections != VM_PROT_READ ) {
diag.warning("__LINKEDIT segment permissions is not 'r--'");
badPermissions = true;
stop = true;
}
hasLINKEDIT = true;
}
else if ( (protections & 0xFFFFFFF8) != 0 ) {
diag.warning("%s segment permissions has invalid bits set", segName);
badPermissions = true;
stop = true;
}
if (greaterThanAddOrOverflow(segFileOffset, segFileSize, fileLen)) {
diag.warning("%s segment content extends beyond end of file", segName);
badSize = true;
stop = true;
}
if ( is64() ) {
if ( vmAddr+vmSize < vmAddr ) {
diag.warning("%s segment vm range wraps", segName);
badSize = true;
stop = true;
}
}
else {
if ( (uint32_t)(vmAddr+vmSize) < (uint32_t)(vmAddr) ) {
diag.warning("%s segment vm range wraps", segName);
badSize = true;
stop = true;
}
}
});
if ( badPermissions || badSize )
return false;
if ( !hasTEXT ) {
diag.warning("missing __TEXT segment");
return false;
}
if ( !hasLINKEDIT ) {
diag.warning("missing __LINKEDIT segment");
return false;
}
__block bool badSegments = false;
forEachSegment(^(const char* seg1Name, uint32_t seg1FileOffset, uint32_t seg1FileSize, uint64_t seg1vmAddr, uint64_t seg1vmSize, uint8_t seg1Protections, uint32_t seg1Index, uint64_t seg1SizeOfSections, uint8_t seg1Align, bool& stop1) {
uint64_t seg1vmEnd = seg1vmAddr + seg1vmSize;
uint32_t seg1FileEnd = seg1FileOffset + seg1FileSize;
forEachSegment(^(const char* seg2Name, uint32_t seg2FileOffset, uint32_t seg2FileSize, uint64_t seg2vmAddr, uint64_t seg2vmSize, uint8_t seg2Protections, uint32_t seg2Index, uint64_t seg2SizeOfSections, uint8_t seg2Align, bool& stop2) {
if ( seg1Index == seg2Index )
return;
uint64_t seg2vmEnd = seg2vmAddr + seg2vmSize;
uint32_t seg2FileEnd = seg2FileOffset + seg2FileSize;
if ( ((seg2vmAddr <= seg1vmAddr) && (seg2vmEnd > seg1vmAddr) && (seg1vmEnd > seg1vmAddr)) || ((seg2vmAddr >= seg1vmAddr) && (seg2vmAddr < seg1vmEnd) && (seg2vmEnd > seg2vmAddr)) ) {
diag.warning("segment %s vm range overlaps segment %s", seg1Name, seg2Name);
badSegments = true;
stop1 = true;
stop2 = true;
}
if ( ((seg2FileOffset <= seg1FileOffset) && (seg2FileEnd > seg1FileOffset) && (seg1FileEnd > seg1FileOffset)) || ((seg2FileOffset >= seg1FileOffset) && (seg2FileOffset < seg1FileEnd) && (seg2FileEnd > seg2FileOffset)) ) {
diag.warning("segment %s file content overlaps segment %s", seg1Name, seg2Name);
badSegments = true;
stop1 = true;
stop2 = true;
}
if ( (seg1Index < seg2Index) && !stop1 ) {
if ( (seg1vmAddr > seg2vmAddr) || ((seg1FileOffset > seg2FileOffset) && (seg1FileOffset != 0) && (seg2FileOffset != 0)) ){
diag.warning("segment load commands out of order with respect to layout for %s and %s", seg1Name, seg2Name);
badSegments = true;
stop1 = true;
stop2 = true;
}
}
});
});
if ( badSegments )
return false;
__block bool badSections = false;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_SEGMENT_64 ) {
const segment_command_64* seg = (segment_command_64*)cmd;
const section_64* const sectionsStart = (section_64*)((char*)seg + sizeof(struct segment_command_64));
const section_64* const sectionsEnd = §ionsStart[seg->nsects];
for (const section_64* sect=sectionsStart; (sect < sectionsEnd); ++sect) {
if ( (int64_t)(sect->size) < 0 ) {
diag.warning("section %s size too large 0x%llX", sect->sectname, sect->size);
badSections = true;
}
else if ( sect->addr < seg->vmaddr ) {
diag.warning("section %s start address 0x%llX is before containing segment's address 0x%0llX", sect->sectname, sect->addr, seg->vmaddr);
badSections = true;
}
else if ( sect->addr+sect->size > seg->vmaddr+seg->vmsize ) {
diag.warning("section %s end address 0x%llX is beyond containing segment's end address 0x%0llX", sect->sectname, sect->addr+sect->size, seg->vmaddr+seg->vmsize);
badSections = true;
}
}
}
else if ( cmd->cmd == LC_SEGMENT ) {
const segment_command* seg = (segment_command*)cmd;
const section* const sectionsStart = (section*)((char*)seg + sizeof(struct segment_command));
const section* const sectionsEnd = §ionsStart[seg->nsects];
for (const section* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) {
if ( (int64_t)(sect->size) < 0 ) {
diag.warning("section %s size too large 0x%X", sect->sectname, sect->size);
badSections = true;
}
else if ( sect->addr < seg->vmaddr ) {
diag.warning("section %s start address 0x%X is before containing segment's address 0x%0X", sect->sectname, sect->addr, seg->vmaddr);
badSections = true;
}
else if ( sect->addr+sect->size > seg->vmaddr+seg->vmsize ) {
diag.warning("section %s end address 0x%X is beyond containing segment's end address 0x%0X", sect->sectname, sect->addr+sect->size, seg->vmaddr+seg->vmsize);
badSections = true;
}
}
}
});
return !badSections;
}
struct LinkEditContent
{
const char* name;
uint32_t stdOrder;
uint32_t fileOffsetStart;
uint32_t size;
};
bool MachOParser::validLinkeditLayout(Diagnostics& diag)
{
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() )
return false;
const bool is64Bit = is64();
const uint32_t pointerSize = (is64Bit ? 8 : 4);
std::vector<LinkEditContent> blobs;
if ( leInfo.dyldInfo != nullptr ) {
if ( leInfo.dyldInfo->rebase_size != 0 )
blobs.push_back({"rebase opcodes", 1, leInfo.dyldInfo->rebase_off, leInfo.dyldInfo->rebase_size});
if ( leInfo.dyldInfo->bind_size != 0 )
blobs.push_back({"bind opcodes", 2, leInfo.dyldInfo->bind_off, leInfo.dyldInfo->bind_size});
if ( leInfo.dyldInfo->weak_bind_size != 0 )
blobs.push_back({"weak bind opcodes", 3, leInfo.dyldInfo->weak_bind_off, leInfo.dyldInfo->weak_bind_size});
if ( leInfo.dyldInfo->lazy_bind_size != 0 )
blobs.push_back({"lazy bind opcodes", 4, leInfo.dyldInfo->lazy_bind_off, leInfo.dyldInfo->lazy_bind_size});
if ( leInfo.dyldInfo->export_size!= 0 )
blobs.push_back({"exports trie", 5, leInfo.dyldInfo->export_off, leInfo.dyldInfo->export_size});
}
if ( leInfo.dynSymTab != nullptr ) {
if ( leInfo.dynSymTab->nlocrel != 0 )
blobs.push_back({"local relocations", 6, leInfo.dynSymTab->locreloff, static_cast<uint32_t>(leInfo.dynSymTab->nlocrel*sizeof(relocation_info))});
if ( leInfo.dynSymTab->nextrel != 0 )
blobs.push_back({"external relocations", 11, leInfo.dynSymTab->extreloff, static_cast<uint32_t>(leInfo.dynSymTab->nextrel*sizeof(relocation_info))});
if ( leInfo.dynSymTab->nindirectsyms != 0 )
blobs.push_back({"indirect symbol table", 12, leInfo.dynSymTab->indirectsymoff, leInfo.dynSymTab->nindirectsyms*4});
}
if ( leInfo.splitSegInfo != nullptr ) {
if ( leInfo.splitSegInfo->datasize != 0 )
blobs.push_back({"shared cache info", 6, leInfo.splitSegInfo->dataoff, leInfo.splitSegInfo->datasize});
}
if ( leInfo.functionStarts != nullptr ) {
if ( leInfo.functionStarts->datasize != 0 )
blobs.push_back({"function starts", 7, leInfo.functionStarts->dataoff, leInfo.functionStarts->datasize});
}
if ( leInfo.dataInCode != nullptr ) {
if ( leInfo.dataInCode->datasize != 0 )
blobs.push_back({"data in code", 8, leInfo.dataInCode->dataoff, leInfo.dataInCode->datasize});
}
if ( leInfo.symTab != nullptr ) {
if ( leInfo.symTab->nsyms != 0 )
blobs.push_back({"symbol table", 10, leInfo.symTab->symoff, static_cast<uint32_t>(leInfo.symTab->nsyms*(is64Bit ? sizeof(nlist_64) : sizeof(struct nlist)))});
if ( leInfo.symTab->strsize != 0 )
blobs.push_back({"symbol table strings", 20, leInfo.symTab->stroff, leInfo.symTab->strsize});
}
if ( leInfo.codeSig != nullptr ) {
if ( leInfo.codeSig->datasize != 0 )
blobs.push_back({"code signature", 21, leInfo.codeSig->dataoff, leInfo.codeSig->datasize});
}
if ( (leInfo.dyldInfo != nullptr) && (leInfo.dyldInfo->cmd == LC_DYLD_INFO_ONLY) && (leInfo.dynSymTab != nullptr) ) {
if ( leInfo.dynSymTab->nlocrel != 0 ) {
diag.error("malformed mach-o contains LC_DYLD_INFO_ONLY and local relocations");
return false;
}
if ( leInfo.dynSymTab->nextrel != 0 ) {
diag.error("malformed mach-o contains LC_DYLD_INFO_ONLY and external relocations");
return false;
}
}
if ( (leInfo.dyldInfo == nullptr) && (leInfo.dynSymTab == nullptr) ) {
diag.error("malformed mach-o misssing LC_DYLD_INFO and LC_DYSYMTAB");
return false;
}
if ( blobs.empty() ) {
diag.error("malformed mach-o misssing LINKEDIT");
return false;
}
std::sort(blobs.begin(), blobs.end(), [&](const LinkEditContent& a, const LinkEditContent& b) {
return a.fileOffsetStart < b.fileOffsetStart;
});
uint32_t prevEnd = (uint32_t)(leInfo.layout.segments[leInfo.layout.linkeditSegIndex].fileOffset);
const char* prevName = "start of LINKEDIT";
for (const LinkEditContent& blob : blobs) {
if ( blob.fileOffsetStart < prevEnd ) {
diag.error("LINKEDIT overlap of %s and %s", prevName, blob.name);
return false;
}
prevEnd = blob.fileOffsetStart + blob.size;
prevName = blob.name;
}
const LinkEditContent& lastBlob = blobs.back();
uint32_t linkeditFileEnd = (uint32_t)(leInfo.layout.segments[leInfo.layout.linkeditSegIndex].fileOffset + leInfo.layout.segments[leInfo.layout.linkeditSegIndex].fileSize);
if (greaterThanAddOrOverflow(lastBlob.fileOffsetStart, lastBlob.size, linkeditFileEnd)) {
diag.error("LINKEDIT content '%s' extends beyond end of segment", lastBlob.name);
return false;
}
std::sort(blobs.begin(), blobs.end(), [&](const LinkEditContent& a, const LinkEditContent& b) {
return a.stdOrder < b.stdOrder;
});
prevEnd = (uint32_t)(leInfo.layout.segments[leInfo.layout.linkeditSegIndex].fileOffset);
prevName = "start of LINKEDIT";
for (const LinkEditContent& blob : blobs) {
if ( ((blob.fileOffsetStart & (pointerSize-1)) != 0) && (blob.stdOrder != 20) ) diag.warning("mis-aligned LINKEDIT content '%s'", blob.name);
if ( blob.fileOffsetStart < prevEnd ) {
diag.warning("LINKEDIT out of order %s", blob.name);
}
prevEnd = blob.fileOffsetStart;
prevName = blob.name;
}
if ( leInfo.symTab != nullptr ) {
if ( leInfo.symTab->nsyms > 0x10000000 ) {
diag.error("malformed mach-o image: symbol table too large");
return false;
}
if ( leInfo.dynSymTab != nullptr ) {
if ( leInfo.dynSymTab->nindirectsyms != 0 ) {
if ( leInfo.dynSymTab->nindirectsyms > 0x10000000 ) {
diag.error("malformed mach-o image: indirect symbol table too large");
return false;
}
}
if ( (leInfo.dynSymTab->nlocalsym > leInfo.symTab->nsyms) || (leInfo.dynSymTab->ilocalsym > leInfo.symTab->nsyms) ) {
diag.error("malformed mach-o image: indirect symbol table local symbol count exceeds total symbols");
return false;
}
if ( leInfo.dynSymTab->ilocalsym + leInfo.dynSymTab->nlocalsym < leInfo.dynSymTab->ilocalsym ) {
diag.error("malformed mach-o image: indirect symbol table local symbol count wraps");
return false;
}
if ( (leInfo.dynSymTab->nextdefsym > leInfo.symTab->nsyms) || (leInfo.dynSymTab->iextdefsym > leInfo.symTab->nsyms) ) {
diag.error("malformed mach-o image: indirect symbol table extern symbol count exceeds total symbols");
return false;
}
if ( leInfo.dynSymTab->iextdefsym + leInfo.dynSymTab->nextdefsym < leInfo.dynSymTab->iextdefsym ) {
diag.error("malformed mach-o image: indirect symbol table extern symbol count wraps");
return false;
}
if ( (leInfo.dynSymTab->nundefsym > leInfo.symTab->nsyms) || (leInfo.dynSymTab->iundefsym > leInfo.symTab->nsyms) ) {
diag.error("malformed mach-o image: indirect symbol table undefined symbol count exceeds total symbols");
return false;
}
if ( leInfo.dynSymTab->iundefsym + leInfo.dynSymTab->nundefsym < leInfo.dynSymTab->iundefsym ) {
diag.error("malformed mach-o image: indirect symbol table undefined symbol count wraps");
return false;
}
}
}
return true;
}
bool MachOParser::isArch(const mach_header* mh, const std::string& archName)
{
for (const ArchInfo& info : _s_archInfos) {
if ( archName == info.name ) {
return ( (mh->cputype == info.cputype) && ((mh->cpusubtype & ~CPU_SUBTYPE_MASK) == info.cpusubtype) );
}
}
return false;
}
std::string MachOParser::archName(uint32_t cputype, uint32_t cpusubtype)
{
for (const ArchInfo& info : _s_archInfos) {
if ( (cputype == info.cputype) && ((cpusubtype & ~CPU_SUBTYPE_MASK) == info.cpusubtype) ) {
return info.name;
}
}
return "unknown";
}
uint32_t MachOParser::cpuTypeFromArchName(const std::string& archName)
{
for (const ArchInfo& info : _s_archInfos) {
if ( archName == info.name ) {
return info.cputype;
}
}
return 0;
}
uint32_t MachOParser::cpuSubtypeFromArchName(const std::string& archName)
{
for (const ArchInfo& info : _s_archInfos) {
if ( archName == info.name ) {
return info.cpusubtype;
}
}
return 0;
}
std::string MachOParser::archName() const
{
return archName(header()->cputype, header()->cpusubtype);
}
std::string MachOParser::platformName(Platform platform)
{
switch ( platform ) {
case Platform::unknown:
return "unknown";
case Platform::macOS:
return "macOS";
case Platform::iOS:
return "iOS";
case Platform::tvOS:
return "tvOS";
case Platform::watchOS:
return "watchOS";
case Platform::bridgeOS:
return "bridgeOS";
}
return "unknown platform";
}
std::string MachOParser::versionString(uint32_t packedVersion)
{
char buff[64];
sprintf(buff, "%d.%d.%d", (packedVersion >> 16), ((packedVersion >> 8) & 0xFF), (packedVersion & 0xFF));
return buff;
}
#else
bool MachOParser::isMachO(Diagnostics& diag, const void* fileContent, size_t mappedLength)
{
if ( mappedLength < 4096 ) {
diag.error("file too short");
return false;
}
const mach_header* mh = (const mach_header*)fileContent;
#if __LP64__
const uint32_t requiredMagic = MH_MAGIC_64;
#else
const uint32_t requiredMagic = MH_MAGIC;
#endif
if ( mh->magic != requiredMagic ) {
diag.error("not a mach-o file");
return false;
}
#if __x86_64__
const uint32_t requiredCPU = CPU_TYPE_X86_64;
#elif __i386__
const uint32_t requiredCPU = CPU_TYPE_I386;
#elif __arm__
const uint32_t requiredCPU = CPU_TYPE_ARM;
#elif __arm64__
const uint32_t requiredCPU = CPU_TYPE_ARM64;
#else
#error unsupported architecture
#endif
if ( mh->cputype != requiredCPU ) {
diag.error("wrong cpu type");
return false;
}
return true;
}
bool MachOParser::wellFormedMachHeaderAndLoadCommands(const mach_header* mh)
{
const load_command* startCmds = nullptr;
if ( mh->magic == MH_MAGIC_64 )
startCmds = (load_command*)((char *)mh + sizeof(mach_header_64));
else if ( mh->magic == MH_MAGIC )
startCmds = (load_command*)((char *)mh + sizeof(mach_header));
else
return false;
const load_command* const cmdsEnd = (load_command*)((char*)startCmds + mh->sizeofcmds);
const load_command* cmd = startCmds;
for(uint32_t i = 0; i < mh->ncmds; ++i) {
const load_command* nextCmd = (load_command*)((char *)cmd + cmd->cmdsize);
if ( (cmd->cmdsize < 8) || (nextCmd > cmdsEnd) || (nextCmd < startCmds)) {
return false;
}
cmd = nextCmd;
}
return true;
}
#endif
Platform MachOParser::currentPlatform()
{
#if TARGET_OS_BRIDGE
return Platform::bridgeOS;
#elif TARGET_OS_WATCH
return Platform::watchOS;
#elif TARGET_OS_TV
return Platform::tvOS;
#elif TARGET_OS_IOS
return Platform::iOS;
#elif TARGET_OS_MAC
return Platform::macOS;
#else
#error unknown platform
#endif
}
bool MachOParser::valid(Diagnostics& diag)
{
#if DYLD_IN_PROCESS
const mach_header* inImage = dyld3::dyld_image_header_containing_address(header());
if ( inImage != header() ) {
diag.error("only dyld loaded images can be parsed by MachOParser");
return false;
}
#else
#endif
return true;
}
void MachOParser::forEachLoadCommand(Diagnostics& diag, void (^callback)(const load_command* cmd, bool& stop)) const
{
bool stop = false;
const load_command* startCmds = nullptr;
if ( header()->magic == MH_MAGIC_64 )
startCmds = (load_command*)((char *)header() + sizeof(mach_header_64));
else if ( header()->magic == MH_MAGIC )
startCmds = (load_command*)((char *)header() + sizeof(mach_header));
else {
diag.error("file does not start with MH_MAGIC[_64]");
return; }
const load_command* const cmdsEnd = (load_command*)((char*)startCmds + header()->sizeofcmds);
const load_command* cmd = startCmds;
for(uint32_t i = 0; i < header()->ncmds; ++i) {
const load_command* nextCmd = (load_command*)((char *)cmd + cmd->cmdsize);
if ( cmd->cmdsize < 8 ) {
diag.error("malformed load command #%d, size too small %d", i, cmd->cmdsize);
return;
}
if ( (nextCmd > cmdsEnd) || (nextCmd < startCmds) ) {
diag.error("malformed load command #%d, size too large 0x%X", i, cmd->cmdsize);
return;
}
callback(cmd, stop);
if ( stop )
return;
cmd = nextCmd;
}
}
UUID MachOParser::uuid() const
{
uuid_t uuid;
getUuid(uuid);
return uuid;
}
bool MachOParser::getUuid(uuid_t uuid) const
{
Diagnostics diag;
__block bool found = false;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_UUID ) {
const uuid_command* uc = (const uuid_command*)cmd;
memcpy(uuid, uc->uuid, sizeof(uuid_t));
found = true;
stop = true;
}
});
diag.assertNoError(); if ( !found )
bzero(uuid, sizeof(uuid_t));
return found;
}
uint64_t MachOParser::preferredLoadAddress() const
{
__block uint64_t result = 0;
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) {
if ( strcmp(segName, "__TEXT") == 0 ) {
result = vmAddr;
stop = true;
}
});
return result;
}
bool MachOParser::getPlatformAndVersion(Platform* platform, uint32_t* minOS, uint32_t* sdk) const
{
Diagnostics diag;
__block bool found = false;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
const version_min_command* versCmd;
switch ( cmd->cmd ) {
case LC_VERSION_MIN_IPHONEOS:
versCmd = (version_min_command*)cmd;
*platform = Platform::iOS;
*minOS = versCmd->version;
*sdk = versCmd->sdk;
found = true;
stop = true;
break;
case LC_VERSION_MIN_MACOSX:
versCmd = (version_min_command*)cmd;
*platform = Platform::macOS;
*minOS = versCmd->version;
*sdk = versCmd->sdk;
found = true;
stop = true;
break;
case LC_VERSION_MIN_TVOS:
versCmd = (version_min_command*)cmd;
*platform = Platform::tvOS;
*minOS = versCmd->version;
*sdk = versCmd->sdk;
found = true;
stop = true;
break;
case LC_VERSION_MIN_WATCHOS:
versCmd = (version_min_command*)cmd;
*platform = Platform::watchOS;
*minOS = versCmd->version;
*sdk = versCmd->sdk;
found = true;
stop = true;
break;
case LC_BUILD_VERSION: {
const build_version_command* buildCmd = (build_version_command *)cmd;
*minOS = buildCmd->minos;
*sdk = buildCmd->sdk;
switch(buildCmd->platform) {
case PLATFORM_MACOS:
*platform = Platform::macOS;
break;
case PLATFORM_IOS:
*platform = Platform::iOS;
break;
case PLATFORM_TVOS:
*platform = Platform::tvOS;
break;
case PLATFORM_WATCHOS:
*platform = Platform::watchOS;
break;
case PLATFORM_BRIDGEOS:
*platform = Platform::bridgeOS;
break;
}
found = true;
stop = true;
} break;
}
});
diag.assertNoError(); return found;
}
bool MachOParser::isSimulatorBinary() const
{
Platform platform;
uint32_t minOS;
uint32_t sdk;
switch ( header()->cputype ) {
case CPU_TYPE_I386:
case CPU_TYPE_X86_64:
if ( getPlatformAndVersion(&platform, &minOS, &sdk) ) {
return (platform != Platform::macOS);
}
break;
}
return false;
}
bool MachOParser::getDylibInstallName(const char** installName, uint32_t* compatVersion, uint32_t* currentVersion) const
{
Diagnostics diag;
__block bool found = false;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_ID_DYLIB ) {
const dylib_command* dylibCmd = (dylib_command*)cmd;
*compatVersion = dylibCmd->dylib.compatibility_version;
*currentVersion = dylibCmd->dylib.current_version;
*installName = (char*)dylibCmd + dylibCmd->dylib.name.offset;
found = true;
stop = true;
}
});
diag.assertNoError(); return found;
}
const char* MachOParser::installName() const
{
assert(header()->filetype == MH_DYLIB);
const char* result;
uint32_t ignoreVersion;
assert(getDylibInstallName(&result, &ignoreVersion, &ignoreVersion));
return result;
}
uint32_t MachOParser::dependentDylibCount() const
{
__block uint32_t count = 0;
forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
++count;
});
return count;
}
const char* MachOParser::dependentDylibLoadPath(uint32_t depIndex) const
{
__block const char* foundLoadPath = nullptr;
__block uint32_t curDepIndex = 0;
forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
if ( curDepIndex == depIndex ) {
foundLoadPath = loadPath;
stop = true;
}
++curDepIndex;
});
return foundLoadPath;
}
void MachOParser::forEachDependentDylib(void (^callback)(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop)) const
{
Diagnostics diag;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
switch ( cmd->cmd ) {
case LC_LOAD_DYLIB:
case LC_LOAD_WEAK_DYLIB:
case LC_REEXPORT_DYLIB:
case LC_LOAD_UPWARD_DYLIB: {
const dylib_command* dylibCmd = (dylib_command*)cmd;
assert(dylibCmd->dylib.name.offset < cmd->cmdsize);
const char* loadPath = (char*)dylibCmd + dylibCmd->dylib.name.offset;
callback(loadPath, (cmd->cmd == LC_LOAD_WEAK_DYLIB), (cmd->cmd == LC_REEXPORT_DYLIB), (cmd->cmd == LC_LOAD_UPWARD_DYLIB),
dylibCmd->dylib.compatibility_version, dylibCmd->dylib.current_version, stop);
}
break;
}
});
diag.assertNoError(); }
void MachOParser::forEachRPath(void (^callback)(const char* rPath, bool& stop)) const
{
Diagnostics diag;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_RPATH ) {
const char* rpath = (char*)cmd + ((struct rpath_command*)cmd)->path.offset;
callback(rpath, stop);
}
});
diag.assertNoError(); }
#if !DYLD_IN_PROCESS
const uint8_t* MachOParser::getContentForVMAddr(const LayoutInfo& info, uint64_t addr) const
{
for (uint32_t i=0; i < info.segmentCount; ++i) {
if ( (addr >= info.segments[i].segUnslidAddress) && (addr < (info.segments[i].segUnslidAddress+info.segments[i].segSize)) )
return (uint8_t*)header() + info.segments[i].mappingOffset + (addr - info.segments[i].segUnslidAddress);
}
if ( inDyldCache() ) {
return (uint8_t*)header() + info.segments[0].mappingOffset + (addr - info.segments[0].segUnslidAddress);
}
assert(0 && "address not found in segment");
return nullptr;
}
#endif
const uint8_t* MachOParser::getLinkEditContent(const LayoutInfo& info, uint32_t fileOffset) const
{
#if DYLD_IN_PROCESS
uint32_t offsetInLinkedit = fileOffset - info.linkeditFileOffset;
uintptr_t linkeditStartAddr = info.linkeditUnslidVMAddr + info.slide;
return (uint8_t*)(linkeditStartAddr + offsetInLinkedit);
#else
uint32_t offsetInLinkedit = fileOffset - (uint32_t)(info.segments[info.linkeditSegIndex].fileOffset);
const uint8_t* linkeditStart = (uint8_t*)header() + info.segments[info.linkeditSegIndex].mappingOffset;
return linkeditStart + offsetInLinkedit;
#endif
}
void MachOParser::getLayoutInfo(LayoutInfo& result) const
{
#if DYLD_IN_PROCESS
result.slide = getSlide();
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) {
if ( strcmp(segName, "__TEXT") == 0 ) {
result.textUnslidVMAddr = (uintptr_t)vmAddr;
}
else if ( strcmp(segName, "__LINKEDIT") == 0 ) {
result.linkeditUnslidVMAddr = (uintptr_t)vmAddr;
result.linkeditFileOffset = fileOffset;
}
});
#else
bool inCache = inDyldCache();
bool intel32 = (header()->cputype == CPU_TYPE_I386);
result.segmentCount = 0;
result.linkeditSegIndex = 0xFFFFFFFF;
__block uint64_t textSegAddr = 0;
__block uint64_t textSegFileOffset = 0;
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) {
auto& segInfo = result.segments[result.segmentCount];
if ( strcmp(segName, "__TEXT") == 0 ) {
textSegAddr = vmAddr;
textSegFileOffset = fileOffset;
}
__block bool textRelocsAllowed = false;
if ( intel32 ) {
forEachSection(^(const char* curSegName, uint32_t segIndex, uint64_t segVMAddr, const char* sectionName, uint32_t sectFlags,
uint64_t sectAddr, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& sectStop) {
if ( strcmp(curSegName, segName) == 0 ) {
if ( sectFlags & (S_ATTR_EXT_RELOC|S_ATTR_LOC_RELOC) ) {
textRelocsAllowed = true;
sectStop = true;
}
}
});
}
if ( inCache ) {
if ( inRawCache() ) {
segInfo.mappingOffset = fileOffset - textSegFileOffset;
}
else {
segInfo.mappingOffset = vmAddr - textSegAddr;
}
}
else {
segInfo.mappingOffset = fileOffset;
}
segInfo.fileOffset = fileOffset;
segInfo.fileSize = fileSize;
segInfo.segUnslidAddress = vmAddr;
segInfo.segSize = vmSize;
segInfo.writable = ((protections & VM_PROT_WRITE) == VM_PROT_WRITE);
segInfo.executable = ((protections & VM_PROT_EXECUTE) == VM_PROT_EXECUTE);
segInfo.textRelocsAllowed = textRelocsAllowed;
if ( strcmp(segName, "__LINKEDIT") == 0 ) {
result.linkeditSegIndex = result.segmentCount;
}
++result.segmentCount;
if ( result.segmentCount > 127 )
stop = true;
});
#endif
}
void MachOParser::forEachSection(void (^callback)(const char* segName, const char* sectionName, uint32_t flags,
const void* content, size_t size, bool illegalSectionSize, bool& stop)) const
{
forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr,
const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop) {
callback(segName, sectionName, flags, content, (size_t)size, illegalSectionSize, stop);
});
}
void MachOParser::forEachSection(void (^callback)(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr,
const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2,
bool illegalSectionSize, bool& stop)) const
{
Diagnostics diag;
LayoutInfo layout;
getLayoutInfo(layout);
forEachSection(^(const char* segName, uint32_t segIndex, uint64_t segVMAddr, const char* sectionName, uint32_t sectFlags,
uint64_t sectAddr, uint64_t sectSize, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop) {
#if DYLD_IN_PROCESS
const uint8_t* segContentStart = (uint8_t*)(segVMAddr + layout.slide);
#else
const uint8_t* segContentStart = (uint8_t*)header() + layout.segments[segIndex].mappingOffset;
#endif
const void* contentAddr = segContentStart + (sectAddr - segVMAddr);
callback(segName, sectionName, sectFlags, sectAddr, contentAddr, sectSize, alignP2, reserved1, reserved2, illegalSectionSize, stop);
});
}
void MachOParser::forEachSection(void (^callback)(const char* segName, uint32_t segIndex, uint64_t segVMAddr, const char* sectionName, uint32_t sectFlags,
uint64_t sectAddr, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop)) const
{
Diagnostics diag;
__block uint32_t segIndex = 0;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_SEGMENT_64 ) {
const segment_command_64* seg = (segment_command_64*)cmd;
const section_64* const sectionsStart = (section_64*)((char*)seg + sizeof(struct segment_command_64));
const section_64* const sectionsEnd = §ionsStart[seg->nsects];
for (const section_64* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) {
const char* sectName = sect->sectname;
char sectNameCopy[20];
if ( sectName[15] != '\0' ) {
strlcpy(sectNameCopy, sectName, 17);
sectName = sectNameCopy;
}
bool illegalSectionSize = (sect->addr < seg->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, seg->vmaddr + seg->filesize);
callback(seg->segname, segIndex, seg->vmaddr, sectName, sect->flags, sect->addr, sect->size, sect->align, sect->reserved1, sect->reserved2, illegalSectionSize, stop);
}
++segIndex;
}
else if ( cmd->cmd == LC_SEGMENT ) {
const segment_command* seg = (segment_command*)cmd;
const section* const sectionsStart = (section*)((char*)seg + sizeof(struct segment_command));
const section* const sectionsEnd = §ionsStart[seg->nsects];
for (const section* sect=sectionsStart; !stop && (sect < sectionsEnd); ++sect) {
const char* sectName = sect->sectname;
char sectNameCopy[20];
if ( sectName[15] != '\0' ) {
strlcpy(sectNameCopy, sectName, 17);
sectName = sectNameCopy;
}
bool illegalSectionSize = (sect->addr < seg->vmaddr) || greaterThanAddOrOverflow(sect->addr, sect->size, seg->vmaddr + seg->filesize);
callback(seg->segname, segIndex, seg->vmaddr, sectName, sect->flags, sect->addr, sect->size, sect->align, sect->reserved1, sect->reserved2, illegalSectionSize, stop);
}
++segIndex;
}
});
diag.assertNoError(); }
void MachOParser::forEachGlobalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const
{
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() )
return;
const bool is64Bit = is64();
if ( leInfo.symTab != nullptr ) {
uint32_t globalsStartIndex = 0;
uint32_t globalsCount = leInfo.symTab->nsyms;
if ( leInfo.dynSymTab != nullptr ) {
globalsStartIndex = leInfo.dynSymTab->iextdefsym;
globalsCount = leInfo.dynSymTab->nextdefsym;
}
uint32_t maxStringOffset = leInfo.symTab->strsize;
const char* stringPool = (char*)getLinkEditContent(leInfo.layout, leInfo.symTab->stroff);
const struct nlist* symbols = (struct nlist*) (getLinkEditContent(leInfo.layout, leInfo.symTab->symoff));
const struct nlist_64* symbols64 = (struct nlist_64*)(getLinkEditContent(leInfo.layout, leInfo.symTab->symoff));
bool stop = false;
for (uint32_t i=0; (i < globalsCount) && !stop; ++i) {
if ( is64Bit ) {
const struct nlist_64& sym = symbols64[globalsStartIndex+i];
if ( sym.n_un.n_strx > maxStringOffset )
continue;
if ( (sym.n_type & N_EXT) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
}
else {
const struct nlist& sym = symbols[globalsStartIndex+i];
if ( sym.n_un.n_strx > maxStringOffset )
continue;
if ( (sym.n_type & N_EXT) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
}
}
}
}
void MachOParser::forEachLocalSymbol(Diagnostics& diag, void (^callback)(const char* symbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop)) const
{
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() )
return;
const bool is64Bit = is64();
if ( leInfo.symTab != nullptr ) {
uint32_t localsStartIndex = 0;
uint32_t localsCount = leInfo.symTab->nsyms;
if ( leInfo.dynSymTab != nullptr ) {
localsStartIndex = leInfo.dynSymTab->ilocalsym;
localsCount = leInfo.dynSymTab->nlocalsym;
}
uint32_t maxStringOffset = leInfo.symTab->strsize;
const char* stringPool = (char*)getLinkEditContent(leInfo.layout, leInfo.symTab->stroff);
const struct nlist* symbols = (struct nlist*) (getLinkEditContent(leInfo.layout, leInfo.symTab->symoff));
const struct nlist_64* symbols64 = (struct nlist_64*)(getLinkEditContent(leInfo.layout, leInfo.symTab->symoff));
bool stop = false;
for (uint32_t i=0; (i < localsCount) && !stop; ++i) {
if ( is64Bit ) {
const struct nlist_64& sym = symbols64[localsStartIndex+i];
if ( sym.n_un.n_strx > maxStringOffset )
continue;
if ( ((sym.n_type & N_EXT) == 0) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
}
else {
const struct nlist& sym = symbols[localsStartIndex+i];
if ( sym.n_un.n_strx > maxStringOffset )
continue;
if ( ((sym.n_type & N_EXT) == 0) && ((sym.n_type & N_TYPE) == N_SECT) && ((sym.n_type & N_STAB) == 0) )
callback(&stringPool[sym.n_un.n_strx], sym.n_value, sym.n_type, sym.n_sect, sym.n_desc, stop);
}
}
}
}
bool MachOParser::findExportedSymbol(Diagnostics& diag, const char* symbolName, void* extra, FoundSymbol& foundInfo, DependentFinder findDependent) const
{
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() )
return false;
if ( leInfo.dyldInfo != nullptr ) {
const uint8_t* trieStart = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->export_off);
const uint8_t* trieEnd = trieStart + leInfo.dyldInfo->export_size;
const uint8_t* node = trieWalk(diag, trieStart, trieEnd, symbolName);
if ( node == nullptr ) {
__block unsigned depIndex = 0;
__block bool foundInReExportedDylib = false;
forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
if ( isReExport && findDependent ) {
const mach_header* depMH;
void* depExtra;
if ( findDependent(depIndex, loadPath, extra, &depMH, &depExtra) ) {
bool depInRawCache = inRawCache() && (depMH->flags & 0x80000000);
MachOParser dep(depMH, depInRawCache);
if ( dep.findExportedSymbol(diag, symbolName, depExtra, foundInfo, findDependent) ) {
stop = true;
foundInReExportedDylib = true;
}
}
else {
fprintf(stderr, "could not find re-exported dylib %s\n", loadPath);
}
}
++depIndex;
});
return foundInReExportedDylib;
}
const uint8_t* p = node;
const uint64_t flags = read_uleb128(diag, p, trieEnd);
if ( flags & EXPORT_SYMBOL_FLAGS_REEXPORT ) {
if ( !findDependent )
return false;
const uint64_t ordinal = read_uleb128(diag, p, trieEnd);
const char* importedName = (char*)p;
if ( importedName[0] == '\0' )
importedName = symbolName;
assert(ordinal >= 1);
if (ordinal > dependentDylibCount()) {
diag.error("ordinal %lld out of range for %s", ordinal, symbolName);
return false;
}
uint32_t depIndex = (uint32_t)(ordinal-1);
const mach_header* depMH;
void* depExtra;
if ( findDependent(depIndex, dependentDylibLoadPath(depIndex), extra, &depMH, &depExtra) ) {
bool depInRawCache = inRawCache() && (depMH->flags & 0x80000000);
MachOParser depParser(depMH, depInRawCache);
return depParser.findExportedSymbol(diag, importedName, depExtra, foundInfo, findDependent);
}
else {
diag.error("dependent dylib %lld not found for re-exported symbol %s", ordinal, symbolName);
return false;
}
}
foundInfo.kind = FoundSymbol::Kind::headerOffset;
foundInfo.isThreadLocal = false;
foundInfo.foundInDylib = header();
foundInfo.foundExtra = extra;
foundInfo.value = read_uleb128(diag, p, trieEnd);
foundInfo.resolverFuncOffset = 0;
foundInfo.foundSymbolName = symbolName;
if ( diag.hasError() )
return false;
switch ( flags & EXPORT_SYMBOL_FLAGS_KIND_MASK ) {
case EXPORT_SYMBOL_FLAGS_KIND_REGULAR:
if ( flags & EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER ) {
foundInfo.kind = FoundSymbol::Kind::headerOffset;
foundInfo.resolverFuncOffset = (uint32_t)read_uleb128(diag, p, trieEnd);
}
else {
foundInfo.kind = FoundSymbol::Kind::headerOffset;
}
break;
case EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL:
foundInfo.isThreadLocal = true;
break;
case EXPORT_SYMBOL_FLAGS_KIND_ABSOLUTE:
foundInfo.kind = FoundSymbol::Kind::absolute;
break;
default:
diag.error("unsupported exported symbol kind. flags=%llu at node offset=0x%0lX", flags, (long)(node-trieStart));
return false;
}
return true;
}
else {
foundInfo.foundInDylib = nullptr;
uint64_t baseAddress = preferredLoadAddress();
forEachGlobalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop) {
if ( strcmp(aSymbolName, symbolName) == 0 ) {
foundInfo.kind = FoundSymbol::Kind::headerOffset;
foundInfo.isThreadLocal = false;
foundInfo.foundInDylib = header();
foundInfo.foundExtra = extra;
foundInfo.value = n_value - baseAddress;
foundInfo.resolverFuncOffset = 0;
foundInfo.foundSymbolName = symbolName;
stop = true;
}
});
return (foundInfo.foundInDylib != nullptr);
}
}
void MachOParser::getLinkEditLoadCommands(Diagnostics& diag, LinkEditInfo& result) const
{
result.dyldInfo = nullptr;
result.symTab = nullptr;
result.dynSymTab = nullptr;
result.splitSegInfo = nullptr;
result.functionStarts = nullptr;
result.dataInCode = nullptr;
result.codeSig = nullptr;
__block bool hasUUID = false;
__block bool hasVersion = false;
__block bool hasEncrypt = false;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
switch ( cmd->cmd ) {
case LC_DYLD_INFO:
case LC_DYLD_INFO_ONLY:
if ( cmd->cmdsize != sizeof(dyld_info_command) )
diag.error("LC_DYLD_INFO load command size wrong");
else if ( result.dyldInfo != nullptr )
diag.error("multiple LC_DYLD_INFO load commands");
result.dyldInfo = (dyld_info_command*)cmd;
break;
case LC_SYMTAB:
if ( cmd->cmdsize != sizeof(symtab_command) )
diag.error("LC_SYMTAB load command size wrong");
else if ( result.symTab != nullptr )
diag.error("multiple LC_SYMTAB load commands");
result.symTab = (symtab_command*)cmd;
break;
case LC_DYSYMTAB:
if ( cmd->cmdsize != sizeof(dysymtab_command) )
diag.error("LC_DYSYMTAB load command size wrong");
else if ( result.dynSymTab != nullptr )
diag.error("multiple LC_DYSYMTAB load commands");
result.dynSymTab = (dysymtab_command*)cmd;
break;
case LC_SEGMENT_SPLIT_INFO:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
diag.error("LC_SEGMENT_SPLIT_INFO load command size wrong");
else if ( result.splitSegInfo != nullptr )
diag.error("multiple LC_SEGMENT_SPLIT_INFO load commands");
result.splitSegInfo = (linkedit_data_command*)cmd;
break;
case LC_FUNCTION_STARTS:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
diag.error("LC_FUNCTION_STARTS load command size wrong");
else if ( result.functionStarts != nullptr )
diag.error("multiple LC_FUNCTION_STARTS load commands");
result.functionStarts = (linkedit_data_command*)cmd;
break;
case LC_DATA_IN_CODE:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
diag.error("LC_DATA_IN_CODE load command size wrong");
else if ( result.dataInCode != nullptr )
diag.error("multiple LC_DATA_IN_CODE load commands");
result.dataInCode = (linkedit_data_command*)cmd;
break;
case LC_CODE_SIGNATURE:
if ( cmd->cmdsize != sizeof(linkedit_data_command) )
diag.error("LC_CODE_SIGNATURE load command size wrong");
else if ( result.codeSig != nullptr )
diag.error("multiple LC_CODE_SIGNATURE load commands");
result.codeSig = (linkedit_data_command*)cmd;
break;
case LC_UUID:
if ( cmd->cmdsize != sizeof(uuid_command) )
diag.error("LC_UUID load command size wrong");
else if ( hasUUID )
diag.error("multiple LC_UUID load commands");
hasUUID = true;
break;
case LC_VERSION_MIN_IPHONEOS:
case LC_VERSION_MIN_MACOSX:
case LC_VERSION_MIN_TVOS:
case LC_VERSION_MIN_WATCHOS:
if ( cmd->cmdsize != sizeof(version_min_command) )
diag.error("LC_VERSION_* load command size wrong");
else if ( hasVersion )
diag.error("multiple LC_VERSION_MIN_* load commands");
hasVersion = true;
break;
case LC_BUILD_VERSION:
if ( cmd->cmdsize != (sizeof(build_version_command) + ((build_version_command*)cmd)->ntools * sizeof(build_tool_version)) )
diag.error("LC_BUILD_VERSION load command size wrong");
else if ( hasVersion )
diag.error("multiple LC_BUILD_VERSION load commands");
hasVersion = true;
break;
case LC_ENCRYPTION_INFO:
if ( cmd->cmdsize != sizeof(encryption_info_command) )
diag.error("LC_ENCRYPTION_INFO load command size wrong");
else if ( hasEncrypt )
diag.error("multiple LC_ENCRYPTION_INFO load commands");
else if ( is64() )
diag.error("LC_ENCRYPTION_INFO found in 64-bit mach-o");
hasEncrypt = true;
break;
case LC_ENCRYPTION_INFO_64:
if ( cmd->cmdsize != sizeof(encryption_info_command_64) )
diag.error("LC_ENCRYPTION_INFO_64 load command size wrong");
else if ( hasEncrypt )
diag.error("multiple LC_ENCRYPTION_INFO_64 load commands");
else if ( !is64() )
diag.error("LC_ENCRYPTION_INFO_64 found in 32-bit mach-o");
hasEncrypt = true;
break;
}
});
if ( diag.noError() && (result.dynSymTab != nullptr) && (result.symTab == nullptr) )
diag.error("LC_DYSYMTAB but no LC_SYMTAB load command");
}
void MachOParser::getLinkEditPointers(Diagnostics& diag, LinkEditInfo& result) const
{
getLinkEditLoadCommands(diag, result);
if ( diag.noError() )
getLayoutInfo(result.layout);
}
void MachOParser::forEachSegment(void (^callback)(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop)) const
{
Diagnostics diag;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_SEGMENT_64 ) {
const segment_command_64* seg = (segment_command_64*)cmd;
callback(seg->segname, (uint32_t)seg->fileoff, (uint32_t)seg->filesize, seg->vmaddr, seg->vmsize, seg->initprot, stop);
}
else if ( cmd->cmd == LC_SEGMENT ) {
const segment_command* seg = (segment_command*)cmd;
callback(seg->segname, seg->fileoff, seg->filesize, seg->vmaddr, seg->vmsize, seg->initprot, stop);
}
});
diag.assertNoError(); }
const uint8_t* MachOParser::trieWalk(Diagnostics& diag, const uint8_t* start, const uint8_t* end, const char* symbol)
{
uint32_t visitedNodeOffsets[128];
int visitedNodeOffsetCount = 0;
visitedNodeOffsets[visitedNodeOffsetCount++] = 0;
const uint8_t* p = start;
while ( p < end ) {
uint64_t terminalSize = *p++;
if ( terminalSize > 127 ) {
--p;
terminalSize = read_uleb128(diag, p, end);
if ( diag.hasError() )
return nullptr;
}
if ( (*symbol == '\0') && (terminalSize != 0) ) {
return p;
}
const uint8_t* children = p + terminalSize;
if ( children > end ) {
diag.error("malformed trie node, terminalSize=0x%llX extends past end of trie\n", terminalSize);
return nullptr;
}
uint8_t childrenRemaining = *children++;
p = children;
uint64_t nodeOffset = 0;
for (; childrenRemaining > 0; --childrenRemaining) {
const char* ss = symbol;
bool wrongEdge = false;
char c = *p;
while ( c != '\0' ) {
if ( !wrongEdge ) {
if ( c != *ss )
wrongEdge = true;
++ss;
}
++p;
c = *p;
}
if ( wrongEdge ) {
++p; while ( (*p & 0x80) != 0 )
++p;
++p; if ( p > end ) {
diag.error("malformed trie node, child node extends past end of trie\n");
return nullptr;
}
}
else {
++p;
nodeOffset = read_uleb128(diag, p, end);
if ( diag.hasError() )
return nullptr;
if ( (nodeOffset == 0) || ( &start[nodeOffset] > end) ) {
diag.error("malformed trie child, nodeOffset=0x%llX out of range\n", nodeOffset);
return nullptr;
}
symbol = ss;
break;
}
}
if ( nodeOffset != 0 ) {
if ( nodeOffset > (end-start) ) {
diag.error("malformed trie child, nodeOffset=0x%llX out of range\n", nodeOffset);
return nullptr;
}
for (int i=0; i < visitedNodeOffsetCount; ++i) {
if ( visitedNodeOffsets[i] == nodeOffset ) {
diag.error("malformed trie child, cycle to nodeOffset=0x%llX\n", nodeOffset);
return nullptr;
}
}
visitedNodeOffsets[visitedNodeOffsetCount++] = (uint32_t)nodeOffset;
if ( visitedNodeOffsetCount >= 128 ) {
diag.error("malformed trie too deep\n");
return nullptr;
}
p = &start[nodeOffset];
}
else
p = end;
}
return nullptr;
}
uint64_t MachOParser::read_uleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end)
{
uint64_t result = 0;
int bit = 0;
do {
if ( p == end ) {
diag.error("malformed uleb128");
break;
}
uint64_t slice = *p & 0x7f;
if ( bit > 63 ) {
diag.error("uleb128 too big for uint64");
break;
}
else {
result |= (slice << bit);
bit += 7;
}
}
while (*p++ & 0x80);
return result;
}
int64_t MachOParser::read_sleb128(Diagnostics& diag, const uint8_t*& p, const uint8_t* end)
{
int64_t result = 0;
int bit = 0;
uint8_t byte = 0;
do {
if ( p == end ) {
diag.error("malformed sleb128");
break;
}
byte = *p++;
result |= (((int64_t)(byte & 0x7f)) << bit);
bit += 7;
} while (byte & 0x80);
if ( (byte & 0x40) != 0 )
result |= (-1LL) << bit;
return result;
}
bool MachOParser::is64() const
{
#if DYLD_IN_PROCESS
return (sizeof(void*) == 8);
#else
return (header()->magic == MH_MAGIC_64);
#endif
}
bool MachOParser::findClosestSymbol(uint64_t targetUnslidAddress, const char** symbolName, uint64_t* symbolUnslidAddr) const
{
Diagnostics diag;
__block uint64_t closestNValueSoFar = 0;
__block const char* closestNameSoFar = nullptr;
forEachGlobalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop) {
if ( n_value <= targetUnslidAddress ) {
if ( (closestNameSoFar == nullptr) || (closestNValueSoFar < n_value) ) {
closestNValueSoFar = n_value;
closestNameSoFar = aSymbolName;
}
}
});
forEachLocalSymbol(diag, ^(const char* aSymbolName, uint64_t n_value, uint8_t n_type, uint8_t n_sect, uint16_t n_desc, bool& stop) {
if ( n_value <= targetUnslidAddress ) {
if ( (closestNameSoFar == nullptr) || (closestNValueSoFar < n_value) ) {
closestNValueSoFar = n_value;
closestNameSoFar = aSymbolName;
}
}
});
if ( closestNameSoFar == nullptr ) {
return false;
}
*symbolName = closestNameSoFar;
*symbolUnslidAddr = closestNValueSoFar;
return true;
}
#if DYLD_IN_PROCESS
bool MachOParser::findClosestSymbol(const void* addr, const char** symbolName, const void** symbolAddress) const
{
uint64_t slide = getSlide();
uint64_t symbolUnslidAddr;
if ( findClosestSymbol((uint64_t)addr - slide, symbolName, &symbolUnslidAddr) ) {
*symbolAddress = (const void*)(long)(symbolUnslidAddr + slide);
return true;
}
return false;
}
intptr_t MachOParser::getSlide() const
{
Diagnostics diag;
__block intptr_t slide = 0;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
#if __LP64__
if ( cmd->cmd == LC_SEGMENT_64 ) {
const segment_command_64* seg = (segment_command_64*)cmd;
if ( strcmp(seg->segname, "__TEXT") == 0 ) {
slide = ((uint64_t)header()) - seg->vmaddr;
stop = true;
}
}
#else
if ( cmd->cmd == LC_SEGMENT ) {
const segment_command* seg = (segment_command*)cmd;
if ( strcmp(seg->segname, "__TEXT") == 0 ) {
slide = ((uint32_t)header()) - seg->vmaddr;
stop = true;
}
}
#endif
});
diag.assertNoError(); return slide;
}
bool MachOParser::hasExportedSymbol(const char* symbolName, DependentFinder finder, void** result) const
{
typedef void* (*ResolverFunc)(void);
ResolverFunc resolver;
Diagnostics diag;
FoundSymbol foundInfo;
if ( findExportedSymbol(diag, symbolName, (void*)header(), foundInfo, finder) ) {
switch ( foundInfo.kind ) {
case FoundSymbol::Kind::headerOffset:
*result = (uint8_t*)foundInfo.foundInDylib + foundInfo.value;
break;
case FoundSymbol::Kind::absolute:
*result = (void*)(long)foundInfo.value;
break;
case FoundSymbol::Kind::resolverOffset:
resolver = (ResolverFunc)((uint8_t*)foundInfo.foundInDylib + foundInfo.resolverFuncOffset);
*result = (*resolver)();
break;
}
return true;
}
return false;
}
const char* MachOParser::segmentName(uint32_t targetSegIndex) const
{
__block const char* result = nullptr;
__block uint32_t segIndex = 0;
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) {
if ( segIndex == targetSegIndex ) {
result = segName;
stop = true;
}
++segIndex;
});
return result;
}
#else
bool MachOParser::uses16KPages() const
{
return (header()->cputype == CPU_TYPE_ARM64);
}
bool MachOParser::isEncrypted() const
{
__block bool result = false;
Diagnostics diag;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_SEGMENT_64 ) {
const segment_command_64* segCmd = (segment_command_64*)cmd;
if ( segCmd->flags & SG_PROTECTED_VERSION_1 ) {
result = true;
stop = true;
}
}
else if ( cmd->cmd == LC_SEGMENT ) {
const segment_command* segCmd = (segment_command*)cmd;
if ( segCmd->flags & SG_PROTECTED_VERSION_1 ) {
result = true;
stop = true;
}
}
else if ( (cmd->cmd == LC_ENCRYPTION_INFO) || (cmd->cmd == LC_ENCRYPTION_INFO_64) ) {
const encryption_info_command* encCmd = (encryption_info_command*)cmd;
if ( encCmd->cryptid != 0 ) {
result = true;
stop = true;
}
}
});
return result;
}
bool MachOParser::hasWeakDefs() const
{
return (header()->flags & (MH_WEAK_DEFINES|MH_BINDS_TO_WEAK));
}
bool MachOParser::hasObjC() const
{
__block bool result = false;
forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, const void* content, size_t size, bool illegalSectionSize, bool& stop) {
if ( (strncmp(sectionName, "__objc_imageinfo", 16) == 0) && (strncmp(segmentName, "__DATA", 6) == 0) ) {
result = true;
stop = true;
}
if ( (header()->cputype == CPU_TYPE_I386) && (strcmp(sectionName, "__image_info") == 0) && (strcmp(segmentName, "__OBJC") == 0) ) {
result = true;
stop = true;
}
});
return result;
}
bool MachOParser::hasPlusLoadMethod(Diagnostics& diag) const
{
#if 1
__block bool result = false;
forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& stop) {
if ( ( (flags & SECTION_TYPE) == S_CSTRING_LITERALS ) ) {
if (illegalSectionSize) {
diag.error("cstring section %s/%s extends beyond the end of the segment", segmentName, sectionName);
return;
}
const char* s = (char*)content;
const char* end = s + size;
while ( s < end ) {
if ( strcmp(s, "load") == 0 ) {
result = true;
stop = true;
return;
}
while (*s != '\0' )
++s;
++s;
}
}
});
return result;
#else
LayoutInfo layout;
getLayoutInfo(layout);
__block bool hasSwift = false;
__block const void* classList = nullptr;
__block size_t classListSize = 0;
__block const void* objcData = nullptr;
__block size_t objcDataSize = 0;
__block const void* objcConstData = nullptr;
__block size_t objcConstDataSize = 0;
forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool& stop) {
if ( (strcmp(sectionName, "__objc_classlist") == 0) && (strncmp(segmentName, "__DATA", 6) == 0) ) {
classList = content;
classListSize = size;
}
if ( (strcmp(sectionName, "__objc_imageinfo") == 0) && (strncmp(segmentName, "__DATA", 6) == 0) ) {
const uint32_t* info = (uint32_t*)content;
uint8_t swiftVersion = (info[1] >> 8) & 0xFF;
if ( swiftVersion != 0 )
hasSwift = true;
}
});
if ( classList == nullptr )
return false;
if ( hasSwift )
return true;
const bool p64 = is64();
const uint32_t pointerSize = (p64 ? 8 : 4);
const uint64_t* classArray64 = (uint64_t*)classList;
const uint32_t* classArray32 = (uint32_t*)classList;
const uint32_t classListCount = (uint32_t)(classListSize/pointerSize);
for (uint32_t i=0; i < classListCount; ++i) {
if ( p64 ) {
uint64_t classObjAddr = classArray64[i];
const uint64_t* classObjContent = (uint64_t*)getContentForVMAddr(layout, classObjAddr);
uint64_t classROAddr = classObjContent[4];
uint64_t metaClassObjAddr = classObjContent[0];
const uint64_t* metaClassObjContent = (uint64_t*)getContentForVMAddr(layout, metaClassObjAddr);
uint64_t metaClassROObjAddr = metaClassObjContent[4];
const uint64_t* metaClassROObjContent = (uint64_t*)getContentForVMAddr(layout, metaClassROObjAddr);
uint64_t metaClassMethodListAddr = metaClassROObjContent[4];
if ( metaClassMethodListAddr != 0 ) {
const uint64_t* metaClassMethodListContent = (uint64_t*)getContentForVMAddr(layout, metaClassMethodListAddr);
const uint32_t methodListCount = ((uint32_t*)metaClassMethodListContent)[1];
for (uint32_t m=0; m < methodListCount; ++m) {
uint64_t methodNameAddr = metaClassMethodListContent[m*3+1];
const char* methodNameContent = (char*)getContentForVMAddr(layout, methodNameAddr);
if ( strcmp(methodNameContent, "load") == 0 ) {
return true;
}
}
}
}
else {
}
}
return false;
#endif
}
bool MachOParser::getCDHash(uint8_t cdHash[20])
{
Diagnostics diag;
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() || (leInfo.codeSig == nullptr) )
return false;
return cdHashOfCodeSignature(getLinkEditContent(leInfo.layout, leInfo.codeSig->dataoff), leInfo.codeSig->datasize, cdHash);
}
bool MachOParser::usesLibraryValidation() const
{
Diagnostics diag;
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() || (leInfo.codeSig == nullptr) )
return false;
const CS_CodeDirectory* cd = (const CS_CodeDirectory*)findCodeDirectoryBlob(getLinkEditContent(leInfo.layout, leInfo.codeSig->dataoff), leInfo.codeSig->datasize);
if ( cd == nullptr )
return false;
return (htonl(cd->flags) & CS_REQUIRE_LV);
}
bool MachOParser::isRestricted() const
{
__block bool result = false;
forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, const void* content, size_t size, bool illegalSectionSize, bool& stop) {
if ( (strcmp(segName, "__RESTRICT") == 0) && (strcmp(sectionName, "__restrict") == 0) ) {
result = true;
stop = true;
}
});
return result;
}
bool MachOParser::hasCodeSignature(uint32_t& fileOffset, uint32_t& size)
{
fileOffset = 0;
size = 0;
Platform platform;
uint32_t minOS;
uint32_t sdk;
if ( getPlatformAndVersion(&platform, &minOS, &sdk) ) {
if ( (platform == Platform::macOS) && (sdk < 0x000A0900) )
return false;
}
else {
switch ( header()->cputype ) {
case CPU_TYPE_I386:
case CPU_TYPE_X86_64:
return false;
}
}
Diagnostics diag;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_CODE_SIGNATURE ) {
const linkedit_data_command* sigCmd = (linkedit_data_command*)cmd;
fileOffset = sigCmd->dataoff;
size = sigCmd->datasize;
stop = true;
}
});
diag.assertNoError(); return (fileOffset != 0);
}
bool MachOParser::getEntry(uint32_t& offset, bool& usesCRT)
{
Diagnostics diag;
offset = 0;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_MAIN ) {
entry_point_command* mainCmd = (entry_point_command*)cmd;
usesCRT = false;
offset = (uint32_t)mainCmd->entryoff;
stop = true;
}
else if ( cmd->cmd == LC_UNIXTHREAD ) {
stop = true;
usesCRT = true;
const uint32_t* regs32 = (uint32_t*)(((char*)cmd) + 16);
const uint64_t* regs64 = (uint64_t*)(((char*)cmd) + 16);
uint64_t startAddress = 0;
switch ( header()->cputype ) {
case CPU_TYPE_I386:
startAddress = regs32[10]; break;
case CPU_TYPE_X86_64:
startAddress = regs64[16]; break;
case CPU_TYPE_ARM:
startAddress = regs32[15]; break;
case CPU_TYPE_ARM64:
startAddress = regs64[32]; break;
}
offset = (uint32_t)(startAddress - preferredLoadAddress());
}
});
diag.assertNoError(); return (offset != 0);
}
bool MachOParser::canBePlacedInDyldCache(const std::string& path) const {
std::set<std::string> reasons;
return canBePlacedInDyldCache(path, reasons);
}
bool MachOParser::canBePlacedInDyldCache(const std::string& path, std::set<std::string>& reasons) const
{
bool retval = true;
if ( fileType() != MH_DYLIB ) {
reasons.insert("Not MH_DYLIB");
return false; }
const char* dylibName = installName();
if ( (strncmp(dylibName, "/usr/lib/", 9) != 0) && (strncmp(dylibName, "/System/Library/", 16) != 0) ) {
retval = false;
reasons.insert("Not in '/usr/lib/' or '/System/Library/'");
}
if ( (header()->flags & MH_TWOLEVEL) == 0 ) {
retval = false;
reasons.insert("Not built with two level namespaces");
}
if ( endsWith(path, "_profile.dylib") || endsWith(path, "_debug.dylib") || endsWith(path, "_profile") || endsWith(path, "_debug") || endsWith(path, "/CoreADI") ) {
retval = false;
reasons.insert("Variant image");
}
__block bool hasExtraInfo = false;
__block bool hasDyldInfo = false;
Diagnostics diag;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_SEGMENT_SPLIT_INFO )
hasExtraInfo = true;
if ( cmd->cmd == LC_DYLD_INFO_ONLY )
hasDyldInfo = true;
});
if ( !hasExtraInfo ) {
retval = false;
reasons.insert("Missing split seg info");
}
if ( !hasDyldInfo ) {
retval = false;
reasons.insert("Old binary, missing dyld info");
}
__block bool allDepPathsAreGood = true;
forEachDependentDylib(^(const char* loadPath, bool isWeak, bool isReExport, bool isUpward, uint32_t compatVersion, uint32_t curVersion, bool& stop) {
if ( (strncmp(loadPath, "/usr/lib/", 9) != 0) && (strncmp(loadPath, "/System/Library/", 16) != 0) ) {
allDepPathsAreGood = false;
stop = true;
}
});
if ( !allDepPathsAreGood ) {
retval = false;
reasons.insert("Depends on cache inelegible dylibs");
}
__block bool hasInterposing = false;
forEachInterposingTuple(diag, ^(uint32_t segIndex, uint64_t replacementSegOffset, uint64_t replaceeSegOffset, uint64_t replacementContent, bool& stop) {
hasInterposing = true;
});
if ( hasInterposing ) {
retval = false;
reasons.insert("Has interposing tuples");
}
return retval;
}
bool MachOParser::isDynamicExecutable() const
{
if ( fileType() != MH_EXECUTE )
return false;
__block bool hasDyldLoad = false;
Diagnostics diag;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_LOAD_DYLINKER ) {
hasDyldLoad = true;
stop = true;
}
});
return hasDyldLoad;
}
bool MachOParser::isSlideable() const
{
if ( header()->filetype == MH_DYLIB )
return true;
if ( header()->filetype == MH_BUNDLE )
return true;
if ( (header()->filetype == MH_EXECUTE) && (header()->flags & MH_PIE) )
return true;
return false;
}
bool MachOParser::hasInitializer(Diagnostics& diag) const
{
__block bool result = false;
forEachInitializer(diag, ^(uint32_t offset) {
result = true;
});
return result;
}
void MachOParser::forEachInitializer(Diagnostics& diag, void (^callback)(uint32_t offset)) const
{
__block uint64_t textSegAddrStart = 0;
__block uint64_t textSegAddrEnd = 0;
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) {
if ( strcmp(segName, "__TEXT") == 0 ) {
textSegAddrStart = vmAddr;
textSegAddrEnd = vmAddr + vmSize;
stop = true;
}
});
if ( textSegAddrStart == textSegAddrEnd ) {
diag.error("no __TEXT segment");
return;
}
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_ROUTINES ) {
const routines_command* routines = (routines_command*)cmd;
uint64_t dashInit = routines->init_address;
if ( (textSegAddrStart < dashInit) && (dashInit < textSegAddrEnd) )
callback((uint32_t)(dashInit - textSegAddrStart));
else
diag.error("-init does not point within __TEXT segment");
}
else if ( cmd->cmd == LC_ROUTINES_64 ) {
const routines_command_64* routines = (routines_command_64*)cmd;
uint64_t dashInit = routines->init_address;
if ( (textSegAddrStart < dashInit) && (dashInit < textSegAddrEnd) )
callback((uint32_t)(dashInit - textSegAddrStart));
else
diag.error("-init does not point within __TEXT segment");
}
});
bool p64 = is64();
unsigned pointerSize = p64 ? 8 : 4;
forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, const void* content, size_t size, bool illegalSectionSize, bool& stop) {
if ( (flags & SECTION_TYPE) == S_MOD_INIT_FUNC_POINTERS ) {
if ( (size % pointerSize) != 0 ) {
diag.error("initializer section %s/%s has bad size", segmentName, sectionName);
stop = true;
return;
}
if ( illegalSectionSize ) {
diag.error("initializer section %s/%s extends beyond the end of the segment", segmentName, sectionName);
stop = true;
return;
}
if ( ((long)content % pointerSize) != 0 ) {
diag.error("initializer section %s/%s is not pointer aligned", segmentName, sectionName);
stop = true;
return;
}
if ( p64 ) {
const uint64_t* initsStart = (uint64_t*)content;
const uint64_t* initsEnd = (uint64_t*)((uint8_t*)content + size);
for (const uint64_t* p=initsStart; p < initsEnd; ++p) {
uint64_t anInit = *p;
if ( (anInit <= textSegAddrStart) || (anInit > textSegAddrEnd) ) {
diag.error("initializer 0x%0llX does not point within __TEXT segment", anInit);
stop = true;
break;
}
callback((uint32_t)(anInit - textSegAddrStart));
}
}
else {
const uint32_t* initsStart = (uint32_t*)content;
const uint32_t* initsEnd = (uint32_t*)((uint8_t*)content + size);
for (const uint32_t* p=initsStart; p < initsEnd; ++p) {
uint32_t anInit = *p;
if ( (anInit <= textSegAddrStart) || (anInit > textSegAddrEnd) ) {
diag.error("initializer 0x%0X does not point within __TEXT segment", anInit);
stop = true;
break;
}
callback(anInit - (uint32_t)textSegAddrStart);
}
}
}
});
}
void MachOParser::forEachDOFSection(Diagnostics& diag, void (^callback)(uint32_t offset)) const
{
forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, const void* content, size_t size, bool illegalSectionSize, bool& stop) {
if ( ( (flags & SECTION_TYPE) == S_DTRACE_DOF ) && !illegalSectionSize ) {
callback((uint32_t)((uintptr_t)content - (uintptr_t)header()));
}
});
}
uint32_t MachOParser::segmentCount() const
{
__block uint32_t count = 0;
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& stop) {
++count;
});
return count;
}
void MachOParser::forEachSegment(void (^callback)(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& stop)) const
{
Diagnostics diag;
__block uint32_t segIndex = 0;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( cmd->cmd == LC_SEGMENT_64 ) {
const segment_command_64* segCmd = (segment_command_64*)cmd;
uint64_t sizeOfSections = segCmd->vmsize;
uint8_t p2align = 0;
const section_64* const sectionsStart = (section_64*)((char*)segCmd + sizeof(struct segment_command_64));
const section_64* const sectionsEnd = §ionsStart[segCmd->nsects];
for (const section_64* sect=sectionsStart; sect < sectionsEnd; ++sect) {
sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
if ( sect->align > p2align )
p2align = sect->align;
}
callback(segCmd->segname, (uint32_t)segCmd->fileoff, (uint32_t)segCmd->filesize, segCmd->vmaddr, segCmd->vmsize, segCmd->initprot, segIndex, sizeOfSections, p2align, stop);
++segIndex;
}
else if ( cmd->cmd == LC_SEGMENT ) {
const segment_command* segCmd = (segment_command*)cmd;
uint64_t sizeOfSections = segCmd->vmsize;
uint8_t p2align = 0;
const section* const sectionsStart = (section*)((char*)segCmd + sizeof(struct segment_command));
const section* const sectionsEnd = §ionsStart[segCmd->nsects];
for (const section* sect=sectionsStart; sect < sectionsEnd; ++sect) {
sizeOfSections = sect->addr + sect->size - segCmd->vmaddr;
if ( sect->align > p2align )
p2align = sect->align;
}
callback(segCmd->segname, (uint32_t)segCmd->fileoff, (uint32_t)segCmd->filesize, segCmd->vmaddr, segCmd->vmsize, segCmd->initprot, segIndex, sizeOfSections, p2align, stop);
++segIndex;
}
});
diag.assertNoError(); }
void MachOParser::forEachExportedSymbol(Diagnostics diag, void (^handler)(const char* symbolName, uint64_t imageOffset, bool isReExport, bool& stop)) const
{
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() )
return;
if ( leInfo.dyldInfo != nullptr ) {
const uint8_t* trieStart = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->export_off);
const uint8_t* trieEnd = trieStart + leInfo.dyldInfo->export_size;
std::vector<ExportInfoTrie::Entry> exports;
if ( !ExportInfoTrie::parseTrie(trieStart, trieEnd, exports) ) {
diag.error("malformed exports trie");
return;
}
bool stop = false;
for (const ExportInfoTrie::Entry& exp : exports) {
bool isReExport = (exp.info.flags & EXPORT_SYMBOL_FLAGS_REEXPORT);
handler(exp.name.c_str(), exp.info.address, isReExport, stop);
if ( stop )
break;
}
}
}
bool MachOParser::invalidRebaseState(Diagnostics& diag, const char* opcodeName, const MachOParser::LinkEditInfo& leInfo,
bool segIndexSet, uint32_t pointerSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type) const
{
if ( !segIndexSet ) {
diag.error("%s missing preceding REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB", opcodeName);
return true;
}
if ( segmentIndex >= leInfo.layout.segmentCount ) {
diag.error("%s segment index %d too large", opcodeName, segmentIndex);
return true;
}
if ( segmentOffset > (leInfo.layout.segments[segmentIndex].segSize-pointerSize) ) {
diag.error("%s current segment offset 0x%08llX beyond segment size (0x%08llX)", opcodeName, segmentOffset, leInfo.layout.segments[segmentIndex].segSize);
return true;
}
switch ( type ) {
case REBASE_TYPE_POINTER:
if ( !leInfo.layout.segments[segmentIndex].writable ) {
diag.error("%s pointer rebase is in non-writable segment", opcodeName);
return true;
}
if ( leInfo.layout.segments[segmentIndex].executable ) {
diag.error("%s pointer rebase is in executable segment", opcodeName);
return true;
}
break;
case REBASE_TYPE_TEXT_ABSOLUTE32:
case REBASE_TYPE_TEXT_PCREL32:
if ( !leInfo.layout.segments[segmentIndex].textRelocsAllowed ) {
diag.error("%s text rebase is in segment that does not support text relocations", opcodeName);
return true;
}
if ( leInfo.layout.segments[segmentIndex].writable ) {
diag.error("%s text rebase is in writable segment", opcodeName);
return true;
}
if ( !leInfo.layout.segments[segmentIndex].executable ) {
diag.error("%s pointer rebase is in non-executable segment", opcodeName);
return true;
}
break;
default:
diag.error("%s unknown rebase type %d", opcodeName, type);
return true;
}
return false;
}
void MachOParser::forEachRebase(Diagnostics& diag, void (^handler)(uint32_t segIndex, uint64_t segOffset, uint8_t type, bool& stop)) const
{
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() )
return;
if ( leInfo.dyldInfo != nullptr ) {
__block int lpSegIndex = 0;
__block uint64_t lpSegOffsetStart = 0;
__block uint64_t lpSegOffsetEnd = 0;
bool hasWeakBinds = (leInfo.dyldInfo->weak_bind_size != 0);
if ( leInfo.dyldInfo->lazy_bind_size == 0 ) {
__block uint64_t lpAddr = 0;
__block uint64_t lpSize = 0;
forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& sectStop) {
if ( (flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS ) {
lpAddr = addr;
lpSize = size;
sectStop = true;
}
});
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool& segStop) {
if ( (vmAddr <= lpAddr) && (vmAddr+vmSize >= lpAddr+lpSize) ) {
lpSegOffsetStart = lpAddr - vmAddr;
lpSegOffsetEnd = lpSegOffsetStart + lpSize;
segStop = true;
return;
}
++lpSegIndex;
});
}
bool (^weakBindAt)(uint64_t segOffset) = ^(uint64_t segOffset) {
if ( !hasWeakBinds )
return false;
__block bool result = false;
Diagnostics weakDiag;
forEachWeakDef(weakDiag, ^(bool strongDef, uint32_t dataSegIndex, uint64_t dataSegOffset, uint64_t addend, const char* symbolName, bool& weakStop) {
if ( segOffset == dataSegOffset ) {
result = true;
weakStop = true;
}
});
return result;
};
const uint8_t* p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->rebase_off);
const uint8_t* end = p + leInfo.dyldInfo->rebase_size;
const uint32_t pointerSize = (is64() ? 8 : 4);
uint8_t type = 0;
int segIndex = 0;
uint64_t segOffset = 0;
uint64_t count;
uint64_t skip;
bool segIndexSet = false;
bool stop = false;
while ( !stop && diag.noError() && (p < end) ) {
uint8_t immediate = *p & REBASE_IMMEDIATE_MASK;
uint8_t opcode = *p & REBASE_OPCODE_MASK;
++p;
switch (opcode) {
case REBASE_OPCODE_DONE:
stop = true;
break;
case REBASE_OPCODE_SET_TYPE_IMM:
type = immediate;
break;
case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
segIndex = immediate;
segOffset = read_uleb128(diag, p, end);
segIndexSet = true;
break;
case REBASE_OPCODE_ADD_ADDR_ULEB:
segOffset += read_uleb128(diag, p, end);
break;
case REBASE_OPCODE_ADD_ADDR_IMM_SCALED:
segOffset += immediate*pointerSize;
break;
case REBASE_OPCODE_DO_REBASE_IMM_TIMES:
for (int i=0; i < immediate; ++i) {
if ( invalidRebaseState(diag, "REBASE_OPCODE_DO_REBASE_IMM_TIMES", leInfo, segIndexSet, pointerSize, segIndex, segOffset, type) )
return;
if ( (segIndex != lpSegIndex) || (segOffset > lpSegOffsetEnd) || (segOffset < lpSegOffsetStart) || weakBindAt(segOffset) )
handler(segIndex, segOffset, type, stop);
segOffset += pointerSize;
}
break;
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES:
count = read_uleb128(diag, p, end);
for (uint32_t i=0; i < count; ++i) {
if ( invalidRebaseState(diag, "REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", leInfo, segIndexSet, pointerSize, segIndex, segOffset, type) )
return;
if ( (segIndex != lpSegIndex) || (segOffset > lpSegOffsetEnd) || (segOffset < lpSegOffsetStart) || weakBindAt(segOffset) )
handler(segIndex, segOffset, type, stop);
segOffset += pointerSize;
}
break;
case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB:
if ( invalidRebaseState(diag, "REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB", leInfo, segIndexSet, pointerSize, segIndex, segOffset, type) )
return;
handler(segIndex, segOffset, type, stop);
segOffset += read_uleb128(diag, p, end) + pointerSize;
break;
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
count = read_uleb128(diag, p, end);
if ( diag.hasError() )
break;
skip = read_uleb128(diag, p, end);
for (uint32_t i=0; i < count; ++i) {
if ( invalidRebaseState(diag, "REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB", leInfo, segIndexSet, pointerSize, segIndex, segOffset, type) )
return;
handler(segIndex, segOffset, type, stop);
segOffset += skip + pointerSize;
}
break;
default:
diag.error("unknown rebase opcode 0x%02X", opcode);
}
}
}
else {
const relocation_info* const relocsStart = (relocation_info*)getLinkEditContent(leInfo.layout, leInfo.dynSymTab->locreloff);
const relocation_info* const relocsEnd = &relocsStart[leInfo.dynSymTab->nlocrel];
bool stop = false;
const uint8_t relocSize = (is64() ? 3 : 2);
for (const relocation_info* reloc=relocsStart; (reloc < relocsEnd) && !stop; ++reloc) {
if ( reloc->r_length != relocSize ) {
diag.error("local relocation has wrong r_length");
break;
}
if ( reloc->r_type != 0 ) { diag.error("local relocation has wrong r_type");
break;
}
doLocalReloc(diag, reloc->r_address, stop, handler);
}
forEachIndirectPointer(diag, ^(uint32_t segIndex, uint64_t segOffset, bool bind, int bindLibOrdinal,
const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& indStop) {
if ( !bind && !bindLazy )
handler(segIndex, segOffset, REBASE_TYPE_POINTER, indStop);
});
}
}
bool MachOParser::doLocalReloc(Diagnostics& diag, uint32_t r_address, bool& stop, void (^handler)(uint32_t segIndex, uint64_t segOffset, uint8_t type, bool& stop)) const
{
bool firstWritable = (header()->cputype == CPU_TYPE_X86_64);
__block uint64_t relocBaseAddress = 0;
__block bool baseFound = false;
__block uint32_t segIndex = 0;
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool &stopSeg) {
if ( !baseFound ) {
if ( !firstWritable || (protections & VM_PROT_WRITE) ) {
baseFound = true;
relocBaseAddress = vmAddr;
}
}
if ( baseFound && (vmAddr < relocBaseAddress+r_address) && (relocBaseAddress+r_address < vmAddr+vmSize) ) {
uint8_t type = REBASE_TYPE_POINTER;
uint64_t segOffset = relocBaseAddress + r_address - vmAddr;
handler(segIndex, segOffset, type, stop);
stopSeg = true;
}
++segIndex;
});
return false;
}
int MachOParser::libOrdinalFromDesc(uint16_t n_desc) const
{
if ( (header()->flags & MH_TWOLEVEL) == 0 )
return BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
int libIndex = GET_LIBRARY_ORDINAL(n_desc);
switch ( libIndex ) {
case SELF_LIBRARY_ORDINAL:
return BIND_SPECIAL_DYLIB_SELF;
case DYNAMIC_LOOKUP_ORDINAL:
return BIND_SPECIAL_DYLIB_FLAT_LOOKUP;
case EXECUTABLE_ORDINAL:
return BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE;
}
return libIndex;
}
bool MachOParser::doExternalReloc(Diagnostics& diag, uint32_t r_address, uint32_t r_symbolnum, LinkEditInfo& leInfo, bool& stop,
void (^handler)(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type, int libOrdinal,
uint64_t addend, const char* symbolName, bool weakImport, bool lazy, bool& stop)) const
{
const bool firstWritable = (header()->cputype == CPU_TYPE_X86_64);
const bool is64Bit = is64();
__block uint64_t relocBaseAddress = 0;
__block bool baseFound = false;
__block uint32_t segIndex = 0;
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool &stopSeg) {
if ( !baseFound ) {
if ( !firstWritable || (protections & VM_PROT_WRITE) ) {
baseFound = true;
relocBaseAddress = vmAddr;
}
}
if ( baseFound && (vmAddr < relocBaseAddress+r_address) && (relocBaseAddress+r_address < vmAddr+vmSize) ) {
uint8_t type = BIND_TYPE_POINTER;
uint64_t segOffset = relocBaseAddress + r_address - vmAddr;
const void* symbolTable = getLinkEditContent(leInfo.layout, leInfo.symTab->symoff);
const struct nlist_64* symbols64 = (nlist_64*)symbolTable;
const struct nlist* symbols32 = (struct nlist*)symbolTable;
const char* stringPool = (char*)getLinkEditContent(leInfo.layout, leInfo.symTab->stroff);
uint32_t symCount = leInfo.symTab->nsyms;
uint32_t poolSize = leInfo.symTab->strsize;
if ( r_symbolnum < symCount ) {
uint16_t n_desc = is64Bit ? symbols64[r_symbolnum].n_desc : symbols32[r_symbolnum].n_desc;
uint32_t libOrdinal = libOrdinalFromDesc(n_desc);
uint32_t strOffset = is64Bit ? symbols64[r_symbolnum].n_un.n_strx : symbols32[r_symbolnum].n_un.n_strx;
if ( strOffset < poolSize ) {
const char* symbolName = stringPool + strOffset;
bool weakImport = (n_desc & N_WEAK_REF);
bool lazy = false;
uint64_t addend = is64Bit ? (*((uint64_t*)((char*)header()+fileOffset+segOffset))) : (*((uint32_t*)((char*)header()+fileOffset+segOffset)));
handler(segIndex, segOffset, type, libOrdinal, addend, symbolName, weakImport, lazy, stop);
stopSeg = true;
}
}
}
++segIndex;
});
return false;
}
bool MachOParser::invalidBindState(Diagnostics& diag, const char* opcodeName, const LinkEditInfo& leInfo, bool segIndexSet, bool libraryOrdinalSet,
uint32_t dylibCount, int libOrdinal, uint32_t pointerSize, uint8_t segmentIndex, uint64_t segmentOffset, uint8_t type, const char* symbolName) const
{
if ( !segIndexSet ) {
diag.error("%s missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB", opcodeName);
return true;
}
if ( segmentIndex >= leInfo.layout.segmentCount ) {
diag.error("%s segment index %d too large", opcodeName, segmentIndex);
return true;
}
if ( segmentOffset > (leInfo.layout.segments[segmentIndex].segSize-pointerSize) ) {
diag.error("%s current segment offset 0x%08llX beyond segment size (0x%08llX)", opcodeName, segmentOffset, leInfo.layout.segments[segmentIndex].segSize);
return true;
}
if ( symbolName == NULL ) {
diag.error("%s missing preceding BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM", opcodeName);
return true;
}
if ( !libraryOrdinalSet ) {
diag.error("%s missing preceding BIND_OPCODE_SET_DYLIB_ORDINAL", opcodeName);
return true;
}
if ( libOrdinal > (int)dylibCount ) {
diag.error("%s has library ordinal too large (%d) max (%d)", opcodeName, libOrdinal, dylibCount);
return true;
}
if ( libOrdinal < -2 ) {
diag.error("%s has unknown library special ordinal (%d)", opcodeName, libOrdinal);
return true;
}
switch ( type ) {
case BIND_TYPE_POINTER:
if ( !leInfo.layout.segments[segmentIndex].writable ) {
diag.error("%s pointer bind is in non-writable segment", opcodeName);
return true;
}
if ( leInfo.layout.segments[segmentIndex].executable ) {
diag.error("%s pointer bind is in executable segment", opcodeName);
return true;
}
break;
case BIND_TYPE_TEXT_ABSOLUTE32:
case BIND_TYPE_TEXT_PCREL32:
if ( !leInfo.layout.segments[segmentIndex].textRelocsAllowed ) {
diag.error("%s text bind is in segment that does not support text relocations", opcodeName);
return true;
}
if ( leInfo.layout.segments[segmentIndex].writable ) {
diag.error("%s text bind is in writable segment", opcodeName);
return true;
}
if ( !leInfo.layout.segments[segmentIndex].executable ) {
diag.error("%s pointer bind is in non-executable segment", opcodeName);
return true;
}
break;
default:
diag.error("%s unknown bind type %d", opcodeName, type);
return true;
}
return false;
}
void MachOParser::forEachBind(Diagnostics& diag, void (^handler)(uint32_t dataSegIndex, uint64_t dataSegOffset, uint8_t type,
int libOrdinal, uint64_t addend, const char* symbolName, bool weakImport, bool lazy, bool& stop)) const
{
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() )
return;
const uint32_t dylibCount = dependentDylibCount();
if ( leInfo.dyldInfo != nullptr ) {
const uint8_t* p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->bind_off);
const uint8_t* end = p + leInfo.dyldInfo->bind_size;
const uint32_t pointerSize = (is64() ? 8 : 4);
uint8_t type = 0;
uint64_t segmentOffset = 0;
uint8_t segmentIndex = 0;
const char* symbolName = NULL;
int libraryOrdinal = 0;
bool segIndexSet = false;
bool libraryOrdinalSet = false;
int64_t addend = 0;
uint64_t count;
uint64_t skip;
bool weakImport = false;
bool done = false;
bool stop = false;
while ( !done && !stop && diag.noError() && (p < end) ) {
uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
uint8_t opcode = *p & BIND_OPCODE_MASK;
++p;
switch (opcode) {
case BIND_OPCODE_DONE:
done = true;
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
libraryOrdinal = immediate;
libraryOrdinalSet = true;
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
libraryOrdinal = (int)read_uleb128(diag, p, end);
libraryOrdinalSet = true;
break;
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
if ( immediate == 0 )
libraryOrdinal = 0;
else {
int8_t signExtended = BIND_OPCODE_MASK | immediate;
libraryOrdinal = signExtended;
}
libraryOrdinalSet = true;
break;
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
symbolName = (char*)p;
while (*p != '\0')
++p;
++p;
break;
case BIND_OPCODE_SET_TYPE_IMM:
type = immediate;
break;
case BIND_OPCODE_SET_ADDEND_SLEB:
addend = read_sleb128(diag, p, end);
break;
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
segmentIndex = immediate;
segmentOffset = read_uleb128(diag, p, end);
segIndexSet = true;
break;
case BIND_OPCODE_ADD_ADDR_ULEB:
segmentOffset += read_uleb128(diag, p, end);
break;
case BIND_OPCODE_DO_BIND:
if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) )
return;
handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, false, stop);
segmentOffset += pointerSize;
break;
case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) )
return;
handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, false, stop);
segmentOffset += read_uleb128(diag, p, end) + pointerSize;
break;
case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) )
return;
handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, false, stop);
segmentOffset += immediate*pointerSize + pointerSize;
break;
case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
count = read_uleb128(diag, p, end);
skip = read_uleb128(diag, p, end);
for (uint32_t i=0; i < count; ++i) {
if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) )
return;
handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, false, stop);
segmentOffset += skip + pointerSize;
}
break;
default:
diag.error("bad bind opcode 0x%02X", *p);
}
}
if ( diag.hasError() || stop )
return;
if ( leInfo.dyldInfo->lazy_bind_size != 0 ) {
p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->lazy_bind_off);
end = p + leInfo.dyldInfo->lazy_bind_size;
type = BIND_TYPE_POINTER;
segmentOffset = 0;
segmentIndex = 0;
symbolName = NULL;
libraryOrdinal = 0;
segIndexSet = false;
libraryOrdinalSet= false;
addend = 0;
weakImport = false;
stop = false;
while ( !stop && diag.noError() && (p < end) ) {
uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
uint8_t opcode = *p & BIND_OPCODE_MASK;
++p;
switch (opcode) {
case BIND_OPCODE_DONE:
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
libraryOrdinal = immediate;
libraryOrdinalSet = true;
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
libraryOrdinal = (int)read_uleb128(diag, p, end);
libraryOrdinalSet = true;
break;
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
if ( immediate == 0 )
libraryOrdinal = 0;
else {
int8_t signExtended = BIND_OPCODE_MASK | immediate;
libraryOrdinal = signExtended;
}
libraryOrdinalSet = true;
break;
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
weakImport = ( (immediate & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0 );
symbolName = (char*)p;
while (*p != '\0')
++p;
++p;
break;
case BIND_OPCODE_SET_ADDEND_SLEB:
addend = read_sleb128(diag, p, end);
break;
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
segmentIndex = immediate;
segmentOffset = read_uleb128(diag, p, end);
segIndexSet = true;
break;
case BIND_OPCODE_DO_BIND:
if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, libraryOrdinalSet, dylibCount, libraryOrdinal, pointerSize, segmentIndex, segmentOffset, type, symbolName) )
return;
handler(segmentIndex, segmentOffset, type, libraryOrdinal, addend, symbolName, weakImport, true, stop);
segmentOffset += pointerSize;
break;
case BIND_OPCODE_SET_TYPE_IMM:
case BIND_OPCODE_ADD_ADDR_ULEB:
case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
default:
diag.error("bad lazy bind opcode 0x%02X", opcode);
break;
}
}
}
}
else {
const relocation_info* const relocsStart = (relocation_info*)getLinkEditContent(leInfo.layout, leInfo.dynSymTab->extreloff);
const relocation_info* const relocsEnd = &relocsStart[leInfo.dynSymTab->nextrel];
bool stop = false;
const uint8_t relocSize = (is64() ? 3 : 2);
for (const relocation_info* reloc=relocsStart; (reloc < relocsEnd) && !stop; ++reloc) {
if ( reloc->r_length != relocSize ) {
diag.error("external relocation has wrong r_length");
break;
}
if ( reloc->r_type != 0 ) { diag.error("external relocation has wrong r_type");
break;
}
doExternalReloc(diag, reloc->r_address, reloc->r_symbolnum, leInfo, stop, handler);
}
forEachIndirectPointer(diag, ^(uint32_t segIndex, uint64_t segOffset, bool bind, int bindLibOrdinal,
const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& indStop) {
if ( bind )
handler(segIndex, segOffset, (selfModifyingStub ? BIND_TYPE_IMPORT_JMP_REL32 : BIND_TYPE_POINTER), bindLibOrdinal, 0, bindSymbolName, bindWeakImport, bindLazy, indStop);
});
}
}
void MachOParser::forEachWeakDef(Diagnostics& diag, void (^handler)(bool strongDef, uint32_t dataSegIndex, uint64_t dataSegOffset,
uint64_t addend, const char* symbolName, bool& stop)) const
{
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() )
return;
const uint32_t dylibCount = dependentDylibCount();
if ( leInfo.dyldInfo != nullptr ) {
const uint8_t* p = getLinkEditContent(leInfo.layout, leInfo.dyldInfo->weak_bind_off);
const uint8_t* end = p + leInfo.dyldInfo->weak_bind_size;
const uint32_t pointerSize = (is64() ? 8 : 4);
uint8_t type = 0;
uint64_t segmentOffset = 0;
uint8_t segmentIndex = 0;
const char* symbolName = NULL;
int64_t addend = 0;
uint64_t count;
uint64_t skip;
bool segIndexSet = false;
bool done = false;
bool stop = false;
while ( !done && !stop && diag.noError() && (p < end) ) {
uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
uint8_t opcode = *p & BIND_OPCODE_MASK;
++p;
switch (opcode) {
case BIND_OPCODE_DONE:
done = true;
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
diag.error("unexpected dylib ordinal in weak binding info");
return;
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
symbolName = (char*)p;
while (*p != '\0')
++p;
++p;
if ( (immediate & BIND_SYMBOL_FLAGS_NON_WEAK_DEFINITION) != 0 )
handler(true, 0, 0, 0, symbolName, stop);
break;
case BIND_OPCODE_SET_TYPE_IMM:
type = immediate;
break;
case BIND_OPCODE_SET_ADDEND_SLEB:
addend = read_sleb128(diag, p, end);
break;
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
segmentIndex = immediate;
segmentOffset = read_uleb128(diag, p, end);
segIndexSet = true;
break;
case BIND_OPCODE_ADD_ADDR_ULEB:
segmentOffset += read_uleb128(diag, p, end);
break;
case BIND_OPCODE_DO_BIND:
if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, true, dylibCount, -2, pointerSize, segmentIndex, segmentOffset, type, symbolName) )
return;
handler(false, segmentIndex, segmentOffset, addend, symbolName, stop);
segmentOffset += pointerSize;
break;
case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, true, dylibCount, -2, pointerSize, segmentIndex, segmentOffset, type, symbolName) )
return;
handler(false, segmentIndex, segmentOffset, addend, symbolName, stop);
segmentOffset += read_uleb128(diag, p, end) + pointerSize;
break;
case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, true, dylibCount, -2, pointerSize, segmentIndex, segmentOffset, type, symbolName) )
return;
handler(false, segmentIndex, segmentOffset, addend, symbolName, stop);
segmentOffset += immediate*pointerSize + pointerSize;
break;
case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
count = read_uleb128(diag, p, end);
skip = read_uleb128(diag, p, end);
for (uint32_t i=0; i < count; ++i) {
if ( invalidBindState(diag, "BIND_OPCODE_DO_BIND", leInfo, segIndexSet, true, dylibCount, -2, pointerSize, segmentIndex, segmentOffset, type, symbolName) )
return;
handler(false, segmentIndex, segmentOffset, addend, symbolName, stop);
segmentOffset += skip + pointerSize;
}
break;
default:
diag.error("bad weak bind opcode 0x%02X", *p);
}
}
if ( diag.hasError() || stop )
return;
}
else {
}
}
void MachOParser::forEachIndirectPointer(Diagnostics& diag, void (^handler)(uint32_t dataSegIndex, uint64_t dataSegOffset, bool bind, int bindLibOrdinal,
const char* bindSymbolName, bool bindWeakImport, bool bindLazy, bool selfModifyingStub, bool& stop)) const
{
LinkEditInfo leInfo;
getLinkEditPointers(diag, leInfo);
if ( diag.hasError() )
return;
const bool is64Bit = is64();
const uint32_t* const indirectSymbolTable = (uint32_t*)getLinkEditContent(leInfo.layout, leInfo.dynSymTab->indirectsymoff);
const uint32_t indirectSymbolTableCount = leInfo.dynSymTab->nindirectsyms;
const uint32_t pointerSize = is64Bit ? 8 : 4;
const void* symbolTable = getLinkEditContent(leInfo.layout, leInfo.symTab->symoff);
const struct nlist_64* symbols64 = (nlist_64*)symbolTable;
const struct nlist* symbols32 = (struct nlist*)symbolTable;
const char* stringPool = (char*)getLinkEditContent(leInfo.layout, leInfo.symTab->stroff);
uint32_t symCount = leInfo.symTab->nsyms;
uint32_t poolSize = leInfo.symTab->strsize;
__block bool stop = false;
forEachSection(^(const char* segName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content,
uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& sectionStop) {
uint8_t sectionType = (flags & SECTION_TYPE);
if ( (sectionType != S_LAZY_SYMBOL_POINTERS) && (sectionType != S_NON_LAZY_SYMBOL_POINTERS) && (sectionType != S_SYMBOL_STUBS) )
return;
bool selfModifyingStub = (sectionType == S_SYMBOL_STUBS) && (flags & S_ATTR_SELF_MODIFYING_CODE) && (reserved2 == 5) && (header()->cputype == CPU_TYPE_I386);
if ( (flags & S_ATTR_SELF_MODIFYING_CODE) && !selfModifyingStub ) {
diag.error("S_ATTR_SELF_MODIFYING_CODE section type only valid in old i386 binaries");
sectionStop = true;
return;
}
uint32_t elementSize = selfModifyingStub ? reserved2 : pointerSize;
uint32_t elementCount = (uint32_t)(size/elementSize);
if (greaterThanAddOrOverflow(reserved1, elementCount, indirectSymbolTableCount)) {
diag.error("section %s overflows indirect symbol table", sectionName);
sectionStop = true;
return;
}
__block uint32_t index = 0;
__block uint32_t segIndex = 0;
__block uint64_t sectionSegOffset;
forEachSegment(^(const char* segmentName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, bool &segStop) {
if ( (vmAddr <= addr) && (addr < vmAddr+vmSize) ) {
sectionSegOffset = addr - vmAddr;
segIndex = index;
segStop = true;
}
++index;
});
for (int i=0; (i < elementCount) && !stop; ++i) {
uint32_t symNum = indirectSymbolTable[reserved1 + i];
if ( symNum == INDIRECT_SYMBOL_ABS )
continue;
uint64_t segOffset = sectionSegOffset+i*elementSize;
if ( symNum == INDIRECT_SYMBOL_LOCAL ) {
handler(segIndex, segOffset, false, 0, "", false, false, false, stop);
continue;
}
if ( symNum > symCount ) {
diag.error("indirect symbol[%d] = %d which is invalid symbol index", reserved1 + i, symNum);
sectionStop = true;
return;
}
uint16_t n_desc = is64Bit ? symbols64[symNum].n_desc : symbols32[symNum].n_desc;
uint32_t libOrdinal = libOrdinalFromDesc(n_desc);
uint32_t strOffset = is64Bit ? symbols64[symNum].n_un.n_strx : symbols32[symNum].n_un.n_strx;
if ( strOffset > poolSize ) {
diag.error("symbol[%d] string offset out of range", reserved1 + i);
sectionStop = true;
return;
}
const char* symbolName = stringPool + strOffset;
bool weakImport = (n_desc & N_WEAK_REF);
bool lazy = (sectionType == S_LAZY_SYMBOL_POINTERS);
handler(segIndex, segOffset, true, libOrdinal, symbolName, weakImport, lazy, selfModifyingStub, stop);
}
sectionStop = stop;
});
}
void MachOParser::forEachInterposingTuple(Diagnostics& diag, void (^handler)(uint32_t segIndex, uint64_t replacementSegOffset, uint64_t replaceeSegOffset, uint64_t replacementContent, bool& stop)) const
{
const bool is64Bit = is64();
const unsigned entrySize = is64Bit ? 16 : 8;
const unsigned pointerSize = is64Bit ? 8 : 4;
forEachSection(^(const char* segmentName, const char* sectionName, uint32_t flags, uint64_t addr, const void* content, uint64_t size, uint32_t alignP2, uint32_t reserved1, uint32_t reserved2, bool illegalSectionSize, bool& secStop) {
if ( ((flags & SECTION_TYPE) == S_INTERPOSING) || ((strcmp(sectionName, "__interpose") == 0) && (strcmp(segmentName, "__DATA") == 0)) ) {
if ( (size % entrySize) != 0 ) {
diag.error("interposing section %s/%s has bad size", segmentName, sectionName);
secStop = true;
return;
}
if ( illegalSectionSize ) {
diag.error("interposing section %s/%s extends beyond the end of the segment", segmentName, sectionName);
secStop = true;
return;
}
if ( ((long)content % pointerSize) != 0 ) {
diag.error("interposing section %s/%s is not pointer aligned", segmentName, sectionName);
secStop = true;
return;
}
__block uint32_t sectionSegIndex = 0;
__block uint64_t sectionSegOffset = 0;
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool& segStop) {
if ( (vmAddr <= addr) && (addr < vmAddr+vmSize) ) {
sectionSegIndex = segIndex;
sectionSegOffset = addr - vmAddr;
segStop = true;
}
});
if ( sectionSegIndex == 0 ) {
diag.error("interposing section %s/%s is not in a segment", segmentName, sectionName);
secStop = true;
return;
}
uint32_t offset = 0;
bool tupleStop = false;
for (int i=0; i < (size/entrySize); ++i) {
uint64_t replacementContent = is64Bit ? (*(uint64_t*)((char*)content + offset)) : (*(uint32_t*)((char*)content + offset));
handler(sectionSegIndex, sectionSegOffset+offset, sectionSegOffset+offset+pointerSize, replacementContent, tupleStop);
offset += entrySize;
if ( tupleStop )
break;
}
}
});
}
const void* MachOParser::content(uint64_t vmOffset)
{
__block const void* result = nullptr;
__block uint32_t firstSegFileOffset = 0;
__block uint64_t firstSegVmAddr = 0;
if ( isRaw() ) {
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool &stop) {
if ( firstSegFileOffset == 0) {
if ( fileSize == 0 )
return; firstSegFileOffset = fileOffset;
firstSegVmAddr = vmAddr;
}
uint64_t segVmOffset = vmAddr - firstSegVmAddr;
if ( (vmOffset >= segVmOffset) && (vmOffset < segVmOffset+vmSize) ) {
result = (char*)(header()) + (fileOffset - firstSegFileOffset) + (vmOffset - segVmOffset);
stop = true;
}
});
}
else if ( inRawCache() ) {
forEachSegment(^(const char* segName, uint32_t fileOffset, uint32_t fileSize, uint64_t vmAddr, uint64_t vmSize, uint8_t protections, uint32_t segIndex, uint64_t sizeOfSections, uint8_t p2align, bool &stop) {
if ( firstSegFileOffset == 0 ) {
firstSegFileOffset = fileOffset;
firstSegVmAddr = vmAddr;
}
uint64_t segVmOffset = vmAddr - firstSegVmAddr;
if ( (vmOffset >= segVmOffset) && (vmOffset < segVmOffset+vmSize) ) {
result = (char*)(header()) + (fileOffset - firstSegFileOffset) + (vmOffset - segVmOffset);
stop = true;
}
});
}
else {
result = (char*)(header()) + vmOffset;
}
return result;
}
#endif // !DYLD_IN_PROCESS
bool MachOParser::isFairPlayEncrypted(uint32_t& textOffset, uint32_t& size)
{
textOffset = 0;
size = 0;
Diagnostics diag;
forEachLoadCommand(diag, ^(const load_command* cmd, bool& stop) {
if ( (cmd->cmd == LC_ENCRYPTION_INFO) || (cmd->cmd == LC_ENCRYPTION_INFO_64) ) {
const encryption_info_command* encCmd = (encryption_info_command*)cmd;
if ( encCmd->cryptid == 1 ) {
textOffset = encCmd->cryptoff;
size = encCmd->cryptsize;
}
stop = true;
}
});
diag.assertNoError(); return (textOffset != 0);
}
bool MachOParser::cdHashOfCodeSignature(const void* codeSigStart, size_t codeSignLen, uint8_t cdHash[20])
{
const CS_CodeDirectory* cd = (const CS_CodeDirectory*)findCodeDirectoryBlob(codeSigStart, codeSignLen);
if ( cd == nullptr )
return false;
uint32_t cdLength = htonl(cd->length);
if ( cd->hashType == CS_HASHTYPE_SHA256 ) {
uint8_t digest[CC_SHA256_DIGEST_LENGTH];
CC_SHA256(cd, cdLength, digest);
memcpy(cdHash, digest, 20);
return true;
}
else if ( cd->hashType == CS_HASHTYPE_SHA1 ) {
CC_SHA1(cd, cdLength, cdHash);
return true;
}
return false;
}
const void* MachOParser::findCodeDirectoryBlob(const void* codeSigStart, size_t codeSignLen)
{
if ( codeSignLen < sizeof(CS_SuperBlob) )
return nullptr;
const CS_SuperBlob* codeSuperBlob = (CS_SuperBlob*)codeSigStart;
if ( codeSuperBlob->magic != htonl(CSMAGIC_EMBEDDED_SIGNATURE) )
return nullptr;
uint32_t subBlobCount = htonl(codeSuperBlob->count);
if ( (codeSignLen-sizeof(CS_SuperBlob))/sizeof(CS_BlobIndex) < subBlobCount )
return nullptr;
for (uint32_t i=0; i < subBlobCount; ++i) {
if ( codeSuperBlob->index[i].type != htonl(CSSLOT_CODEDIRECTORY) )
continue;
uint32_t cdOffset = htonl(codeSuperBlob->index[i].offset);
if ( cdOffset > (codeSignLen - sizeof(CS_CodeDirectory)) )
return nullptr;
const CS_CodeDirectory* cd = (CS_CodeDirectory*)((uint8_t*)codeSuperBlob + cdOffset);
uint32_t cdLength = htonl(cd->length);
if ( cdLength > (codeSignLen - cdOffset) )
return nullptr;
if ( cd->magic == htonl(CSMAGIC_CODEDIRECTORY) )
return cd;
}
return nullptr;
}
}