Angelo DeLuca
Angelo DeLuca

Reputation: 55

Injecting a Dylib into Processes Running Under Rosetta

I need to inject a dynamic library into a process exclusively targeting the x86_64 instruction set. My host architecture is aarch64.

I attempted injection using the following C++ code...


#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;
}

A problem occurs at the function create_thread_running where it consistently returns error 4 (KERN_INVALID_ARGUMENT). This is because the arm64 version of the XNU kernel does not support x86_THREAD_STATE64 as a thread state flavor.

I confirmed this as the issue by digging through the kernel source. Where you can see that x86_THREAD_STATE64 is not included in any switch case, and it defaults to KERN_INVALID_ARGUMENT.

Are there any compatible alternatives to this function or method of injection?

Upvotes: 1

Views: 320

Answers (0)

Related Questions