813 lines
31 KiB
C
813 lines
31 KiB
C
#include "Fat.h"
|
|
#include "FileStream.h"
|
|
#include "MachO.h"
|
|
#include "MachOByteOrder.h"
|
|
#include "MachOLoadCommand.h"
|
|
#include "MemoryStream.h"
|
|
|
|
#include <mach-o/loader.h>
|
|
#include <mach-o/nlist.h>
|
|
#include <mach/machine.h>
|
|
#include <stdlib.h>
|
|
#include <dlfcn.h>
|
|
|
|
int macho_read_at_offset(MachO *macho, uint64_t offset, size_t size, void *outBuf)
|
|
{
|
|
if (macho->containingCache) {
|
|
// When this MachO is inside the DSC, it will have segments that point to "out of macho" memory
|
|
// So, attempt to translate every offset we get
|
|
// Problem: Translation doesn't work before the segments have been loaded and loading the segments requires this function
|
|
// Solution: Gracefully fall back to memory_stream_read in the case where translation fails
|
|
uint64_t vmaddr = 0;
|
|
if (macho_translate_fileoff_to_vmaddr(macho, offset, &vmaddr, NULL) == 0) {
|
|
return macho_read_at_vmaddr(macho, vmaddr, size, outBuf);
|
|
}
|
|
}
|
|
|
|
return memory_stream_read(macho->stream, offset, size, outBuf);
|
|
}
|
|
|
|
int macho_read_string_at_offset(MachO *macho, uint64_t offset, char **outString)
|
|
{
|
|
if (macho->containingCache) {
|
|
// Same as above
|
|
uint64_t vmaddr = 0;
|
|
if (macho_translate_fileoff_to_vmaddr(macho, offset, &vmaddr, NULL) == 0) {
|
|
return macho_read_string_at_vmaddr(macho, vmaddr, outString);
|
|
}
|
|
}
|
|
return memory_stream_read_string(macho->stream, offset, outString);
|
|
}
|
|
|
|
int macho_read_uleb128_at_offset(MachO *macho, uint64_t offset, uint64_t maxOffset, uint64_t *endOffsetOut, uint64_t *valueOut)
|
|
{
|
|
uint64_t result = 0;
|
|
int bit = 0;
|
|
|
|
uint64_t curOffset = offset;
|
|
int r = 0;
|
|
|
|
uint8_t v = 0;
|
|
do {
|
|
macho_read_at_offset(macho, curOffset, sizeof(v), &v);
|
|
if (curOffset == maxOffset) {
|
|
r = -1;
|
|
break;
|
|
}
|
|
uint64_t slice = v & 0x7f;
|
|
|
|
if (bit > 63) {
|
|
r = -1;
|
|
break;
|
|
}
|
|
else {
|
|
result |= (slice << bit);
|
|
bit += 7;
|
|
}
|
|
|
|
curOffset++;
|
|
}
|
|
while (v & 0x80);
|
|
|
|
if (r == 0) {
|
|
if (endOffsetOut) *endOffsetOut = curOffset;
|
|
if (valueOut) *valueOut = result;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
int macho_write_at_offset(MachO *macho, uint64_t offset, size_t size, const void *inBuf)
|
|
{
|
|
return memory_stream_write(macho->stream, offset, size, inBuf);
|
|
}
|
|
|
|
MemoryStream *macho_get_stream(MachO *macho)
|
|
{
|
|
return macho->stream;
|
|
}
|
|
|
|
uint32_t macho_get_filetype(MachO *macho)
|
|
{
|
|
return macho->machHeader.filetype;
|
|
}
|
|
|
|
struct mach_header *macho_get_mach_header(MachO *macho)
|
|
{
|
|
return &macho->machHeader;
|
|
}
|
|
|
|
size_t macho_get_mach_header_size(MachO *macho)
|
|
{
|
|
return macho->is32Bit ? sizeof(struct mach_header) : sizeof(struct mach_header_64);
|
|
}
|
|
|
|
DyldSharedCache *macho_get_containing_cache(MachO *macho)
|
|
{
|
|
return macho->containingCache;
|
|
}
|
|
|
|
int macho_translate_fileoff_to_vmaddr(MachO *macho, uint64_t fileoff, uint64_t *vmaddrOut, MachOSegment **segmentOut)
|
|
{
|
|
for (uint32_t i = 0; i < macho->segmentCount; i++) {
|
|
MachOSegment *segment = macho->segments[i];
|
|
uint64_t segmentStartOff = segment->command.fileoff;
|
|
uint64_t segmentEndOff = segment->command.fileoff + segment->command.filesize;
|
|
if (fileoff >= segmentStartOff && fileoff < segmentEndOff) {
|
|
uint64_t relativeFileoff = fileoff - segmentStartOff;
|
|
*vmaddrOut = segment->command.vmaddr + relativeFileoff;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int macho_translate_vmaddr_to_fileoff(MachO *macho, uint64_t vmaddr, uint64_t *fileoffOut, MachOSegment **segmentOut)
|
|
{
|
|
for (uint32_t i = 0; i < macho->segmentCount; i++) {
|
|
MachOSegment *segment = macho->segments[i];
|
|
uint64_t segmentVmAddr = segment->command.vmaddr;
|
|
uint64_t segmentVmEnd = segment->command.vmaddr + segment->command.vmsize;
|
|
if (vmaddr >= segmentVmAddr && vmaddr < segmentVmEnd) {
|
|
uint64_t relativeVmAddr = vmaddr - segmentVmAddr;
|
|
*fileoffOut = segment->command.fileoff + relativeVmAddr;
|
|
if (segmentOut) *segmentOut = segment;
|
|
return 0;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
int macho_read_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, void *outBuf)
|
|
{
|
|
if (macho->containingCache) {
|
|
return dsc_read_from_vmaddr(macho->containingCache, vmaddr, size, outBuf);
|
|
}
|
|
else {
|
|
MachOSegment *segment;
|
|
uint64_t fileoff = 0;
|
|
int r = macho_translate_vmaddr_to_fileoff(macho, vmaddr, &fileoff, &segment);
|
|
if (r != 0) return r;
|
|
|
|
uint64_t readEnd = vmaddr + size;
|
|
if (readEnd >= (segment->command.vmaddr + segment->command.vmsize)) {
|
|
// prevent OOB
|
|
return -1;
|
|
}
|
|
|
|
return macho_read_at_offset(macho, fileoff, size, outBuf);
|
|
}
|
|
}
|
|
|
|
int macho_read_string_at_vmaddr(MachO *macho, uint64_t vmaddr, char **outString)
|
|
{
|
|
if (macho->containingCache) {
|
|
return dsc_read_string_from_vmaddr(macho->containingCache, vmaddr, outString);
|
|
}
|
|
else {
|
|
MachOSegment *segment;
|
|
uint64_t fileoff = 0;
|
|
int r = macho_translate_vmaddr_to_fileoff(macho, vmaddr, &fileoff, &segment);
|
|
if (r != 0) return r;
|
|
|
|
if (vmaddr >= (segment->command.vmaddr + segment->command.vmsize)) {
|
|
// prevent OOB
|
|
return -1;
|
|
}
|
|
|
|
return macho_read_string_at_offset(macho, fileoff, outString);
|
|
}
|
|
}
|
|
|
|
int macho_write_at_vmaddr(MachO *macho, uint64_t vmaddr, size_t size, const void *inBuf)
|
|
{
|
|
MachOSegment *segment;
|
|
uint64_t fileoff = 0;
|
|
int r = macho_translate_vmaddr_to_fileoff(macho, vmaddr, &fileoff, &segment);
|
|
if (r != 0) return r;
|
|
|
|
uint64_t readEnd = vmaddr + size;
|
|
if (readEnd >= (segment->command.vmaddr + segment->command.vmsize)) {
|
|
// prevent OOB
|
|
return -1;
|
|
}
|
|
|
|
return macho_write_at_offset(macho, fileoff, size, inBuf);
|
|
}
|
|
|
|
int macho_enumerate_load_commands(MachO *macho, void (^enumeratorBlock)(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop))
|
|
{
|
|
if (macho->machHeader.ncmds < 1 || macho->machHeader.ncmds > 1000) {
|
|
printf("Error: invalid number of load commands (%d).\n", macho->machHeader.ncmds);
|
|
return -1;
|
|
}
|
|
|
|
// First load command starts after mach header
|
|
uint64_t offset = macho_get_mach_header_size(macho);
|
|
|
|
for (int j = 0; j < macho->machHeader.ncmds; j++) {
|
|
struct load_command loadCommand;
|
|
if (macho_read_at_offset(macho, offset, sizeof(loadCommand), &loadCommand) != 0) continue;
|
|
LOAD_COMMAND_APPLY_BYTE_ORDER(&loadCommand, LITTLE_TO_HOST_APPLIER);
|
|
|
|
if (strcmp(load_command_to_string(loadCommand.cmd), "LC_UNKNOWN") == 0)
|
|
{
|
|
printf("Ignoring unknown command: 0x%x.\n", loadCommand.cmd);
|
|
}
|
|
else {
|
|
// TODO: Check if cmdsize matches expected size for cmd
|
|
uint8_t cmd[loadCommand.cmdsize];
|
|
if (macho_read_at_offset(macho, offset, loadCommand.cmdsize, cmd) != 0) continue;
|
|
bool stop = false;
|
|
enumeratorBlock(loadCommand, offset, (void *)cmd, &stop);
|
|
if (stop) break;
|
|
}
|
|
offset += loadCommand.cmdsize;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int macho_enumerate_segments(MachO *macho, void (^enumeratorBlock)(struct segment_command_64 *segment, bool *stop))
|
|
{
|
|
for (uint32_t i = 0; i < macho->segmentCount; i++) {
|
|
bool stop = false;
|
|
enumeratorBlock(&macho->segments[i]->command, &stop);
|
|
if (stop) return 0;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int macho_enumerate_sections(MachO *macho, void (^enumeratorBlock)(struct section_64 *section, struct segment_command_64 *segment, bool *stop))
|
|
{
|
|
for (uint32_t i = 0; i < macho->segmentCount; i++) {
|
|
for (uint32_t k = 0; k < macho->segments[i]->command.nsects; k++) {
|
|
bool stop = false;
|
|
enumeratorBlock(&macho->segments[i]->sections[k], &macho->segments[i]->command, &stop);
|
|
if (stop) return 0;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
struct trie_node {
|
|
char *string;
|
|
uint64_t value;
|
|
};
|
|
|
|
int macho_read_trie_node_at_offset(MachO *macho, uint64_t offset, uint64_t maxOffset, uint64_t *endOffsetOut, struct trie_node **trieNodesOut, unsigned *trieNodesCountOut)
|
|
{
|
|
if (!trieNodesOut || !trieNodesCountOut) return -1;
|
|
|
|
uint8_t trieValue = 0;
|
|
macho_read_at_offset(macho, offset, sizeof(trieValue), &trieValue);
|
|
offset += sizeof(trieValue);
|
|
|
|
if (trieValue != 0) {
|
|
*trieNodesOut = NULL;
|
|
*trieNodesCountOut = 0;
|
|
goto done;
|
|
}
|
|
|
|
uint8_t numberOfBranches = 0;
|
|
macho_read_at_offset(macho, offset, sizeof(numberOfBranches), &numberOfBranches);
|
|
offset += sizeof(numberOfBranches);
|
|
|
|
*trieNodesCountOut = numberOfBranches;
|
|
*trieNodesOut = malloc(sizeof(struct trie_node) * *trieNodesCountOut);
|
|
|
|
for (uint8_t i = 0; i < numberOfBranches; i++) {
|
|
struct trie_node *node = &(*trieNodesOut)[i];
|
|
macho_read_string_at_offset(macho, offset, &node->string);
|
|
offset += strlen(node->string)+1;
|
|
macho_read_uleb128_at_offset(macho, offset, maxOffset, &offset, &node->value);
|
|
}
|
|
|
|
done:
|
|
if (endOffsetOut) *endOffsetOut = offset;
|
|
return 0;
|
|
}
|
|
|
|
int macho_enumerate_symbols(MachO *macho, void (^enumeratorBlock)(const char *name, uint8_t type, uint64_t vmaddr, bool *stop))
|
|
{
|
|
bool didScanDSC = false;
|
|
|
|
if (macho->containingCache && macho->cacheImage) {
|
|
// For stuff inside the DSC we need to use the cache image to also be able to fetch private symbols
|
|
// Private symbols are normally replaced with <redacted> in the LC_SYMTAB of the MachO
|
|
didScanDSC = (dsc_image_enumerate_symbols(macho->containingCache, macho->cacheImage, enumeratorBlock) == 0);
|
|
}
|
|
|
|
__block bool hasSymtab = false;
|
|
__block struct symtab_command symtabCommand;
|
|
__block bool hasExportsTrie = false;
|
|
__block struct linkedit_data_command trieCommand;
|
|
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
|
|
if (loadCommand.cmd == LC_SYMTAB) {
|
|
hasSymtab = true;
|
|
memcpy(&symtabCommand, cmd, sizeof(symtabCommand));
|
|
SYMTAB_COMMAND_APPLY_BYTE_ORDER(&symtabCommand, LITTLE_TO_HOST_APPLIER);
|
|
}
|
|
else if (loadCommand.cmd == LC_DYLD_EXPORTS_TRIE) {
|
|
hasExportsTrie = true;
|
|
memcpy(&trieCommand, cmd, sizeof(trieCommand));
|
|
LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(&trieCommand, LITTLE_TO_HOST_APPLIER);
|
|
}
|
|
|
|
if (hasSymtab && hasExportsTrie) *stop = true;
|
|
});
|
|
|
|
if (hasSymtab) {
|
|
char *strtbl = malloc(symtabCommand.strsize);
|
|
if (macho_read_at_offset(macho, symtabCommand.stroff, symtabCommand.strsize, strtbl) != 0) {
|
|
free(strtbl);
|
|
return -1;
|
|
}
|
|
|
|
for (int i = 0; i < symtabCommand.nsyms; i++) {
|
|
uint64_t n_strx = 0;
|
|
uint64_t n_value = 0;
|
|
uint8_t n_type = 0;
|
|
int r = 0;
|
|
|
|
#define _GENERIC_READ_NLIST(nlistType, APPLIER) do { \
|
|
struct nlistType entry; \
|
|
if ((r = macho_read_at_offset(macho, symtabCommand.symoff + (i * sizeof(entry)), sizeof(entry), &entry)) != 0) break; \
|
|
APPLIER(&entry, LITTLE_TO_HOST_APPLIER); \
|
|
n_strx = entry.n_un.n_strx; \
|
|
n_value = entry.n_value; \
|
|
n_type = entry.n_type; \
|
|
} while (0)
|
|
|
|
if (macho->is32Bit) {
|
|
_GENERIC_READ_NLIST(nlist, NLIST_APPLY_BYTE_ORDER);
|
|
}
|
|
else {
|
|
_GENERIC_READ_NLIST(nlist_64, NLIST_64_APPLY_BYTE_ORDER);
|
|
}
|
|
|
|
if (r != 0) continue;
|
|
|
|
#undef _GENERIC_READ_NLIST
|
|
|
|
if (n_strx >= symtabCommand.strsize || n_strx == 0) continue;
|
|
|
|
const char *symbolName = &strtbl[n_strx];
|
|
if (symbolName[0] == 0) continue;
|
|
|
|
if (didScanDSC) {
|
|
/* If we already got the real private symbols from the DSC, omit any censored ones */
|
|
if (!strcmp(symbolName, "<redacted>")) continue;
|
|
}
|
|
|
|
bool stopSym = false;
|
|
enumeratorBlock(symbolName, n_type, n_value, &stopSym);
|
|
if (stopSym) {
|
|
break;
|
|
}
|
|
}
|
|
free(strtbl);
|
|
}
|
|
else if (hasExportsTrie) {
|
|
uint64_t trieBegin = trieCommand.dataoff;
|
|
uint64_t trieSize = trieCommand.datasize;
|
|
uint64_t trieEnd = trieBegin + trieSize;
|
|
|
|
uint64_t trieBeginVm = 0;
|
|
uint64_t trieEndVm = 0;
|
|
macho_translate_fileoff_to_vmaddr(macho, trieBegin, &trieBeginVm, NULL);
|
|
macho_translate_fileoff_to_vmaddr(macho, trieEnd, &trieEndVm, NULL);
|
|
|
|
uint64_t trieCur = trieBegin;
|
|
|
|
struct trie_node *frontier = NULL;
|
|
unsigned frontierCount = 0;
|
|
macho_read_trie_node_at_offset(macho, trieCur, trieEnd, &trieCur, &frontier, &frontierCount);
|
|
for (unsigned i = 0; i < frontierCount; i++) {
|
|
struct trie_node *node = &frontier[i];
|
|
|
|
uint64_t offset = trieBegin + node->value;
|
|
struct trie_node *children = NULL;
|
|
unsigned childrenCount = 0;
|
|
macho_read_trie_node_at_offset(macho, offset, trieEnd, &offset, &children, &childrenCount);
|
|
if (!childrenCount) {
|
|
uint64_t ulebOffset = offset - sizeof(uint8_t);
|
|
uint64_t flags = 0;
|
|
macho_read_uleb128_at_offset(macho, ulebOffset, trieEnd, &ulebOffset, &flags);
|
|
ulebOffset += sizeof(uint8_t);
|
|
|
|
uint64_t addrOff = 0;
|
|
macho_read_uleb128_at_offset(macho, ulebOffset, trieEnd, &ulebOffset, &addrOff);
|
|
|
|
bool stop = false;
|
|
enumeratorBlock(node->string, 0, macho_get_base_address(macho) + addrOff, &stop);
|
|
if (stop) break;
|
|
}
|
|
else {
|
|
unsigned prevEnd = frontierCount;
|
|
frontierCount += childrenCount;
|
|
frontier = realloc(frontier, sizeof(struct trie_node) * frontierCount);
|
|
node = &frontier[i];
|
|
|
|
for (unsigned k = 0; k < childrenCount; k++) {
|
|
frontier[prevEnd + k].string = malloc(strlen(node->string) + strlen(children[k].string) + 1);
|
|
strcpy(frontier[prevEnd + k].string, node->string);
|
|
strcat(frontier[prevEnd + k].string, children[k].string);
|
|
frontier[prevEnd + k].value = children[k].value;
|
|
}
|
|
|
|
for (unsigned k = 0; k < childrenCount; k++) {
|
|
free(children[k].string);
|
|
}
|
|
free(children);
|
|
}
|
|
}
|
|
|
|
for (unsigned i = 0; i < frontierCount; i++) {
|
|
free(frontier[i].string);
|
|
}
|
|
free(frontier);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int macho_enumerate_dependencies(MachO *macho, void (^enumeratorBlock)(const char *dylibPath, uint32_t cmd, struct dylib* dylib, bool *stop))
|
|
{
|
|
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop){
|
|
if (loadCommand.cmd == LC_LOAD_DYLIB ||
|
|
loadCommand.cmd == LC_LOAD_WEAK_DYLIB ||
|
|
loadCommand.cmd == LC_REEXPORT_DYLIB ||
|
|
loadCommand.cmd == LC_LAZY_LOAD_DYLIB ||
|
|
loadCommand.cmd == LC_LOAD_UPWARD_DYLIB) {
|
|
struct dylib_command *dylibCommand = (struct dylib_command *)cmd;
|
|
DYLIB_COMMAND_APPLY_BYTE_ORDER(dylibCommand, LITTLE_TO_HOST_APPLIER);
|
|
if (dylibCommand->dylib.name.offset >= loadCommand.cmdsize || dylibCommand->dylib.name.offset < sizeof(struct dylib_command)) {
|
|
printf("WARNING: Malformed dependency at 0x%llx (Name offset out of bounds)\n", offset);
|
|
return;
|
|
}
|
|
char *dependencyPath = ((char *)cmd + dylibCommand->dylib.name.offset);
|
|
size_t dependencyLength = strnlen(dependencyPath, loadCommand.cmdsize - dylibCommand->dylib.name.offset);
|
|
if (!dependencyLength) {
|
|
printf("WARNING: Malformed dependency at 0x%llx (Name has zero length)\n", offset);
|
|
return;
|
|
}
|
|
if (dependencyPath[dependencyLength] != 0) {
|
|
printf("WARNING: Malformed dependency at 0x%llx (Name has non NULL end byte)\n", offset);
|
|
return;
|
|
}
|
|
|
|
bool stopDepdendency = false;
|
|
enumeratorBlock(dependencyPath, loadCommand.cmd, &dylibCommand->dylib, &stopDepdendency);
|
|
if (stopDepdendency) {
|
|
*stop = true;
|
|
return;
|
|
}
|
|
}
|
|
});
|
|
return 0;
|
|
}
|
|
|
|
int macho_enumerate_rpaths(MachO *macho, void (^enumeratorBlock)(const char *rpath, bool *stop))
|
|
{
|
|
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
|
|
if (loadCommand.cmd == LC_RPATH) {
|
|
struct rpath_command *rpathCommand = (struct rpath_command *)cmd;
|
|
RPATH_COMMAND_APPLY_BYTE_ORDER(rpathCommand, LITTLE_TO_HOST_APPLIER);
|
|
|
|
if (rpathCommand->path.offset >= loadCommand.cmdsize || rpathCommand->path.offset < sizeof(struct rpath_command)) {
|
|
printf("WARNING: Malformed rpath at 0x%llx (Path offset out of bounds)\n", offset);
|
|
return;
|
|
}
|
|
|
|
char *rpath = ((char *)cmd) + rpathCommand->path.offset;
|
|
size_t rpathLength = strnlen(rpath, rpathCommand->cmdsize - rpathCommand->path.offset);
|
|
if (!rpathLength) {
|
|
printf("WARNING: Malformed rpath at 0x%llx (Path has zero length)\n", offset);
|
|
return;
|
|
}
|
|
if (rpath[rpathLength] != 0) {
|
|
printf("WARNING: Malformed rpath at 0x%llx (Name has non NULL end byte)\n", offset);
|
|
return;
|
|
}
|
|
|
|
bool stopRpath = false;
|
|
enumeratorBlock(rpath, &stopRpath);
|
|
if (stopRpath) {
|
|
*stop = true;
|
|
}
|
|
}
|
|
});
|
|
return 0;
|
|
}
|
|
|
|
uint64_t macho_get_base_address(MachO *macho)
|
|
{
|
|
if (macho->cachedBase) return macho->cachedBase;
|
|
__block int64_t base = UINT64_MAX;
|
|
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
|
|
if (loadCommand.cmd == LC_SEGMENT_64) {
|
|
struct segment_command_64 *segmentCommand = (struct segment_command_64 *)cmd;
|
|
SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(segmentCommand, LITTLE_TO_HOST_APPLIER);
|
|
if (strncmp(segmentCommand->segname, "__PRELINK", 9) != 0
|
|
&& strncmp(segmentCommand->segname, "__PLK", 5) != 0
|
|
&& strncmp(segmentCommand->segname, "__PAGEZERO", 10) != 0) {
|
|
// PRELINK and PAGEZERO are before the actual base, so we ignore them
|
|
if (segmentCommand->vmaddr < base) {
|
|
base = segmentCommand->vmaddr;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
macho->cachedBase = base;
|
|
return macho->cachedBase;
|
|
}
|
|
|
|
int macho_enumerate_function_starts(MachO *macho, void (^enumeratorBlock)(uint64_t funcAddr, bool *stop))
|
|
{
|
|
__block uint64_t functionStartsDataOff = 0, functionStartsDataSize = 0;
|
|
|
|
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stopLC) {
|
|
if (loadCommand.cmd == LC_FUNCTION_STARTS) {
|
|
struct linkedit_data_command *functionStartsCommand = (struct linkedit_data_command *)cmd;
|
|
LINKEDIT_DATA_COMMAND_APPLY_BYTE_ORDER(functionStartsCommand, LITTLE_TO_HOST_APPLIER);
|
|
functionStartsDataOff = functionStartsCommand->dataoff;
|
|
functionStartsDataSize = functionStartsCommand->datasize;
|
|
*stopLC = true;
|
|
}
|
|
});
|
|
|
|
if (!functionStartsDataOff || !functionStartsDataSize) return -1;
|
|
|
|
uint8_t *info = malloc(functionStartsDataSize);
|
|
if (macho_read_at_offset(macho, functionStartsDataOff, functionStartsDataSize, info) == 0) {
|
|
uint8_t *infoEnd = &info[functionStartsDataSize];
|
|
uint64_t address = macho_get_base_address(macho);
|
|
for (uint8_t *p = info; (*p != 0) && (p < infoEnd); ) {
|
|
bool stop = false;
|
|
uint64_t delta = 0;
|
|
uint32_t shift = 0;
|
|
bool more = true;
|
|
do {
|
|
uint8_t byte = *p++;
|
|
delta |= ((byte & 0x7F) << shift);
|
|
shift += 7;
|
|
if (byte < 0x80) {
|
|
address += delta;
|
|
enumeratorBlock(address, &stop);
|
|
more = false;
|
|
}
|
|
} while (more);
|
|
if (stop) break;
|
|
}
|
|
}
|
|
free(info);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int macho_lookup_segment_by_addr(MachO *macho, uint64_t vmaddr, struct segment_command_64 *segmentOut)
|
|
{
|
|
__block int r = -1;
|
|
macho_enumerate_segments(macho, ^(struct segment_command_64 *segment, bool *stop){
|
|
if (vmaddr >= segment->vmaddr && vmaddr < (segment->vmaddr + segment->vmsize)) {
|
|
r = 0;
|
|
memcpy(segmentOut, segment, sizeof(struct segment_command_64));
|
|
*stop = true;
|
|
}
|
|
});
|
|
return r;
|
|
}
|
|
|
|
int macho_lookup_section_by_addr(MachO *macho, uint64_t vmaddr, struct section_64 *sectionOut)
|
|
{
|
|
__block int r = -1;
|
|
macho_enumerate_sections(macho, ^(struct section_64 *section, struct segment_command_64 *segment, bool *stop) {
|
|
if (vmaddr >= section->addr && vmaddr < (section->addr + section->size)) {
|
|
r = 0;
|
|
memcpy(sectionOut, section, sizeof(struct section_64));
|
|
*stop = true;
|
|
}
|
|
});
|
|
return r;
|
|
}
|
|
|
|
int macho_parse_segments(MachO *macho)
|
|
{
|
|
return macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
|
|
if (macho->is32Bit && loadCommand.cmd == LC_SEGMENT) {
|
|
macho->segmentCount++;
|
|
if (macho->segments == NULL) { macho->segments = malloc(macho->segmentCount * sizeof(MachOSegment*)); }
|
|
else { macho->segments = realloc(macho->segments, macho->segmentCount * sizeof(MachOSegment*)); }
|
|
|
|
// For simplicity, we convert segment_command's to segment_command_64's
|
|
// This allowes all logic to unifily operate on segment_command_64
|
|
|
|
struct segment_command segmentCommand;
|
|
memcpy(&segmentCommand, cmd, sizeof(segmentCommand));
|
|
SEGMENT_COMMAND_APPLY_BYTE_ORDER(&segmentCommand, LITTLE_TO_HOST_APPLIER);
|
|
|
|
MachOSegment **segment = &macho->segments[macho->segmentCount-1];
|
|
|
|
*segment = malloc(sizeof(MachOSegment) + segmentCommand.nsects * sizeof(struct section_64));
|
|
(*segment)->command = (struct segment_command_64) {
|
|
.cmd = segmentCommand.cmd,
|
|
.cmdsize = segmentCommand.cmdsize,
|
|
.fileoff = segmentCommand.fileoff,
|
|
.filesize = segmentCommand.filesize,
|
|
.vmaddr = segmentCommand.vmaddr,
|
|
.vmsize = segmentCommand.vmsize,
|
|
.flags = segmentCommand.flags,
|
|
.initprot = segmentCommand.initprot,
|
|
.maxprot = segmentCommand.maxprot,
|
|
.nsects = segmentCommand.nsects,
|
|
};
|
|
memcpy((*segment)->command.segname, segmentCommand.segname, sizeof(segmentCommand.segname));
|
|
|
|
for (uint32_t i = 0; i < macho->segments[macho->segmentCount-1]->command.nsects; i++) {
|
|
struct section section;
|
|
memcpy(§ion, cmd + sizeof(struct segment_command) + (i * sizeof(struct section)), sizeof(section));
|
|
SECTION_APPLY_BYTE_ORDER(§ion, LITTLE_TO_HOST_APPLIER);
|
|
|
|
(*segment)->sections[i] = (struct section_64) {
|
|
.addr = section.addr,
|
|
.align = section.align,
|
|
.flags = section.flags,
|
|
.nreloc = section.nreloc,
|
|
.offset = section.offset,
|
|
.reserved1 = section.reserved1,
|
|
.reserved2 = section.reserved2,
|
|
.reserved3 = 0,
|
|
.size = section.size,
|
|
};
|
|
|
|
memcpy((*segment)->sections[i].sectname, section.sectname, sizeof(section.sectname));
|
|
memcpy((*segment)->sections[i].segname, section.segname, sizeof(section.segname));
|
|
}
|
|
}
|
|
else if (!macho->is32Bit && loadCommand.cmd == LC_SEGMENT_64) {
|
|
macho->segmentCount++;
|
|
if (macho->segments == NULL) { macho->segments = malloc(macho->segmentCount * sizeof(MachOSegment*)); }
|
|
else { macho->segments = realloc(macho->segments, macho->segmentCount * sizeof(MachOSegment*)); }
|
|
macho->segments[macho->segmentCount-1] = malloc(loadCommand.cmdsize);
|
|
memcpy(macho->segments[macho->segmentCount-1], cmd, loadCommand.cmdsize);
|
|
SEGMENT_COMMAND_64_APPLY_BYTE_ORDER(&macho->segments[macho->segmentCount-1]->command, LITTLE_TO_HOST_APPLIER);
|
|
for (uint32_t i = 0; i < macho->segments[macho->segmentCount-1]->command.nsects; i++) {
|
|
SECTION_64_APPLY_BYTE_ORDER(&macho->segments[macho->segmentCount-1]->sections[i], LITTLE_TO_HOST_APPLIER);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
int macho_parse_fileset_machos(MachO *macho)
|
|
{
|
|
if (macho_get_filetype(macho) != MH_FILESET) return -1;
|
|
return macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
|
|
if (loadCommand.cmd == LC_FILESET_ENTRY) {
|
|
uint32_t i = macho->filesetCount;
|
|
macho->filesetCount++;
|
|
|
|
struct fileset_entry_command *filesetCommand = cmd;
|
|
FILESET_ENTRY_COMMAND_APPLY_BYTE_ORDER(filesetCommand, LITTLE_TO_HOST_APPLIER);
|
|
|
|
if (macho->filesetMachos == NULL) { macho->filesetMachos = malloc(macho->filesetCount * sizeof(FilesetMachO)); }
|
|
else { macho->filesetMachos = realloc(macho->filesetMachos, macho->filesetCount * sizeof(FilesetMachO)); }
|
|
|
|
FilesetMachO *filesetMacho = &macho->filesetMachos[i];
|
|
filesetMacho->entry_id = strdup((char *)cmd + filesetCommand->entry_id.offset);
|
|
filesetMacho->vmaddr = filesetCommand->vmaddr;
|
|
filesetMacho->fileoff = filesetCommand->fileoff;
|
|
|
|
MemoryStream *subStream = memory_stream_softclone(macho->stream);
|
|
|
|
// TODO: Also cut trim to the end of the macho, but for that we would need to determine it's size
|
|
memory_stream_trim(subStream, filesetCommand->fileoff, 0);
|
|
filesetMacho->underlyingMachO = fat_init_from_memory_stream(subStream);
|
|
}
|
|
});
|
|
}
|
|
|
|
int _macho_parse(MachO *macho)
|
|
{
|
|
macho->is32Bit = (macho->archDescriptor.cpusubtype == CPU_SUBTYPE_ARM_V6 || macho->archDescriptor.cpusubtype == CPU_SUBTYPE_ARM_V7 || macho->archDescriptor.cpusubtype == CPU_SUBTYPE_ARM_V7S);
|
|
if (macho->machHeader.sizeofcmds % (macho->is32Bit ? sizeof(uint32_t) : sizeof(uint64_t)) != 0) {
|
|
printf("Error: sizeofcmds is not a multiple of %lu (%d).\n", macho->is32Bit ? sizeof(uint32_t) : sizeof(uint64_t), macho->machHeader.sizeofcmds);
|
|
return -1;
|
|
}
|
|
macho_parse_segments(macho);
|
|
macho_parse_fileset_machos(macho);
|
|
return 0;
|
|
}
|
|
|
|
MachO *macho_init(MemoryStream *stream, struct fat_arch_64 archDescriptor)
|
|
{
|
|
MachO *macho = malloc(sizeof(MachO));
|
|
if (!macho) return NULL;
|
|
memset(macho, 0, sizeof(MachO));
|
|
|
|
macho->stream = stream;
|
|
macho->archDescriptor = archDescriptor;
|
|
if (macho_read_at_offset(macho, 0, sizeof(macho->machHeader), &macho->machHeader) != 0) goto fail;
|
|
MACH_HEADER_APPLY_BYTE_ORDER(&macho->machHeader, LITTLE_TO_HOST_APPLIER);
|
|
|
|
// Check the magic against the expected values
|
|
if (macho->machHeader.magic != MH_MAGIC_64 && macho->machHeader.magic != MH_MAGIC) {
|
|
goto fail;
|
|
}
|
|
|
|
if (_macho_parse(macho) != 0) goto fail;
|
|
|
|
return macho;
|
|
|
|
fail:
|
|
macho_free(macho);
|
|
return NULL;
|
|
}
|
|
|
|
MachO *macho_init_for_writing(const char *filePath)
|
|
{
|
|
MachO *macho = malloc(sizeof(MachO));
|
|
if (!macho) return NULL;
|
|
memset(macho, 0, sizeof(MachO));
|
|
|
|
macho->stream = file_stream_init_from_path(filePath, 0, FILE_STREAM_SIZE_AUTO, FILE_STREAM_FLAG_WRITABLE | FILE_STREAM_FLAG_AUTO_EXPAND);
|
|
if (!macho->stream) goto fail;
|
|
|
|
size_t fileSize = memory_stream_get_size(macho->stream);
|
|
memory_stream_read(macho->stream, 0, sizeof(struct mach_header), &macho->machHeader);
|
|
MACH_HEADER_APPLY_BYTE_ORDER(&macho->machHeader, HOST_TO_LITTLE_APPLIER);
|
|
if (macho->machHeader.magic != MH_MAGIC_64) goto fail;
|
|
|
|
macho->archDescriptor.cpusubtype = macho->machHeader.cpusubtype;
|
|
macho->archDescriptor.cputype = macho->machHeader.cputype;
|
|
macho->archDescriptor.offset = 0;
|
|
macho->archDescriptor.size = fileSize;
|
|
macho->archDescriptor.align = 0x4000;
|
|
|
|
if (_macho_parse(macho) != 0) goto fail;
|
|
|
|
return macho;
|
|
|
|
fail:
|
|
macho_free(macho);
|
|
return NULL;
|
|
}
|
|
|
|
MachO **macho_array_create_for_paths(char **inputPaths, int inputPathsCount) {
|
|
Fat **fatArray = malloc(sizeof(Fat *) * inputPathsCount);
|
|
MachO **machoArray;
|
|
int sliceCount = 0;
|
|
for (int i = 0; i < inputPathsCount; i++) {
|
|
Fat *fat = fat_init_from_path(inputPaths[i]);
|
|
if (!fat) {
|
|
printf("Error: failed to create Fat from file: %s\n", inputPaths[i]);
|
|
return NULL;
|
|
}
|
|
sliceCount += fat->slicesCount;
|
|
fatArray[i] = fat;
|
|
}
|
|
machoArray = malloc(sizeof(MachO *) * sliceCount);
|
|
for (int i = 0; i < inputPathsCount; i++) {
|
|
for (int j = 0; j < fatArray[i]->slicesCount; j++) {
|
|
machoArray[i] = fatArray[i]->slices[j];
|
|
}
|
|
}
|
|
return machoArray;
|
|
}
|
|
|
|
bool macho_is_encrypted(MachO *macho)
|
|
{
|
|
__block bool isEncrypted = false;
|
|
macho_enumerate_load_commands(macho, ^(struct load_command loadCommand, uint64_t offset, void *cmd, bool *stop) {
|
|
if (loadCommand.cmd == LC_ENCRYPTION_INFO_64 || loadCommand.cmd == LC_ENCRYPTION_INFO) {
|
|
struct encryption_info_command *encryptionInfoCommand = cmd;
|
|
ENCRYPTION_INFO_COMMAND_APPLY_BYTE_ORDER(encryptionInfoCommand, LITTLE_TO_HOST_APPLIER);
|
|
if (encryptionInfoCommand->cryptid == 1) {
|
|
*stop = true;
|
|
isEncrypted = true;
|
|
}
|
|
}
|
|
});
|
|
return isEncrypted;
|
|
}
|
|
|
|
void macho_free(MachO *macho)
|
|
{
|
|
if (macho->filesetCount != 0 && macho->filesetMachos) {
|
|
for (uint32_t i = 0; i < macho->filesetCount; i++) {
|
|
fat_free(macho->filesetMachos[i].underlyingMachO);
|
|
free(macho->filesetMachos[i].entry_id);
|
|
}
|
|
free(macho->filesetMachos);
|
|
}
|
|
if (macho->segmentCount != 0 && macho->segments) {
|
|
for (uint32_t i = 0; i < macho->segmentCount; i++) {
|
|
free(macho->segments[i]);
|
|
}
|
|
free(macho->segments);
|
|
}
|
|
if (macho->stream) {
|
|
memory_stream_free(macho->stream);
|
|
}
|
|
free(macho);
|
|
}
|