ld there is an option for wrapping, quote from the manual :
- wrap symbol
Use the wrapper function for the character. Any undefined reference to a character will be resolved by __wrap_symbol. Any undefined reference to __real_symbol will be resolved to the character. This can be used to provide a wrapper for a system function. The wrapper function should be called __wrap_symbol. If he wants to call a system function, he must call __real_symbol.
It works great with system calls. Here is an example with readlink :
#include <stdio.h> #include <string.h> #include <unistd.h> ssize_t __real_readlink(const char *path, char *buf, size_t bufsiz); ssize_t __wrap_readlink(const char *path, char *buf, size_t bufsiz) { puts("Hello from the wrapped readlink :"); __real_readlink(path, buf, bufsiz); } int main(void) { const char testLink[] = "/usr/bin/gnome-www-browser"; char buf[256]; memset(buf, 0, sizeof(buf)); readlink(testLink, buf, sizeof(buf)-1); puts(buf); }
To pass an option to the linker from the compiler, use the -Wl option:
$ gcc test.c -oa -Wl,
The idea is that __wrap_func is your wrapper of functions. The __real_func component binds to the real func function. And every func call in the code will be replaced with __wrap_func .
UPD: You may notice that a binary compiled statically causes another readlink that is not intercepted. To understand the reason, just do a little experiment - compile the code into an object file and list the characters, for example:
$ gcc test.c -c -o ao -Wl,--wrap=readlink $ nm ao 0000000000000037 T main U memset U puts U readlink U __real_readlink U __stack_chk_fail 0000000000000000 T __wrap_readlink
Interestingly, you will not see links to a bunch of functions that are visible with strace before entering the main function - for example. uname() , brk() , access() , etc. This is because the main function is not the first code that is called in your binary. A bit of research on objdump will show you that the first function is called _start .
Now let's take another example: override the _start function:
$ cat test2.c #include <stdio.h> #include <unistd.h> void _start() { puts("Hello"); _exit(0); } $ gcc test2.c -oa -nostartfiles $ strace ./a execve("./a", ["./a"], [/* 69 vars */]) = 0 brk(0) = 0x150c000 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece55d000 access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory) open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3 fstat(3, {st_mode=S_IFREG|0644, st_size=177964, ...}) = 0 mmap(NULL, 177964, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f3ece531000 close(3) = 0 access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory) open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\320\37\2\0\0\0\0\0"..., 832) = 832 fstat(3, {st_mode=S_IFREG|0755, st_size=1840928, ...}) = 0 mmap(NULL, 3949248, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f3ecdf78000 mprotect(0x7f3ece133000, 2093056, PROT_NONE) = 0 mmap(0x7f3ece332000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1ba000) = 0x7f3ece332000 mmap(0x7f3ece338000, 17088, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3ece338000 close(3) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece530000 mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece52e000 arch_prctl(ARCH_SET_FS, 0x7f3ece52e740) = 0 mprotect(0x7f3ece332000, 16384, PROT_READ) = 0 mprotect(0x600000, 4096, PROT_READ) = 0 mprotect(0x7f3ece55f000, 4096, PROT_READ) = 0 munmap(0x7f3ece531000, 177964) = 0 fstat(1, {st_mode=S_IFCHR|0600, st_rdev=makedev(136, 10), ...}) = 0 mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f3ece55c000 write(1, "Hello\n", 6Hello ) = 6 exit_group(0) = ? +++ exited with 0 +++ $
What was it?! We just redefined the first function in the binary and still see the system calls - why?
In fact, this is because the calls are not made by your application, but rather by the kernel before your application is loaded into memory and allowed to work.
UPD: as we saw earlier, functions are not called by your application. Honestly, I could not find what is done for static binaries after the shell calls execve for your application, but from the list it looks like every call you see made by the kernel itself - without any side application, such as a dynamic linker that are not needed for static binaries (and because there are functions like brk that work with data segments).
Regardless of the fact that you, of course, cannot easily change this behavior, you will need a hack. Since if you can easily override the function for the code that is executed before your binary launch, that is, from another binary file, it will be a big black hole in security, imagine: as soon as you need root privileges, you will redefine the function with one, to execute your code, and wait a while while some root daemon executes the script, and thus launches your code into the game.