nisargjhaveri
nisargjhaveri

Reputation: 1509

Interposing or wrapping syscall made from a linked framework

I have a executable, which links to one system framework on Mac. The system framework makes some system calls. I'm interested in socket and connect calls, to redirect the socket connection to some other address. I only need this to work for my executable, I don't care about injecting it in other processes.

I've experimented creating another dylib and using DYLD_INTERPOSE with DYLD_INSERT_LIBRARIES with my executable. It is fairly simple and seems to work as expected.

Though, I'd prefer not having another dylib and having a standalone executable that can achieve this. Is there any way to do this?

I saw some references to --wrap option in gcc linker, and -alias option in OS X linker, but not sure how and if they can be used to achieve this.

If not these options what else can I try?

Upvotes: 1

Views: 332

Answers (1)

Siguza
Siguza

Reputation: 23880

Dyld interposing only works at import boundaries. If you're on any semi-recent version of macOS, then all system libraries and framework exist inside the "dyld shared cache", which means there is not gonna be any import boundaries between your target framework and libsystem_kernel.dylib.

You basically have two options:

  1. Patch the system framework.
    With the com.apple.security.cs.allow-unsigned-executable-memory entitlement, you should be able to:

    • mach_vm_protect(..., VM_PROT_READ|VM_PROT_WRITE|VM_PROT_COPY)
    • apply your patches
    • sys_dcache_flush(...)
    • mach_vm_protect(..., VM_PROT_READ|VM_PROT_EXECUTE)
    • sys_icache_invalidate(...)

    This allows you to do basically anything. Just be sure to not use the specific code on those pages while patching it. Due to the entitlement requirement, this will only work on macOS.

  2. Use hardware breakpoints.
    As long as your process still only has one thread, you can use task_set_state() to set up a debug state that will be copied to all new threads, and install an exception handler for debug exceptions.
    Here you are quite constrained by the number of hardware breakpoints available (6 on Apple Silicon) and it's slower due to the whole mach message handler, but it's less invasive and it can work on iOS too.

In either case you'll have to decide how you want to go about this. If you can afford to hook socket and connect themselves, then that's gonna be simple: just dlsym() them and you know where they are.
If you have a more complex codebase where lots of things call socket and connect and you can't filter by arguments, then you'll likely want to hook at the callsite. And that will most likely involve finding the mach_header of the framework in question and parsing it to find the segment bounds. If your code only has to work on macOS 13 and up, then you can use _dyld_get_dlopen_image_header with a handle from dlopen() - otherwise you'll have to look up a known exported symbol with dlsym(), then use dladdr() on that to get the binary header.

Upvotes: 6

Related Questions