rodionovd / task_vaccine

Yet another code injection library for macOS
MIT License
54 stars 8 forks source link

thread_get_ax_register returns 0x0 when target isn't launched through Xcode #16

Open tiagosiebler opened 7 years ago

tiagosiebler commented 7 years ago

Hi @rodionovd, great work on this. I've been experimenting with this vs how mach_inject has been working for me. This is one thing I can't get my head around - I'm doing two tests in this scenario:

  1. Run target app through xcode, attempt to inject with task_vaccine.
  2. Run target app manually through finder, attempt to inject with task_vaccine.

When the target is launched through xcode, it works fine very time. The injection is a success. When I try the same after launching the target app through finder, injection fails. I've narrowed down the issue to the thread_get_ax_register function and added some debugging statements to follow the workflow:

static
int64_t thread_get_ax_register(thread_act_t thread, thread_state_flavor_t flavor) {
    assert(thread), assert(flavor);
    printf("enter thread_get_ax_register\n");
    int64_t ax = 0;
    int err = KERN_FAILURE;

    if (flavor == x86_THREAD_STATE32) {
        printf("thread_get_ax_register - 32 bit handler\n");

        x86_thread_state32_t state;
        mach_msg_type_number_t count = x86_THREAD_STATE32_COUNT;
        printf("thread_get_ax_register - getting state\n");

        err = thread_get_state(thread, x86_THREAD_STATE32,
            (thread_state_t) & state, & count);

        if (err) printf("thread_get_ax_register - error: %d\n", err);
        VaccineFailOnError(thread_get_state);
        int32_t tmp = (int32_t) state.__eax;
        ax = tmp;
    } else {
        printf("thread_get_ax_register - 64 bit handler\n");
        x86_thread_state64_t state;
        mach_msg_type_number_t count = x86_THREAD_STATE64_COUNT;
        printf("thread_get_ax_register - getting state\n");
        err = thread_get_state(thread, x86_THREAD_STATE64,
            (thread_state_t) & state, & count);
        if (err) printf("thread_get_ax_register - error: %d\n", err);

        VaccineFailOnError(thread_get_state);
        int64_t tmp = (int64_t) state.__rax;
        ax = tmp;

    }

    printf("exit thread_get_ax_register - ax: 0x%llx\n", ax);
    return ax;
}

This is the output I get when it fails:

enter thread_get_ax_register
thread_get_ax_register - 64 bit handler
thread_get_ax_register - getting state
exit thread_get_ax_register - ax: 0x0

When successful, ax will always contain a value. So I'm guessing something about thread_get_state isn't working as expected? Any ideas? Do you need a ready-to-go reproduction sample? Thanks!

tiagosiebler commented 7 years ago

Set a breakpoint on ax=tmp to inspect the x86_thread_state64_t value

(lldb) print state
(x86_thread_state64_t) $0 = {
  __rax = 0
  __rbx = 0
  __rcx = 0
  __rdx = 0
  __rdi = 0
  __rsi = 0
  __rbp = 0
  __rsp = 0
  __r8 = 0
  __r9 = 0
  __r10 = 0
  __r11 = 0
  __r12 = 0
  __r13 = 0
  __r14 = 0
  __r15 = 0
  __rip = 0
  __rflags = 512
  __cs = 43
  __fs = 0
  __gs = 0
}
rodionovd commented 7 years ago

Hey @tiagosiebler! Thanks for getting in touch.

When successful, ax will always contain a value. So I'm guessing something about thread_get_state isn't working as expected?

Nah, the point of thread_get_ax_register() is to simply read an injection status value which is either 0 (means error) or 1 (success). So the problem is 100% somewhere else in the injection process workflow. @smic has already reported a problem with injecting into 64 processes in Sierra here: #15. I guess you might want to cooperate on this one :)

tiagosiebler commented 7 years ago

@rodionovd thanks! I'll check on that thread, maybe he managed to get further. It's odd it's happening only when the target isn't run through xcode, otherwise it works smoothly. Any diagnostic ideas are appreciated.

rodionovd commented 7 years ago

@tiagosiebler I believe Xcode injects its own stuff into a target for debugging — this may introduce a loophole for us as well

tiagosiebler commented 7 years ago

@rodionovd okay this is weirder, task_vaccine is failing with KERN_INVALID_TASK, but it turns out that the injection was actually still a success. My constructor is called, and the hooks are deployed successfully. I see my printf statements when manually launching the target through console, yet task_vaccine still thinks the injection failed, even though it did work correctly.

Inside task_vaccine, task_loadlib is returning 0. task_loadlib is returning 0 because thread_get_ax_register is returning 0. Everything still seems to point to this line: return_value = thread_get_ax_register(remote_thread, flavor);

Any ideas? It's great that it seems to be working, though I'd love to get error handling reliable.