add selfbind functionality

This commit is contained in:
nganhkhoa 2023-07-12 13:34:02 +07:00
parent 6815ea6556
commit 4ee62a2d93
4 changed files with 305 additions and 88 deletions

View File

@ -15,6 +15,15 @@ type saveImports struct{
}
func (action *saveImports) withMacho(mf *MachoFile) error {
action.saveToInfo(mf)
mc := mf.Context()
if mc.Header().IsDylib() {
mc.WriteInfoToData(mf.Info())
}
return nil
}
func (action *saveImports) saveToInfo(mf *MachoFile) error {
// calculateHash := func(name string) uint32 {
// var h uint32 = 0x811c9dc5
// for _, s := range name {

View File

@ -7,9 +7,11 @@ import (
"math/rand"
"time"
"strings"
"encoding/binary"
log "github.com/sirupsen/logrus"
"ios-wrapper/pkg/protomodel"
. "ios-wrapper/pkg/ios"
)
@ -681,3 +683,130 @@ func (mc *MachoContext) RemoveExportTrie() {
// should never occur unless this binary is modified
}
}
func (mc *MachoContext) AddSection(segname string, name string, size int) Section {
// mc.file.WriteAt(mc.header.Serialize(mc), 0)
var ret Section
var buffer bytes.Buffer
for _, command := range mc.commands {
switch command.(type) {
case *Segment64:
var virtualAddr uint64
var fileOffset uint64
var segment = command.(*Segment64)
if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte(segname)) != 0 {
buffer.Write(segment.Serialize(mc))
continue
} else {
virtualAddr = segment.Vmaddr()
fileOffset = segment.Fileoff()
for _, section := range segment.Sections() {
virtualAddr += section.Size()
// if section.Offset() != 0 {
fileOffset += section.Size()
// }
}
align := uint64(4)
alignment := align - (fileOffset % align)
fileOffset += alignment
virtualAddr += alignment
enoughSpace := segment.Fileoff() + segment.Filesize() >= fileOffset + uint64(size)
if !enoughSpace {
fmt.Println("Not enough space to store saved info in __DATA segment, need resize (not supported now)")
panic("Not enough space to store saved info in __DATA segment, need resize (not supported now)")
}
}
var section Section64
section.sectname = make([]byte, 16)
copy(section.sectname, name)
section.segname = make([]byte, 16)
copy(section.segname, segname)
section.size = uint64(size)
section.reloff = 0
section.nreloc = 0
section.flags = 0
section.align = 3
section.reserved1 = 0
section.reserved2 = 0
section.reserved3 = 0
// addr will increment from the last section
// offset will increment from the last section + size
// be careful of Virtual section (bss)
section.addr = virtualAddr
section.offset = uint32(fileOffset)
segment.nsects += 1
buffer.Write(segment.Serialize(mc))
buffer.Write(section.Serialize(mc))
fmt.Printf("Add a new section with addr=0x%x, fileoffset=0x%x\n", section.addr, section.offset)
ret = &section
continue
default:
buffer.Write(command.Serialize(mc))
continue
}
}
mc.header.sizeofcmds = uint32(buffer.Len())
header := mc.header.Serialize(mc)
mc.file.WriteAt(header, 0)
mc.file.WriteAt(buffer.Bytes(), int64(len(header)))
return ret
}
func (mc *MachoContext) WriteInfoToData(info *protomodel.MachoInfo) {
encode := func () []byte {
buffer := new(bytes.Buffer)
for _, table := range info.Symbols.Tables {
binary.Write(buffer, mc.byteorder, table.LibIndex)
binary.Write(buffer, mc.byteorder, table.Nsymbols)
for _, symbol := range table.Symbols {
binary.Write(buffer, mc.byteorder, (symbol.SymbolIndex<<8)|symbol.SegmentIndex)
binary.Write(buffer, mc.byteorder, symbol.Offset)
}
}
instructions := buffer.Bytes()
buffer = new(bytes.Buffer)
for _, lib := range info.Symbols.Libs {
buffer.WriteString(lib)
buffer.WriteByte(0)
}
liblist := buffer.Bytes()
buffer = new(bytes.Buffer)
for _, symbol := range info.Symbols.Symbols {
buffer.WriteString(symbol)
buffer.WriteByte(0)
}
symbollist := buffer.Bytes()
buffer = new(bytes.Buffer)
// offset to liblist
binary.Write(buffer, mc.byteorder, uint32(len(instructions)))
// offset to symbollist
binary.Write(buffer, mc.byteorder, uint32(len(instructions) + len(liblist)))
buffer.Write(instructions)
buffer.Write(liblist)
buffer.Write(symbollist)
return buffer.Bytes()
}
encoded := encode()
// encoded := []byte{0x11,0x22,0x33, 0x44}
section := mc.AddSection("__DATA", "selfbind", len(encoded))
if mc.Is64bit() {
var s *Section64 = section.(*Section64)
mc.file.WriteAt(encoded, int64(s.Offset()))
} else {
var s *Section32 = section.(*Section32)
mc.file.WriteAt(encoded, int64(s.Offset()))
}
}

