diff --git a/research/custom_loader/a.mm b/research/custom_loader/a.mm index 3eb2ec8..c5407ea 100644 --- a/research/custom_loader/a.mm +++ b/research/custom_loader/a.mm @@ -10,11 +10,11 @@ void* sel_lookUpByName(const char* name); @implementation Foo - (void)bar { - NSLog(@"Invoke instance method %@", self); + NSLog(@"Invoke instance method original bar in Foo"); } - (void)tobehijacked:(NSString*)input { - NSLog(@"Invoke tobehijacked method %@", input); + NSLog(@"Invoke tobehijacked method %@ from Foo", input); } @end diff --git a/research/custom_loader/b.cc b/research/custom_loader/b.cc index e3aba95..c0bab50 100644 --- a/research/custom_loader/b.cc +++ b/research/custom_loader/b.cc @@ -3,6 +3,7 @@ #include #include #include +#include // #include #include @@ -11,6 +12,8 @@ char *pwd; uint32_t pwd_len; +clock_t start, end; +#define ISARM(header) ((*((uint32_t *)(header)+1) & 0xff) == 0xc) int custom_strcmp(const char *p1, const char *p2) { const unsigned char *s1 = (const unsigned char *)p1; @@ -816,6 +819,7 @@ void test(struct libcache &cache); __attribute__((constructor)) static void bruh(int argc, const char *const argv[], const char *const envp[], const char *const apple[], const struct ProgramVars *vars) { + start = clock(); printf("=== manual symbol bind starts ===\n"); // set_cwd(envp); @@ -931,7 +935,7 @@ void build_cache(struct libcache &cache, void *main) { char *name = dyld_get_image_name_func(i); bootstrap_libcache_item(&cache.libs[i], header, name); cache.libs[i].hash = calculate_libname_hash(&cache, name); - printf("%p %s\n", header, name); + // printf("%p %s\n", header, name); } } @@ -1267,14 +1271,76 @@ void volatile custom_initializer(int argc, const char *const argv[], printf("[+] initializers completed\n"); free(custom_initializer_i); + end = clock(); + double cpu_time_used = ((double)(end - start)) / CLOCKS_PER_SEC; + printf("restoration library time: %lf\n", cpu_time_used); } void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache); +void fix_class_refs(struct libcache_item *libfixing, struct libcache &cache); void run_objc_readclass(struct libcache_item *libfixing, struct libcache &cache); +// method are splited into 3 kinds, but for simplicity, we think of it as +// 2 kinds: big and small +// our example are small method list, which all pointers are relative and 32-bit +// the size should be 0xc == 12 but we have padding 4-byte 0x0 for some reason? +union _objc_method{ + struct { + const char* name; + const char* types; + void* imp; + }; + struct { + int32_t sel_offset; + int32_t typ_offset; + int32_t imp_offset; + }; +}; + +struct method_t { + const char* name; /* Pointer to name (or selector reference?) */ + const char* types; /* Pointer to type info */ + void* imp; /* Pointer to implementation (code) */ +}; + +// entsize & 0x80000000 is small method kind +// entsize = kind | sizeof(_objc_method) +struct _method_list_t { + uint32_t entsize; // sizeof(struct _objc_method) + uint32_t method_count; + union _objc_method method_list[]; +}; + +struct _class_ro_t { + uint32_t flags; + uint32_t const instanceStart; + uint32_t const instanceSize; + uint32_t const reserved; // only when building for 64bit targets + const uint8_t * const ivarLayout; + const char *const name; + struct _method_list_t * baseMethods; + const /*struct _protocol_list_t*/void *const baseProtocols; + const /*struct _ivar_list_t*/void *const ivars; + const uint8_t * const weakIvarLayout; + const /*struct _prop_list_t*/void *const properties; +}; + +struct _class_t { + struct _class_t *isa; + struct _class_t * superclass; + void *cache; + void *vtable; + struct _class_ro_t *ro; +}; void fix_objc(struct libcache_item *libfixing, struct libcache &cache) { printf("[+] dealing with Objective-C\n"); +#ifdef METH1 fix_objc_classdata(libfixing, cache); +#endif +#ifdef METH3 + printf("METH3\n"); + fix_class_refs(libfixing, cache); +#endif run_objc_readclass(libfixing, cache); } @@ -1365,46 +1431,6 @@ void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache) // classref can also point to outside classes that are imported if (custom_strncmp(secname, "__objc_data", 16) == 0) { - // method are splited into 3 kinds, but for simplicity, we think of it as - // 2 kinds: big and small - // our example are small method list, which all pointers are relative and 32-bit - // the size should be 0xc == 12 but we have padding 4-byte 0x0 for some reason? - struct _objc_method { - int32_t sel_offset; - int32_t typ_offset; - int32_t imp_offset; - }; - - // entsize & 0x80000000 is small method kind - // entsize = kind | sizeof(_objc_method) - struct _method_list_t { - uint32_t entsize; // sizeof(struct _objc_method) - uint32_t method_count; - struct _objc_method method_list[]; - }; - - struct _class_ro_t { - uint32_t const flags; - uint32_t const instanceStart; - uint32_t const instanceSize; - uint32_t const reserved; // only when building for 64bit targets - const uint8_t * const ivarLayout; - const char *const name; - struct _method_list_t * baseMethods; - const /*struct _protocol_list_t*/void *const baseProtocols; - const /*struct _ivar_list_t*/void *const ivars; - const uint8_t * const weakIvarLayout; - const /*struct _prop_list_t*/void *const properties; - }; - - struct _class_t { - struct _class_t *isa; - struct _class_t * const superclass; - void *cache; - void *vtable; - struct _class_ro_t *ro; - }; - uint64_t addr = *((uint64_t *)sections_ptr + 4); uint64_t size = *((uint64_t *)sections_ptr + 5); struct _class_t *data_ptr = (struct _class_t *)(addr + slide); @@ -1420,7 +1446,7 @@ void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache) for (int i_method = 0; i_method < methods->method_count; i_method++) { // have to use reference because the relative offset is calculated with the variable address // if not using reference, then the variable will be a COPY value and the address is localized - struct _objc_method* method = &methods->method_list[i_method]; + union _objc_method* method = &methods->method_list[i_method]; if (methods->entsize & 0x80000000) { const char* imp = *(char**)((char*)(&method->sel_offset) + method->sel_offset); if (custom_strcmp(class_name, "Foo") == 0 && custom_strcmp(imp, "tobehijacked:") == 0) { @@ -1443,6 +1469,18 @@ void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache) printf(" typ=%x --> %s\n", method->typ_offset, (char*)&method->typ_offset + method->typ_offset); printf(" fun=%x --> %p\n", method->imp_offset, (char*)(&method->imp_offset) + method->imp_offset); } + else { + const char* imp = method->name; + if (custom_strcmp(class_name, "Foo") == 0 && custom_strcmp(imp, "tobehijacked:") == 0) { + void* replace = (void*)test_objc_hijack; + printf("modify the Objective-C method at %p with legacy format.\n", &method->imp); + method->imp = replace; + } + printf(" method=%p\n", method); + printf(" sel=%s\n", method->name); + printf(" typ=%p\n", method->types); + printf(" fun=%p\n", method->imp); + } } } } @@ -1461,6 +1499,112 @@ void fix_objc_classdata(struct libcache_item *libfixing, struct libcache &cache) VM_PROT_READ | VM_PROT_EXECUTE); } +uint64_t find_replace_cls_refs(struct libcache cache) { + void *header = cache.thislib; + 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); + if (custom_strcmp(name, "__TEXT") == 0) + slide = (uint64_t)header - vmaddr; + + if (custom_strcmp(name, "__DATA") == 0){ + uint64_t nsect = *((uint32_t*)ptr + 8 * 2); + char* sections_ptr = (char*)((uint32_t*)ptr + 18); + for (int sec = 0; sec < nsect; sec++){ + char* secname = sections_ptr; + if (custom_strncmp(secname, "__objc_data", 11) == 0){ + uint64_t addr = *((uint64_t *)sections_ptr + 4); + uint64_t size = *((uint64_t *)sections_ptr + 5); + struct _class_t *data_ptr = (struct _class_t *)(addr + slide); + for (int nclass = 0; nclass < size / sizeof(struct _class_t); nclass++, data_ptr++) { + if (!data_ptr->ro) + continue; + if (data_ptr->ro->flags & 0x01) { continue; } + if (custom_strcmp(data_ptr->ro->name, "Hooker") == 0){ + printf("Found Hooker @ %p\n", data_ptr); + return (uint64_t)data_ptr; + } + } + } + sections_ptr += 16 * 2 + 8 * 2 + 4 * 8; + } + } + } + ptr += cmdsize; + } +} + +void fix_class_refs(struct libcache_item *libfixing, struct libcache &cache) { + uint64_t replace = find_replace_cls_refs(cache); + void *header = libfixing->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); + if (custom_strcmp(name, "__TEXT") == 0) + slide = (uint64_t)header - vmaddr; + + if (custom_strcmp(name, "__DATA") == 0){ + uint64_t nsect = *((uint32_t*)ptr + 8 * 2); + char* sections_ptr = (char*)((uint32_t*)ptr + 18); + for (int sec = 0; sec < nsect; sec++){ + char* secname = sections_ptr; + if (custom_strncmp(secname, "__objc_classrefs", 16) == 0){ + uint64_t addr = *((uint64_t*)sections_ptr + 4) + slide; + uint64_t size = *((uint64_t*)sections_ptr + 5); + struct _class_t* target_clsref = NULL; + for (int nclass = 0; nclass < size / sizeof(uint64_t*); nclass++){ + target_clsref = (_class_t*)(*((uint64_t *)addr + nclass)); + // printf("Target clasref @ %p: %p\n", (uint64_t *)addr + nclass, target_clsref); + if (custom_strcmp(target_clsref->ro->name, "Foo") == 0){ + // TODO + printf("Target clasref @ %p: %p\n", (uint64_t *)addr + nclass, target_clsref); + *((uint64_t *)addr + nclass) = replace; + printf("New clasref @ %p: %p\n", (uint64_t *)addr + nclass, *((uint64_t *)addr + nclass)); + struct _class_t* hooker = (struct _class_t*)replace; + printf("superclass hooker: %p\n", target_clsref->superclass); + hooker->superclass = target_clsref; + printf("New superclass hooker: %p\n", hooker->superclass); + break; + } + } + + } + sections_ptr += 16 * 2 + 8 * 2 + 4 * 8; + } + } + } + ptr += cmdsize; + } +} + void run_objc_readclass(struct libcache_item *libfixing, struct libcache &cache) { // Manually run the Objective-C runtime for each class // diff --git a/research/custom_loader/build.sh b/research/custom_loader/build.sh index 5ea383b..ba853a1 100755 --- a/research/custom_loader/build.sh +++ b/research/custom_loader/build.sh @@ -1,9 +1,10 @@ set -e - +clear VERSION=${1:-14} +METH=${2} OUT=./out -LOGIC=${2} - +LOGIC=3 +make -C ../../macho-go mkdir -p $OUT echo "using mach-o version $VERSION" @@ -81,9 +82,17 @@ clang -fobjc-arc -ObjC -mmacosx-version-min=$VERSION -o $OUT/a a.mm # ../../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 --dylibs=./out/libb.dylib --remove-imports --remove-exports --remove-symbol-table --remove-others $OUT/a ../../macho-go/bin/ios-wrapper bcell2header -b $OUT/b.bcell -o $OUT/b.h + +if [ "$METH" = "METH1" ]; then # 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 --remove-symbol-table --remove-others --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports ___stack_chk_guard --keep-imports _printf $OUT/libb.dylib +clang++ -D $METH -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 --remove-symbol-table --remove-others --keep-imports _dyld_get_sdk_version --keep-imports _malloc --keep-imports ___stack_chk_guard --keep-imports _printf $OUT/libb.dylib + +elif [ "$METH" = "METH3" ]; then +clang -mmacosx-version-min=$VERSION -fobjc-arc -ObjC -c -o $OUT/hooking.o hooking.mm +clang++ -mmacosx-version-min=$VERSION -D $METH -c -o $OUT/b.o b.cc +clang++ -fobjc-arc -ObjC -shared -Wl,-reexport_library -o $OUT/libb.dylib $OUT/b.o $OUT/hooking.o +fi # resign codesign --force --deep -s - $OUT/a-fixed diff --git a/research/custom_loader/hooking.mm b/research/custom_loader/hooking.mm new file mode 100644 index 0000000..64af167 --- /dev/null +++ b/research/custom_loader/hooking.mm @@ -0,0 +1,31 @@ +#import +#include +#include + +@interface Hehe : NSObject +- (void)bar; +- (void)tobehijacked:(NSString*)input; +@end + +@interface Hooker : Hehe +@end + +@implementation Hehe +- (void)bar { + NSLog(@"Invoke instance method %@", self); +} + - (void)tobehijacked:(NSString*)input { + NSLog(@"Invoke tobehijacked method %@", input); + } +@end + +@implementation Hooker +- (void)tobehijacked:(NSString*)input { + NSLog(@"Hijacked tobehijacked method %@ from Hooker", input); +} + +- (void)bar { + [super bar]; +} + +@end