之前写过一篇文章简单对 frida-objc-bridge 原理进行分析,这里分享个使用 objc 代码的实现个只有frida-objc-bridge 的 “frida-mini”。
目前仅支持 M1/M2(arm64) 的版本。
这里我参考kk用rust 写的方案:https://github.com/kekeimiku/PointerSearcher-X/tree/main/inject。
原理挺简单的,找 _dlopen
符号地址,拼个加载执行dylib
asm 写到一块可执行的内存中再执行下,无聊用 objc 重写了一遍:
#import <Foundation/Foundation.h>
#include <mach-o/dyld.h>
#import <mach-o/dyld_images.h>
#include <mach-o/loader.h>
#include <mach-o/nlist.h>
#import <mach/mach_vm.h>
#import <malloc/malloc.h>
#define STACK_SIZE 0x4000
uint64_t find_library_addr(task_t task, const char *library) {
kern_return_t kr;
// get the dyld info of the task
task_dyld_info_data_t dyld_info;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
kr = task_info(task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
if (kr != KERN_SUCCESS) {
return 0;
}
// get the dyld_all_image_infos struct
struct dyld_all_image_infos image_infos;
mach_vm_size_t size = dyld_info.all_image_info_size;
kr = mach_vm_read_overwrite(task, dyld_info.all_image_info_addr,
dyld_info.all_image_info_size,
(mach_vm_address_t)&image_infos, &size);
if (kr != KERN_SUCCESS) {
return 0;
}
// get the dyld_image_info structs
size_t module_size =
image_infos.infoArrayCount * sizeof(struct dyld_image_info);
struct dyld_image_info *modules =
(struct dyld_image_info *)malloc(module_size);
kr = mach_vm_read_overwrite(task, (mach_vm_address_t)image_infos.infoArray,
module_size, (mach_vm_address_t)modules, &size);
if (kr != KERN_SUCCESS) {
free(modules);
return 0;
}
char buf[512];
size_t bufSize = sizeof(buf);
uint64_t out_addr = 0;
for (int i = 0; i < image_infos.infoArrayCount; i++) {
// get the path of the library
kr = mach_vm_read_overwrite(task,
(mach_vm_address_t)modules[i].imageFilePath,
bufSize, (mach_vm_address_t)buf, &size);
if (kr != KERN_SUCCESS) {
continue;
}
NSString *filename =
[[NSString stringWithUTF8String:buf] lastPathComponent];
// compare the filename with the library name
if ([filename isEqualToString:[NSString stringWithUTF8String:library]]) {
out_addr = (uint64_t)modules[i].imageLoadAddress;
break;
}
}
free(modules);
return out_addr;
}
// default on 64bit
uint64_t find_symbol_addr(task_t task, mach_vm_address_t library_header_address,
const char *symbol) {
kern_return_t kr;
// get the macho header of the library
struct mach_header_64 header;
mach_vm_size_t size = sizeof(header);
kr = mach_vm_read_overwrite(task, library_header_address, size,
(mach_vm_address_t)&header, &size);
if (kr != KERN_SUCCESS) {
return 0;
}
// get the dyld info of the task
task_dyld_info_data_t dyld_info;
mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT;
kr = task_info(task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count);
if (kr != KERN_SUCCESS) {
return 0;
}
// get the dyld_all_image_infos struct
struct dyld_all_image_infos image_infos;
size = dyld_info.all_image_info_size;
kr = mach_vm_read_overwrite(task, dyld_info.all_image_info_addr,
dyld_info.all_image_info_size,
(mach_vm_address_t)&image_infos, &size);
if (kr != KERN_SUCCESS) {
return 0;
}
struct segment_command_64 seg_linkedit;
struct segment_command_64 seg_text;
struct symtab_command symtab;
// iterate over the load commands
mach_vm_address_t load_command_address =
library_header_address + sizeof(header);
for (uint32_t i = 0; i < header.ncmds; i++) {
// get the load command
struct load_command command;
size = sizeof(command);
kr = mach_vm_read_overwrite(task, load_command_address, size,
(mach_vm_address_t)&command, &size);
if (kr != KERN_SUCCESS) {
continue;
}
// get the segment command
if (command.cmd == LC_SEGMENT_64) {
struct segment_command_64 seg_command;
size = sizeof(seg_command);
// read the segment command
kr = mach_vm_read_overwrite(task, load_command_address, size,
(mach_vm_address_t)&seg_command, &size);
if (kr != KERN_SUCCESS) {
continue;
}
if (strcmp(seg_command.segname, SEG_TEXT) == 0) {
size = sizeof(seg_text);
kr = mach_vm_read_overwrite(task, load_command_address, size,
(mach_vm_address_t)&seg_text, &size);
if (kr != KERN_SUCCESS) {
return 0;
}
} else if (strcmp(seg_command.segname, SEG_LINKEDIT) == 0) {
size = sizeof(seg_linkedit);
kr = mach_vm_read_overwrite(task, load_command_address, size,
(mach_vm_address_t)&seg_linkedit, &size);
if (kr != KERN_SUCCESS) {
return 0;
}
}
}
if (command.cmd == LC_SYMTAB) {
size = sizeof(symtab);
kr = mach_vm_read_overwrite(task, load_command_address, size,
(mach_vm_address_t)&symtab, &size);
if (kr != KERN_SUCCESS) {
return 0;
}
}
// next load command
load_command_address += command.cmdsize;
}
// TODO
uint64_t file_slide =
seg_linkedit.vmaddr - seg_text.vmaddr - seg_linkedit.fileoff;
// symtab.stroff -> 字符串表在 Mach-O 文件中的偏移
uint64_t strings = library_header_address + symtab.stroff + file_slide;
// symtab.symoff -> 符号表在 Mach-O 文件中的偏移
mach_vm_address_t sym_addr =
library_header_address + symtab.symoff + file_slide;
char name[512];
size = sizeof(name);
// TODO
// <https://developer.apple.com/documentation/kernel/nlist_64>
struct nlist_64 sym;
mach_vm_size_t sym_size = sizeof(sym);
mach_vm_address_t symname_offset;
// iterate over the symbols
for (uint32_t i = 0; i < symtab.nsyms; i++) {
// get the symbol
kr = mach_vm_read_overwrite(task, sym_addr, sym_size,
(mach_vm_address_t)&sym, &sym_size);
if (kr != KERN_SUCCESS) {
continue;
}
// get the symbol name
symname_offset = strings + sym.n_un.n_strx;
kr = mach_vm_read_overwrite(task, symname_offset, size,
(mach_vm_address_t)name, &size);
if (kr != KERN_SUCCESS) {
continue;
}
// compare the symbol name with the symbol we are looking for
if (strcmp(name, symbol) == 0) {
return (uint64_t)sym.n_value + image_infos.sharedCacheSlide;
}
sym_addr += sym_size;
}
return 0;
}
uint32_t copyBits(uint32_t reg, uint16_t value) {
for (int i = 0; i <= 15; i++) {
BOOL bitToSet = ((value >> i) & 1) != 0;
reg &= ~(1 << (i + 5));
reg |= (bitToSet ? 1 : 0) << (i + 5);
}
return reg;
}
void write_instruction_address(uint8_t *code, uint length, uint32_t v,
uint offset) {
uint32_t instructions;
NSData *instructionData = [NSData dataWithBytes:&code[length + offset]
length:4];
[instructionData getBytes:&instructions length:4];
instructions = copyBits(instructions, (uint16_t)(v & 0xFFFF));
memcpy(&code[length + offset], &instructions, 4);
}
NSData *genasm(uint64_t dlopen) {
#if defined(__arm64__)
uint8_t code[136] = {
0xFD, 0x7B, 0xBD, 0xA9, 0xF5, 0x0B, 0x00, 0xF9, 0xF4, 0x4F, 0x02, 0xA9,
0xFD, 0x03, 0x00, 0x91, 0x02, 0x4C, 0x40, 0xA9, 0x08, 0x50, 0x41, 0xA9,
0x15, 0x10, 0x40, 0xF9, 0xBF, 0x0F, 0x00, 0xF9, 0xE3, 0x03, 0x01, 0xAA,
0xE1, 0x03, 0x1F, 0xAA, 0xA0, 0x63, 0x00, 0x91, 0x00, 0x01, 0x3F, 0xD6,
0xEA, 0x03, 0x00, 0xAA, 0xA0, 0x0F, 0x40, 0xF9, 0xEB, 0x03, 0x00, 0xAA,
0x60, 0x02, 0x3F, 0xD6, 0xA0, 0x02, 0x3F, 0xD6, 0x80, 0x02, 0x3F, 0xD6,
0xA0, 0x0F, 0x40, 0xF9, 0xF4, 0x4F, 0x42, 0xA9, 0xF5, 0x0B, 0x40, 0xF9,
0xFD, 0x7B, 0xC3, 0xA8, 0xC0, 0x03, 0x5F, 0xD6, 0x1F, 0x20, 0x03, 0xD5,
0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5, 0x1F, 0x20, 0x03, 0xD5,
0x1F, 0x20, 0x03, 0xD5, 0x41, 0x00, 0x80, 0x52, 0xE2, 0xDD, 0x97, 0xD2,
0xA2, 0xD5, 0xBB, 0xF2, 0x02, 0x00, 0xD6, 0xF2, 0x02, 0x00, 0xF4, 0xF2,
0x40, 0x00, 0x1F, 0xD6};
int codeSize = sizeof(code);
uint32_t beef = (uint32_t)(dlopen & 0x000000000000FFFF);
uint32_t dead = (uint32_t)((dlopen & 0x00000000FFFF0000) >> 16);
uint32_t b000 = (uint32_t)((dlopen & 0x0000FFFF00000000) >> 32);
uint32_t a000 = (uint32_t)((dlopen & 0xFFFF000000000000) >> 48);
write_instruction_address(code, codeSize, a000, -8);
write_instruction_address(code, codeSize, b000, -12);
write_instruction_address(code, codeSize, dead, -16);
write_instruction_address(code, codeSize, beef, -20);
return [NSData dataWithBytes:code length:codeSize];
#endif
}
int main(int argc, char *argv[]) {
if (argc < 3) {
printf("Usage: %s <pid> <path>\\n", argv[0]);
return 1;
}
pid_t pid = atoi(argv[1]);
char *path = argv[2];
@autoreleasepool {
task_t rtask;
kern_return_t kr;
kr = task_for_pid(mach_task_self(), pid, &rtask);
if (kr != KERN_SUCCESS) {
printf("[-] Failed to get task port for pid:%d, error: %s\\n", pid,
mach_error_string(kr));
return 1;
}
printf("[+] Got access to the task port of process: %d\\n", pid);
uint64_t libdyld = find_library_addr(rtask, "libdyld.dylib");
uint64_t libsystem_pthread =
find_library_addr(rtask, "libsystem_pthread.dylib");
uint64_t libsystem_kernel =
find_library_addr(rtask, "libsystem_kernel.dylib");
if (libdyld == 0 || libsystem_pthread == 0 || libsystem_kernel == 0) {
printf("[-] Failed to find libraries\\n");
return 1;
}
printf("[+] Found libdyld.dylib at: 0x%llx\\n", libdyld);
printf("[+] Found libsystem_pthread.dylib at: 0x%llx\\n", libsystem_pthread);
printf("[+] Found libsystem_kernel.dylib at: 0x%llx\\n", libsystem_kernel);
uint64_t dlopen = find_symbol_addr(rtask, libdyld, "_dlopen");
uint64_t pthread_create_from_mach_thread = find_symbol_addr(
rtask, libsystem_pthread, "_pthread_create_from_mach_thread");
uint64_t pthread_set_self =
find_symbol_addr(rtask, libsystem_pthread, "__pthread_set_self");
uint64_t thread_suspend =
find_symbol_addr(rtask, libsystem_kernel, "_thread_suspend");
uint64_t mach_thread_self =
find_symbol_addr(rtask, libsystem_kernel, "_mach_thread_self");
if (dlopen == 0 || pthread_create_from_mach_thread == 0 ||
pthread_set_self == 0 || thread_suspend == 0 || mach_thread_self == 0) {
printf("[-] Failed to find symbols\\n");
return 1;
}
printf("[+] Found _dlopen at: 0x%llx\\n", dlopen);
printf("[+] Found _pthread_create_from_mach_thread at: 0x%llx\\n",
pthread_create_from_mach_thread);
printf("[+] Found __pthread_set_self at: 0x%llx\\n", pthread_set_self);
printf("[+] Found _thread_suspend at: 0x%llx\\n", thread_suspend);
printf("[+] Found _mach_thread_self at: 0x%llx\\n", mach_thread_self);
mach_vm_address_t remote_path;
printf("[+] Dylib path: %s length: %lu\\n", path, strlen(path));
mach_vm_allocate(rtask, &remote_path, strlen(path), VM_FLAGS_ANYWHERE);
mach_vm_write(rtask, remote_path, (vm_offset_t)path, strlen(path));
mach_vm_protect(rtask, remote_path, strlen(path), 0,
VM_PROT_READ | VM_PROT_WRITE);
printf("[+] Generated asm done\\n");
NSData *codeasm = genasm(dlopen);
printf("[+] Wrote asm to remote process\\n");
mach_vm_address_t remote_code;
mach_vm_allocate(rtask, &remote_code, [codeasm length], VM_FLAGS_ANYWHERE);
mach_vm_write(rtask, remote_code, (vm_offset_t)[codeasm bytes],
[codeasm length]);
mach_vm_protect(rtask, remote_code, [codeasm length], 0,
VM_PROT_READ | VM_PROT_EXECUTE);
printf("[+] Wrote parameters to remote process\\n");
uint64_t parameters[] = {
remote_code + ([codeasm length] - 24),
pthread_set_self,
pthread_create_from_mach_thread,
thread_suspend,
mach_thread_self,
};
mach_vm_address_t remote_parameters;
mach_vm_allocate(rtask, &remote_parameters, sizeof(parameters),
VM_FLAGS_ANYWHERE);
mach_vm_write(rtask, remote_parameters, (vm_offset_t)parameters,
sizeof(parameters));
printf("[+] Created remote stack\\n");
mach_vm_address_t remote_stack;
mach_vm_allocate(rtask, &remote_stack, STACK_SIZE, VM_FLAGS_ANYWHERE);
mach_vm_protect(rtask, remote_stack, STACK_SIZE, TRUE,
VM_PROT_READ | VM_PROT_WRITE);
mach_vm_address_t local_stack = remote_stack;
remote_stack += STACK_SIZE / 2;
#if defined(__arm64__)
arm_thread_state64_t remote_state;
remote_state.__x[0] = remote_parameters;
remote_state.__x[1] = remote_path;
remote_state.__pc = remote_code;
remote_state.__sp = remote_stack;
remote_state.__lr = local_stack;
printf("[+] Created remote arm64 thread\\n");
thread_act_t thread;
thread_create_running(rtask, ARM_THREAD_STATE64,
(thread_state_t)&remote_state,
ARM_THREAD_STATE64_COUNT, &thread);
#endif
}
return 0;
}
主要实现了 “frida-objc-bridge” 的 choose 功能,其他的功能 objc runtime 自带的。
// @Base: <https://gist.github.com/samdmarshall/17f4e66b5e2e579fd396>
#import <Foundation/Foundation.h>
#import <mach/mach_vm.h>
#import <malloc/malloc.h>
#import <objc/message.h>
#import <objc/runtime.h>
#if defined(__arm64__)
#define OBJC_ISA_MASK 0xffffffff8ULL
#elif defined(__i386__) // TODO
#define OBJC_ISA_MASK 0x7ffffffffff8ULL
#endif
// TODO
#define CLASS "TEST"
Class cls;
size_t cls_size;
void CanHasObjects(task_t task, void *context, unsigned type,
vm_range_t *ranges, unsigned count) {
unsigned i;
for (i = 0; i < count; i++) {
vm_range_t *range = &ranges[i];
uintptr_t *address = ((uintptr_t *)range->address);
uintptr_t *isa;
if (address == NULL) {
continue;
}
isa = (uintptr_t *)address[0];
#ifdef OBJC_ISA_MASK
isa = (uintptr_t *)((unsigned long long)isa & OBJC_ISA_MASK);
#endif
if (isa > 0 && range->size >= sizeof(Class) && cls == (Class)isa) {
#ifdef DEBUG
printf("[+] fond isa(%p)->'%s' instance %p \\n", isa,
object_getClassName((Class)isa), address);
#endif
// TODO
((void (*)(id, SEL))objc_msgSend)((__bridge id)address,
@selector(dododo));
}
}
}
static void __attribute__((constructor)) initialize(void) {
@autoreleasepool {
cls = NSClassFromString([NSString stringWithFormat:@"%s", CLASS]);
if (cls == Nil) {
#ifdef DEBUG
printf("[-] Class not found\\n");
#endif
return;
}
cls_size = class_getInstanceSize(cls);
if (cls_size == 0) {
#ifdef DEBUG
printf("[-] Class Instance size is %zu\\n", cls_size);
#endif
return;
}
#ifdef DEBUG
printf("[+] Class %p Instance size is %zu\\n", cls, cls_size);
#endif
vm_address_t *zones;
unsigned count, i = 0;
kern_return_t r =
malloc_get_all_zones(mach_task_self(), NULL, &zones, &count);
if (r == KERN_SUCCESS) {
for (i = 0; i < count; i++) {
vm_address_t zone_address = zones[i];
malloc_zone_t *zone = (malloc_zone_t *)zone_address;
if (zone != NULL && zone->introspect != NULL) {
zone->introspect->enumerator(mach_task_self(), NULL,
MALLOC_PTR_IN_USE_RANGE_TYPE,
zone_address, NULL, &CanHasObjects);
}
}
}
}
}
hacking for fun!