4

The idea of LD_PRELOAD is to load a shared library before the original shared library, for example I can compile mylib.so to load it before libc.so, so when process wants to use printf it searches in the so that loaded one by one and finds it in mylib.so (because this so was loaded first) instead of libc.so, so it uses the printf in mylib.so instead of printf in libc.so.

I understand why this works on functions that are implemented in a so like printf in libc.so.

But when I want to hook on write function or another syscall function, why does it work? The process does not search the function in the so, it goes directly to the kernel.

  1. Does LD_PRELOAD work on a binary that is compiled statically? Why? In this replay https://stackoverflow.com/a/13866611 mentioned that LD PRELOAD Doesn't work on statically binary

  2. Why does LD_PRELOAD work on a binary compiled dynamically to make hooks on syscalls?

The architecture is ARM.

Eric Postpischil
  • 195,579
  • 13
  • 168
  • 312
yfr24493AzzrggAcom
  • 159
  • 1
  • 2
  • 13
  • When you code calls `write` it is not directly calling a syscall. It is calling a wrapper that sets up the syscall and then traps into the kernel. So LD_PRELOAD can work by overriding the wrapper call. – kaylum Feb 28 '20 at 10:56
  • @kaylum are you sure, write is syscall, that not wrapped , I can see it in assembly.So LD_PLELOAD need to work on statically binary too?? – yfr24493AzzrggAcom Feb 28 '20 at 11:12
  • There is a syscall called write but that's not what you are directly invoking. there are syscall wrappers in libc. The preload only works with dynamically linked execs. – kaylum Feb 28 '20 at 11:16
  • I've adjusted the wording of your question to make it clearer, let me know if that's ok. – Marco Bonelli Feb 28 '20 at 11:32
  • Short answer: you've been lied to. OK, that's a bit strong, but the distinction between "syscalls" and "library functions" is artificial and not really accurate. They're all actually library functions, and what are called "syscalls" no longer really map directly to actual system calls - the actual system calls have changed. `open()`, for example, is likely making the `openat()` system call. – Andrew Henle Feb 28 '20 at 12:36
  • This [Q/A on thumb start function](https://stackoverflow.com/questions/20369440/can-start-be-the-thumb-function) has custom function to define syscalls, etc in your code. You can also statically link some libraries and make others dynamic by using partial linking. And also [static library and arm cross compile](https://stackoverflow.com/questions/24616226/how-can-i-select-a-static-library-to-be-linked-while-arm-cross-compiling/24619213#24619213) specifically has a `write()` implementation and a relevant topic. – artless noise Feb 28 '20 at 16:23

1 Answers1

7

The process does not search the function in the so, it goes directly to the kernel.

Wrong, the syscall functions you use (read(), write(), ...) are all libc wrappers around the real syscalls, even the generic syscall() function. Here's the code for the write() function for example. The only way to go directly into kernel from your program is by manually issuing a syscall (see how below).

Does LD_PRELOAD work on a binary that is compiled statically? Why?

No, it doesn't. A static binary has no need to resolve any symbol dynamically, and therefore the dynamic loader is not invoked to resolve the usual library function symbols.

Why does LD_PRELOAD work on a binary compiled dynamically to make hooks on syscalls?

Because those are just normal libc functions, nothing more, nothing less.


The only way of manually calling a syscall without passing through the C library (and therefore without doing symbol resolution) other than compiling statically is to use inline assembly. You can take a look at man 2 syscall to see which registers and instructions to use. On ARM AArch64 for example you can invoke a syscall by loading the syscall number in the register x8, parameters in x0 through x5, and then executing the svc #0 instruction.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
  • So if in my code I will call to `write` directly with `syscall` like `syscall(WRITE_SYSCALL,fd, buf,nbytes);` nobady can apply `LD_PRELOAD` on this process even if it compile dinamicly ? Is my explain write? That LD_PRELOAD puts my so before any so so the loader find my function first? – yfr24493AzzrggAcom Feb 28 '20 at 12:07
  • No. `syscall()` is just another libc *wrapper*. As I said, the only way to do this is to either (1) link statically or (2) manually write assembly. If you don't want library functions to be hooked when linking dynamically, then *don't use library functions*. – Marco Bonelli Feb 28 '20 at 12:13
  • thank you (1) Is my explain write? That LD_PRELOAD puts my so before any so, so the loader find my function first, instead of the original function? (2) can I see in /proc/pid that special function resolved by `mylib.so` ? – yfr24493AzzrggAcom Feb 28 '20 at 12:31
  • (1) yes that's right. (2) `/proc/pid/*` does not really help you. If you want to see that the function is resolved through `mylib.so` you should compile with debugging info (`-g`), then use `gdb` to start the process and do `next` until your function is called. After it's called, check its address with `p funcname`, then check `info proc mappings` to see if the address is inside `mylib.so`. – Marco Bonelli Feb 28 '20 at 12:34
  • Another way you can bypass LD_PRELOAD intercepts is to explicitly dlopen the library you want, dlsym to the function you want and call it directly. – Gem Taylor Feb 28 '20 at 16:36
  • Of course the LD_PRELOAD could intercept dlopen and dlsym and lie to you, but that would be difficult to do in a way that didn't impact other users of the methods. – Gem Taylor Feb 28 '20 at 16:41