parse LC_DYLD_FIXUPS_CHAINS

This commit is contained in:
nganhkhoa 2023-06-05 15:55:02 +07:00
parent b73650258b
commit 7592cfd2dd
4 changed files with 504 additions and 23 deletions

View File

@ -1,10 +1,12 @@
package macho
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"unsafe"
// "bufio"
"io"
log "github.com/sirupsen/logrus"
@ -12,6 +14,9 @@ import (
. "ios-wrapper/pkg/leb128"
)
//#include "fixups.h"
import "C"
type ImportSymbol struct {
name string
typ string
@ -58,6 +63,15 @@ func (mc *MachoContext) CollectBindSymbols() []*ImportSymbol {
}
}
func (mc *MachoContext) findSegmentIndexAt(address uint64) int {
for i, segment := range mc.Segments() {
if segment.Fileoff() <= address && segment.Fileoff() + segment.Filesize() > address {
return i
}
}
return -1
}
// New convention using LC_DYLD_CHAINED_FIXUPS
func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
var buf []byte
@ -70,30 +84,83 @@ func (mc *MachoContext) CollectBindSymbolsModern() []*ImportSymbol {
break
}
r := bytes.NewReader(buf)
rr := bufio.NewReader(r)
// all pointers used are based from this **buf**
// until buf is freed, all pointers are valid
// remember to copy before moving out
header := (*C.uchar)(unsafe.Pointer(&buf[0]))
imports_table := C.GetImportsTable(header);
var fixups_version int32
var starts_offset int32
var imports_offset int32
var symbols_offset int32
var imports_count int32
var imports_format int32
var symbols_format int32
binary.Read(rr, mc.byteorder, &fixups_version)
binary.Read(rr, mc.byteorder, &starts_offset)
binary.Read(rr, mc.byteorder, &imports_offset)
binary.Read(rr, mc.byteorder, &symbols_offset)
binary.Read(rr, mc.byteorder, &imports_count)
binary.Read(rr, mc.byteorder, &imports_format)
binary.Read(rr, mc.byteorder, &symbols_format)
fmt.Printf(
"HMMGE %x %x\n",
imports_offset,
imports_count,
)
// for i := 0; i < int(imports_table.size); i++ {
// s := C.GetImportsAt(&imports_table, C.int(i))
// name := C.GoString(s.name)
// symbol.dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:])
// symbol_table = append(symbol_table, symbol)
// fmt.Printf("id=%d lib=%s name=%s\n", i, dylib, name)
// }
return []*ImportSymbol{}
var syms []*ImportSymbol
var sym ImportSymbol
segment_i := 0
for {
var fix C.struct_SegmentFix
fix_ptr := (*C.struct_SegmentFix)(unsafe.Pointer(&fix))
status := int(C.GetSegmentFixAt(header, C.uint(segment_i), fix_ptr))
segment_i += 1
if status == 2 {
break;
}
if status == 3 {
continue
}
// fmt.Printf("segment=%x format=%x page_count=%d\n", fix.segment, fix.format, fix.page_count)
// fmt.Printf("pages=%x\n", fix.pages)
pages := ([]C.ushort)(unsafe.Slice(fix.pages, fix.page_count))
for page_i := 0; page_i < int(fix.page_count); page_i++ {
// fmt.Printf(" page offset=%x\n", pages[page_i])
address := int64(fix.segment) + int64(pages[page_i])
mc.file.Seek(address, io.SeekStart)
code := make([]byte, 8)
for {
mc.file.Read(code)
v := binary.LittleEndian.Uint64(code)
var bind C.int
var ret1 C.ulonglong
var ret2 C.ulonglong
next := C.ParseFixValue(C.int(fix.format), C.ulonglong(v),
&bind, &ret1, &ret2)
if bind == 1 {
s := C.GetImportsAt(&imports_table, C.int(ret1))
name := C.GoString(s.name)
dylib := string(mc.dylibs[int(s.lib_ordinal)-1].name[:])
// fmt.Printf("%x bind=%d (%s)%s\n", address, bind, dylib, name)
sym.address = uint64(address)
sym.name = name
sym.dylib = dylib
sym.typ = "lazy"
sym.segment = uint32(mc.findSegmentIndexAt(uint64(address)))
sym.file_address = uint64(address)
new_sym := sym
syms = append(syms, &new_sym)
}
address += 8
if int(next) == 0 {
break
}
}
mc.file.Seek(0, io.SeekStart)
}
}
return syms
}
// Old convention using LC_DYLD_INFO_ONLY section and bytecode runner

View File

@ -0,0 +1,125 @@
#include <stdio.h>
#include <stdint.h>
#include "fixups.h"
#include "fixups_type.h"
struct ImportTable GetImportsTable(uint8_t* header_ptr) {
struct dyld_chained_fixups_header* header = (struct dyld_chained_fixups_header*)header_ptr;
char* symbols_table = (char*)(header_ptr + header->symbols_offset);
struct dyld_chained_import* imports_table = (struct dyld_chained_import*)(header_ptr + header->imports_offset);
struct ImportTable table = {
(uint64_t)symbols_table,
(uint64_t)imports_table,
header->imports_count,
};
return table;
}
struct ImportSymbol GetImportsAt(struct ImportTable* table, int i) {
struct dyld_chained_import* imports_table = (struct dyld_chained_import*)table->imports_table;
struct dyld_chained_import import = imports_table[i];
char* name = (char*)table->symbols_table + import.name_offset;
struct ImportSymbol symbol = {
import.lib_ordinal,
name,
};
// printf("imports[%d]: (%d)%s\n", i, import.lib_ordinal, name);
return symbol;
}
int GetSegmentFixAt(uint8_t* buffer, uint32_t i, struct SegmentFix* fix) {
struct dyld_chained_fixups_header* header = (struct dyld_chained_fixups_header*)buffer;
struct dyld_chained_starts_in_image* segment = (struct dyld_chained_starts_in_image*)(buffer + header->starts_offset);
if (fix == 0) {
return 1;
}
if (i >= segment->seg_count) {
return 2;
}
// printf("segment %d\n", i);
uint32_t offset = segment->seg_info_offset[i];
if (offset == 0) {
return 3;
}
struct dyld_chained_starts_in_segment* chain_header = (struct dyld_chained_starts_in_segment*)((char*)segment + offset);
// printf("segment_offset=0x%llx\n", chain_header->segment_offset);
// printf("page count=0x%x\n", chain_header->page_count);
fix->segment = (uint64_t)(chain_header->segment_offset);
fix->format = chain_header->pointer_format;
fix->page_count = chain_header->page_count;
fix->pages = chain_header->page_start;
return 0;
}
inline int isBind(uint64_t value) {
struct dyld_chained_ptr_64_bind* b = (struct dyld_chained_ptr_64_bind*)&value;
return b->bind;
}
int ParseFixValue(int format, uint64_t value, int* bind, uint64_t* ret1, uint64_t* ret2) {
switch (format) {
case DYLD_CHAINED_PTR_64:
case DYLD_CHAINED_PTR_64_OFFSET: {
if (isBind(value)) {
struct dyld_chained_ptr_64_bind* b = (struct dyld_chained_ptr_64_bind*)&value;
// printf("is bind\n");
// printf(" ordinal=%d", b->ordinal);
// printf(" addend=0x%x", b->addend);
// printf(" next=0x%x\n", b->next);
*ret1 = b->ordinal;
*ret2 = b->addend;
*bind = 1;
return b->next;
} else {
struct dyld_chained_ptr_64_rebase* b = (struct dyld_chained_ptr_64_rebase*)&value;
// printf("is rebase\n");
// printf(" target=0x%llx", b->target);
// printf(" high8=0x%x", b->high8);
// printf(" next=0x%x\n", b->next);
*ret1 = b->target;
*ret2 = b->high8;
*bind = 0;
return b->next;
}
break;
}
default:
return 0;
}
}
void ParseFixUps(uint8_t* buffer) {
struct dyld_chained_fixups_header* header = (struct dyld_chained_fixups_header*)buffer;
printf("starts=0x%x\n", header->starts_offset);
printf("imports start=0x%x\n", header->imports_offset);
printf("symbols start=0x%x\n", header->symbols_offset);
printf("imports count=0x%x\n", header->imports_count);
printf("imports format=0x%x\n", header->imports_format);
printf("symbols format=0x%x\n", header->symbols_format);
char* symbols_table = (char*)(buffer + header->symbols_offset);
struct dyld_chained_import* imports_table = (struct dyld_chained_import*)(buffer + header->imports_offset);
for (int i = 0; i < header->imports_count; i++) {
struct dyld_chained_import import = imports_table[i];
char* name = symbols_table + import.name_offset;
printf("(%d)%s\n", import.lib_ordinal, name);
}
struct dyld_chained_starts_in_image* segment = (struct dyld_chained_starts_in_image*)(buffer + header->starts_offset);
for (int i = 0; i < segment->seg_count; i++) {
printf("segment %d\n", i);
uint32_t offset = segment->seg_info_offset[i];
if (offset == 0) continue;
struct dyld_chained_starts_in_segment* chain_header = (struct dyld_chained_starts_in_segment*)((char*)segment + offset);
printf("segment_offset=0x%llx\n", chain_header->segment_offset);
printf("page count=0x%x\n", chain_header->page_count);
}
}

View File

@ -0,0 +1,33 @@
#ifndef _FIXUPS_
#define _FIXUPS_
#include <stdint.h>
// char* symbols_table
// struct dyld_chained_import* imports_table
//
// some conflicting with cgo if using void*
// so use uint64_t as abstraction
struct ImportTable {
uint64_t symbols_table;
uint64_t imports_table;
uint32_t size;
};
struct ImportSymbol {
int lib_ordinal;
char* name;
};
struct SegmentFix {
uint64_t segment;
uint32_t format;
uint32_t page_count;
uint16_t* pages;
};
void ParseFixUps(uint8_t* raw);
struct ImportTable GetImportsTable(uint8_t* header_ptr);
struct ImportSymbol GetImportsAt(struct ImportTable* table, int i);
int GetSegmentFixAt(uint8_t* buffer, uint32_t i, struct SegmentFix* fix);
int ParseFixValue(int format, uint64_t value, int* bind, uint64_t* ret1, uint64_t* ret2);
#endif

View File

@ -0,0 +1,256 @@
/* -*- mode: C++; c-basic-offset: 4; tab-width: 4 -*-
*
* Copyright (c) 2018 Apple Inc. All rights reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#ifndef __MACH_O_FIXUP_CHAINS__
#define __MACH_O_FIXUP_CHAINS__ 5
#include <stdint.h>
//#define LC_DYLD_EXPORTS_TRIE 0x80000033 // used with linkedit_data_command
//#define LC_DYLD_CHAINED_FIXUPS 0x80000034 // used with linkedit_data_command, payload is dyld_chained_fixups_header
// header of the LC_DYLD_CHAINED_FIXUPS payload
struct dyld_chained_fixups_header
{
uint32_t fixups_version; // 0
uint32_t starts_offset; // offset of dyld_chained_starts_in_image in chain_data
uint32_t imports_offset; // offset of imports table in chain_data
uint32_t symbols_offset; // offset of symbol strings in chain_data
uint32_t imports_count; // number of imported symbol names
uint32_t imports_format; // DYLD_CHAINED_IMPORT*
uint32_t symbols_format; // 0 => uncompressed, 1 => zlib compressed
};
// This struct is embedded in LC_DYLD_CHAINED_FIXUPS payload
struct dyld_chained_starts_in_image
{
uint32_t seg_count;
uint32_t seg_info_offset[1]; // each entry is offset into this struct for that segment
// followed by pool of dyld_chain_starts_in_segment data
};
// This struct is embedded in dyld_chain_starts_in_image
// and passed down to the kernel for page-in linking
struct dyld_chained_starts_in_segment
{
uint32_t size; // size of this (amount kernel needs to copy)
uint16_t page_size; // 0x1000 or 0x4000
uint16_t pointer_format; // DYLD_CHAINED_PTR_*
uint64_t segment_offset; // offset in memory to start of segment
uint32_t max_valid_pointer; // for 32-bit OS, any value beyond this is not a pointer
uint16_t page_count; // how many pages are in array
uint16_t page_start[1]; // each entry is offset in each page of first element in chain
// or DYLD_CHAINED_PTR_START_NONE if no fixups on page
// uint16_t chain_starts[1]; // some 32-bit formats may require multiple starts per page.
// for those, if high bit is set in page_starts[], then it
// is index into chain_starts[] which is a list of starts
// the last of which has the high bit set
};
enum {
DYLD_CHAINED_PTR_START_NONE = 0xFFFF, // used in page_start[] to denote a page with no fixups
DYLD_CHAINED_PTR_START_MULTI = 0x8000, // used in page_start[] to denote a page which has multiple starts
DYLD_CHAINED_PTR_START_LAST = 0x8000, // used in chain_starts[] to denote last start in list for page
};
// This struct is embedded in __TEXT,__chain_starts section in firmware
struct dyld_chained_starts_offsets
{
uint32_t pointer_format; // DYLD_CHAINED_PTR_32_FIRMWARE
uint32_t starts_count; // number of starts in array
uint32_t chain_starts[1]; // array chain start offsets
};
// values for dyld_chained_starts_in_segment.pointer_format
enum {
DYLD_CHAINED_PTR_ARM64E = 1, // stride 8, unauth target is vmaddr
DYLD_CHAINED_PTR_64 = 2, // target is vmaddr
DYLD_CHAINED_PTR_32 = 3,
DYLD_CHAINED_PTR_32_CACHE = 4,
DYLD_CHAINED_PTR_32_FIRMWARE = 5,
DYLD_CHAINED_PTR_64_OFFSET = 6, // target is vm offset
DYLD_CHAINED_PTR_ARM64E_OFFSET = 7, // old name
DYLD_CHAINED_PTR_ARM64E_KERNEL = 7, // stride 4, unauth target is vm offset
DYLD_CHAINED_PTR_64_KERNEL_CACHE = 8,
DYLD_CHAINED_PTR_ARM64E_USERLAND = 9, // stride 8, unauth target is vm offset
DYLD_CHAINED_PTR_ARM64E_FIRMWARE = 10, // stride 4, unauth target is vmaddr
DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE = 11, // stride 1, x86_64 kernel caches
};
// DYLD_CHAINED_PTR_ARM64E
struct dyld_chained_ptr_arm64e_rebase
{
uint64_t target : 43,
high8 : 8,
next : 11, // 4 or 8-byte stide
bind : 1, // == 0
auth : 1; // == 0
};
// DYLD_CHAINED_PTR_ARM64E
struct dyld_chained_ptr_arm64e_bind
{
uint64_t ordinal : 16,
zero : 16,
addend : 19, // +/-256K
next : 11, // 4 or 8-byte stide
bind : 1, // == 1
auth : 1; // == 0
};
// DYLD_CHAINED_PTR_ARM64E
struct dyld_chained_ptr_arm64e_auth_rebase
{
uint64_t target : 32, // runtimeOffset
diversity : 16,
addrDiv : 1,
key : 2,
next : 11, // 4 or 8-byte stide
bind : 1, // == 0
auth : 1; // == 1
};
// DYLD_CHAINED_PTR_ARM64E
struct dyld_chained_ptr_arm64e_auth_bind
{
uint64_t ordinal : 16,
zero : 16,
diversity : 16,
addrDiv : 1,
key : 2,
next : 11, // 4 or 8-byte stide
bind : 1, // == 1
auth : 1; // == 1
};
// DYLD_CHAINED_PTR_64/DYLD_CHAINED_PTR_64_OFFSET
struct dyld_chained_ptr_64_rebase
{
uint64_t target : 36, // 64GB max image size (DYLD_CHAINED_PTR_64 => vmAddr, DYLD_CHAINED_PTR_64_OFFSET => runtimeOffset)
high8 : 8, // top 8 bits set to this (DYLD_CHAINED_PTR_64 => after slide added, DYLD_CHAINED_PTR_64_OFFSET => before slide added)
reserved : 7, // all zeros
next : 12, // 4-byte stride
bind : 1; // == 0
};
// DYLD_CHAINED_PTR_64
struct dyld_chained_ptr_64_bind
{
uint64_t ordinal : 24,
addend : 8, // 0 thru 255
reserved : 19, // all zeros
next : 12, // 4-byte stride
bind : 1; // == 1
};
// DYLD_CHAINED_PTR_64_KERNEL_CACHE, DYLD_CHAINED_PTR_X86_64_KERNEL_CACHE
struct dyld_chained_ptr_64_kernel_cache_rebase
{
uint64_t target : 30, // basePointers[cacheLevel] + target
cacheLevel : 2, // what level of cache to bind to (indexes a mach_header array)
diversity : 16,
addrDiv : 1,
key : 2,
next : 12, // 1 or 4-byte stide
isAuth : 1; // 0 -> not authenticated. 1 -> authenticated
};
// DYLD_CHAINED_PTR_32
// Note: for DYLD_CHAINED_PTR_32 some non-pointer values are co-opted into the chain
// as out of range rebases. If an entry in the chain is > max_valid_pointer, then it
// is not a pointer. To restore the value, subtract off the bias, which is
// (64MB+max_valid_pointer)/2.
struct dyld_chained_ptr_32_rebase
{
uint32_t target : 26, // vmaddr, 64MB max image size
next : 5, // 4-byte stride
bind : 1; // == 0
};
// DYLD_CHAINED_PTR_32
struct dyld_chained_ptr_32_bind
{
uint32_t ordinal : 20,
addend : 6, // 0 thru 63
next : 5, // 4-byte stride
bind : 1; // == 1
};
// DYLD_CHAINED_PTR_32_CACHE
struct dyld_chained_ptr_32_cache_rebase
{
uint32_t target : 30, // 1GB max dyld cache TEXT and DATA
next : 2; // 4-byte stride
};
// DYLD_CHAINED_PTR_32_FIRMWARE
struct dyld_chained_ptr_32_firmware_rebase
{
uint32_t target : 26, // 64MB max firmware TEXT and DATA
next : 6; // 4-byte stride
};
// values for dyld_chained_fixups_header.imports_format
enum {
DYLD_CHAINED_IMPORT = 1,
DYLD_CHAINED_IMPORT_ADDEND = 2,
DYLD_CHAINED_IMPORT_ADDEND64 = 3,
};
// DYLD_CHAINED_IMPORT
struct dyld_chained_import
{
uint32_t lib_ordinal : 8,
weak_import : 1,
name_offset : 23;
};
// DYLD_CHAINED_IMPORT_ADDEND
struct dyld_chained_import_addend
{
uint32_t lib_ordinal : 8,
weak_import : 1,
name_offset : 23;
int32_t addend;
};
// DYLD_CHAINED_IMPORT_ADDEND64
struct dyld_chained_import_addend64
{
uint64_t lib_ordinal : 16,
weak_import : 1,
reserved : 15,
name_offset : 32;
uint64_t addend;
};
#endif // __MACH_O_FIXUP_CHAINS__