#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <mach-o/loader.h>
#include <stuff/bool.h>
#include <stuff/allocate.h>
#include <stuff/bytesex.h>
#include "dyld_bind_info.h"
#include "fixup-chains.h"
struct ThreadedBindData {
const char* symbolName;
int64_t addend;
int libraryOrdinal;
uint8_t flags;
uint8_t type;
};
const char *
bindTypeName(
uint8_t type)
{
switch(type){
case BIND_TYPE_POINTER:
return("pointer");
case BIND_TYPE_TEXT_ABSOLUTE32:
return("text abs32");
case BIND_TYPE_TEXT_PCREL32:
return("text rel32");
}
return("!!Unknown!!");
}
static
enum bool
validateOrdinal(
int dylibOrdinal,
const char **dylibs,
uint32_t ndylibs,
const char** dylibName,
const char** error)
{
*dylibName = NULL;
if (error)
*error = NULL;
if (dylibOrdinal < BIND_SPECIAL_DYLIB_WEAK_LOOKUP){
if (error)
*error = "Unknown special ordinal";
return FALSE;
}
if (dylibOrdinal > 0 && dylibOrdinal > ndylibs){
if (error)
*error = "dylibOrdinal out of range";
return FALSE;
}
switch(dylibOrdinal){
case BIND_SPECIAL_DYLIB_SELF:
*dylibName = "this-image";
break;
case BIND_SPECIAL_DYLIB_MAIN_EXECUTABLE:
*dylibName = "main-executable";
break;
case BIND_SPECIAL_DYLIB_FLAT_LOOKUP:
*dylibName = "flat-namespace";
break;
case BIND_SPECIAL_DYLIB_WEAK_LOOKUP:
*dylibName = "weak";
break;
default:
*dylibName = dylibs[dylibOrdinal-1];
}
return TRUE;
}
static
enum bool
validateSegIndex(
uint8_t segIndex,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64,
const char** error)
{
*error = NULL;
if(segs64 != NULL && segIndex >= nsegs64) {
*error = "segment index out of range";
return FALSE;
}
if(segs != NULL && segIndex >= nsegs) {
*error = "segment index out of range";
return FALSE;
}
return TRUE;
}
uint64_t
segStartAddress(
uint8_t segIndex,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64)
{
if(segs != NULL){
if(segIndex >= nsegs)
return(0);
return(segs[segIndex]->vmaddr);
}
else if(segs64 != NULL){
if(segIndex >= nsegs64)
return(0);
return(segs64[segIndex]->vmaddr);
}
return(0);
}
const char *
segmentName(
uint8_t segIndex,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64)
{
if(segs != NULL){
if(segIndex >= nsegs)
return("??");
return(segs[segIndex]->segname);
}
else if(segs64 != NULL){
if(segIndex >= nsegs64)
return("??");
return(segs64[segIndex]->segname);
}
return("??");
}
const char *
sectionName(
uint8_t segIndex,
uint64_t address,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64)
{
struct section *s;
struct section_64 *s64;
uint32_t i;
if(segs != NULL){
if(segIndex >= nsegs)
return("??");
s = (struct section *)((char *)segs[segIndex] +
sizeof(struct segment_command));
for(i = 0; i < segs[segIndex]->nsects; i++){
if(s->addr <= address && address < s->addr + s->size)
return(s->sectname);
s++;
}
}
else if(segs64 != NULL){
if(segIndex >= nsegs64)
return("??");
s64 = (struct section_64 *)((char *)segs64[segIndex] +
sizeof(struct segment_command_64));
for(i = 0; i < segs64[segIndex]->nsects; i++){
if(s64->addr <= address && address < s64->addr + s64->size)
return(s64->sectname);
s64++;
}
}
return("??");
}
const char *
checkSegAndOffset(
int segIndex,
uint64_t segOffset,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64,
enum bool endInvalid)
{
uint64_t address;
if(segIndex == -1)
return("missing preceding BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB");
if(segs != NULL){
if(segIndex >= nsegs)
return("bad segIndex (too large)");
address = segs[segIndex]->vmaddr + segOffset;
if(address > segs[segIndex]->vmaddr + segs[segIndex]->vmsize)
return("bad segOffset, too large");
if(endInvalid == TRUE &&
address == segs[segIndex]->vmaddr + segs[segIndex]->vmsize)
return("bad segOffset, too large");
}
else if(segs64 != NULL){
if(segIndex >= nsegs64)
return("bad segIndex (too large)");
address = segs64[segIndex]->vmaddr + segOffset;
if(address > segs64[segIndex]->vmaddr + segs64[segIndex]->vmsize)
return("bad segOffset, too large");
if(endInvalid == TRUE &&
address == segs64[segIndex]->vmaddr + segs64[segIndex]->vmsize)
return("bad segOffset, too large");
}
return(NULL);
}
const char *
checkCountAndSkip(
uint32_t *count,
uint64_t skip,
int segIndex,
uint64_t segOffset,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64)
{
uint64_t address, i;
i = 0;
if(segs != NULL){
if(segIndex >= nsegs){
*count = 1;
return("bad segIndex (too large)");
}
if(*count > 1)
i = (skip + 4) * (*count - 1);
address = segs[segIndex]->vmaddr + segOffset;
if(address >= segs[segIndex]->vmaddr + segs[segIndex]->vmsize){
*count = 1;
return("bad segOffset, too large");
}
if(address + i >= segs[segIndex]->vmaddr + segs[segIndex]->vmsize){
*count = 1;
return("bad count and skip, too large");
}
}
else if(segs64 != NULL){
if(segIndex >= nsegs64){
*count = 1;
return("bad segIndex (too large)");
}
if(*count > 1)
i = (skip + 8) * (*count - 1);
address = segs64[segIndex]->vmaddr + segOffset;
if(address >= segs64[segIndex]->vmaddr + segs64[segIndex]->vmsize){
*count = 1;
return("bad segOffset, too large");
}
if(address + i >=
segs64[segIndex]->vmaddr + segs64[segIndex]->vmsize){
*count = 1;
return("bad count and skip, too large");
}
}
return(NULL);
}
static
uint64_t
read_uleb128(
const uint8_t **pp,
const uint8_t* end,
const char **error)
{
const uint8_t *p = *pp;
uint64_t result = 0;
int bit = 0;
*error = NULL;
do{
if(p >= end){
*pp = p;
*error = "malformed uleb128, extends past opcode bytes";
return(0);
}
uint64_t slice = *p & 0x7f;
if(bit >= 64 || slice << bit >> bit != slice){
*pp = p;
*error = "uleb128 too big for uint64";
return(0);
}
else {
result |= (slice << bit);
bit += 7;
}
}while(*p++ & 0x80);
*pp = p;
return(result);
}
static
int64_t
read_sleb128(
const uint8_t **pp,
const uint8_t* end,
const char **error)
{
const uint8_t *p = *pp;
int64_t result = 0;
int bit = 0;
uint8_t byte;
*error = NULL;
do{
if(p == end){
*pp = p;
*error = "malformed sleb128, extends past opcode bytes";
return(0);
}
byte = *p++;
result |= (((int64_t)(byte & 0x7f)) << bit);
bit += 7;
}while (byte & 0x80);
if((byte & 0x40) != 0)
result |= (-1LL) << bit;
*pp = p;
return(result);
}
static inline
void
printErrorBind(
int pass,
enum bool verbose,
const char* opName,
unsigned long location,
const char* error,
uint32_t *errorCount)
{
if (1 == pass && verbose && error) {
printf("bad bind info (for %s opcode at 0x%lx) %s\n",
opName, location, error);
}
errorCount++;
}
static inline
void
printErrorOrdinal(
int pass,
enum bool verbose,
const char* opName,
unsigned long location,
int32_t ordinal,
uint32_t ndylibs,
const char* error,
uint32_t *errorCount)
{
if (1 == pass && verbose && error) {
printf("bad bind info (for %s opcode at 0x%lx) bad library ordinal "
"%u (max %u): %s\n",
opName, location, ordinal, ndylibs, error);
}
errorCount +=1;
}
static inline
void
printErrorOrdAddr(
int pass,
enum bool verbose,
const char* opName,
unsigned long location,
int32_t ordinal,
uint64_t ordAddr,
uint32_t *errorCount)
{
if (1 == pass && verbose) {
printf("bad bind info (for %s opcode at 0x%lx) bad ordinal: %u in "
"pointer at address 0x%llx\n",
opName, location, ordinal, ordAddr);
}
errorCount +=1;
}
static inline
void
printErrorBindType(
int pass,
enum bool verbose,
const char* opName,
unsigned long location,
uint8_t bindType,
uint32_t *errorCount)
{
if (1 == pass && verbose) {
printf("bad bind info (for %s opcode at 0x%lx) "
"bad bind type: %u\n",
opName, location, bindType);
}
errorCount +=1;
}
static inline
void
printErrorOffset(
int pass,
enum bool verbose,
const char* opName,
unsigned long location,
uint64_t address,
int reason,
uint32_t *errorCount)
{
if (1 == pass && verbose) {
const char* error = "invalid";
if (0 == reason)
error = "past end of file";
else if (1 == reason)
error = "past end of the same page";
printf("bad bind info (for %s opcode at: 0x%lx) offset to next pointer "
"in the chain after one at address 0x%llx is %s\n",
opName, location, address, error);
}
errorCount +=1;
}
static inline
void
printErrorOpcode(
int pass,
enum bool verbose,
const char* opName,
unsigned long location,
uint8_t opcode,
uint32_t *errorCount)
{
if (1 == pass && verbose) {
if (opName)
printf("bad bind info (for %s opcode at 0x%lx) bad sub-opcode "
"value 0x%x\n",
opName, location, opcode);
else
printf("bad bind info (for opcode at 0x%lx) bad opcode "
"value 0x%x\n",
location, opcode);
}
errorCount++;
}
void
get_dyld_bind_info(
const uint8_t* start,
const uint8_t* end,
const char **dylibs,
uint32_t ndylibs,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64,
enum bool swapped,
char *object_addr,
uint64_t object_size,
struct dyld_bind_info **dbi,
uint64_t *ndbi,
enum chain_format_t *chain_format,
enum bool print_errors)
{
const uint8_t *p, *opcode_start;
uint8_t type;
int segIndex;
uint64_t segOffset;
const char* symbolName;
const char* fromDylib;
enum bool libraryOrdinalSet;
int libraryOrdinal;
int64_t addend;
int64_t flags;
uint32_t count;
uint32_t skip;
uint64_t segStartAddr;
const char* segName;
const char* weak_import;
enum bool done = FALSE;
const char *sectName, *error;
#define MAXERRORCOUNT 20
uint32_t pass, errorCount;
uint64_t n;
uint32_t sizeof_pointer;
char *pointerLocation;
uint64_t offset, ordinalTableCount, ordinalTableIndex, delta;
struct ThreadedBindData *ordinalTable;
uint16_t ordinal;
uint64_t pointerAddress, pointerPageStart;
const char* opName;
*chain_format = CHAIN_FORMAT_NONE;
*dbi = NULL;
*ndbi = 0;
if (start == NULL) {
printf("missing dyld bind info.\n");
return;
}
ordinalTable = NULL;
ordinalTableCount = 0;
sizeof_pointer = segs ? 4 : 8;
errorCount = 0;
n = 0;
for(pass = 1; pass <= 2; pass++){
p = start;
type = 0;
segIndex = -1;
segOffset = 0;
symbolName = NULL;
fromDylib = NULL;
libraryOrdinalSet = FALSE;
libraryOrdinal = 0;
addend = 0;
segStartAddr = 0;
segName = NULL;
sectName = NULL;
weak_import = "";
done = FALSE;
ordinalTableIndex = (uint64_t)-1;
flags = 0;
if(errorCount >= MAXERRORCOUNT){
if(print_errors)
printf("too many bind info errors, giving up.\n");
*dbi = NULL;
*ndbi = 0;
if(ordinalTable != NULL)
free(ordinalTable);
return;
}
if(pass == 2){
*dbi = (struct dyld_bind_info *)
allocate(n * sizeof(struct dyld_bind_info));
memset(*dbi, 0, n * sizeof(struct dyld_bind_info));
*ndbi = n;
n = 0;
}
while(!done && (p < end) && errorCount < MAXERRORCOUNT){
error = NULL;
opcode_start = p;
uint8_t immediate = *p & BIND_IMMEDIATE_MASK;
uint8_t opcode = *p & BIND_OPCODE_MASK;
opcode = *p & BIND_OPCODE_MASK;
++p;
switch(opcode){
case BIND_OPCODE_DONE:
done = TRUE;
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
opName = "BIND_OPCODE_SET_DYLIB_ORDINAL_IMM";
libraryOrdinal = immediate;
libraryOrdinalSet = validateOrdinal(libraryOrdinal,
dylibs, ndylibs,
&fromDylib, &error);
if (FALSE == libraryOrdinalSet)
printErrorOrdinal(pass, print_errors, opName,
opcode_start - start,
libraryOrdinal, ndylibs,
error, &errorCount);
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
opName = "BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB";
libraryOrdinal = (int)read_uleb128(&p, end, &error);
if (error) {
libraryOrdinalSet = FALSE;
}
else {
libraryOrdinalSet = validateOrdinal(libraryOrdinal,
dylibs, ndylibs,
&fromDylib, &error);
}
if (FALSE == libraryOrdinalSet)
printErrorOrdinal(pass, print_errors, opName,
opcode_start - start,
libraryOrdinal, ndylibs,
error, &errorCount);
break;
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
opName = "BIND_OPCODE_SET_DYLIB_SPECIAL_IMM";
if (immediate == 0) {
libraryOrdinal = 0;
}
else {
int8_t signExtended = BIND_OPCODE_MASK | immediate;
libraryOrdinal = signExtended;
}
libraryOrdinalSet = validateOrdinal(libraryOrdinal,
dylibs, ndylibs,
&fromDylib, &error);
if (FALSE == libraryOrdinalSet)
printErrorOrdinal(pass, print_errors, opName,
opcode_start - start,
libraryOrdinal, ndylibs,
error, &errorCount);
break;
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
opName = "BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM";
weak_import = "";
symbolName = (char*)p;
while(*p != '\0' && (p < end))
++p;
if(p == end){
error = "symbol name extends past opcodes";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
}
else {
++p;
}
flags = immediate;
if((flags & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0)
weak_import = " (weak import)";
break;
case BIND_OPCODE_SET_TYPE_IMM:
opName = "BIND_OPCODE_SET_TYPE_IMM";
if (immediate == 0 || immediate > BIND_TYPE_TEXT_PCREL32) {
printErrorBindType(pass, print_errors, opName,
opcode_start - start,
immediate, &errorCount);
type = 0;
}
else {
type = immediate;
}
break;
case BIND_OPCODE_SET_ADDEND_SLEB:
opName = "BIND_OPCODE_SET_ADDEND_SLEB";
addend = read_sleb128(&p, end, &error);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
addend = 0;
}
break;
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
opName = "BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB";
if (validateSegIndex(immediate, segs, nsegs,
segs64, nsegs64, &error))
{
segOffset = read_uleb128(&p, end, &error);
}
if (!error) {
segIndex = immediate;
segStartAddr = segStartAddress(segIndex, segs, nsegs,
segs64, nsegs64);
segName = segmentName(segIndex, segs, nsegs,
segs64, nsegs64);
error = checkSegAndOffset(segIndex, segOffset,
segs, nsegs,
segs64, nsegs64, TRUE);
}
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
segIndex = -1;
segStartAddr = 0;
segName = NULL;
segOffset = 0;
error = NULL;
}
break;
case BIND_OPCODE_ADD_ADDR_ULEB:
opName = "BIND_OPCODE_ADD_ADDR_ULEB";
error = NULL;
if (-1 == segIndex) {
error = "segment index is invalid";
}
if (!error) {
segOffset += read_uleb128(&p, end, &error);
}
if (!error) {
error = checkSegAndOffset(segIndex, segOffset,
segs, nsegs,
segs64, nsegs64, TRUE);
}
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
error = NULL;
}
break;
case BIND_OPCODE_DO_BIND:
opName = "BIND_OPCODE_DO_BIND";
if(CHAIN_FORMAT_NONE == *chain_format)
{
error = checkSegAndOffset(segIndex, segOffset, segs,
nsegs, segs64, nsegs64, TRUE);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error,
&errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
sectName = NULL;
}
else {
sectName = sectionName(segIndex,
segStartAddr + segOffset,
segs, nsegs,
segs64, nsegs64);
}
}
if (NULL == symbolName) {
error = "missing preceding BIND_OPCODE_SET_SYMBOL_"
"TRAILING_FLAGS_IMM opcode";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
}
if (FALSE == libraryOrdinalSet){
error = "missing preceding BIND_OPCODE_SET_DYLIB_"
"ORDINAL_* opcode";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
}
if(CHAIN_FORMAT_ARM64E == *chain_format){
if(ordinalTableIndex >= ordinalTableCount){
error = "incorrect ordinal table size: number of "
"BIND_OPCODE_DO_BIND opcodes exceed the count in "
"previous BIND_SUBOPCODE_THREADED_SET_BIND_"
"ORDINAL_TABLE_SIZE_ULEB opcode";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error,
&errorCount);
}
const uint64_t oti = ordinalTableIndex++;
ordinalTable[oti].symbolName = symbolName;
ordinalTable[oti].addend = addend;
ordinalTable[oti].libraryOrdinal
= libraryOrdinalSet ? libraryOrdinal : 0;
ordinalTable[oti].flags = flags;
ordinalTable[oti].type = type;
}
else
{
if(pass == 2){
char* segname = strdup(segName ? segName : "??");
char* sectname = strdup(sectName ? sectName : "??");
if (strlen(segname)>16)
segname[16] = 0;
if (strlen(sectname)>16)
sectname[16] = 0;
(*dbi)[n].segname = segname;
(*dbi)[n].sectname = sectname;
(*dbi)[n].address = segStartAddr + segOffset;
(*dbi)[n].bind_type = type;
(*dbi)[n].addend = addend;
(*dbi)[n].dylibname = fromDylib ? fromDylib : "??";
(*dbi)[n].symbolname = (symbolName ? symbolName :
"Symbol name not set");
(*dbi)[n].weak_import = *weak_import != '\0';
(*dbi)[n].pointer_value = 0;
}
n++;
segOffset += sizeof_pointer;
}
break;
case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
opName = "BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB";
error = checkSegAndOffset(segIndex, segOffset, segs, nsegs,
segs64, nsegs64, TRUE);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
sectName = NULL;
}
else {
sectName = sectionName(segIndex, segStartAddr+segOffset,
segs, nsegs, segs64, nsegs64);
}
if (NULL == symbolName) {
error = "missing preceding BIND_OPCODE_SET_SYMBOL_"
"TRAILING_FLAGS_IMM opcode";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
}
if (FALSE == libraryOrdinalSet){
error = "missing preceding BIND_OPCODE_SET_DYLIB_"
"ORDINAL_* opcode";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
}
if(2 == pass){
char* segname = strdup(segName ? segName : "??");
char* sectname = strdup(sectName ? sectName : "??");
if (strlen(segname)>16)
segname[16] = 0;
if (strlen(sectname)>16)
sectname[16] = 0;
(*dbi)[n].segname = segname;
(*dbi)[n].sectname = sectname;
(*dbi)[n].address = segStartAddr+segOffset;
(*dbi)[n].bind_type = type;
(*dbi)[n].addend = addend;
(*dbi)[n].dylibname = fromDylib ? fromDylib : "??";
(*dbi)[n].symbolname = (symbolName ? symbolName :
"Symbol name not set");
(*dbi)[n].weak_import = *weak_import != '\0';
(*dbi)[n].pointer_value = 0;
}
n++;
segOffset += read_uleb128(&p, end, &error) + sizeof_pointer;
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
sectName = NULL;
}
error = checkSegAndOffset(segIndex, segOffset, segs, nsegs,
segs64, nsegs64, FALSE);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
sectName = NULL;
}
break;
case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
opName = "BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED";
error = checkSegAndOffset(segIndex, segOffset, segs, nsegs,
segs64, nsegs64, TRUE);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
sectName = NULL;
}
else {
sectName = sectionName(segIndex, segStartAddr+segOffset,
segs, nsegs, segs64, nsegs64);
}
if (NULL == symbolName) {
error = "missing preceding BIND_OPCODE_SET_SYMBOL_"
"TRAILING_FLAGS_IMM opcode";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
}
if (FALSE == libraryOrdinalSet){
error = "missing preceding BIND_OPCODE_SET_DYLIB_"
"ORDINAL_* opcode";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
}
if(2 == pass){
char* segname = strdup(segName ? segName : "??");
char* sectname = strdup(sectName ? sectName : "??");
if (strlen(segname)>16)
segname[16] = 0;
if (strlen(sectname)>16)
sectname[16] = 0;
(*dbi)[n].segname = segname;
(*dbi)[n].sectname = sectname;
(*dbi)[n].address = segStartAddr+segOffset;
(*dbi)[n].bind_type = type;
(*dbi)[n].addend = addend;
(*dbi)[n].dylibname = fromDylib ? fromDylib : "??";
(*dbi)[n].symbolname = (symbolName ? symbolName :
"Symbol name not set");
(*dbi)[n].weak_import = *weak_import != '\0';
(*dbi)[n].pointer_value = 0;
}
n++;
segOffset += immediate * sizeof_pointer + sizeof_pointer;
error = checkSegAndOffset(segIndex, segOffset, segs, nsegs,
segs64, nsegs64, FALSE);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
sectName = NULL;
}
break;
case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
opName = "BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB";
count = (uint32_t)read_uleb128(&p, end, &error);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
count = 0;
}
skip = (uint32_t)read_uleb128(&p, end, &error);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
skip = 0;
}
error = checkSegAndOffset(segIndex, segOffset, segs, nsegs,
segs64, nsegs64, TRUE);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
sectName = NULL;
}
else {
sectName = sectionName(segIndex, segStartAddr + segOffset,
segs, nsegs, segs64, nsegs64);
}
if (NULL == symbolName) {
error = "missing preceding BIND_OPCODE_SET_SYMBOL_"
"TRAILING_FLAGS_IMM opcode";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
}
if (FALSE == libraryOrdinalSet){
error = "missing preceding BIND_OPCODE_SET_DYLIB_"
"ORDINAL_* opcode";
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
}
error = checkCountAndSkip(&count, skip, segIndex,
segOffset, segs, nsegs,
segs64, nsegs64);
if (error) {
printErrorBind(pass, print_errors, opName,
opcode_start-start, error, &errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
sectName = NULL;
}
for (uint32_t i=0; i < count; ++i) {
if(2 == pass){
char* segname = strdup(segName ? segName : "??");
char* sectname = strdup(sectName ? sectName : "??");
if (strlen(segname)>16)
segname[16] = 0;
if (strlen(sectname)>16)
sectname[16] = 0;
(*dbi)[n].segname = segname;
(*dbi)[n].sectname = sectname;
(*dbi)[n].address = segStartAddr+segOffset;
(*dbi)[n].bind_type = type;
(*dbi)[n].addend = addend;
(*dbi)[n].dylibname = fromDylib ? fromDylib : "??";
(*dbi)[n].symbolname = (symbolName ? symbolName :
"Symbol name not set");
(*dbi)[n].weak_import = *weak_import != '\0';
(*dbi)[n].pointer_value = 0;
}
n++;
if (-1 != segIndex)
segOffset += skip + sizeof_pointer;
}
break;
case BIND_OPCODE_THREADED:
switch(immediate){
case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB:
opName = "BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB";
ordinalTableCount = read_uleb128(&p, end, &error);
if (error) {
printErrorBind(pass, print_errors, opName, opcode_start - start, error, &errorCount);
ordinalTableCount = 0;
ordinalTableIndex = (uint64_t)-1;
}
else {
ordinalTableIndex = 0;
}
ordinalTable = reallocate(ordinalTable, sizeof(struct ThreadedBindData) * ordinalTableCount);
*chain_format = CHAIN_FORMAT_ARM64E;
break;
case BIND_SUBOPCODE_THREADED_APPLY:
opName = "BIND_SUBOPCODE_THREADED_APPLY";
if(ordinalTableIndex != ordinalTableCount){
error = ("incorrect ordinal table size: count of previous "
"BIND_OPCODE_DO_BIND opcodes don't match count in previous "
"BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB opcodes");
printErrorBind(pass, print_errors, opName, opcode_start - start, error, &errorCount);
}
error = checkSegAndOffset(segIndex, segOffset, segs, nsegs, segs64, nsegs64, FALSE);
if (error) {
printErrorBind(pass, print_errors, opName, opcode_start-start, error, &errorCount);
segIndex = -1;
segName = NULL;
segStartAddr = 0;
segOffset = 0;
sectName = NULL;
}
else {
sectName = sectionName(segIndex, segStartAddr + segOffset, segs, nsegs, segs64, nsegs64);
}
if(((segStartAddr + segOffset) & 0x3) != 0){
error = "bad segOffset, not 8-byte aligned";
printErrorBind(pass, print_errors, opName, opcode_start - start, error, &errorCount);
}
if (-1 != segIndex) {
offset = segs64[segIndex]->fileoff + segOffset;
pointerAddress = segs64[segIndex]->vmaddr + segOffset;
pointerPageStart = pointerAddress & ~0x3fff;
delta = 0;
do{
uint64_t value;
enum bool isRebase;
pointerLocation = object_addr + offset;
value = *(uint64_t *)pointerLocation;
if(swapped)
value = SWAP_LONG_LONG(value);
isRebase = (value & (1ULL << 62)) == 0;
if(isRebase){
;
} else {
ordinal = value & 0xFFFF;
if(ordinal >= ordinalTableCount){
printErrorOrdAddr(pass, print_errors, opName, opcode_start - start,
ordinal, pointerAddress, &errorCount);
break;
}
libraryOrdinal = ordinalTable[ordinal].libraryOrdinal;
if (!validateOrdinal(libraryOrdinal, dylibs, ndylibs, &fromDylib, &error)) {
printErrorOrdinal(pass, print_errors, opName, opcode_start - start, libraryOrdinal, ndylibs, error, &errorCount);
break;
}
flags = ordinalTable[ordinal].flags;
if((flags & BIND_SYMBOL_FLAGS_WEAK_IMPORT) != 0)
weak_import = " (weak import)";
else
weak_import = "";
if(pass == 2){
char* segname = strdup(segName ? segName : "??");
char* sectname = strdup(sectName ? sectName : "??");
if (strlen(segname)>16)
segname[16] = 0;
if (strlen(sectname)>16)
sectname[16] = 0;
(*dbi)[n].segname = segname;
(*dbi)[n].sectname = sectname;
(*dbi)[n].address = segStartAddr+segOffset;
(*dbi)[n].bind_type = ordinalTable[ordinal].type;
(*dbi)[n].addend = ordinalTable[ordinal].addend;
(*dbi)[n].dylibname = fromDylib;
(*dbi)[n].symbolname = ordinalTable[ordinal].symbolName;
(*dbi)[n].weak_import = *weak_import != '\0';
(*dbi)[n].pointer_value = value;
}
n++;
}
value &= ~(1ULL << 62);
delta = (value & 0x3FF8000000000000) >> 51;
if(delta == 0)
break;
segOffset += delta * 8;
offset = segs64[segIndex]->fileoff + segOffset;
pointerAddress = segs64[segIndex]->vmaddr +
segOffset;
if(offset + 8 > object_size){
printErrorOffset(pass, print_errors, opName, opcode_start - start,
pointerAddress, 0, &errorCount);
break;
}
if(pointerPageStart != (pointerAddress & ~0x3fff)){
printErrorOffset(pass, print_errors, opName, opcode_start - start,
pointerAddress, 1, &errorCount);
break;
}
} while(delta != 0);
}
break;
default:
printErrorOpcode(pass, print_errors, opName,
opcode_start - start, immediate,
&errorCount);
done = TRUE;
break;
}
break;
default:
printErrorOpcode(pass, print_errors, NULL,
opcode_start - start, opcode, &errorCount);
done = TRUE;
break;
}
}
}
if(ordinalTable != NULL)
free(ordinalTable);
}
extern
void
print_dyld_rebase_opcodes(
const uint8_t* object_addr,
uint64_t object_size,
uint32_t offset,
uint32_t size)
{
if (offset > object_size) {
printf("rebase offset %u beyond the size of file %llu\n",
offset, object_size);
return;
}
if (offset + size > object_size) {
printf("rebase offset + size %u beyond the size of file %llu\n",
offset + size, object_size);
size = (uint32_t)(offset - object_size);
}
const uint8_t* p = object_addr + offset;
const uint8_t* end = object_addr + offset + size;
enum bool done = FALSE;
uint32_t idx = 0;
printf("rebase opcodes:\n");
printf(" %4s %-10s %4s %s\n", "idx", "offset", "byte", "opcode");
while (!done)
{
uint32_t poffset = (uint32_t)(p - object_addr);
idx++;
if (poffset >= object_size) {
printf("rebase opcode #%u offset %u beyond the size of file %llu\n",
idx, poffset, object_size);
break;
}
if (p >= end) {
printf("%s opcode #%u offset %u beyond the size of opcodes %u\n",
"rebase", idx, poffset, size);
break;
}
uint8_t byte = *p;
uint8_t immediate = byte & REBASE_IMMEDIATE_MASK;
uint8_t opcode = byte & REBASE_OPCODE_MASK;
++p;
const char* name = NULL;
const char* error = NULL;
uint64_t uleb = 0;
switch (opcode) {
case REBASE_OPCODE_SET_TYPE_IMM:
if (!name)
name = "REBASE_OPCODE_SET_TYPE_IMM";
case REBASE_OPCODE_ADD_ADDR_IMM_SCALED:
if (!name)
name = "REBASE_OPCODE_ADD_ADDR_IMM_SCALED";
case REBASE_OPCODE_DO_REBASE_IMM_TIMES:
if (!name)
name = "REBASE_OPCODE_DO_REBASE_IMM_TIMES";
printf(" %4u 0x%08x 0x%02x %s(%d)\n",
idx, poffset, byte, name, immediate);
break;
case REBASE_OPCODE_ADD_ADDR_ULEB:
if (!name)
name = "REBASE_OPCODE_ADD_ADDR_ULEB";
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES:
if (!name)
name = "REBASE_OPCODE_DO_REBASE_ULEB_TIMES";
case REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB:
if (!name)
name = "REBASE_OPCODE_DO_REBASE_ADD_ADDR_ULEB";
uleb = read_uleb128(&p, end, &error);
if (error)
printf(" %4u 0x%08x 0x%02x %s(error: %s)\n",
idx, poffset, byte, name, error);
else
printf(" %4u 0x%08x 0x%02x %s(0x%llx)\n",
idx, poffset, byte, name, uleb);
break;
case REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
name = "REBASE_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB";
uleb = read_uleb128(&p, end, &error);
if (error)
printf(" %4u 0x%08x 0x%02x %s(%d, error: %s)\n",
idx, poffset, byte, name, immediate, error);
else
printf(" %4u 0x%08x 0x%02x %s(%d, 0x%llx)\n",
idx, poffset, byte, name, immediate, uleb);
break;
case REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB:
name = "REBASE_OPCODE_DO_REBASE_ULEB_TIMES_SKIPPING_ULEB";
printf(" %4u 0x%08x 0x%02x %s(", idx, poffset, byte, name);
uleb = read_uleb128(&p, end, &error);
if (error)
printf("error: %s, ", error);
else
printf("0x%llx, ", uleb);
uleb = read_uleb128(&p, end, &error);
if (error)
printf("error: %s)\n", error);
else
printf("0x%llx)\n", uleb);
break;
case REBASE_OPCODE_DONE:
name = "REBASE_OPCODE_DONE";
done = TRUE;
default:
if (!name)
name = "UNKNOWN OPCODE";
printf(" %4u 0x%08x 0x%02x %s\n", idx, poffset, byte, name);
}
}
}
extern
void
print_dyld_bind_opcodes(
const uint8_t* object_addr,
uint64_t object_size,
const char* type,
uint32_t offset,
uint32_t size)
{
if (offset > object_size) {
printf("%s offset %u beyond the size of file %llu\n",
type, offset, object_size);
return;
}
if (offset + size > object_size) {
printf("%s offset + size %u beyond the size of file %llu\n",
type, offset + size, object_size);
size = (uint32_t)(offset - object_size);
}
const uint8_t* p = object_addr + offset;
const uint8_t* end = object_addr + offset + size;
enum bool done = FALSE;
uint32_t idx = 0;
printf("%s opcodes:\n", type);
printf(" %4s %-10s %4s %s\n", "idx", "offset", "byte", "opcode");
while (!done)
{
uint32_t poffset = (uint32_t)(p - object_addr);
idx++;
if (poffset >= object_size) {
printf("%s opcode #%u offset %u beyond the size of file %llu\n",
type, idx, poffset, object_size);
break;
}
if (p >= end) {
printf("%s opcode #%u offset %u beyond the size of opcodes %u\n",
type, idx, poffset, size);
break;
}
uint8_t byte = *p;
uint8_t immediate = byte & REBASE_IMMEDIATE_MASK;
uint8_t opcode = byte & REBASE_OPCODE_MASK;
++p;
const char* name = NULL;
const char* error = NULL;
const char* symbol = NULL;
uint64_t uleb = 0;
int64_t sleb = 0;
switch (opcode) {
case BIND_OPCODE_SET_DYLIB_ORDINAL_IMM:
if (!name)
name = "BIND_OPCODE_SET_DYLIB_ORDINAL_IMM";
case BIND_OPCODE_SET_DYLIB_SPECIAL_IMM:
if (!name)
name = "BIND_OPCODE_SET_DYLIB_SPECIAL_IMM";
case BIND_OPCODE_SET_TYPE_IMM:
if (!name)
name = "BIND_OPCODE_SET_TYPE_IMM";
case BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED:
if (!name)
name = "BIND_OPCODE_DO_BIND_ADD_ADDR_IMM_SCALED";
printf(" %4u 0x%08x 0x%02x %s(%d)\n",
idx, poffset, byte, name, immediate);
break;
case BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM:
name = "BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM";
printf(" %4u 0x%08x 0x%02x %s(%d, ",
idx, poffset, byte, name, immediate);
symbol = (const char*)p;
while(p < end && *p != '\0')
++p;
if (p < end && *p == '\0') {
if (p == (uint8_t*)symbol)
printf("(empty string))\n");
else
printf("%s)\n", symbol);
++p;
}
else {
size_t len = strlen(symbol);
char* str = malloc(len + 1);
if (str) {
memcpy(str, symbol, len);
str[len] = '\0';
printf("%s (unterminated string)", str);
free(str);
}
printf(")\n");
}
break;
case BIND_OPCODE_SET_ADDEND_SLEB:
name = "BIND_OPCODE_SET_ADDEND_SLEB";
sleb = read_sleb128(&p, end, &error);
if (error)
printf(" %4u 0x%08x 0x%02x %s(error: %s)\n",
idx, poffset, byte, name, error);
else
printf(" %4u 0x%08x 0x%02x %s(%lld)\n",
idx, poffset, byte, name, sleb);
break;
case BIND_OPCODE_SET_DYLIB_ORDINAL_ULEB:
if (!name)
name = "REBASE_OPCODE_ADD_ADDR_ULEB";
case BIND_OPCODE_ADD_ADDR_ULEB:
if (!name)
name = "BIND_OPCODE_ADD_ADDR_ULEB";
case BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB:
if (!name)
name = "BIND_OPCODE_DO_BIND_ADD_ADDR_ULEB";
uleb = read_uleb128(&p, end, &error);
if (error)
printf(" %4u 0x%08x 0x%02x %s(error: %s)\n",
idx, poffset, byte, name, error);
else
printf(" %4u 0x%08x 0x%02x %s(0x%llx)\n",
idx, poffset, byte, name, uleb);
break;
case BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB:
name = "BIND_OPCODE_SET_SEGMENT_AND_OFFSET_ULEB";
uleb = read_uleb128(&p, end, &error);
if (error)
printf(" %4u 0x%08x 0x%02x %s(%d, error: %s)\n",
idx, poffset, byte, name, immediate, error);
else
printf(" %4u 0x%08x 0x%02x %s(%d, 0x%llx)\n",
idx, poffset, byte, name, immediate, uleb);
break;
case BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB:
name = "BIND_OPCODE_DO_BIND_ULEB_TIMES_SKIPPING_ULEB";
printf(" %4u 0x%08x 0x%02x %s(", idx, poffset, byte, name);
uleb = read_uleb128(&p, end, &error);
if (error)
printf("error: %s, ", error);
else
printf("0x%llx, ", uleb);
uleb = read_uleb128(&p, end, &error);
if (error)
printf("error: %s)\n", error);
else
printf("0x%llx)\n", uleb);
break;
case BIND_OPCODE_THREADED:
switch (immediate) {
case BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_TABLE_SIZE_ULEB:
name = ("BIND_OPCODE_THREADED "
"BIND_SUBOPCODE_THREADED_SET_BIND_ORDINAL_"
"TABLE_SIZE_ULEB");
uleb = read_uleb128(&p, end, &error);
if (error)
printf(" %4u 0x%08x 0x%02x %s(error: %s)\n",
idx, poffset, byte, name, error);
else
printf(" %4u 0x%08x 0x%02x %s(0x%llx)\n",
idx, poffset, byte, name, uleb);
break;
case BIND_SUBOPCODE_THREADED_APPLY:
name = ("BIND_OPCODE_THREADED "
"BIND_SUBOPCODE_THREADED_APPLY");
printf(" %4u 0x%08x 0x%02x %s\n",
idx, poffset, byte, name);
break;
}
break;
case BIND_OPCODE_DONE:
name = "BIND_OPCODE_DONE";
done = TRUE;
case BIND_OPCODE_DO_BIND:
if (!name)
name = "BIND_OPCODE_DO_BIND";
default:
if (!name)
name = "UNKNOWN OPCODE";
printf(" %4u 0x%08x 0x%02x %s\n", idx, poffset, byte, name);
}
}
}
void
print_dyld_bind_info(
struct dyld_bind_info *dbi,
uint64_t ndbi)
{
uint64_t n;
uint64_t value;
uint16_t diversity;
enum bool hasAddressDiversity;
uint8_t key;
enum bool isAuthenticated;
static const char *keyNames[] = { "IA", "IB", "DA", "DB" };
const char* bind_name;
const char* symbolName;
const char* dylibName;
char valuestr[20];
if (0 == ndbi)
return;
enum bool is32 = (DYLD_CHAINED_PTR_32 == dbi[0].pointer_format ||
DYLD_CHAINED_PTR_32_CACHE == dbi[0].pointer_format ||
DYLD_CHAINED_PTR_32_FIRMWARE == dbi[0].pointer_format);
size_t maxsegname = strlen("segment");
size_t maxsectname = strlen("section");
size_t maxdylibname = strlen("dylib");
size_t maxaddr = 0;
size_t maxvalue = is32 ? 10 : 18;
for (n = 0; n < ndbi; n++) {
const char* name = dbi[n].segname ? dbi[n].segname : "";
size_t len = strlen(name);
if (len > maxsegname)
maxsegname = len;
name = dbi[n].sectname ? dbi[n].sectname : "";
len = strlen(name);
if (len > maxsectname)
maxsectname = len;
name = dbi[n].dylibname ? dbi[n].dylibname : "";
len = strlen(name);
if (len > maxdylibname)
maxdylibname = len;
len = snprintf(NULL, 0, "%llX", dbi[n].address);
if (len > maxaddr)
maxaddr = len;
}
printf("bind information:\n");
printf("%*s %*s %*s %*s %-10s %-8s %*s %s\n",
-(int)maxsegname, "segment",
-(int)maxsectname, "section",
-(int)(maxaddr+2), "address",
-(int)maxvalue, "value",
"type",
"addend",
-(int)maxdylibname, "dylib",
"symbol");
for(n = 0; n < ndbi; n++){
bind_name = (dbi[n].bind_name ? dbi[n].bind_name :
bindTypeName(dbi[n].bind_type));
dylibName = dbi[n].dylibname ? dbi[n].dylibname : "";
symbolName = dbi[n].symbolname ? dbi[n].symbolname : "";
if (dbi[n].pointer_value) {
if (is32) {
snprintf(valuestr, sizeof(valuestr), "0x%08llX",
dbi[n].pointer_value);
} else {
snprintf(valuestr, sizeof(valuestr), "0x%016llX",
dbi[n].pointer_value);
}
}
else {
snprintf(valuestr, sizeof(valuestr), "");
}
printf("%*s %*s 0x%*llX %*s %-10s 0x%-6llX %*s %s%s",
-(int)maxsegname, dbi[n].segname,
-(int)maxsectname, dbi[n].sectname,
-(int)maxaddr, dbi[n].address,
-(int)maxvalue, valuestr,
bind_name,
dbi[n].addend,
-(int)maxdylibname, dylibName,
symbolName,
dbi[n].weak_import ? " (weak import)" : "");
value = dbi[n].pointer_value;
diversity = (uint16_t)(value >> 32);
hasAddressDiversity = (value & (1ULL << 48)) != 0;
key = (value >> 49) & 0x3;
isAuthenticated = (value & (1ULL << 63)) != 0;
if(isAuthenticated){
printf(" (JOP: diversity %d, address %s, %s)", diversity,
hasAddressDiversity ? "true" : "false", keyNames[key]);
}
printf("\n");
}
}
const char *
get_dyld_bind_info_symbolname(
uint64_t address,
struct dyld_bind_info *dbi,
uint64_t ndbi,
enum chain_format_t chain_format,
int64_t *addend)
{
uint64_t n;
for(n = 0; n < ndbi; n++){
if(dbi[n].address == address){
if(chain_format && addend != NULL)
*addend = dbi[n].addend;
return(dbi[n].symbolname);
}
}
return(NULL);
}
static inline uint16_t swap16(uint16_t x, enum bool swapped)
{
return swapped == TRUE ? OSSwapInt16(x): x;
}
static inline uint32_t swap32(uint32_t x, enum bool swapped)
{
return swapped == TRUE ? OSSwapInt32(x): x;
}
static inline uint64_t swap64(uint64_t x, enum bool swapped)
{
return swapped == TRUE ? OSSwapInt64(x): x;
}
static uint64_t
get_base_addr(struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64)
{
for (uint32_t i = 0; i < nsegs64; ++i) {
if (0 == strcmp("__TEXT", segs64[i]->segname))
return segs64[i]->vmaddr;
}
for (uint32_t i = 0; i < nsegs; ++i) {
if (0 == strcmp("__TEXT", segs[i]->segname))
return segs[i]->vmaddr;
}
return ((uint64_t)-1);
}
static uint32_t
segmentIndexFromAddr(uint64_t addr,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64)
{
for (uint32_t i = 0; i < nsegs64; ++i) {
if (segs64[i]->vmaddr <= addr &&
segs64[i]->vmaddr + segs64[i]->vmsize > addr)
return i;
}
for (uint32_t i = 0; i < nsegs; ++i) {
if (segs[i]->vmaddr <= addr &&
segs[i]->vmaddr + segs[i]->vmsize > addr)
return i;
}
return ((uint32_t)-1);
}
struct fixup_target
{
uint64_t value;
const char* dylib;
const char* symbolName;
uint64_t addend;
enum bool weakImport;
};
static uint64_t signExtendedAddend64(uint64_t addend27)
{
uint64_t top8Bits = addend27 & 0x00007F80000ULL;
uint64_t bottom19Bits = addend27 & 0x0000007FFFFULL;
uint64_t newValue = (top8Bits << 13) |
(((uint64_t)(bottom19Bits << 37) >> 37) &
0x00FFFFFFFFFFFFFF);
return newValue;
}
static uint64_t signExtendedAddend64e(uint64_t addend19)
{
if ( addend19 & 0x40000 )
return addend19 | 0xFFFFFFFFFFFC0000ULL;
else
return addend19;
}
static int libOrdinalFromUInt8(uint8_t value)
{
if (((uint8_t)-1) == value)
return -1;
else if (((uint8_t)-2) == value)
return -2;
else if (((uint8_t)-3) == value)
return -3;
return value;
}
static int libOrdinalFromUInt16(uint16_t value)
{
if (((uint16_t)-1) == value)
return -1;
else if (((uint16_t)-2) == value)
return -2;
else if (((uint16_t)-3) == value)
return -3;
return value;
}
static void get_fixup_targets(
uint32_t datasize,
struct dyld_chained_fixups_header* header,
enum bool swapped,
const char **dylibs,
uint32_t ndylibs,
struct fixup_target **targets,
uint32_t *ntarget
)
{
uint32_t imports_count = swap32(header->imports_count, swapped);
uint32_t imports_format = swap32(header->imports_format, swapped);
uint32_t symbols_offset = swap32(header->symbols_offset, swapped);
uint32_t max_symbols_offset = datasize - symbols_offset;
const char* symbolsPool = (char*)header + symbols_offset;
const char* dylibName;
*ntarget = imports_count;
*targets = reallocf(*targets, sizeof(struct fixup_target) * imports_count);
memset(*targets, 0, sizeof(struct fixup_target) * imports_count);
if (imports_format < 1 || imports_format > 3) {
printf("unknown chained-fixups import format: %d\n", imports_format);
return;
}
for (uint32_t i=0; i < imports_count; ++i) {
int lib_ordinal = 0;
if (imports_format == DYLD_CHAINED_IMPORT) {
uint32_t values[1];
memcpy(values, ((uint8_t*)header + header->imports_offset +
i * sizeof(struct dyld_chained_import)),
sizeof(values));
values[0] = swap32(values[0], swapped);
struct dyld_chained_import imports;
memcpy(&imports, &values, sizeof(imports));
if (imports.name_offset > max_symbols_offset) {
printf("chained-fixups import #%u symbol offset extends "
"past end: %u (max %u)\n", i, imports.name_offset,
max_symbols_offset);
imports.name_offset = 0;
}
(*targets)[i].symbolName = &symbolsPool[imports.name_offset];
(*targets)[i].weakImport = imports.weak_import;
lib_ordinal = libOrdinalFromUInt8(imports.lib_ordinal);
}
else if (imports_format == DYLD_CHAINED_IMPORT_ADDEND) {
uint32_t values[2];
memcpy(values, ((uint8_t*)header + header->imports_offset +
i * sizeof(struct dyld_chained_import_addend)),
sizeof(values));
values[0] = swap32(values[0], swapped);
values[1] = swap32(values[1], swapped);
struct dyld_chained_import_addend imports;
memcpy(&imports, &values, sizeof(imports));
if (imports.name_offset > max_symbols_offset) {
printf("chained-fixups import #%u symbol offset extends "
"past end: %u (max %u)\n", i, imports.name_offset,
max_symbols_offset);
imports.name_offset = 0;
}
(*targets)[i].symbolName = &symbolsPool[imports.name_offset];
(*targets)[i].weakImport = imports.weak_import;
(*targets)[i].addend = imports.addend;
lib_ordinal = libOrdinalFromUInt8(imports.lib_ordinal);
}
else if (imports_format == DYLD_CHAINED_IMPORT_ADDEND64) {
uint64_t values[2];
memcpy(values, ((uint8_t*)header + header->imports_offset +
i * sizeof(struct dyld_chained_import_addend64)),
sizeof(values));
values[0] = swap64(values[0], swapped);
values[1] = swap64(values[1], swapped);
struct dyld_chained_import_addend64 imports;
memcpy(&imports, &values, sizeof(imports));
if (imports.name_offset > max_symbols_offset) {
printf("chained-fixups import #%u symbol offset extends "
"past end: %u (max %u)\n", i, imports.name_offset,
max_symbols_offset);
imports.name_offset = 0;
}
(*targets)[i].symbolName = &symbolsPool[imports.name_offset];
(*targets)[i].weakImport = imports.weak_import;
(*targets)[i].addend = imports.addend;
lib_ordinal = libOrdinalFromUInt16(imports.lib_ordinal);
}
if (!validateOrdinal(lib_ordinal, dylibs, ndylibs, &dylibName, NULL)) {
if (lib_ordinal < 0)
printf("chained-fixups import #%u bad special library ordinal: "
"%d\n", i, lib_ordinal);
else
printf("chained-fixups import #%u bad library ordinal: %d "
"(max %u)\n", i, lib_ordinal, ndylibs);
continue;
}
(*targets)[i].dylib = dylibName;
}
}
static void walk_chain(
char *object_addr,
uint64_t object_size,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64,
struct fixup_target *targets,
uint32_t ntarget,
enum bool swapped,
struct dyld_chained_starts_in_segment* segInfo,
uint16_t pageIndex,
uint16_t offsetInPage,
struct dyld_bind_info **dbi,
uint64_t *ndbi)
{
uint64_t segment_offset = swap64(segInfo->segment_offset, swapped);
uint16_t page_size = swap16(segInfo->page_size, swapped);
uint16_t pointer_format = swap16(segInfo->pointer_format, swapped);
uint64_t vmaddr = segment_offset + pageIndex * page_size;
uint64_t baseaddr = get_base_addr(segs, nsegs, segs64, nsegs64);
uint8_t* pageContentStart = (uint8_t*)(object_addr + vmaddr);
void* chain = (pageContentStart+offsetInPage);
uint64_t chain_addr = baseaddr + vmaddr + offsetInPage;
enum bool done = FALSE;
while (!done) {
uint64_t dbii = *ndbi;
*ndbi += 1;
*dbi = reallocf(*dbi, sizeof(**dbi) * (*ndbi));
memset(&(*dbi)[dbii], 0, sizeof(**dbi));
uint64_t addend = 0;
uint32_t segIndex = segmentIndexFromAddr(chain_addr, segs, nsegs,
segs64, nsegs64);
const char* symbolName = NULL;
const char* dylibName = NULL;
const char* bindName = NULL;
enum bool weakImport = FALSE;
switch (pointer_format) {
case DYLD_CHAINED_PTR_ARM64E:
{
uint64_t chain_value = swap64(*(uint64_t*)chain, swapped);
uint64_t address = chain_addr;
uint64_t pointer_value = chain_value;
struct dyld_chained_ptr_arm64e_auth_rebase auth_rebase;
memcpy(&auth_rebase, &chain_value, sizeof(auth_rebase));
if (auth_rebase.auth) {
struct dyld_chained_ptr_arm64e_auth_bind auth_bind;
memcpy(&auth_bind, &chain_value, sizeof(auth_bind));
if (auth_bind.bind) {
struct fixup_target* target =
&targets[auth_bind.ordinal];
if (target->addend) {
addend = target->addend;
}
symbolName = target->symbolName;
dylibName = target->dylib;
weakImport = target->weakImport;
bindName = "bind aptr";
}
else {
uint64_t targetOffset = auth_rebase.target;
address = targetOffset + baseaddr;
bindName = "rebase aptr";
pointer_value = targetOffset + baseaddr;
}
}
else {
struct dyld_chained_ptr_arm64e_bind bind;
memcpy(&bind, &chain_value, sizeof(bind));
if (bind.bind) {
struct fixup_target* target = &targets[bind.ordinal];
addend = target->addend +
signExtendedAddend64e(bind.addend);
symbolName = target->symbolName;
dylibName = target->dylib;
weakImport = target->weakImport;
bindName = "bind ptr";
pointer_value = 0;
}
else {
struct dyld_chained_ptr_arm64e_rebase rebase;
memcpy(&rebase, &chain_value, sizeof(rebase));
bindName = "rebase ptr";
pointer_value = ((uint64_t)(rebase.high8) << 56) |
rebase.target;
}
}
const char* csegname = segmentName(segIndex, segs, nsegs,
segs64, nsegs64);
const char* csectname = sectionName(segIndex, chain_addr, segs,
nsegs, segs64, nsegs64);
char* segname = strdup(csegname);
char* sectname = strdup(csectname);
if (strlen(segname)>16)
segname[16] = 0;
if (strlen(sectname)>16)
sectname[16] = 0;
(*dbi)[dbii].segname = segname;
(*dbi)[dbii].sectname = sectname;
(*dbi)[dbii].address = address;
(*dbi)[dbii].bind_name = bindName;
(*dbi)[dbii].addend = addend;
(*dbi)[dbii].dylibname = dylibName;
(*dbi)[dbii].symbolname = symbolName;
(*dbi)[dbii].weak_import = weakImport;
(*dbi)[dbii].pointer_value = pointer_value;
if (auth_rebase.next == 0) {
done = TRUE;
}
else {
chain += auth_rebase.next * 8;
chain_addr += auth_rebase.next * 8;
}
}
break;
case DYLD_CHAINED_PTR_64:
{
uint64_t chain_value = swap64(*(uint64_t*)chain, swapped);
uint64_t pointer_value = chain_value;
struct dyld_chained_ptr_64_bind bind;
memcpy(&bind, &chain_value, sizeof(bind));
if (bind.bind) {
struct fixup_target* target = &targets[bind.ordinal];
addend = target->addend +
signExtendedAddend64(bind.addend);
symbolName = target->symbolName;
dylibName = target->dylib;
weakImport = target->weakImport;
bindName = "bind ptr";
pointer_value = 0;
}
else {
struct dyld_chained_ptr_64_rebase rebase;
memcpy(&rebase, &chain_value, sizeof(rebase));
bindName = "rebase ptr";
pointer_value = ((uint64_t)(rebase.high8) << 56) |
rebase.target;
}
const char* csegname = segmentName(segIndex, segs, nsegs,
segs64, nsegs64);
const char* csectname = sectionName(segIndex, chain_addr, segs,
nsegs, segs64, nsegs64);
char* segname = strdup(csegname);
char* sectname = strdup(csectname);
if (strlen(segname)>16)
segname[16] = 0;
if (strlen(sectname)>16)
sectname[16] = 0;
(*dbi)[dbii].segname = segname;
(*dbi)[dbii].sectname = sectname;
(*dbi)[dbii].address = chain_addr;
(*dbi)[dbii].bind_name = bindName;
(*dbi)[dbii].addend = addend;
(*dbi)[dbii].dylibname = dylibName;
(*dbi)[dbii].symbolname = symbolName;
(*dbi)[dbii].weak_import = weakImport;
(*dbi)[dbii].pointer_value = pointer_value;
if (bind.next == 0) {
done = TRUE;
}
else {
chain += bind.next * 4;
chain_addr += bind.next * 4;
}
}
break;
case DYLD_CHAINED_PTR_32:
{
uint32_t chain_value = swap32(*(uint32_t*)chain, swapped);
uint32_t pointer_value = chain_value;
struct dyld_chained_ptr_32_bind bind;
memcpy(&bind, &chain_value, sizeof(bind));
if (bind.bind) {
struct fixup_target* target = &targets[bind.ordinal];
addend = target->addend + bind.addend;
symbolName = target->symbolName;
dylibName = target->dylib;
weakImport = target->weakImport;
bindName = "bind ptr";
pointer_value = 0;
}
else {
struct dyld_chained_ptr_32_rebase rebase;
memcpy(&rebase, &chain_value, sizeof(rebase));
bindName = "rebase ptr";
pointer_value = rebase.target;
}
const char* csegname = segmentName(segIndex, segs, nsegs,
segs64, nsegs64);
const char* csectname = sectionName(segIndex, chain_addr, segs,
nsegs, segs64, nsegs64);
char* segname = strdup(csegname);
char* sectname = strdup(csectname);
if (strlen(segname)>16)
segname[16] = 0;
if (strlen(sectname)>16)
sectname[16] = 0;
(*dbi)[dbii].segname = segname;
(*dbi)[dbii].sectname = sectname;
(*dbi)[dbii].address = chain_addr;
(*dbi)[dbii].bind_name = bindName;
(*dbi)[dbii].addend = addend;
(*dbi)[dbii].dylibname = dylibName;
(*dbi)[dbii].symbolname = symbolName;
(*dbi)[dbii].weak_import = weakImport;
(*dbi)[dbii].pointer_value = pointer_value;
(*dbi)[dbii].pointer_format = pointer_format;
if (bind.next == 0) {
done = TRUE;
}
else {
struct dyld_chained_ptr_32_rebase rebase;
memcpy(&rebase, &chain_value, sizeof(rebase));
chain += rebase.next * 4;
chain_addr += rebase.next * 4;
chain_value = swap32(*(uint32_t*)chain, swapped);
memcpy(&rebase, &chain_value, sizeof(rebase));
while ((rebase.bind == 0) &&
(rebase.target > segInfo->max_valid_pointer))
{
chain += rebase.next * 4;
chain_addr += rebase.next * 4;
chain_value = swap32(*(uint32_t*)chain, swapped);
memcpy(&rebase, &chain_value, sizeof(rebase));
}
}
}
break;
case DYLD_CHAINED_PTR_32_CACHE:
case DYLD_CHAINED_PTR_32_FIRMWARE:
done = TRUE;
break;
}
}
}
void
get_dyld_chained_fixups(
const uint8_t* start,
const uint8_t* end,
const char **dylibs,
uint32_t ndylibs,
struct segment_command **segs,
uint32_t nsegs,
struct segment_command_64 **segs64,
uint32_t nsegs64,
enum bool swapped,
char *object_addr,
uint64_t object_size,
struct dyld_bind_info **dbi,
uint64_t *ndbi,
enum chain_format_t *chain_format,
enum bool print_errors)
{
uint64_t end_offset = end - start;
struct fixup_target* targets = NULL;
uint32_t ntarget = 0;
struct dyld_chained_fixups_header* header;
struct dyld_chained_starts_in_image* imageStarts;
*chain_format = CHAIN_FORMAT_NONE;
if (sizeof(struct dyld_chained_fixups_header) + start > end) {
printf("bad chained fixups: header size %lu extends past end %llu\n",
sizeof(struct dyld_chained_fixups_header), end_offset);
return;
}
header = (struct dyld_chained_fixups_header*)start;
get_fixup_targets((uint32_t)(end - start), header, swapped, dylibs, ndylibs,
&targets, &ntarget);
uint32_t starts_offset = swap32(header->starts_offset, swapped);
if (starts_offset < sizeof(struct dyld_chained_fixups_header)) {
printf("bad chained fixups: image starts offset %u overlaps with "
"fixups header size %lu\n", starts_offset,
sizeof(struct dyld_chained_fixups_header));
return;
}
if (starts_offset +
sizeof(struct dyld_chained_starts_in_image) > end_offset) {
printf("bad chained fixups: image starts end %lu extends past "
"end %llu\n", starts_offset +
sizeof(struct dyld_chained_starts_in_image), end_offset);
return;
}
imageStarts = (struct dyld_chained_starts_in_image*)(start + starts_offset);
uint32_t nseg = swap32(imageStarts->seg_count, swapped);
for (uint32_t iseg = 0; iseg < nseg; ++iseg)
{
struct dyld_chained_starts_in_segment* segInfo;
uint32_t seg_info_offset = swap32(imageStarts->seg_info_offset[iseg],
swapped);
if (0 == seg_info_offset)
continue;
if (starts_offset + seg_info_offset > end_offset) {
printf("bad chained fixups: seg_info %i offset %u starts past "
"end %llu\n", iseg, starts_offset + seg_info_offset,
end_offset);
continue;
}
if (starts_offset + seg_info_offset +
sizeof(struct dyld_chained_starts_in_segment) > end_offset) {
printf("bad chained fixups: seg_info %i end %lu extends past "
"end %llu\n", iseg, starts_offset + seg_info_offset +
sizeof(struct dyld_chained_starts_in_segment), end_offset);
}
segInfo = ((struct dyld_chained_starts_in_segment*)
(start + starts_offset + seg_info_offset));
uint16_t page_count = swap16(segInfo->page_count, swapped);
for (uint16_t pageIndex = 0; pageIndex < page_count; ++pageIndex)
{
uint16_t pointer_format = swap16(segInfo->pointer_format, swapped);
if (CHAIN_FORMAT_NONE == *chain_format) {
*chain_format = (enum chain_format_t)pointer_format;
}
else if (pointer_format != *chain_format) {
printf("bad chained fixups: seg_info %i pointer format %d "
"does not match previous pointer format %d\n", iseg,
pointer_format, *chain_format);
}
uint16_t offsetInPage = swap16(segInfo->page_start[pageIndex],
swapped);
if ( offsetInPage == DYLD_CHAINED_PTR_START_NONE )
continue;
if ( offsetInPage & DYLD_CHAINED_PTR_START_MULTI ) {
uint32_t overflowIndex =
offsetInPage & ~DYLD_CHAINED_PTR_START_MULTI;
enum bool chainEnd = FALSE;
while (!chainEnd) {
uint16_t page_start =
swap16(segInfo->page_start[overflowIndex], swapped);
chainEnd = (page_start & DYLD_CHAINED_PTR_START_LAST);
offsetInPage = (page_start & ~DYLD_CHAINED_PTR_START_LAST);
walk_chain(object_addr, object_size, segs, nsegs,
segs64, nsegs64, targets, ntarget, swapped,
segInfo, pageIndex, offsetInPage, dbi, ndbi);
++overflowIndex;
}
}
else {
walk_chain(object_addr, object_size, segs, nsegs,
segs64, nsegs64, targets, ntarget, swapped,
segInfo, pageIndex, offsetInPage, dbi, ndbi);
}
}
}
free(targets);
}
uint64_t get_chained_rebase_value(
uint64_t chain_value,
enum chain_format_t chain_format,
enum bool *has_auth)
{
if (has_auth)
*has_auth = FALSE;
switch (chain_format) {
case CHAIN_FORMAT_NONE:
break;
case CHAIN_FORMAT_ARM64E:
{
struct dyld_chained_ptr_arm64e_auth_rebase* auth_rebase =
(struct dyld_chained_ptr_arm64e_auth_rebase*)&chain_value;
if (auth_rebase->auth) {
if (!auth_rebase->bind) {
if (has_auth)
*has_auth = TRUE;
return auth_rebase->target;
}
}
else {
struct dyld_chained_ptr_arm64e_rebase* rebase =
(struct dyld_chained_ptr_arm64e_rebase*)&chain_value;
if (!rebase->bind) {
return rebase->target;
}
}
}
break;
case CHAIN_FORMAT_PTR_64:
{
struct dyld_chained_ptr_64_rebase* rebase =
(struct dyld_chained_ptr_64_rebase*)&chain_value;
if (!rebase->bind) {
return rebase->target;
}
}
break;
case CHAIN_FORMAT_PTR_32:
{
struct dyld_chained_ptr_32_rebase* rebase =
(struct dyld_chained_ptr_32_rebase*)&chain_value;
if (!rebase->bind) {
return rebase->target;
}
}
break;
case CHAIN_FORMAT_PTR_32_CACHE:
{
struct dyld_chained_ptr_32_cache_rebase* rebase =
(struct dyld_chained_ptr_32_cache_rebase*)&chain_value;
return rebase->target;
}
break;
case CHAIN_FORMAT_PTR_32_FIRMWARE:
{
struct dyld_chained_ptr_32_firmware_rebase* rebase =
(struct dyld_chained_ptr_32_firmware_rebase*)&chain_value;
return rebase->target;
}
break;
}
return chain_value;
}