frida / frida-core

Frida core library intended for static linking into bindings
https://frida.re
Other
592 stars 187 forks source link

Massage libunwind around Frida hooks #515

Closed mrmacete closed 1 month ago

mrmacete commented 3 months ago

The problem this change intends to solve is that, currently, with code like this:

NSURL * url = nil;
@try {
    [NSBundle bundleWithURL:url];
} @catch (id err) {
    NSLog(@"No worries mate!");
}

if there's any Frida hook (either via Interceptor or via NativeCallback swizzling like ObjC.implement) anywhere in the call stack between the raise and the catch of the exception, libunwind fails to find the exception handler, leading to uncaught exceptions which wouldn't happen without instrumentation.

When libunwind fails to unwind it usually means it can't resolve the unwind info, and it can happen for 2 different reasons:

  1. libunwind asks dyld (via _dyld_find_unwind_sections()) but if the Frida agent is injected then dyld doesn't know about it and libunwind gives up on the first instruction pointer to any Frida agent's code in the stack (like normally happens for swizzling-like hooks)
  2. in case of Interceptor instead, for function-aware hooks, the original return address on the stack is replaced with the one needed to implement onLeave() which belongs to a runtime-generated code thunk therefore missing its mapping to unwind info

The solution proposed here addresses the two above problems directly, via the new UnwindSitter component:

  1. by hooking _dyld_find_unwind_sections to provide the right unwind info for the "invader"
  2. by hooking UnwindCursor::setInfoBasedOnIPRegister() to call gum_invocation_stack_translate() on the return address to restore the original one on the stack at unwind-time

All this can be disabled via the "unwind-sitter:off" frida-agent option.

oleavr commented 1 month ago

Thanks! :raised_hands: Amazing work!! :fire: