diff --git a/macho-go/internal/wrapper/action/save_imports.go b/macho-go/internal/wrapper/action/save_imports.go index 454b4d3..7f4df1b 100644 --- a/macho-go/internal/wrapper/action/save_imports.go +++ b/macho-go/internal/wrapper/action/save_imports.go @@ -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 { diff --git a/macho-go/pkg/ios/macho/edit.go b/macho-go/pkg/ios/macho/edit.go index 01e3868..9189e8b 100644 --- a/macho-go/pkg/ios/macho/edit.go +++ b/macho-go/pkg/ios/macho/edit.go @@ -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 = §ion + 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())) + } +} diff --git a/research/custom_loader/b.cc b/research/custom_loader/b.cc index d6eefb6..7aa0b46 100644 --- a/research/custom_loader/b.cc +++ b/research/custom_loader/b.cc @@ -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,80 +1020,52 @@ 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; - } - } - 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); + printf("[*] performing selfbind (instructions=%p)\n", selfbind); + fix_binds(libfixing, &cache, + n_instructions, encoded_table, + libs, symbols); } } + // 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++) { // uint64_t start_page = page_rw_fixed[j]; // 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); } diff --git a/research/custom_loader/build.sh b/research/custom_loader/build.sh index b22c194..b3f84c1 100755 --- a/research/custom_loader/build.sh +++ b/research/custom_loader/build.sh @@ -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