ios-kernel-patch/data/mach-o/binary.c
2021-02-18 10:42:34 +07:00

475 lines
17 KiB
C

#include "binary.h"
#include "headers/loader.h"
#include "headers/nlist.h"
#include "headers/fat.h"
#include "read_dyld_info.h"
const int desired_cputype = CPU_TYPE_ARM;
const int desired_cpusubtype = CPU_SUBTYPE_ARM_V7;
static addr_t sym(const struct binary *binary, const char *name, int options);
static void copy_syms(const struct binary *binary, struct data_sym **syms, uint32_t *nsyms, int options);
static void do_load_commands(struct binary *binary) {
struct mach_header *hdr = b_mach_hdr(binary);
if(!prange_check(binary, (prange_t) {hdr, hdr->sizeofcmds})) {
die("not enough room for commands");
}
uint32_t nsegs = 0;
CMD_ITERATE(hdr, cmd) {
if(cmd + 1 > end || cmd > end - 1) {
die("sizeofcmds is not even");
}
if(cmd->cmdsize < sizeof(struct load_command)) {
die("tiny command");
}
if(cmd->cmdsize > (size_t) ((char *) end - (char *) cmd)) {
die("cmdsize overflows (%u)", cmd->cmdsize);
}
uint32_t required = 0;
switch(cmd->cmd) {
MACHO_SPECIALIZE(
case LC_SEGMENT_X:
required = sizeof(segment_command_x);
nsegs++;
break;
)
case LC_REEXPORT_DYLIB:
required = sizeof(struct dylib_command);
break;
case LC_SYMTAB:
required = sizeof(struct symtab_command);
break;
case LC_DYSYMTAB:
required = sizeof(struct dysymtab_command);
break;
case LC_DYLD_INFO:
case LC_DYLD_INFO_ONLY:
required = sizeof(struct dyld_info_command);
break;
case LC_ID_DYLIB:
required = sizeof(struct dylib_command);
break;
}
if(cmd->cmdsize < required) {
die("cmdsize (%u) too small for cmd (0x%x)", cmd->cmdsize, cmd->cmd);
}
}
if(nsegs > MAX_ARRAY(struct data_segment)) {
die("segment overflow");
}
binary->nsegments = nsegs;
struct data_segment *seg = binary->segments = malloc(sizeof(*binary->segments) * binary->nsegments);
CMD_ITERATE(hdr, cmd) {
switch(cmd->cmd) {
MACHO_SPECIALIZE(
case LC_SEGMENT_X: {
segment_command_x *scmd = (void *) cmd;
if((scmd->cmdsize - sizeof(*scmd)) / sizeof(section_x) < scmd->nsects) {
die("section overflow");
}
seg->file_range = (range_t) {binary, scmd->fileoff, scmd->filesize};
seg->vm_range = (range_t) {binary, scmd->vmaddr, scmd->vmsize};
seg->native_segment = cmd;
seg++;
break;
}
)
}
}
}
static void do_symbols(struct binary *binary) {
binary->mach = calloc(sizeof(*binary->mach), 1);
binary->mach->hdr = b_mach_hdr(binary);
CMD_ITERATE(b_mach_hdr(binary), cmd) {
MACHO_SPECIALIZE(
if(cmd->cmd == LC_SEGMENT_X) {
segment_command_x *seg = (void *) cmd;
if(seg->fileoff == 0) {
binary->mach->export_baseaddr = seg->vmaddr;
}
}
)
if(cmd->cmd == LC_SYMTAB) {
struct symtab_command *scmd = (void *) cmd;
if(scmd->nsyms > MAX_ARRAY(struct data_sym) || scmd->nsyms > MAX_ARRAY(struct nlist_64)) {
die("ridiculous number of symbols (%u)", scmd->nsyms);
}
binary->mach->nsyms = scmd->nsyms;
binary->mach->strsize = scmd->strsize;
binary->mach->symtab = rangeconv_off((range_t) {binary, scmd->symoff, scmd->nsyms * (b_pointer_size(binary) == 8 ? sizeof(struct nlist_64) : sizeof(struct nlist))}, MUST_FIND).start;
binary->mach->strtab = rangeconv_off((range_t) {binary, scmd->stroff, scmd->strsize}, MUST_FIND).start;
if(binary->mach->strtab[binary->mach->strsize - 1]) {
die("string table does not end with \\0");
}
} else if(cmd->cmd == LC_DYSYMTAB) {
binary->mach->dysymtab = (void *) cmd;
} else if(cmd->cmd == LC_DYLD_INFO_ONLY || cmd->cmd == LC_DYLD_INFO) {
struct dyld_info_command *dcmd = (void *) cmd;
binary->mach->dyld_info = dcmd;
binary->mach->export_trie = rangeconv_off((range_t) {binary, dcmd->export_off, dcmd->export_size}, MUST_FIND);
}
}
const struct dysymtab_command *dc;
if(binary->mach->symtab && (dc = binary->mach->dysymtab)) {
size_t size;
MACHO_SPECIALIZE_POINTER_SIZE(binary, size = sizeof(nlist_x);)
#define do_it(isym, nsym, x_symtab, x_nsyms) \
if(dc->isym <= binary->mach->nsyms && dc->nsym <= binary->mach->nsyms - dc->isym && dc->nsym <= MAX_ARRAY(struct nlist_64) && dc->nsym <= MAX_ARRAY(struct data_sym)) { \
binary->mach->x_symtab = binary->mach->symtab + dc->isym * size; \
binary->mach->x_nsyms = dc->nsym; \
} else { \
fprintf(stderr, "warning: bad %s/%s (%u, %u)\n", #isym, #nsym, dc->isym, dc->nsym); \
}
do_it(iextdefsym, nextdefsym, ext_symtab, ext_nsyms)
do_it(iundefsym, nundefsym, imp_symtab, imp_nsyms)
#undef do_it
} else {
binary->mach->ext_symtab = binary->mach->symtab;
binary->mach->ext_nsyms = binary->mach->nsyms;
}
}
void b_prange_load_macho(struct binary *binary, prange_t pr, size_t offset, const char *name) {
b_prange_load_macho_nosyms(binary, pr, offset, name);
do_symbols(binary);
binary->_sym = sym;
binary->_copy_syms = copy_syms;
}
void b_prange_load_macho_nosyms(struct binary *binary, prange_t pr, size_t offset, const char *name) {
#define _arg name
binary->valid = true;
binary->header_offset = offset;
if(offset >= pr.size || offset - pr.size < sizeof(struct mach_header)) {
die("not enough room");
}
struct mach_header *hdr = pr.start + offset;
if(hdr->magic == MH_MAGIC) {
// thin file
binary->valid_range = pr;
binary->pointer_size = 4;
} else if(ADDR64 && hdr->magic == MH_MAGIC_64) {
binary->valid_range = pr;
binary->pointer_size = 8;
} else if(hdr->magic == FAT_CIGAM) {
if(offset) die("fat, offset != 0");
struct fat_header *fathdr = (void *) hdr;
struct fat_arch *arch = (void *)(fathdr + 1);
uint32_t nfat_arch = SWAP32(fathdr->nfat_arch);
if(nfat_arch > (pr.size - sizeof(struct fat_header)) / sizeof(struct fat_arch)) {
die("fat header is too small");
}
if(!nfat_arch) {
die("fat file is empty");
}
prange_t fat_pr = {NULL, 0}; /* no, gcc, it won't be used uninitialized */
int highest_score = 0;
while(nfat_arch--) {
int score = 0;
if(desired_cputype != CPU_TYPE_ANY && SWAP32(arch->cputype) == desired_cputype) {
score = 1;
if(arch->cpusubtype == 0 || (desired_cpusubtype != 0 && SWAP32(arch->cpusubtype) == desired_cpusubtype)) {
score = 2;
}
}
if(score >= highest_score) {
highest_score = score;
uint32_t fat_offset = SWAP32(arch->offset);
if(fat_offset >= pr.size || pr.size - fat_offset < sizeof(struct mach_header)) {
die("fat_offset too big");
}
fat_pr = (prange_t) {pr.start + fat_offset, pr.size - fat_offset};
}
arch++;
}
binary->valid_range = fat_pr;
} else if(hdr->magic == MH_CIGAM || hdr->magic == MH_CIGAM_64 || hdr->magic == FAT_MAGIC) {
die("wrong endian");
} else {
die("(%08x) what is this I don't even", hdr->magic);
}
binary->cputype = b_mach_hdr(binary)->cputype;
binary->cpusubtype = b_mach_hdr(binary)->cpusubtype;
do_load_commands(binary);
#undef _arg
}
static inline struct data_sym convert_nlist(const struct binary *binary, const void *nl_, int options) {
struct data_sym result;
MACHO_SPECIALIZE_POINTER_SIZE(binary,
const nlist_x *nl = nl_;
uint32_t strx = nl->n_un.n_strx;
if(strx >= binary->mach->strsize) {
die("insane strx: %u", strx);
}
result.name = binary->mach->strtab + strx;
result.address = nl->n_value;
if((options & TO_EXECUTE) && (nl->n_desc & N_ARM_THUMB_DEF)) {
result.address |= 1;
}
)
return result;
}
void *b_macho_nth_symbol(const struct binary *binary, uint32_t n) {
if(!binary->mach->symtab) {
die("no symbol table");
}
if(n >= binary->mach->nsyms) {
die("sym too high: %u", n);
}
MACHO_SPECIALIZE_POINTER_SIZE(binary,
nlist_x *nl = binary->mach->symtab + n * sizeof(*nl);
if((uint32_t) nl->n_un.n_strx >= binary->mach->strsize) {
die("insane strx: %d", (int) nl->n_un.n_strx);
}
return nl;
)
}
static addr_t sym_nlist(const struct binary *binary, const char *name, int options) {
// I stole dyld's codez
const struct nlist *base = binary->mach->ext_symtab;
for(uint32_t n = binary->mach->ext_nsyms; n > 0; n /= 2) {
const struct nlist *pivot = base + n/2;
struct data_sym ds = convert_nlist(binary, pivot, options);
int cmp = strcmp(name, ds.name);
if(cmp == 0) {
return ds.address;
} else if(cmp > 0) {
base = pivot + 1;
n--;
}
}
for(unsigned int i = 0; i < binary->nreexports; i++) {
addr_t result;
if(result = b_sym(&binary->reexports[i], name, options)) {
return result;
}
}
return 0;
}
static addr_t trie_recurse(const struct binary *binary, void *ptr, char *start, char *end, const char *name0, const char *name, int options) {
if(start == end) return 0;
uint8_t terminal_size = read_int(&ptr, end, uint8_t);
if(terminal_size) {
uint32_t flags = read_uleb128(&ptr, end);
uint32_t address = read_uleb128(&ptr, end);
uint32_t resolver = 0;
if(flags & 0x10) {
resolver = read_uleb128(&ptr, end);
}
if(!name[0]) {
if(resolver) {
fprintf(stderr, "trie_recurse: %s has a resolver; returning failure\n", name0);
return 0;
}
if(flags & 8) {
// indirect definition
address--;
if(address >= binary->nreexports) {
die("invalid sub-library %d", address);
}
return b_sym(&binary->reexports[address], name0, options);
}
if(binary->cputype == CPU_TYPE_ARM && !(options & TO_EXECUTE)) {
address &= ~1u;
}
return ((addr_t) address) + binary->mach->export_baseaddr;
}
}
uint8_t child_count = read_int(&ptr, end, uint8_t);
while(child_count--) {
const char *name2 = name;
char c;
while(1) {
c = read_int(&ptr, end, char);
if(!c) {
uint64_t offset = read_uleb128(&ptr, end);
if(offset >= (size_t) (end - start)) die("invalid child offset");
return trie_recurse(binary, start + offset, start, end, name0, name2, options);
}
if(c != *name2++) {
break;
}
}
// skip the rest
read_cstring(&ptr, end);
read_uleb128(&ptr, end);
}
return 0;
}
static addr_t sym_trie(const struct binary *binary, const char *name, int options) {
return trie_recurse(binary,
binary->mach->export_trie.start,
binary->mach->export_trie.start,
(char *)binary->mach->export_trie.start + binary->mach->export_trie.size,
name,
name,
options);
}
static addr_t sym_private(const struct binary *binary, const char *name, int options) {
if(!binary->mach->symtab) {
die("we wanted %s but there is no symbol table", name);
}
MACHO_SPECIALIZE_POINTER_SIZE(binary,
const nlist_x *base = binary->mach->symtab;
for(uint32_t i = 0; i < binary->mach->nsyms; i++) {
struct data_sym ds = convert_nlist(binary, base + i, options);
if(!strcmp(ds.name, name)) return ds.address;
}
)
return 0;
}
static addr_t sym_imported(const struct binary *binary, const char *name, __unused int options) {
// most of this function is copied and pasted from link.c :$
CMD_ITERATE(b_mach_hdr(binary), cmd) {
MACHO_SPECIALIZE(
if(cmd->cmd == LC_SEGMENT_X) {
segment_command_x *seg = (void *) cmd;
section_x *sect = (void *) (seg + 1);
for(uint32_t i = 0; i < seg->nsects; i++, sect++) {
uint8_t type = sect->flags & SECTION_TYPE;
if(type != S_NON_LAZY_SYMBOL_POINTERS && type != S_LAZY_SYMBOL_POINTERS) continue;
uint32_t indirect_table_offset = sect->reserved1;
uint32_t *indirect = rangeconv_off((range_t) {binary, (addr_t) (binary->mach->dysymtab->indirectsymoff + indirect_table_offset*sizeof(uint32_t)), (sect->size / 4) * sizeof(uint32_t)}, MUST_FIND).start;
for(uint32_t i = 0; i < sect->size / 4; i++) {
uint32_t sym = indirect[i];
if(sym == INDIRECT_SYMBOL_LOCAL || sym == INDIRECT_SYMBOL_ABS) continue;
nlist_x *nl = b_macho_nth_symbol(binary, sym);
if(!strcmp(binary->mach->strtab + nl->n_un.n_strx, name)) {
return sect->addr + 4*i;
}
}
}
}
)
}
return 0;
}
static addr_t sym(const struct binary *binary, const char *name, int options) {
addr_t (*func)(const struct binary *binary, const char *name, int options);
if(options & PRIVATE_SYM)
func = sym_private;
else if(options & IMPORTED_SYM)
func = sym_imported;
else if(binary->mach->export_trie.start)
func = sym_trie;
else
func = sym_nlist;
return func(binary, name, options & ~MUST_FIND);
}
static void copy_syms(const struct binary *binary, struct data_sym **syms, uint32_t *nsyms, int options) {
uint32_t n;
const void *nl;
size_t size;
MACHO_SPECIALIZE_POINTER_SIZE(binary, size = sizeof(nlist_x);)
bool can_be_zero = false;
if(options & PRIVATE_SYM) {
nl = binary->mach->symtab;
n = binary->mach->nsyms;
} else if(options & IMPORTED_SYM) {
nl = binary->mach->imp_symtab;
n = binary->mach->imp_nsyms;
can_be_zero = true;
} else {
nl = binary->mach->ext_symtab;
n = binary->mach->ext_nsyms;
}
struct data_sym *s = *syms = malloc(sizeof(struct data_sym) * n);
for(uint32_t i = 0; i < n; i++) {
*s = convert_nlist(binary, nl, options);
nl += size;
if(can_be_zero || s->address) s++;
}
*nsyms = s - *syms;
}
range_t b_macho_segrange(const struct binary *binary, const char *segname) {
CMD_ITERATE(b_mach_hdr(binary), cmd) {
MACHO_SPECIALIZE(
if(cmd->cmd == LC_SEGMENT_X) {
segment_command_x *seg = (void *) cmd;
if(!strncmp(seg->segname, segname, 16)) {
return (range_t) {binary, seg->vmaddr, seg->filesize};
}
}
)
}
die("no such segment %s", segname);
}
range_t b_macho_sectrange(const struct binary *binary, const char *segname, const char *sectname) {
CMD_ITERATE(b_mach_hdr(binary), cmd) {
MACHO_SPECIALIZE(
if(cmd->cmd == LC_SEGMENT_X) {
segment_command_x *seg = (void *) cmd;
if(!strncmp(seg->segname, segname, 16)) {
section_x *sect = (void *) (seg + 1);
for(uint32_t i = 0; i < seg->nsects; i++) {
if(!strncmp(sect[i].sectname, sectname, 16)) {
return (range_t) {binary, sect->addr, sect->size};
}
}
}
}
)
}
die("no such segment %s", segname);
}
void b_load_macho(struct binary *binary, const char *filename) {
return b_prange_load_macho(binary, load_file(filename, true, NULL), 0, filename);
}
addr_t b_macho_reloc_base(const struct binary *binary) {
// copying dyld's behavior
CMD_ITERATE(b_mach_hdr(binary), cmd) {
MACHO_SPECIALIZE(
if(cmd->cmd == LC_SEGMENT_X) {
segment_command_x *seg = (void *) cmd;
if(b_mach_hdr(binary)->cputype != CPU_TYPE_X86_64 || (seg->initprot & PROT_WRITE)) {
return seg->vmaddr;
}
}
)
}
die("no segments");
}
const char *convert_lc_str(const struct load_command *cmd, uint32_t offset) {
const char *ret = ((const char *) cmd) + offset;
size_t size = cmd->cmdsize - offset;
if(offset >= cmd->cmdsize || strnlen(ret, size) == size) {
die("bad lc_str");
}
return ret;
}