Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding execve() with LD_PRELOAD only works sometimes

Tags:

c

linux

I want to override the execve() syscall by using LD_PRELOAD and can't figure out why it sometimes works and sometimes doesn't.

Consider this very simple code overriding execve() (I'll keep it complete so you can try it if you like):

#define _GNU_SOURCE
#include <unistd.h>
#include <dlfcn.h>
typedef ssize_t (*execve_func_t)(const char* filename, char* const argv[], char* const envp[]);
static execve_func_t old_execve = NULL;
int execve(const char* filename, char* const argv[], char* const envp[]) {
    printf("Running hook\n");
    old_execve = dlsym(RTLD_NEXT, "execve");
    return old_execve(filename, argv, envp);
}

(compile with: gcc -std=c99 -o exec.so -shared exec.c -Wall -Wfatal-errors -fPIC -g -ldl)

and this very simple test program:

#define _GNU_SOURCE
#include <unistd.h>
#include <dlfcn.h>
int main() {
    char* args[] = {"ls", "/usr", NULL};
    char* envp[] = {"LD_PRELOAD=/path/to/exec.so", NULL};
    execve("/usr/bin/ls", args, envp);
    return 0;
}

Now, when I do export LD_PRELOAD=/path/to/exec.so in my shell, I would expect any binary I run to first execute the hook. That is not true already, which confuses me: edit: Ok, this part is clear now. The issue below is still unsolved.

» strace -f -e trace=execve ./test
execve("./test", ["./test"], [/* 58 vars */]) = 0
Running hook
execve("/usr/bin/ls", ["ls", "/usr"], [/* 1 var */]) = 0
arm-none-eabi  avr  bin  games  include  lib  lib32  lib64  libexec  local  python  sbin  share  src  usr  x86_64-pc-linux-gnu
+++ exited with 0 +++

As you see, the hook is only run for the second execve, not for the first.

Still unclear: What confuses me even more however is that in some cases, the code is not preloaded ever, not even for the child processes; for example, when running ls /usr with Python's subprocess module, this happens:

» strace -f -e trace=execve /usr/bin/python -c "import subprocess; subprocess.Popen(['ls', '/usr'])"
execve("/usr/bin/python", ["/usr/bin/python", "-c", "import subprocess; subprocess.Po"...], [/* 58 vars */]) = 0
strace: Process 8350 attached
[pid  8350] execve("/usr/local/sbin/ls", ["ls", "/usr"], [/* 58 vars */]) = -1 ENOENT (No such file or directory)
[pid  8350] execve("/usr/local/bin/ls", ["ls", "/usr"], [/* 58 vars */]) = -1 ENOENT (No such file or directory)
[pid  8350] execve("/usr/bin/ls", ["ls", "/usr"], [/* 58 vars */]) = 0
arm-none-eabi  avr  bin  games  include  lib  lib32  lib64  libexec  local  python  sbin  share  src  usr  x86_64-pc-linux-gnu
[pid  8350] +++ exited with 0 +++
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=8350, si_uid=1000, si_status=0, si_utime=0, si_stime=0} ---
+++ exited with 0 +++

How is that possible? It's the exact same syscall with the exact same environment for the calling process, but it does something different. I'd be happy about any pointers about this.

like image 475
Sven Brauch Avatar asked Sep 03 '25 02:09

Sven Brauch


1 Answers

Ok, the solution is actually quite simple: Python calls execv, not execve; and the standard output printed is taken up by the code calling the process and not printed to the terminal. That's why it appears to not work (while it actually does).

like image 67
Sven Brauch Avatar answered Sep 04 '25 17:09

Sven Brauch