View File

@ -216,6 +216,51 @@ uint64_t get_slide(const void *header) {
return 0;
}
void* get_selfbind(const void *header) {
const uint32_t magic = *(uint32_t *)header;
char *ptr = (char *)header;
if (magic == magic64) {
ptr += 0x20;
} else {
ptr += 0x20 - 0x4;
}
const uint32_t ncmds = *((uint32_t *)header + 4);
char* command_ptr = ptr;
uint64_t slide;
for (int i = 0; i < ncmds; i++) {
const uint32_t cmd = *((uint32_t *)ptr + 0);
const uint32_t cmdsize = *((uint32_t *)ptr + 1);
if (cmd == LC_SEGMENT_64) {
char *name = (char *)((uint64_t *)ptr + 1);
uint64_t vmaddr = *((uint64_t *)ptr + 3);
uint64_t fileoffset = *((uint64_t *)ptr + 5);
// this assumes that __TEXT comes before __DATA_CONST
if (custom_strcmp(name, "__TEXT") == 0) {
slide = (uint64_t)header - vmaddr;
} else if (custom_strcmp(name, "__DATA") == 0) {
uint64_t nsect = *((uint32_t *)ptr + 8*2);
char* sections_ptr = (char*)((uint32_t*)ptr + 18);
sections_ptr += (16 * 2 + 8 * 2 + 4 * 8) * (nsect - 1);
for (int sec = 0; sec < nsect; sec++) {
char* secname = sections_ptr;
if (custom_strcmp(secname, "selfbind") == 0) {
uint64_t addr = *((uint64_t*)sections_ptr + 4);
uint64_t size = *((uint64_t*)sections_ptr + 5);
uint32_t *data_ptr = (uint32_t*)(addr + slide);
return (void*)data_ptr;
}
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
}
}
}
ptr += cmdsize;
}
return 0;
}
void print_macho_summary(const void *header) {
const uint32_t magic = *(uint32_t *)header;
char *ptr = (char *)header;
@ -450,11 +495,11 @@ void *find_in_reexport(struct libcache *cache, struct libcache_item *lib,
char *name = (char *)ptr + name_offset;
uint32_t hash = calculate_libname_hash(cache, name);
for (int j = 0; j < cache->size; j++) {
struct libcache_item reexport = cache->libs[j];
if (reexport.hash != hash) {
struct libcache_item* reexport = &cache->libs[j];
if (reexport->hash != hash) {
continue;
}
void *found = find_in_lib(cache, &reexport, symbol);
void *found = find_in_lib(cache, reexport, symbol);
if (found)
return found;
}
@ -466,33 +511,27 @@ void *find_in_reexport(struct libcache *cache, struct libcache_item *lib,
void *find_in_lib(struct libcache *cache, struct libcache_item *lib,
const char *symbol) {
void *direct = find_in_export_trie(lib->header, lib->trie, symbol);
if (direct)
if (direct) {
return direct;
}
// cannot find in directly exported trie, loop through all reexport libs
return find_in_reexport(cache, lib, symbol);
}
void *custom_dlsym(struct libcache *cache, uint32_t hash, const char *symbol) {
for (size_t i = 0; i < cache->size; i++) {
struct libcache_item* cache_lib = &cache->libs[i];
if (cache_lib->hash == hash) {
return find_in_lib(cache, cache_lib, symbol);
}
}
return 0;
}
void *custom_dlsym(struct libcache *cache, const char *libname,
const char *symbol) {
uint32_t hash = calculate_libname_hash(cache, libname);
for (int i = 0; i < cache->size; i++) {
struct libcache_item cache_lib = cache->libs[i];
if (cache_lib.hash == hash) {
return find_in_lib(cache, &cache_lib, symbol);
}
}
printf("cannot find lib with hash 0x%x\n", hash);
return 0;
}
void *custom_dlsym(struct libcache *cache, uint32_t hash, const char *symbol) {
for (int i = 0; i < cache->size; i++) {
struct libcache_item cache_lib = cache->libs[i];
if (cache_lib.hash == hash) {
return find_in_lib(cache, &cache_lib, symbol);
}
}
return 0;
return custom_dlsym(cache, hash, symbol);
}
void bootstrap_libcache_item(struct libcache_item* item, const void* header, const char* name) {
@ -536,7 +575,6 @@ void bootstrap_libcache_item(struct libcache_item* item, const void* header, con
ptr += cmdsize;
}
return;
}
struct libcache_item* get_libcache_with_name(struct libcache* cache, const char* name) {
@ -707,7 +745,7 @@ __attribute__((constructor)) static void
bruh(int argc, const char *const argv[], const char *const envp[],
const char *const apple[], const struct ProgramVars *vars) {
printf("=== manual symbol bind starts ===\n");
set_cwd(envp);
// set_cwd(envp);
// ProgramVars contains pointer to main executable (mapped) file
@ -817,6 +855,72 @@ void build_cache(struct libcache& cache, void* main) {
}
}
void fix_binds(struct libcache_item* libfixing, struct libcache* cache,
int n_ins, uint32_t* instructions, char* libs, char* symbols) {
uint32_t libsystem_hash =
calculate_libname_hash(cache, "/usr/lib/libSystem.B.dylib");
typedef void *(*vm_protect_t)(void *, uint64_t, uint64_t, int, int);
typedef void *(*mach_task_self_t)();
mach_task_self_t mach_task_self_func =
(mach_task_self_t)custom_dlsym(cache, libsystem_hash, "_mach_task_self");
vm_protect_t vm_protect_func =
(vm_protect_t)custom_dlsym(cache, libsystem_hash, "_vm_protect");
int npage_rw_fixed = 0;
uint64_t page_rw_fixed[10]; // should be dynamic, but works for now
int pc = 0;
for (;pc != n_ins;) {
uint32_t libidx = instructions[pc];
uint32_t nsym = instructions[pc + 1];
pc += 2;
char* lib = libs + libidx;
for (int i = 0; i < nsym; i++) {
uint32_t op = instructions[pc];
uint32_t offset = instructions[pc + 1];
pc += 2;
uint32_t symidx = op >> 8;
uint32_t segment = op & 0xff;
char* sym = symbols + symidx;
uint64_t fix_at = offset + libfixing->segment[segment];
// enable WRITE protection for this data segment
int need_rw_fix = true;
for (int j = 0; j < npage_rw_fixed; j++) {
if (page_rw_fixed[j] <= fix_at &&
page_rw_fixed[j] + 0x1000 > fix_at) {
need_rw_fix = false;
}
}
if (need_rw_fix) {
uint64_t start_page = fix_at - (fix_at % 0x1000);
vm_protect_func(mach_task_self_func(), start_page, 0x1000, 0,
VM_PROT_READ | VM_PROT_WRITE);
page_rw_fixed[npage_rw_fixed++] = start_page;
printf("modify page starts at 0x%llx to RW\n", start_page);
}
void *resolved = 0;
// search with hash is faster
// resolved = custom_dlsym(&cache, symbol.hash, symbol.name);
if (resolved == 0) {
// but fuck apple they have relative path and rpath
resolved = custom_dlsym(cache, lib, sym);
}
*(uint64_t *)fix_at = (uint64_t)resolved;
printf("imports need to fix: %s at 0x%llx\n", sym, fix_at);
printf(" from=%s\n", lib);
printf(" segment id=%d; offset=0x%x;", segment, offset);
printf(" resolved=%llx(%p)\n", *(uint64_t*)fix_at, resolved);
}
}
}
void fix_objc(struct libcache_item* libfixing, struct libcache& cache);
void fix_initializer(struct libcache_item* libfixing, struct libcache& cache);
@ -916,73 +1020,34 @@ void fix(struct libcache& cache) {
// may need to look into why this happens so we can deal with this more
// generic
uint32_t libsystem_hash =
calculate_libname_hash(&cache, "/usr/lib/libSystem.B.dylib");
// resolve selfbind if exist
{ // stored inside __DATA,selfbind
struct libcache_item* libfixing = get_libcache_with_name(&cache, "thislib");
struct selfbind_t {
uint32_t liblist_offset;
uint32_t symbollist_offset;
};
struct selfbind_t* selfbind = (struct selfbind_t*)get_selfbind(libfixing->header);
typedef void *(*vm_protect_t)(void *, uint64_t, uint64_t, int, int);
typedef void *(*mach_task_self_t)();
mach_task_self_t mach_task_self_func =
(mach_task_self_t)custom_dlsym(&cache, libsystem_hash, "_mach_task_self");
vm_protect_t vm_protect_func =
(vm_protect_t)custom_dlsym(&cache, libsystem_hash, "_vm_protect");
if (selfbind) {
char* libs = (char*)(selfbind+1) + selfbind->liblist_offset;
char* symbols = (char*)(selfbind+1) + selfbind->symbollist_offset;
uint64_t n_instructions = ((uint64_t)libs - (uint64_t)(selfbind + 1)) / 4;
uint32_t* encoded_table = (uint32_t*)(selfbind+1);
int npage_rw_fixed = 0;
uint64_t page_rw_fixed[10]; // should be dynamic, but works for now
// think of a way to get what binary to fix
// so we can iterate through them
char* lib_to_resolve = "main";
struct libcache_item* libfixing = get_libcache_with_name(&cache, lib_to_resolve);
int pc = 0;
for (;pc != bshield_data::n_instructions;) {
uint32_t libidx = bshield_data::encoded_table[pc];
uint32_t nsym = bshield_data::encoded_table[pc + 1];
pc += 2;
char* lib = bshield_data::libs + libidx;
for (int i = 0; i < nsym; i++) {
uint32_t op = bshield_data::encoded_table[pc];
uint32_t offset = bshield_data::encoded_table[pc + 1];
pc += 2;
uint32_t symidx = op >> 8;
uint32_t segment = op & 0xff;
char* sym = bshield_data::symbols + symidx;
uint64_t fix_at = offset + libfixing->segment[segment];
// enable WRITE protection for this data segment
int need_rw_fix = true;
for (int j = 0; j < npage_rw_fixed; j++) {
if (page_rw_fixed[j] <= fix_at &&
page_rw_fixed[j] + 0x1000 > fix_at) {
need_rw_fix = false;
printf("[*] performing selfbind (instructions=%p)\n", selfbind);
fix_binds(libfixing, &cache,
n_instructions, encoded_table,
libs, symbols);
}
}
if (need_rw_fix) {
uint64_t start_page = fix_at - (fix_at % 0x1000);
vm_protect_func(mach_task_self_func(), start_page, 0x1000, 0,
VM_PROT_READ | VM_PROT_WRITE);
page_rw_fixed[npage_rw_fixed++] = start_page;
printf("modify page starts at 0x%llx to RW\n", start_page);
}
void *resolved = 0;
// search with hash is faster
// resolved = custom_dlsym(&cache, symbol.hash, symbol.name);
if (resolved == 0) {
// but fuck apple they have relative path and rpath
resolved = custom_dlsym(&cache, lib, sym);
}
*(uint64_t *)fix_at = (uint64_t)resolved;
printf("imports need to fix: %s at 0x%llx\n", sym, fix_at);
printf(" from=%s\n", lib);
printf(" segment id=%d; offset=0x%llx;", segment, offset);
printf(" resolved=%llx(%p)\n", *(uint64_t*)fix_at, resolved);
}
}
// the rest of the fixes are in main executable
printf("[*] performing bind for main executable\n");
struct libcache_item* libfixing = get_libcache_with_name(&cache, "main");
fix_binds(libfixing, &cache,
bshield_data::n_instructions, bshield_data::encoded_table,
bshield_data::libs, bshield_data::symbols);
// TODO: Reformat the region as per before, or leave as it
// for (int j = 0; j < npage_rw_fixed; j++) {
@ -990,6 +1055,17 @@ void fix(struct libcache& cache) {
// vm_protect_func(mach_task_self_func(), start_page, 0x4000, 0, VM_PROT_READ);
// }
// Encrypted __TEXT segment
// char* text_start = (char*)libfixing->header + 0x3000;
// vm_protect_func(mach_task_self_func(), (uint64_t)text_start, 0x1000, 0,
// VM_PROT_READ | VM_PROT_WRITE);
// printf("text fix at %p\n", text_start + 0xb8c);
// for (int i = 0; i < 0x2ac; i++) {
// text_start[0xb8c + i] = text_start[0xb8c + i] ^ 0xcc;
// }
// vm_protect_func(mach_task_self_func(), (uint64_t)text_start, 0x1000, 0,
// VM_PROT_READ | VM_PROT_EXECUTE);
fix_objc(libfixing, cache);
fix_initializer(libfixing, cache);
}

View File

@ -64,12 +64,15 @@ clang -fobjc-arc -ObjC -mmacosx-version-min=$VERSION -o $OUT/a -L"./out" -lb a.m
# extract symbols from a
# ../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --remove-symbol-table --keep-imports _printf $OUT/a
../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports --keep-imports _printf $OUT/a
../../macho-go/bin/ios-wrapper pepe -o $OUT/a-fixed -b $OUT/b.bcell --remove-imports --remove-exports $OUT/a
../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h
# build libb with symbols extracted from a
clang++ -mmacosx-version-min=$VERSION -o $OUT/libb.dylib -shared -Wl,-reexport_library out/libc.dylib b.cc
../../macho-go/bin/ios-wrapper pepe -o $OUT/libb.dylib -b $OUT/libb.bcell --remove-imports --remove-exports --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports _printf --keep-imports ___stack_chk_guard $OUT/libb.dylib
# resign
codesign --force --deep -s - $OUT/a-fixed
codesign --force --deep -s - $OUT/libb.dylib
# export OBJC_PRINT_LOAD_METHODS=1
# export OBJC_PRINT_CLASS_SETUP=1