ios-kernel-patch/data/mach-o/link.c

467 lines
18 KiB
C
Raw Permalink Normal View History

2021-02-18 10:42:34 +07:00
#include "link.h"
#include "headers/loader.h"
#include "headers/nlist.h"
#include "headers/reloc.h"
#include "headers/arm_reloc.h"
#include <ctype.h>
#include "read_dyld_info.h"
static addr_t lookup_symbol_or_do_stuff(lookupsym_t lookup_sym, void *context, const char *name, bool weak, bool userland) {
addr_t sym = lookup_sym(context, name);
if(!sym) {
if(userland) {
// let it pass
} else if(!strcmp(name, "dyld_stub_binder")) {
sym = 0xdeadbeef;
} else if(weak) {
fprintf(stderr, "lookup_nth_symbol: warning: couldn't find weak symbol %s\n", name);
} else {
die("couldn't find symbol %s\n", name);
}
}
return sym;
}
static addr_t lookup_nth_symbol(const struct binary *load, uint32_t symbolnum, lookupsym_t lookup_sym, void *context, bool userland) {
struct nlist *nl = b_macho_nth_symbol(load, symbolnum);
bool weak = nl->n_desc & N_WEAK_REF;
const char *name = load->mach->strtab + nl->n_un.n_strx;
return lookup_symbol_or_do_stuff(lookup_sym, context, name, weak, userland);
}
static void relocate_area(struct binary *load, uint32_t reloff, uint32_t nreloc, enum reloc_mode mode, lookupsym_t lookup_sym, void *context, addr_t slide) {
struct relocation_info *things = rangeconv_off((range_t) {load, reloff, nreloc * sizeof(struct relocation_info)}, MUST_FIND).start;
for(uint32_t i = 0; i < nreloc; i++) {
if(things[i].r_length != 2) {
die("bad relocation length");
}
addr_t address = things[i].r_address;
if(address == 0 || things[i].r_symbolnum == R_ABS) continue;
address += b_macho_reloc_base(load);
uint32_t *p = rangeconv((range_t) {load, address, 4}, MUST_FIND).start;
addr_t value;
if(things[i].r_extern) {
if(mode == RELOC_LOCAL_ONLY) continue;
value = lookup_nth_symbol(load, things[i].r_symbolnum, lookup_sym, context, mode == RELOC_USERLAND);
if(value == 0 && mode == RELOC_USERLAND) continue;
} else {
if(mode == RELOC_EXTERN_ONLY || mode == RELOC_USERLAND) continue;
// *shrug*
value = slide;
}
things[i].r_address = 0;
things[i].r_symbolnum = R_ABS;
if(mode == RELOC_EXTERN_ONLY && things[i].r_type != ARM_RELOC_VANILLA) {
die("non-VANILLA relocation but we are relocating without knowing the slide; use __attribute__((long_call)) to get rid of these");
}
switch(things[i].r_type) {
case ARM_RELOC_VANILLA:
//printf("%x, %x += %x\n", address, *p, value);
if(rangeconv((range_t) {load, *p, 0}, 0).start) {
// when dyld_stub_binding_helper (which would just crash, btw) is present, entries in the indirect section point to it; usually this increments to point to the right dyld_stub_binding_helper, then that's clobbered by the indirect code. when we do prelinking, the indirect code runs first and we would be relocating the already-correctly-located importee symbol, so we add this check (easier than actually checking that it's not in the indirect section) to make sure we're not relocating nonsense.
*p += value;
}
//else printf("skipping %x\n", *p);
break;
case ARM_RELOC_BR24: {
if(!things[i].r_pcrel) die("weird relocation");
uint32_t ins = *p;
uint32_t off = ins & 0x00ffffff;
if(ins & 0x00800000) off |= 0xff000000;
off <<= 2;
off += (value - slide);
if((off & 0xfc000000) != 0 &&
(off & 0xfc000000) != 0xfc000000) {
die("BR24 relocation out of range");
}
uint32_t cond = ins >> 28;
if(value & 1) {
if(cond != 0xe && cond != 0xf) die("can't convert BL with condition to BLX (which must be unconditional)");
ins = (ins & 0x0effffff) | 0xf0000000 | ((off & 2) << 24);
} else if(cond == 0xf) {
ins = (ins & 0x0fffffff) | 0xe0000000;
}
ins = (ins & 0xff000000) | ((off >> 2) & 0x00ffffff);
*p = ins;
break;
}
default:
die("unknown relocation type %d", things[i].r_type);
}
}
}
static void go_indirect(struct binary *load, uint32_t offset, uint32_t size, uint32_t flags, uint32_t reserved1, uint32_t reserved2, enum reloc_mode mode, lookupsym_t lookup_sym, void *context, addr_t slide) {
uint8_t type = flags & SECTION_TYPE;
uint8_t pointer_size = b_pointer_size(load);
switch(type) {
case S_NON_LAZY_SYMBOL_POINTERS:
case S_LAZY_SYMBOL_POINTERS: {
uint32_t indirect_table_offset = reserved1;
const struct dysymtab_command *dysymtab = load->mach->dysymtab;
uint32_t stride = type == S_SYMBOL_STUBS ? reserved2 : pointer_size;
uint32_t num_syms = size / stride;
if(stride < pointer_size ||
num_syms * stride != size ||
dysymtab->nindirectsyms > ((addr_t) -(dysymtab->indirectsymoff - 1)) / sizeof(uint32_t) ||
indirect_table_offset > dysymtab->nindirectsyms ||
num_syms > dysymtab->nindirectsyms - indirect_table_offset) {
die("bad indirect section");
}
uint32_t *indirect_syms = rangeconv_off((range_t) {load, (addr_t) dysymtab->indirectsymoff + indirect_table_offset * sizeof(uint32_t), num_syms * sizeof(uint32_t)}, MUST_FIND).start;
void *addrs = rangeconv_off((range_t) {load, offset, size}, MUST_FIND).start;
for(uint32_t i = 0; i < num_syms; i++, indirect_syms++, addrs += stride) {
addr_t addr, found_addr;
switch(*indirect_syms) {
case INDIRECT_SYMBOL_LOCAL:
if(mode == RELOC_EXTERN_ONLY || mode == RELOC_USERLAND) continue;
addr = read_pointer(addrs, pointer_size) + slide;
break;
case INDIRECT_SYMBOL_ABS:
continue;
default:
if(mode == RELOC_LOCAL_ONLY) continue;
found_addr = lookup_nth_symbol(load, *indirect_syms, lookup_sym, context, mode == RELOC_USERLAND);
if(!found_addr && mode == RELOC_USERLAND) {
// don't set to ABS!
continue;
}
addr = found_addr;
break;
}
write_pointer(addrs, addr, pointer_size);
*indirect_syms = INDIRECT_SYMBOL_ABS;
}
break;
}
case S_ZEROFILL:
case S_MOD_INIT_FUNC_POINTERS:
case S_MOD_TERM_FUNC_POINTERS:
case S_REGULAR:
case S_CSTRING_LITERALS:
case S_4BYTE_LITERALS:
case S_8BYTE_LITERALS:
case S_16BYTE_LITERALS:
break;
default:
if(mode != RELOC_USERLAND) {
die("unrecognized section type %02x", type);
}
}
}
static void relocate_with_symtab(struct binary *load, enum reloc_mode mode, lookupsym_t lookup_sym, void *context, addr_t slide) {
if(mode != RELOC_EXTERN_ONLY && mode != RELOC_USERLAND) {
relocate_area(load, load->mach->dysymtab->locreloff, load->mach->dysymtab->nlocrel, mode, lookup_sym, context, slide);
}
if(mode != RELOC_LOCAL_ONLY) {
relocate_area(load, load->mach->dysymtab->extreloff, load->mach->dysymtab->nextrel, mode, lookup_sym, context, slide);
}
CMD_ITERATE(b_mach_hdr(load), cmd) {
MACHO_SPECIALIZE(
if(cmd->cmd == LC_SEGMENT_X) {
segment_command_x *seg = (void *) cmd;
//printf("%.16s %08x\n", seg->segname, seg->vmaddr);
section_x *sect = (void *) (seg + 1);
for(uint32_t i = 0; i < seg->nsects; i++, sect++) {
//printf(" %.16s\n", sect->sectname);
go_indirect(load, sect->offset, sect->size, sect->flags, sect->reserved1, sect->reserved2, mode, lookup_sym, context, slide);
relocate_area(load, sect->reloff, sect->nreloc, mode, lookup_sym, context, slide);
}
}
)
}
}
static void do_bind_section(prange_t opcodes, struct binary *load, bool weak, bool userland, lookupsym_t lookup_sym, void *context) {
uint8_t pointer_size = b_pointer_size(load);
uint8_t symbol_flags;
char *sym = NULL;
uint8_t type = BIND_TYPE_POINTER;
addr_t addend = 0;
prange_t segment = {NULL, 0};
addr_t segaddr = 0;
addr_t offset = 0;
void *ptr = opcodes.start, *end = ptr + opcodes.size;
while(ptr != end) {
void *orig_ptr = ptr;
uint8_t byte = read_int(&ptr, end, uint8_t);
uint8_t immediate = byte & BIND_IMMEDIATE_MASK;
uint8_t opcode = byte & BIND_OPCODE_MASK;
addr_t count, stride;
switch(opcode) {
case BIND_OPCODE_DONE:
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
// do nothing
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
read_uleb128(&ptr, end);
break;
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
sym = read_cstring(&ptr, end);
symbol_flags = immediate;
break;
case BIND_OPCODE_SET_TYPE_IMM:
type = immediate;
break;
case BIND_OPCODE_SET_ADDEND_SLEB:
addend = read_sleb128(&ptr, end);
break;
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
if(immediate >= load->nsegments) {
die("segment too high");
}
segment = rangeconv_off(load->segments[immediate].file_range, MUST_FIND);
segaddr = load->segments[immediate].vm_range.start;
offset = read_uleb128(&ptr, end);
break;
case BIND_OPCODE_ADD_ADDR_ULEB:
{
addr_t o = read_uleb128(&ptr, end);
offset += o;
}
break;
case BIND_OPCODE_DO_BIND:
count = 1;
stride = pointer_size;
goto bind;
case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
count = 1;
stride = read_uleb128(&ptr, end) + pointer_size;
goto bind;
case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
count = 1;
stride = immediate * pointer_size + pointer_size;
goto bind;
case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
count = read_uleb128(&ptr, end);
stride = read_uleb128(&ptr, end) + pointer_size;
goto bind;
bind: {
if(!sym || !segment.start) die("improper bind");
bool _64b;
addr_t value;
value = lookup_symbol_or_do_stuff(lookup_sym, context, sym, weak, userland);
if(!value) {
offset += stride * count;
break;
}
value += addend;
switch(type) {
case BIND_TYPE_POINTER:
_64b = pointer_size == 8;
break;
case BIND_TYPE_TEXT_ABSOLUTE32:
_64b = false;
break;
case BIND_TYPE_TEXT_PCREL32:
_64b = false;
value = -value + (segaddr + offset + 4);
break;
default:
die("bad bind type %d", (int) type);
}
if(offset >= segment.size ||
stride < (_64b ? sizeof(uint64_t) : sizeof(uint32_t)) ||
(segment.size - offset) / stride < count) {
die("bad address while binding");
}
while(count--) {
if(_64b) {
*((uint64_t *) (segment.start + offset)) = value;
} else {
*((uint32_t *) (segment.start + offset)) = value;
}
offset += stride;
if(type == BIND_TYPE_TEXT_PCREL32) value += stride;
}
memset(orig_ptr, BIND_OPCODE_SET_TYPE_IMM, ptr - orig_ptr);
type = BIND_TYPE_POINTER;
break;
}
default:
die("unknown bind opcode 0x%x", (int) opcode);
}
}
}
static void do_rebase(struct binary *load, prange_t opcodes, addr_t slide) {
uint8_t pointer_size = b_pointer_size(load);
uint8_t type = REBASE_TYPE_POINTER;
addr_t offset = 0;
prange_t segment = {NULL, 0};
void *ptr = opcodes.start, *end = ptr + opcodes.size;
while(ptr != end) {
uint8_t byte = read_int(&ptr, end, uint8_t);
uint8_t immediate = byte & BIND_IMMEDIATE_MASK;
uint8_t opcode = byte & BIND_OPCODE_MASK;
addr_t count, stride;
switch(opcode) {
// this code is very similar to do_bind_section
case REBASE_OPCODE_DONE:
return;
case REBASE_OPCODE_SET_TYPE_IMM:
type = immediate;
break;
case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
if(immediate >= load->nsegments) {
die("segment too high");
}
segment = rangeconv_off(load->segments[immediate].file_range, MUST_FIND);
offset = read_uleb128(&ptr, end);
break;
case REBASE_OPCODE_ADD_ADDR_ULEB:
offset += read_uleb128(&ptr, end);
break;
case REBASE_OPCODE_ADD_ADDR_IMM_SCALED:
offset += immediate * pointer_size;
break;
case REBASE_OPCODE_DO_REBASE_IMM_TIMES:
count = immediate;
stride = pointer_size;
goto rebase;
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES:
count = read_uleb128(&ptr, end);
stride = pointer_size;
goto rebase;
case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB:
count = 1;
stride = read_uleb128(&ptr, end) + pointer_size;
goto rebase;
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
count = read_uleb128(&ptr, end);
stride = read_uleb128(&ptr, end) + pointer_size;
goto rebase;
rebase: {
bool _64b;
switch(type) {
case REBASE_TYPE_POINTER:
_64b = pointer_size == 8;
break;
case REBASE_TYPE_TEXT_ABSOLUTE32:
case REBASE_TYPE_TEXT_PCREL32:
_64b = false;
break;
default:
die("bad rebase type %d", (int) type);
}
if(offset >= segment.size || (segment.size - offset) / stride < count) {
die("bad address while rebasing");
}
while(count--) {
if(_64b) {
*((uint64_t *) (segment.start + offset)) += slide;
} else {
uint32_t *ptr = segment.start + offset;
*ptr += slide;
if(type == REBASE_TYPE_TEXT_PCREL32) {
// WTF!? This is actually what dyld does.
*ptr = -*ptr;
}
}
offset += stride;
}
break;
}
default:
die("unknown rebase opcode 0x%x", (int) opcode);
}
}
}
static void relocate_with_dyld_info(struct binary *load, enum reloc_mode mode, lookupsym_t lookup_sym, void *context, addr_t slide) {
// It gets more complicated
struct dyld_info_command *dyld_info = load->mach->dyld_info;
#define fetch(type) prange_t type = dyld_info->type##_off ? rangeconv_off((range_t) {load, dyld_info->type##_off, dyld_info->type##_size}, MUST_FIND) : (prange_t) {NULL, 0};
if(mode != RELOC_EXTERN_ONLY && slide != 0) {
fetch(rebase)
do_rebase(load, rebase, slide);
dyld_info->rebase_size = 0;
}
if(mode != RELOC_LOCAL_ONLY) {
fetch(bind)
fetch(weak_bind)
fetch(lazy_bind)
bool userland = mode == RELOC_USERLAND;
do_bind_section(bind, load, userland, userland, lookup_sym, context);
do_bind_section(weak_bind, load, true, userland, lookup_sym, context);
do_bind_section(lazy_bind, load, userland, userland, lookup_sym, context);
}
}
void b_relocate(struct binary *load, const struct binary *target, enum reloc_mode mode, lookupsym_t lookup_sym, void *context, addr_t slide) {
if(mode == RELOC_USERLAND && slide != 0) {
die("sliding is not supported in userland mode");
}
if(!load->mach->symtab || !load->mach->dysymtab) {
die("no LC_SYMTAB/LC_DYSYMTAB");
}
// check for overlap
if(target) {
for(uint32_t i = 0; i < load->nsegments; i++) {
struct data_segment *a = &load->segments[i];
for(uint32_t j = 0; j < target->nsegments; j++) {
struct data_segment *b = &target->segments[j];
addr_t diff = b->vm_range.start - (a->vm_range.start + slide);
if(diff < a->vm_range.size || -diff < b->vm_range.size) {
die("segments of load and target overlap; load:%llx+%zu target:%llx+%zu", (uint64_t) a->vm_range.start, a->vm_range.size, (uint64_t) b->vm_range.start, b->vm_range.size);
}
}
}
}
(load->mach->dyld_info ? relocate_with_dyld_info : relocate_with_symtab)(load, mode, lookup_sym, context, slide);
if(mode != RELOC_EXTERN_ONLY && slide != 0) {
CMD_ITERATE(b_mach_hdr(load), cmd) {
MACHO_SPECIALIZE(
if(cmd->cmd == LC_SEGMENT_X) {
segment_command_x *seg = (void *) cmd;
section_x *sect = (void *) (seg + 1);
seg->vmaddr += slide;
for(uint32_t i = 0; i < seg->nsects; i++, sect++) {
sect->addr += slide;
}
}
)
}
}
}