add full rebuild for Objective-C binaries
This commit is contained in:
parent
f5144fec4f
commit
7eb43a35fb
@ -1,4 +1,5 @@
|
|||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
@interface Foo : NSObject
|
@interface Foo : NSObject
|
||||||
@end
|
@end
|
||||||
@ -15,11 +16,20 @@
|
|||||||
@implementation Bar
|
@implementation Bar
|
||||||
+ (void)load {
|
+ (void)load {
|
||||||
NSLog(@"%@", self);
|
NSLog(@"%@", self);
|
||||||
|
printf("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n");
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@implementation Baz : Bar
|
// @implementation Baz : Bar
|
||||||
@end
|
// @end
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
__attribute__((constructor)) static void
|
||||||
|
hmmge() {
|
||||||
|
// create a dummy blank function to be replaced to call OBJC load
|
||||||
|
printf("hmmge\n");
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, const char * argv[]) {
|
int main(int argc, const char * argv[]) {
|
||||||
@autoreleasepool {
|
@autoreleasepool {
|
||||||
|
@ -780,6 +780,7 @@ void build_cache(struct libcache& cache, void* main) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void fix_objc(struct libcache_item* libfixing, struct libcache& cache);
|
void fix_objc(struct libcache_item* libfixing, struct libcache& cache);
|
||||||
|
void fix_initializer(struct libcache_item* libfixing, struct libcache& cache);
|
||||||
void fix(struct libcache& cache) {
|
void fix(struct libcache& cache) {
|
||||||
// now we have function to find exported symbols
|
// now we have function to find exported symbols
|
||||||
// it supports full name search or hash search
|
// it supports full name search or hash search
|
||||||
@ -938,6 +939,82 @@ void fix(struct libcache& cache) {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
fix_objc(libfixing, cache);
|
fix_objc(libfixing, cache);
|
||||||
|
fix_initializer(libfixing, cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
typedef void *(*readClass_t)(void *, bool, bool);
|
||||||
|
typedef void *(*realizeClassWithoutSwift_t)(void *, void*);
|
||||||
|
typedef void *(*remapClass_t)(void *);
|
||||||
|
typedef void *(*load_method_t)(void*, void*);
|
||||||
|
typedef void *(*sel_lookUpByName_t)(const char*);
|
||||||
|
typedef void (*addClassTableEntry_t)(void *);
|
||||||
|
typedef void (*schedule_class_load_t)(void *);
|
||||||
|
|
||||||
|
typedef void *(*objc_autoreleasePoolPush_t)();
|
||||||
|
typedef void (*objc_autoreleasePoolPop_t)(void *);
|
||||||
|
|
||||||
|
struct custom_initializer_t {
|
||||||
|
uint64_t* loadable_classes;
|
||||||
|
uint32_t* loadable_classes_used;
|
||||||
|
sel_lookUpByName_t sel_lookUpByName;
|
||||||
|
objc_autoreleasePoolPush_t objc_autoreleasePoolPush;
|
||||||
|
objc_autoreleasePoolPop_t objc_autoreleasePoolPop;
|
||||||
|
remapClass_t remapClass;
|
||||||
|
schedule_class_load_t schedule_class_load;
|
||||||
|
uint64_t* cls;
|
||||||
|
size_t ncls;
|
||||||
|
};
|
||||||
|
|
||||||
|
// global variable for PoC
|
||||||
|
struct custom_initializer_t* custom_initializer_i;
|
||||||
|
|
||||||
|
void volatile
|
||||||
|
custom_initializer(int argc, const char *const argv[], const char *const envp[],
|
||||||
|
const char *const apple[], const struct ProgramVars *vars) {
|
||||||
|
printf("run custom initializers %p\n", custom_initializer_i);
|
||||||
|
// return;
|
||||||
|
uint64_t* loadable_classes = custom_initializer_i->loadable_classes;
|
||||||
|
uint32_t* loadable_classes_used = custom_initializer_i->loadable_classes_used;
|
||||||
|
sel_lookUpByName_t sel_lookUpByName = custom_initializer_i->sel_lookUpByName;
|
||||||
|
objc_autoreleasePoolPop_t objc_autoreleasePoolPop = custom_initializer_i->objc_autoreleasePoolPop;
|
||||||
|
objc_autoreleasePoolPush_t objc_autoreleasePoolPush = custom_initializer_i->objc_autoreleasePoolPush;
|
||||||
|
remapClass_t remapClass = custom_initializer_i->remapClass;
|
||||||
|
schedule_class_load_t schedule_class_load = custom_initializer_i->schedule_class_load;
|
||||||
|
|
||||||
|
for (int i = 0; i < custom_initializer_i->ncls; i++) {
|
||||||
|
void* cls0 = (void*)custom_initializer_i->cls[i];
|
||||||
|
void* cls = remapClass(cls0);
|
||||||
|
if (!cls) continue;
|
||||||
|
schedule_class_load(cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("loadable_classes %llx %x\n", *loadable_classes, *loadable_classes_used);
|
||||||
|
|
||||||
|
struct loadable_class_t {
|
||||||
|
void* cls;
|
||||||
|
void* method;
|
||||||
|
};
|
||||||
|
struct loadable_class_t *classes = (struct loadable_class_t*)*loadable_classes;
|
||||||
|
int used = *loadable_classes_used;
|
||||||
|
*loadable_classes = 0;
|
||||||
|
// *loadable_classes_allocated = 0;
|
||||||
|
*loadable_classes_used = 0;
|
||||||
|
void* sel = sel_lookUpByName("load");
|
||||||
|
// Call all +loads for the detached list.
|
||||||
|
void* pool = objc_autoreleasePoolPush();
|
||||||
|
for (int i = 0; i < used; i++) {
|
||||||
|
void* cls = classes[i].cls;
|
||||||
|
load_method_t load_method = (load_method_t)classes[i].method;
|
||||||
|
printf("call load of class %p %p\n", cls, load_method);
|
||||||
|
if (!cls) continue;
|
||||||
|
(load_method)(cls, sel);
|
||||||
|
}
|
||||||
|
// Destroy the detached list.
|
||||||
|
if (classes) free(classes);
|
||||||
|
objc_autoreleasePoolPop(pool);
|
||||||
|
|
||||||
|
free(custom_initializer_i);
|
||||||
}
|
}
|
||||||
|
|
||||||
void fix_objc(struct libcache_item* libfixing, struct libcache& cache) {
|
void fix_objc(struct libcache_item* libfixing, struct libcache& cache) {
|
||||||
@ -954,17 +1031,6 @@ void fix_objc(struct libcache_item* libfixing, struct libcache& cache) {
|
|||||||
// "mov rcx, 123;"
|
// "mov rcx, 123;"
|
||||||
// "call r12;");
|
// "call r12;");
|
||||||
|
|
||||||
typedef void *(*readClass_t)(void *, bool, bool);
|
|
||||||
typedef void *(*realizeClassWithoutSwift_t)(void *, void*);
|
|
||||||
typedef void *(*remapClass_t)(void *);
|
|
||||||
typedef void *(*load_method_t)(void*, void*);
|
|
||||||
typedef void *(*sel_lookUpByName_t)(const char*);
|
|
||||||
typedef void (*addClassTableEntry_t)(void *);
|
|
||||||
typedef void (*schedule_class_load_t)(void *);
|
|
||||||
|
|
||||||
typedef void *(*objc_autoreleasePoolPush_t)();
|
|
||||||
typedef void (*objc_autoreleasePoolPop_t)(void *);
|
|
||||||
|
|
||||||
void* header = libfixing->header;
|
void* header = libfixing->header;
|
||||||
const uint32_t magic = *(uint32_t *)header;
|
const uint32_t magic = *(uint32_t *)header;
|
||||||
char *ptr = (char *)header;
|
char *ptr = (char *)header;
|
||||||
@ -1010,8 +1076,11 @@ void fix_objc(struct libcache_item* libfixing, struct libcache& cache) {
|
|||||||
for (int ptr_i = 0; ptr_i < size / 8; ptr_i++) {
|
for (int ptr_i = 0; ptr_i < size / 8; ptr_i++) {
|
||||||
// this pointer is rebased by dyld and points to the correct class interface
|
// this pointer is rebased by dyld and points to the correct class interface
|
||||||
// for some reason, we can skip this and it should still work
|
// for some reason, we can skip this and it should still work
|
||||||
readClass((void*)data_ptr[ptr_i], false, false);
|
void* newCls = readClass((void*)data_ptr[ptr_i], false, false);
|
||||||
realizeClassWithoutSwift((void*)data_ptr[ptr_i], 0);
|
if (newCls != (void*)data_ptr[ptr_i]) {
|
||||||
|
realizeClassWithoutSwift(newCls, 0);
|
||||||
|
}
|
||||||
|
printf("add class init (%llx)%p\n", data_ptr[ptr_i], newCls);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (custom_strncmp(secname, "__objc_nlclsbruh", 16) == 0) {
|
else if (custom_strncmp(secname, "__objc_nlclsbruh", 16) == 0) {
|
||||||
@ -1050,40 +1119,17 @@ void fix_objc(struct libcache_item* libfixing, struct libcache& cache) {
|
|||||||
printf("build nonlazy class at (%llx)%p\n", data_ptr[ptr_i], cls);
|
printf("build nonlazy class at (%llx)%p\n", data_ptr[ptr_i], cls);
|
||||||
}
|
}
|
||||||
|
|
||||||
printf("loadable_classes %llx %llx %llx\n", *loadable_classes, *loadable_classes_used, *loadable_classes_allocated);
|
custom_initializer_i = (custom_initializer_t*)malloc(sizeof(custom_initializer_t));
|
||||||
for (int ptr_i = 0; ptr_i < size / 8; ptr_i++) {
|
custom_initializer_i->sel_lookUpByName = sel_lookUpByName;
|
||||||
void* cls = remapClass((void*)data_ptr[ptr_i]);
|
custom_initializer_i->loadable_classes = loadable_classes;
|
||||||
schedule_class_load(cls);
|
custom_initializer_i->loadable_classes_used = loadable_classes_used;
|
||||||
printf("add class load (%llx)%p\n", data_ptr[ptr_i], cls);
|
custom_initializer_i->objc_autoreleasePoolPush = objc_autoreleasePoolPush;
|
||||||
}
|
custom_initializer_i->objc_autoreleasePoolPop = objc_autoreleasePoolPop;
|
||||||
printf("loadable_classes %llx\n", *loadable_classes);
|
custom_initializer_i->schedule_class_load = schedule_class_load;
|
||||||
|
custom_initializer_i->remapClass = remapClass;
|
||||||
void* pool = objc_autoreleasePoolPush();
|
custom_initializer_i->cls = data_ptr;
|
||||||
{
|
custom_initializer_i->ncls = size / 8;
|
||||||
struct loadable_class_t {
|
// printf("loadable_classes %llx\n", *loadable_classes);
|
||||||
void* cls;
|
|
||||||
void* method;
|
|
||||||
};
|
|
||||||
struct loadable_class_t *classes = (struct loadable_class_t*)*loadable_classes;
|
|
||||||
int used = *loadable_classes_used;
|
|
||||||
*loadable_classes = 0;
|
|
||||||
*loadable_classes_allocated = 0;
|
|
||||||
*loadable_classes_used = 0;
|
|
||||||
void* sel = sel_lookUpByName("load");
|
|
||||||
printf("selector %p\n", sel);
|
|
||||||
// Call all +loads for the detached list.
|
|
||||||
for (i = 0; i < used; i++) {
|
|
||||||
void* cls = classes[i].cls;
|
|
||||||
load_method_t load_method = (load_method_t)classes[i].method;
|
|
||||||
printf("call load of class %p %p\n", cls, load_method);
|
|
||||||
if (!cls) continue;
|
|
||||||
(load_method)(cls, sel);
|
|
||||||
}
|
|
||||||
// Destroy the detached list.
|
|
||||||
if (classes) free(classes);
|
|
||||||
}
|
|
||||||
objc_autoreleasePoolPop(pool);
|
|
||||||
printf("loadable_classes %llx\n", *loadable_classes);
|
|
||||||
}
|
}
|
||||||
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
||||||
}
|
}
|
||||||
@ -1096,6 +1142,135 @@ void fix_objc(struct libcache_item* libfixing, struct libcache& cache) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void fix_initializer(struct libcache_item* libfixing, struct libcache& cache) {
|
||||||
|
// fix the initializers
|
||||||
|
// The Objective-C runtime loads the NSObject after this lib booted
|
||||||
|
// So all calls to NSObject (and its children classes) will segfault/throw error
|
||||||
|
//
|
||||||
|
// So we will fix the main initializers, which runs after all Objective-C setup
|
||||||
|
// The initializers will run these Objective-C classes' load methods
|
||||||
|
//
|
||||||
|
// (THIS IDEA IS TESTED AND WILL NOT WORK)
|
||||||
|
// As of now, we assume the main executable has a __mod_init_func section
|
||||||
|
// In practice, we should always inject an empty __mod_init_func
|
||||||
|
// But if the binary already has a __mod_init_func section, this section must
|
||||||
|
// reallocate into a different section to allow for a bigger size (oldsize+1)
|
||||||
|
//
|
||||||
|
// This idea can't work because dyld check the pointer to be inside the image,
|
||||||
|
// which is not if we point it here
|
||||||
|
//
|
||||||
|
// (THIS IS THE CORRECT IDEA)
|
||||||
|
// So for Objective-C binaries, the load chain happens like this
|
||||||
|
// [(no objc?)lib init] -> [objc runtime] -> [foundations] ->
|
||||||
|
// [main obj-c load] -> [main constructor] -> [main]
|
||||||
|
// We need to inject between [objc runtime] and [main]
|
||||||
|
//
|
||||||
|
// There could be many ways to do this. I discovered 1 method of doing this.
|
||||||
|
//
|
||||||
|
// The idea is to hijack the main function to do the rest of the initalizations.
|
||||||
|
// By fixing the LC_MAIN command, we can make dyld jump to anywhere we want as main.
|
||||||
|
// But the command can't be edited at runtime.
|
||||||
|
// And pointing to the function we want needs a workaround.
|
||||||
|
//
|
||||||
|
// So we will write a shellcode in the binary and point main to that shellcode.
|
||||||
|
// The shellcode basically loads the address of the initalization function,
|
||||||
|
// call it, then call main, and return.
|
||||||
|
//
|
||||||
|
// The shellcode must be able ot get the current pc address to correctly calculate
|
||||||
|
// address from any offset. In arm64, we can use `adr x8, 0`.
|
||||||
|
// If we know where the shellcode is, we can effectively calculate the header of main.
|
||||||
|
// Now, everything is easy, just need offsets to anywhere we want and we can get it.
|
||||||
|
//
|
||||||
|
// Now the address of the initalization function can be fetched using many methods,
|
||||||
|
// but it resides inside this library. To reduce redundance work, we can write the
|
||||||
|
// address of this function somewhere inside main, which will then be easily found.
|
||||||
|
//
|
||||||
|
// As a result, we choose the space before __text to write the shellcode,
|
||||||
|
// the space after __DATA to write the address for initalization function.
|
||||||
|
// Because all segment is allocated/pre-allocated with page alignement,
|
||||||
|
// we can be pretty sure that there are free space.
|
||||||
|
// (note: __TEXT segment is aligned to the end of the page, free space in the middle)
|
||||||
|
//
|
||||||
|
// Below is the shellcode.
|
||||||
|
//
|
||||||
|
// sub sp, sp, #0x10
|
||||||
|
// str x30, [sp]
|
||||||
|
// adr x8, 0
|
||||||
|
// movz x9, #0x3d68 ; offset at this point
|
||||||
|
// sub x8, x8, x9
|
||||||
|
// str x8, [sp, #8]
|
||||||
|
// movz x9, #0x81d8
|
||||||
|
// add x9, x8, x9
|
||||||
|
// ldr x9, [x9]
|
||||||
|
// blr x9
|
||||||
|
// ldr x8, [sp, #8]
|
||||||
|
// movz x9, #0x3e3c ; offset to original main
|
||||||
|
// add x9, x8, x9
|
||||||
|
// blr x9
|
||||||
|
// ldr x30, [sp]
|
||||||
|
// add sp, sp, #0x10
|
||||||
|
// ret
|
||||||
|
|
||||||
|
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 linkedit_vmaddr;
|
||||||
|
uint64_t linkedit_fileoffset;
|
||||||
|
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
|
||||||
|
printf("segment %s\n", name);
|
||||||
|
if (custom_strcmp(name, "__TEXT") == 0) {
|
||||||
|
slide = (uint64_t)header - vmaddr;
|
||||||
|
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;
|
||||||
|
printf("section %s\n", secname);
|
||||||
|
if (custom_strcmp(secname, "__init_offsets") == 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);
|
||||||
|
|
||||||
|
printf("found initializer at %p\n", data_ptr);
|
||||||
|
}
|
||||||
|
sections_ptr += 16 * 2 + 8 * 2 + 4 * 8;
|
||||||
|
}
|
||||||
|
} 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);
|
||||||
|
|
||||||
|
uint64_t addr = *((uint64_t*)sections_ptr + 4);
|
||||||
|
uint64_t size = *((uint64_t*)sections_ptr + 5);
|
||||||
|
|
||||||
|
uint64_t* dummy = (uint64_t*)(addr + slide + size);
|
||||||
|
*dummy = (uint64_t)custom_initializer;
|
||||||
|
printf("add custom main-peg at %p\n", dummy);
|
||||||
|
} else if (custom_strcmp(name, "__LINKEDIT") == 0) {
|
||||||
|
linkedit_vmaddr = vmaddr;
|
||||||
|
linkedit_fileoffset = fileoffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ptr += cmdsize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void test(struct libcache& cache) {
|
void test(struct libcache& cache) {
|
||||||
uint32_t libsystem_hash =
|
uint32_t libsystem_hash =
|
||||||
calculate_libname_hash(&cache, "/usr/lib/libSystem.B.dylib");
|
calculate_libname_hash(&cache, "/usr/lib/libSystem.B.dylib");
|
||||||
|
Loading…
Reference in New Issue
Block a user