You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 17:15:36