
Using LD_PRELOAD to cheat, inject features and investigate programs (2013) - striking
https://rafalcieslak.wordpress.com/2013/04/02/dynamic-linker-tricks-using-ld_preload-to-cheat-inject-features-and-investigate-programs/
======
jwilk
Interesting projects that employ LD_PRELOAD:

[https://github.com/stewartsmith/libeatmydata](https://github.com/stewartsmith/libeatmydata)

[https://github.com/wolfcw/libfaketime](https://github.com/wolfcw/libfaketime)

[https://cwrap.org/](https://cwrap.org/)

[https://github.com/dex4er/fakechroot](https://github.com/dex4er/fakechroot)

[https://blitiri.com.ar/p/libfiu/](https://blitiri.com.ar/p/libfiu/)

[https://github.com/paultag/tmperamental](https://github.com/paultag/tmperamental)

~~~
Fnoord
libfaketime can be used for nefarious things such as using software past its
trial stage (same with using serials attached to MAC addresses + mac spoofing)
or returning future TOTP data. Its generally useful for QA as well. It
could've been used by Apple to avoid the reset bug in iOS 11.1.2

fakechroot is used when you build your own *.deb file.

libeatmydata can be used in environments where you'd destroy the data
afterwards anyway, or where you don't need logging or keep state after a
reboot. You can also mount a bunch of filesystems such as /tmp and /var/log as
tmpfs though.

~~~
rrix2
libfaketime is also still crucial for reproduceable builds with a lot of
compiers embedding compilation date, too, right?

~~~
jwilk
At least Debian folks didn't have to resort to using libfaketime. Instead,
they patch the embedding tools to use timestamp from environment:

[https://reproducible-builds.org/specs/source-date-
epoch/](https://reproducible-builds.org/specs/source-date-epoch/)

------
joshumax
I actually had to use this trick several months ago in order to install Nessus
on my Debian box. The installer was written in C and it called uname() to
check if I was running a "supported kernel version" despite not actuality
installing any kernel module to the system. It simply refused to install until
my fake uname() returned the 3.10.x series kernel in the utsname structure...

~~~
johncolanduoni
It sounds like this wasn't the case in this instance, but applications could
depend on kernel features without loading any kernel modules. The fact that
they required 3.10 is interesting because my first example would be user
namespaces, which were completed around that point (I think they were mostly
done by 3.8, but IIRC there were some significant features that weren't
complete until 3.9-3.10).

Edit - Here's some details about Chromium's dependency on these features:
[https://chromium.googlesource.com/chromium/src/+/lkcr/docs/l...](https://chromium.googlesource.com/chromium/src/+/lkcr/docs/linux_sandboxing.md)

~~~
pm215
Yes. "We assume and rely on system call X being implemented" is another common
case. In particular, glibc will check your kernel version and won't run if it
is too old for what it was built against:
[https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/uni...](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/dl-
osinfo.h;h=823cd8224df939134018fbd8f0227e9f501393ab;hb=HEAD#l42) (Comments in
that code notwithstanding, I think that the glibc configure will always define
a minimum kernel version:
[https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/uni...](https://sourceware.org/git/?p=glibc.git;a=blob;f=sysdeps/unix/sysv/linux/configure.ac;h=13abda0a51484c5951ffc6d718aa36b72f3a9429;hb=HEAD#l36)
and so the check always happens.)

~~~
joshumax
The problem for me is that my kernel appeared to be too "new" for Nessus since
they set minimum and maximum version ranges. Oddly enough it never impacted
function AFAIK.

------
pentestercrab
There is a nice list of useful utilities called preeny that can be downloaded
here[1].

1\. [https://github.com/zardus/preeny](https://github.com/zardus/preeny)

------
self_awareness
Full sandboxing is not possible with LD_PRELOAD, because the application can
still issue raw system calls (syscalls) by using the 'int 0x80' instruction,
skipping any library call.

~~~
webkike
In order to wrap system calls, the syscall ptrace can be used:
[https://en.m.wikipedia.org/wiki/Ptrace](https://en.m.wikipedia.org/wiki/Ptrace)

~~~
Graziano_M
The proper way to do this is to use seccomp.

~~~
microcolonel
How exactly does one use seccomp to alter/intercept syscalls instead of simply
blocking or allowing them? I can't find anything which uses it like that with
a search.

~~~
jmgao
GP is misinformed. seccomp is an optimization to ptrace-based syscall
interception, when you're only interested in intercepting a subset of a
process's syscalls. Instead of getting an event and processing every single
syscall a process makes, you can register a seccomp filter that fires a ptrace
event in only specific cases (which can be super coarse like "the syscall
number is open", or more fine grained like "the syscall number is open and the
file is being opened with O_WRONLY or O_RDWR"). Without something like this,
ptrace based interception would be almost unusably slow for many cases.

~~~
Hello71
although unfortunately it can't do "the syscall number is open and the path is
five characters long".

------
timtosi
As C language teacher assistants in Uni, we used LD_PRELOAD during project
defences to replace the lib standard malloc by one of our own in order to be
sure that students allocated & freed memory correclty. Never saw so much
Segmentation faults in such a short time window.

~~~
d33
How about libdislocator?

[https://github.com/mirrorer/afl/tree/master/libdislocator](https://github.com/mirrorer/afl/tree/master/libdislocator)

------
Slartie
We used (and actually still use) this to override hostname lookup in an
automated test scenario, where we want to run multiple test suites of the same
software (consisting of several processes which all communicate with each
other via networking) on the same build server in parallel. We ended up just
configuring everything to bind/connect to "localhost", so it runs fine on
developer machines, but when executed on the build server - which is a lot
more beefier than a dev laptop - the exact same configuration can be run 20
times, executing different test suites in parallel. The trick is that there's
one independent loopback network interface for each run on that server, and by
overriding the hostname lookup of the software via a small library provided in
LD_PRELOAD, we replace the default lookup to 127.0.0.1 with these other
loopback devices' IPs.

You could of course achieve the same thing (and even more, like filesystem
isolation) in a way more elegant and better-understandable way via Docker
containers, but we started doing this in 2011 - there was no Docker back then,
so we built our own ultra-low-overhead network virtualization. It'll probably
be replaced by Docker someday, but for the moment it's still being used as it
"just works" and requires practically no maintenance after being set up once.

~~~
jamespo
Couldn't you just bind to different addresses under 127.0.0.1/8 ? eg
127.0.0.1, 127.0.0.2 etc

~~~
Slartie
Not if there isn't an interface. If I create one, sure, but then I would have
to do that on all dev computers as well and I'd have to forward the address
somehow into all server and client configs and/or command-lines of tools
started as part of the build process.

Just overriding "localhost" spares me from both (the address to resolve is
provided as an env variable, just like LD_PRELOAD itself - and env variables
are inherited by child processes, which is great in my scenario).

~~~
AnIdiotOnTheNet
You can't just pass in which interface to bind via an environment variable or
parameter?

------
steinuil
If I'm not mistaken putting DLLs in the same directory as the executable lets
you do the same on Windows. Mods and cracks for videogames usually plant a
fake DirectX dll in the game folder.

~~~
AnIdiotOnTheNet
Yeah, but UNIX developers love namespace conflicts and hardcoded paths, so
something as simple as "look for libs in the directory your binary is in"
either never occurred to them or was rejected under various arbitrary
concerns.

~~~
theamk
Huh? It has not been rejected -- it is up to compiler/linker what to put in
RPATH, and you can totally set RPATH to $ORIGIN. There is a nice tool,
chrpath, to do so. For example, java sets it to:

    
    
        /usr/bin/java: RPATH=$ORIGIN/../lib/amd64/jli:$ORIGIN/../lib/amd64
    
    

I believe the reason it does not happen by default is that binaries go to
/usr/bin, and no .so files should appear there. Also it breaks when hardlinks
are involved.

~~~
AnIdiotOnTheNet
> I believe the reason it does not happen by default is that binaries go to
> /usr/bin, and no .so files should appear there. Also it breaks when
> hardlinks are involved.

I other words, nobody uses it. So many conflicts could be easily solved if
applications dropped this retarded insistence on spreading files over the
hierarchy by type, but they just keep doing it because they mistake tradition
for wisdom.

------
IncRnd
This fun local hack is related to binary planting vulnerabilities, where a
dynamic resource can be planted on-disk prior to program execution for later
loading due to being in the load path.

~~~
joshumax
Interestingly, I had an article featured om HN related to how easy this is to
exploit:
[https://news.ycombinator.com/item?id=14518356](https://news.ycombinator.com/item?id=14518356)

~~~
IncRnd
I just read through that. Nice debugging.

Binary planting, incidentally, was one of those persistent bugs in all Windows
versions, since the local directory in Windows generally is in the beginning
of the path. That variant is called DLL planting or a DLL loading
vulnerability.

That single design decision to execute from the local folder has caused untold
havoc, including half a dozen APIs for how to handle DLL loading.

~~~
AnIdiotOnTheNet
I don't see what the big deal is. That behavior allowed a ton of flexibility
at the cost of DLL planting problems iff someone can already drop arbitrary
files in the application's directory.

~~~
IncRnd
> _I don 't see what the big deal is._

Let's say that a Windows user wants to use sandboxie to isolate web components
when browsing, until Aug of this year.

You're Pwned:
[https://nvd.nist.gov/vuln/detail/CVE-2017-12480](https://nvd.nist.gov/vuln/detail/CVE-2017-12480)

~~~
AnIdiotOnTheNet
I think I'm missing something. It has been a while since I've used Sandboxie,
but it seems like this is a behavioral oversight on it's part.

~~~
IncRnd
> _It has been a while since I 've used Sandboxie, but it seems like this is a
> behavioral oversight on it's part._

What do you mean by a behavioral oversight?

The installer loaded a non-existent dll from the temp folder. Any DLL of the
same name would get executed upon installation of Sandboxie. That's why DLL
hijacking is an issue.

~~~
AnIdiotOnTheNet
The link you gave gives a single line description of the issue. It isn't very
helpful in understanding what conditions were required to exploit. Is this a
Sandboxie installer issue or all installers?

If the latter, yeah that's a problem, but there are a lot of ways to fix it
that don't involve crippling the flexibility.

Edit: ok, read the medium link at the bottom. This is an issue with
Sandboxie's installer behavior, for which I think it is unfair to blame the
way Windows searches for and loads DLLs.

~~~
IncRnd
> _The link you gave gives a single line description of the issue. It isn 't
> very helpful in understanding what conditions were required to exploit. Is
> this a Sandboxie installer issue or all installers?_

The single line description is literally an answer to both of those questions,
"Sandboxie installer 5071703 has a DLL Hijacking or Unsafe DLL Loading
Vulnerability via a Trojan horse dwmapi.dll or profapi.dll file in an
AppData\Local\Temp directory."

> _Edit: ok, read the medium link at the bottom. This is an issue with
> Sandboxie 's installer behavior, for which I think it is unfair to blame the
> way Windows searches for and loads DLLs._

The vulnerability is literally caused by the dll load path containing the cwd.
That is the subject of this entire thread.

~~~
AnIdiotOnTheNet
If that were true, it'd be applicable to every installer on earth. Sandboxie
installer does something it shouldn't.

But hey, fuck it, let's throw the baby out with the bathwater and have
namespace conflicts because now all DLLs must be in system32 or some bullshit.

For that matter, let's just not use computers at all! That's where
vulnerabilities come from after all.

~~~
IncRnd
None of that has anything to do with the discussion about binary planting, one
variant of which is dll planting due to the cwd directory being used as a path
component in microsoft windows versions.

------
craigds
GDAL has a vsipreload.so which you can preload to replace all the libc file
handling functions with ones that understand special filesystem paths, like
/vsizip/path/to/zipfile.zip/myfile.csv

We used it for years to do remote inspection of zip files without downloading
the whole thing. Now there's a proper API for virtual filesystems in GDAL and
we don't have to preload any more.

------
zbentley
Is this possible when trying to intercept and/or otherwise mess with system
calls or stdlib function calls in a language that statically links all/most of
its libraries? Are Go binaries affected? Statically-linked Rust binaries? If
not, this is (depending on how you look at it) either a security point in
favor of those languages or a hindrance to patching/debuggability.

~~~
jwilk
If the program was linked statically to libfoo, you won't be able to preempt
any libfoo symbols using LD_PRELOAD.

There are other interception techniques that work against any binaries, even
fully static ones:

[http://man7.org/linux/man-
pages/man2/ptrace.2.html](http://man7.org/linux/man-pages/man2/ptrace.2.html)

[http://man7.org/linux/man-
pages/man2/seccomp.2.html](http://man7.org/linux/man-
pages/man2/seccomp.2.html)

------
sebcat
LD_PRELOAD is a pretty decent tool to have in your toolbox when debugging, and
you can't fire up lldb/gdb.

I've used it in the past to get an overview of what gets passed to SSL_write,
SSL_read: [https://github.com/sebcat/openssl-
hook](https://github.com/sebcat/openssl-hook)

------
Xophmeister
Colleagues of mine use LD_PRELOAD for roll-you-own profiling. They supply
their own `malloc`, for example, that does the same as regular-`malloc` but
also writes to a log file that can be analysed later.

~~~
sebcat
Massif is a good tool for memory profiling:
[http://valgrind.org/docs/manual/ms-
manual.html](http://valgrind.org/docs/manual/ms-manual.html)

------
photonios
I wrote up a little hack a while ago to work around a limitation in gcov:
[https://github.com/Photonios/fgcov](https://github.com/Photonios/fgcov)

It also uses `LD_PRELOAD` to overwrite a function in gcov and hash a filename
to prevent exceeding a limit. Even more interesting, it comes with a stand-
alone binary. The shared library is copied into the binary and when the binary
runs, it extracts the shared library and runs the real gcov and injects the
shared library using `LD_PRELOAD`.

I had fun :)

------
convery
This may be a good time to ask since I've been recommended to use LD_PRELOAD
but it's always caused issues.

Imagine this, a 'Bootstrap.so' is injected into a process via LD_PRELOAD. This
bootstrapper places a jump on the programs entrypoint that jumps to a
bootstrap::callback function. This function wants to load other shared
libraries though. So when it starts loading another library the bootstrapper
gets unloaded and reloaded, which ofcourse breaks execution.

Would anyone know how to properly deal with this?

~~~
saurik
Why is the bootstrap.so being unloaded? It should be loaded into the process
pretty permanently. If you have something calling dlopen/dlclose on it, maybe
give it an extra dlopen.

~~~
convery
That's the issue, it shouldn't be unloaded. Yet when bootstrap.so calls
dlopen(other.so); bootstrap.so gets unloaded and reloaded. I'll try adding a
dlopen(bootstap.so) in the callback to increase the refcount though.

------
oleavr
Another approach is using LD_PRELOAD with Frida to be able to write the
instrumentation logic in JavaScript:
[https://www.frida.re/docs/gadget/](https://www.frida.re/docs/gadget/) This
also supports monitoring the .js file for changes and reloading the
instrumentation logic live, which is great for game tweaks. Just save the file
and instantly see the results.

------
nivviv
[https://nwnx.io/](https://nwnx.io/)

A project that injects via LD_PRELOAD into a game called Neverwinter Nights
(now Enhanced Edition). It lets content authors/admins add things like
database support, more scripting languages, extensive ruleset changes, and
whatever else you want to the game.

------
przemoc
~8 years ago I was using LD_PRELOAD to be able to (s)fdisk fixed-size VDI
image (VirtualBox Disk Image) outside of virtual machine.

[https://gist.github.com/przemoc/571086](https://gist.github.com/przemoc/571086)

Don't know if it still builds and works flawlessly.

------
BearOso
I've used LD_PRELOAD to thunk calls to glXSwapBuffers, adding a call to
glFinish right afterwards. This keeps the OpenGL driver from getting too far
ahead in rendering, since both the buffer swap and command queue are
asynchronous, and reduces input lag somewhat.

~~~
jusssi
Any chance you have this on Github?

For some reason my card/driver combo creates a huge output lag when it can't
keep up with with framerate cap. I'd like to try if something like this would
help with it.

On topic of things that use LD_PRELOAD, there's this fps capper (not my work):
[https://github.com/torkel104/libstrangle](https://github.com/torkel104/libstrangle)

------
tfmatt
Is there a similar Java centric type override? Would it be as simple as
dropping a jar in the right location or is there an environment variable that
can be over written? Also it would be neat to know the java code to chain the
"open" system call like he did.

~~~
nitwit005
Probably not with an environment variable, but you can always replace the java
executable with another executable that adds an option like -Xbootclasspath.

------
Shorel
This is both an argument in favour of static linking, and an argument against
static linking.

Rust and go programmers will claim this is an attack they are not vulnerable
to it, and other people will say this is an useful feature and static linking
prevents its use.

------
sandGorgon
the state of art here is Microsoft's Detours ([https://www.microsoft.com/en-
us/research/project/detours/](https://www.microsoft.com/en-
us/research/project/detours/)) . I think it is commercially used by security
software, printer interceptors, etc

~~~
banthar
I've used both LD_PRELOAD and Detours and I like LD_PRELOAD so much better.
LD_PRELOAD just needs single standard shared library and one environment
variable. With Detours you have to: inject code into executable, stop threads,
worry about races, protecting/unprotecting memory. The way it's implemented
also feels like a big hack. The hooks are injected into first few instructions
of hooked methods. Some system functions even deliberately leave blank space
there. I'm yet to encounter a use case where all this complexity is useful.

~~~
nwellnhof
That's because ELF allows symbol interposition and PE (Portable Executable)
doesn't have a comparable feature. Symbol interposition doesn't come without a
price, though, and is often criticized:
[https://www.macieira.org/blog/2012/01/sorry-state-of-
dynamic...](https://www.macieira.org/blog/2012/01/sorry-state-of-dynamic-
libraries-on-linux/) |
[https://www.airs.com/blog/archives/307](https://www.airs.com/blog/archives/307)
|
[https://news.ycombinator.com/item?id=8029564](https://news.ycombinator.com/item?id=8029564)

~~~
sandGorgon
> _The ELF dynamic linking mechanism is designed to emulate static linking.
> That 's like designing cars to neigh and occasionally kick people to death
> with robot legs that exist only for this purpose._

That comment wins thread.

