Hacker News new | past | comments | ask | show | jobs | submit login
Using LD_PRELOAD to cheat, inject features and investigate programs (2013) (rafalcieslak.wordpress.com)
142 points by striking on Dec 4, 2017 | hide | past | web | favorite | 86 comments

Here's an idea that I like (so much that I wrote an implementation): use LD_PRELOAD to swap out readline with version that forks on each input. Kill the forked child process and you have undo! https://github.com/thomasballinger/rlundo post: http://ballingt.com/interactive-interpreter-undo/#hacking-fo...

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.

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

That's "fakeroot", a different program, which is used for simulating superuser privileges.

I was going to mention fakeroot, too, but it doesn't seem have a useful homepage. https://fakeroot.alioth.debian.org/ looks very much uninviting.

fakeroot was written for Debian, and it's still developed by Debian developers, although there's nothing Debian-specific about it. Debian has giving back to the free software community in their Social Contract¹, but in practice there's no culture of making Debian software easily accessible to outsiders. :-(

¹ https://www.debian.org/social_contract §2

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

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


Here's my take on libfaketime, which hooks also hooks syscalls:


It isn't intended for any kind of serious usage, I mainly just wanted to understand ptrace and LD_PRELOAD.

Onload [1], which is extensively used in some corners of the computing world, also relies on LD_PRELOAD.

[1] http://www.openonload.org/

Addendum: https://github.com/sickill/stderred - colorizing wrapper for stderr

There is also retrace (https://github.com/riboseinc/retrace)

tsocks [1] is also interesting. It lets you run a program forwarding all its network data through a SOCKS proxy. It's very useful if you want to VPN a single application over a SSH connection.

[1] http://tsocks.sourceforge.net/

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...

I used the same trick to modify how X11 server sends keyboard messages. There's a boolean flag that denotes whether or not a key event is "real" or generated artificially.

For some !@$!@'d up reason, 99% of apps actually discard all synthetic keyboard events. So doing something as innocent as mapping joystick to keyboard commands fails. It also took HOURS to find out why my keyboard generating test code wouldn't work until I piped it to an Ubuntu postit note program and hazzah... it worked with no code changes. Every other app was just blocking the events.

I did exactly the same thing in the early 2000s to get an ancient Java application working under Linux 2.4 -- it was calling some syscall via libc and getting a slightly different result than it had under Linux 2.2, and dying for no useful reason. I'm pretty sure that LD_PRELOAD hack ran in prod for years :-) Very handy trick.

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...

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... (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... and so the check always happens.)

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.

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

1. https://github.com/zardus/preeny

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.

In order to wrap system calls, the syscall ptrace can be used: https://en.m.wikipedia.org/wiki/Ptrace

The proper way to do this is to use seccomp.

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.

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.

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

If you also intercept the exec* family calls, you can forbid the user from running any but the trusted executables (so that will exclude e.g. anything written in Python etc).

It does get tricky since you are depending on glibc internals to find out what symbols to override. IIRC some of the exec* functions called each other internally without going through the symbol table, so you cannot override just execve which corresponds to the system call, as execvp will not call the execve function.

Then there's a whole ton of new secure Linux system calls that instead of a full path name take a directory file descriptor + name in that directory. E.g. execveat, openat etc.

So I wouldn't recommend it in a fully hostile (secure) environment.

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.

If you can afford rebuilding from source, using AddressSanitizer (perhaps also UndefinedBehaviorSanitizer) should be better at uncovering memory safety bugs than any LD_PRELOAD trick.

Electric Fence is one such library. [1]

[1] https://elinux.org/Electric_Fence

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 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.

Couldn't you just bind to different addresses under ? eg, etc

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).

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

But the loopback interface is up & available as standard?

Only advantage I can see is if localhost is hardcoded everywhere.

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.

Absolutely correct. Although this was intentional behaviour, it was given a security advisory[1]. In practice it allowed all sorts of exploits, including dumping packed executables for cracking, as you say.

[1]: https://docs.microsoft.com/en-us/security-updates/SecurityAd...

Though to be clear, this is like one of a billion ways to inject code into processes on Windows.

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.

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.

> 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.

This is possible on Linux using

  gcc -Wl,-rpath,'$ORIGIN/../lib'
See http://man7.org/linux/man-pages/man8/ld.so.8.html

This should work also on other ELF-based systems.

I wouldn't categorise security as an "arbitrary concern".

In order to exploit this you need to be able to write to the directory where the binary is.

"Security" is trotted out as an arbitrary concern quite often because a lot of security people don't have any concept of risk analysis or cost/benefit. If it was up to them no one would ever do anything because that way they can't make a mistake.

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.

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

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.

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.

> 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

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.

> 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.

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.

> 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.

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.

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.

And this is especially bad when your browser drops all downloaded .DLLs and .EXEs in the same Downloads folder

or when

your email program downloads files

you go to a network share and click

java loads the configuration from the current directory

or, or, or

You're absolutely right :(

Accidentally adding empty item to LD_LIBRARY_PATH (or PATH, or PYTHONPATH, or ...) is a pretty common bug.

This should be fixed all at once, by stopping treating empty string as cwd. People who actually need to have cwd in *PATH can add it explicitly.

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.

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.

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:



If your ELF is static, there is no ELF interpreter (ld.so), so LD_PRELOAD means nothing.

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

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.

Massif is a good tool for memory profiling: http://valgrind.org/docs/manual/ms-manual.html

I wrote up a little hack a while ago to work around a limitation in gcov: 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 :)

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?

I experienced something similar in my data-driven permission restriction DSO. Turns out I was calling my initialization function multiple times because the system calls I overrode were called before my attribute((constructor)) function.

I'd recommend running your program under gdb with a breakpoint in your function and checking out what the stack is like. Maybe your function is getting called from an unexpected place.

In my case I was overriding fopen64 but glibc wanted to open some NSS related files before my init function was called.

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.

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.

Another approach is using LD_PRELOAD with Frida to be able to write the instrumentation logic in JavaScript: 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.


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.

~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.


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

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.

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

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.

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

You can override the ClassLoader (and use other reflection tricks after that) to do almost anything, see e.g. http://blog.cyberborean.org/2007/07/04/custom-classloaders-t...

You can write a Java agent to overwrite the classfiles as they're loaded.

At startup, you can use the `-javaagent:<path/to/jar>` option, and you can also bind an agent to other JVMs, programmatically, at runtime with a bit of hackery.

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.

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

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.

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.airs.com/blog/archives/307 | https://news.ycombinator.com/item?id=8029564

>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.

Can confirm;

Used it while working on Azure Security Forensics team, however sometimes we also used EasyHook for C#

Applications are open for YC Winter 2020

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact