diff --git a/macho-go/pkg/ios/macho/dyld_info.go b/macho-go/pkg/ios/macho/dyld_info.go index c5e7bb4..6365912 100644 --- a/macho-go/pkg/ios/macho/dyld_info.go +++ b/macho-go/pkg/ios/macho/dyld_info.go @@ -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 diff --git a/macho-go/pkg/ios/macho/fixups.c b/macho-go/pkg/ios/macho/fixups.c new file mode 100644 index 0000000..4442237 --- /dev/null +++ b/macho-go/pkg/ios/macho/fixups.c @@ -0,0 +1,125 @@ +#include +#include +#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); + } +} diff --git a/macho-go/pkg/ios/macho/fixups.h b/macho-go/pkg/ios/macho/fixups.h new file mode 100644 index 0000000..6ab2a77 --- /dev/null +++ b/macho-go/pkg/ios/macho/fixups.h @@ -0,0 +1,33 @@ +#ifndef _FIXUPS_ +#define _FIXUPS_ +#include + +// 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 diff --git a/macho-go/pkg/ios/macho/fixups_type.h b/macho-go/pkg/ios/macho/fixups_type.h new file mode 100644 index 0000000..7b6adbb --- /dev/null +++ b/macho-go/pkg/ios/macho/fixups_type.h @@ -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 + + +//#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__