csystem-callsmethod-swizzling

issue while intercepting and replacing syscall in C


I'm trying to intercept all "syscall"s for my code. I'm able to receive the calls, but for some reason my implementation of syscall is not equivalent, because at some point the program crashes if I activate the replacement, while it works without the replacement.

The reason why I'm doing this is to understand better which calls in my program (and its dependencies) use which syscall's. I know I could use things like ptrace, etc. but now for me it's really more about understanding why this does not work. This is running on arm64 linux.

Here my syscall replacement implementation:

long syscall(long number, ...){
    printf("Intercepted syscall: %lu\n", number);
    va_list original_args;
    va_start(original_args, number);

    long (*original_syscall)(long number, ...) = NULL;
    original_syscall = (long (*)(long, ...))dlsym(RTLD_NEXT, "syscall");
    long result = original_syscall(number, original_args);
    va_end(original_args);
    return result;
}

Here the logs:

Intercepted syscall: 178
Intercepted syscall: 178
Intercepted syscall: 178
Intercepted syscall: 178
Intercepted syscall: 178
Intercepted syscall: 57
Fatal signal 6 (SIGABRT), code -1 (SI_QUEUE) in tid 3606 (Thread-2)

Solution

  • What you want to do is not very easy to do in C. You would have to manually parse the argument list (because there is no standard way to redirect a va_list to a variadic function).

    It is probably easier to use assembly (not tested):

    message:
        .asciz "Intercepted syscall: %lu\n"
    func:
        .asciz "syscall"
    
    syscall:
        stp x29, x30, [sp, -80]!
        stp x0, x1, [sp, 16]
        stp x2, x3, [sp, 32]
        stp x4, x5, [sp, 48]
        stp x6, x7, [sp, 64]
        mov x1, x0
        adr x0, message
        bl printf
        adr x1, func
        mov x0, -1
        bl dlsym
        mov x8, x0
        ldp x6, x7, [sp, 64]
        ldp x4, x5, [sp, 48]
        ldp x2, x3, [sp, 32]
        ldp x0, x1, [sp, 16]
        ldp x29, x30, [sp], 80
        br x0
    

    The idea being that you leave the argument list intact (preserving all possible register arguments x0-x7) and simply jump to the original system call function (via br). Since the return address is already in x30, the function will return to our caller and not to us. This is an optimization that compilers already apply to wrappers, but unfortunately there's no way to forward the varargs list to another variadic function in C.

    As @stark mentioned, the printf may perform a write, which can lead to infinite recursion (especially because stdout is line-buffered and the \n flushes it). The only real "safe" place where you can attempt to log your data is memory (and try to write it later), but that brings a whole lot of headaches by itself so I wouldn't recommend it.