在Rosetta下向x86_64进程注入Dylib的兼容方案咨询
问题描述
我的主机架构为aarch64,需要向仅支持x86_64指令集的进程注入动态库。我尝试使用以下C++代码进行注入:
#define CHKERR(x) if (kr != KERN_SUCCESS) {std::cout << kr << std::endl; return x;}; #define STACK_SIZE 0x1000 #define asm_pthread_offset 6 #define asm_dylib_offset 19 #define asm_dlopen_offset 39 #define asm_mach_thread_self_offset 51 #define asm_thread_suspend_offset 66 inject_result inject_dylib(int pid, const char *dylib_path) { task_t remoteTask; struct stat buf; // check if the dynamic library exists... int check = stat(dylib_path, &buf); if (check != 0) return INJECT_ERROR_NOT_FOUND; mach_error_t kr = 0; // request the task port of the target process... kr = task_for_pid(mach_task_self(), pid, &remoteTask); CHKERR(INJECT_ERROR_MACH_TASK); // allocate space for library path in the task mach_vm_address_t dylib_address; kr = mach_vm_allocate(remoteTask, &dylib_address, strlen(dylib_path) + 1, 1); CHKERR(INJECT_ERROR_GENERIC) // write library path into the task kr = mach_vm_write(remoteTask, dylib_address, (vm_offset_t)dylib_path, strlen(dylib_path)+1); CHKERR(INJECT_ERROR_GENERIC) mach_vm_address_t stack_address; kr = mach_vm_allocate(remoteTask, &stack_address, STACK_SIZE, 1); CHKERR(INJECT_ERROR_STACK_ALLOC) unsigned char asm_instructions[ 100 ] = "\\x55" // push %rbp "\\x48\\x89\\xe5" // mov %rbp, %rsp "\\x48\\xb8\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00" // mov %rax, _pthread_set_self "\\xff\\xd0" // call %rax "\\x5d" // pop %rbp "\\x48\\xbf\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00" // mov %rdi, dylib_address "\\x48\\xbe\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00" // mov %rsi, 2 "\\x48\\xb8\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00" // mov %rax, dlopen "\\xff\\xd0" // call %rax "\\x48\\xb8\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00" // mov %rax, mach_thread_self "\\xff\\xd0" // call %rax "\\x48\\x89\\xc7" // mov %rdi, %rax "\\x48\\xb8\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00" // mov %rax, thread_suspend "\\xff\\xd0" // call %rax ; // allocate space for assembly instructions... mach_vm_address_t code_address; kr = mach_vm_allocate(remoteTask, &code_address, sizeof(asm_instructions), 1); CHKERR(INJECT_ERROR_CODE_ALLOC) // set some values in our assembly instructions... mach_vm_address_t pthread_set_self_address = (mach_vm_address_t) dlsym(RTLD_DEFAULT, "_pthread_set_self"); mach_vm_address_t mach_thread_self_address = (mach_vm_address_t) mach_thread_self; mach_vm_address_t thread_suspend_address = (mach_vm_address_t) thread_suspend; mach_vm_address_t dlopen_address = (mach_vm_address_t) dlopen; memcpy(&asm_instructions[asm_pthread_offset], &pthread_set_self_address, sizeof(mach_vm_address_t)); memcpy(&asm_instructions[asm_dylib_offset], &dylib_address, sizeof(mach_vm_address_t)); memcpy(&asm_instructions[asm_dlopen_offset], &dlopen_address, sizeof(mach_vm_address_t)); memcpy(&asm_instructions[asm_mach_thread_self_offset], &mach_thread_self_address, sizeof(mach_vm_address_t)); memcpy(&asm_instructions[asm_thread_suspend_offset], &thread_suspend_address, sizeof(mach_vm_address_t)); kr = mach_vm_write(remoteTask, code_address, (vm_offset_t)asm_instructions, sizeof(asm_instructions)); CHKERR(INJECT_ERROR_GENERIC) kr = mach_vm_protect(remoteTask, code_address, sizeof(asm_instructions), 0, VM_PROT_EXECUTE | VM_PROT_READ); CHKERR(INJECT_ERROR_GENERIC) // create thread, set registers, and start thread_t thread = {0}; x86_thread_state64_t thread_state = {0}; thread_state.__rip = code_address; thread_state.__rdi = stack_address; thread_state.__rsp = stack_address; thread_state.__rbp = stack_address; kr = thread_create_running(remoteTask, x86_THREAD_STATE64, (thread_state_t) &thread_state, x86_THREAD_STATE64_COUNT, &thread); CHKERR(INJECT_ERROR_CREATE_THREAD) mach_port_deallocate(mach_task_self(), remoteTask); return INJECT_SUCCESS; }
调用thread_create_running函数时持续返回错误4(KERN_INVALID_ARGUMENT),原因是arm64版本的XNU内核不支持将x86_THREAD_STATE64作为线程状态类型。查看内核源码确认了该问题,其中x86_THREAD_STATE64未被包含在任何switch分支中,默认返回KERN_INVALID_ARGUMENT。请问是否存在兼容的替代函数或注入方法?
解决方案
1. 复用目标进程现有线程执行注入代码
不需要创建新线程,直接修改目标进程中已有的x86_64线程上下文来执行注入代码,这是当前场景下最可靠的方案:
- 调用
task_threads获取目标进程的线程列表 - 选中一个线程后调用
thread_suspend暂停它 - 使用
thread_get_state获取该线程的x86_THREAD_STATE64状态(Rosetta 2会处理跨架构的状态转换) - 保存原有
__rip寄存器值,将其替换为注入代码的地址 - 调用
thread_set_state应用修改后的状态 - 调用
thread_resume恢复线程执行,完成注入后可再恢复原有__rip
示例代码片段:
// 获取目标进程线程列表 thread_array_t threads; mach_msg_type_number_t thread_count; kr = task_threads(remoteTask, &threads, &thread_count); CHKERR(INJECT_ERROR_THREAD_LIST); // 选取第一个线程(可根据实际情况选择合适的线程) thread_t target_thread = threads[0]; // 暂停线程 kr = thread_suspend(target_thread); CHKERR(INJECT_ERROR_THREAD_SUSPEND); // 获取原有线程状态 x86_thread_state64_t orig_state; mach_msg_type_number_t state_count = x86_THREAD_STATE64_COUNT; kr = thread_get_state(target_thread, x86_THREAD_STATE64, (thread_state_t)&orig_state, &state_count); CHKERR(INJECT_ERROR_GET_STATE); // 设置新的执行入口为注入代码地址 x86_thread_state64_t new_state = orig_state; new_state.__rip = code_address; // 应用修改后的线程状态 kr = thread_set_state(target_thread, x86_THREAD_STATE64, (thread_state_t)&new_state, state_count); CHKERR(INJECT_ERROR_SET_STATE); // 恢复线程执行 kr = thread_resume(target_thread); CHKERR(INJECT_ERROR_THREAD_RESUME); // 注:注入代码执行完成后,需要通过同步机制恢复原有__rip,避免影响目标进程正常运行
2. 拆分线程创建与状态设置步骤
不要直接用thread_create_running一步创建并启动线程,而是拆分操作:
- 先调用
thread_create创建线程(不立即运行) - 再用
thread_set_state设置x86_THREAD_STATE64状态 - 最后调用
thread_resume启动线程
示例代码片段:
thread_t thread; // 创建未运行的线程 kr = thread_create(remoteTask, &thread); CHKERR(INJECT_ERROR_CREATE_THREAD); // 配置x86_64线程状态 x86_thread_state64_t thread_state = {0}; thread_state.__rip = code_address; thread_state.__rsp = stack_address; thread_state.__rbp = stack_address; mach_msg_type_number_t state_count = x86_THREAD_STATE64_COUNT; kr = thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)&thread_state, state_count); CHKERR(INJECT_ERROR_SET_STATE); // 启动线程 kr = thread_resume(thread); CHKERR(INJECT_ERROR_RESUME_THREAD);
3. 修正符号地址获取逻辑
你当前直接用dlsym(RTLD_DEFAULT, ...)获取的是主机aarch64架构的函数地址,无法在x86_64进程中使用,必须改为从目标进程的libSystem.dylib中解析正确的x86_64符号地址:
- 解析目标进程的Mach-O头部,找到动态链接库的加载地址
- 遍历符号表,定位
dlopen、_pthread_set_self等函数的实际地址
关键注意事项
- 所有针对x86_64进程的操作,内核都会通过Rosetta 2层处理,不要混用arm64的线程状态类型
- 注入的动态库必须是x86_64架构,否则无法被目标进程加载
task_for_pid需要root权限或对应的系统权限才能调用
内容的提问来源于stack exchange,提问作者Angelo DeLuca
相关产品推荐
相关产品推荐

