Intercepting C Library Function Calls
Writing a sample program
To demonstrate this concept, I am going to write a very simple string comparison program in C. The basic premise is a simple password authentication mechanism that uses (admittedly) insecure C function calls. The security of the program itself is pretty irrelevant to the concept I’d like to demonstrate, so please bear with me. Thanks!
String Comparison
#include <stdio.h>
int main() {
char a[]="password";
char b[]="PASSWORD";
if(strcmp(a,b)) {
printf("WRONG!");
} else printf("CORRECT!");
return 0;
}
This program is going to always output “WRONG” because of how strcmp
works – e.g., we only return 0 as an int
if the strings are identical. This is to simulate an incorrect password input.
Writing an interception library
So what’s our goal here? Simply, to guarantee that strcmp
will output 0
so that we can get the “CORRECT!” output. To do this, we’re going to write out own strcmp
function and then compile it as a library (shared object). I’m not going to go into the details of explaining programming concepts because again, it’s irrelevant to the demonstration. The bigger takeaway is that you’re aware this is possible.
It’s that simple…
int strcmp(const char* a, const char* b) { return 0; }
Yes, this is the whole program. When this library is imported, we’ll overwrite the existing function definition with our own (which only returns 0).
Compile the programs and library
From here we just need to take a few more steps:
- Compile our C program:
cc -o test test.c
(as an example) - Compile our Library:
cc -o intercept.so -shared lib.c -ldl
- Change ENV Variables:
LD_LIBRARY_PATH=./:$LD_LIBRARY_PATH
- Set LD_PRELOAD to use our library then execute the program:
LD_PRELOAD=$PWD/intercept.so ./test
In Action!
Why does this work?
Understanding LD_PRELOAD
This part is going to go down some fundamental architecture of compiled languages (particularly linking) that allows for this type of behavior. It’s not essential to understanding the security implications of something like this, but if you’re a nerd for knowledge then you’ll probably be interested. LD_PRELOAD
is actually just an environment variable that is part of ld.so - aka ld-linux.so - dynamic linker/loader. IBM actually has a phenomenal article on the differences between the two. In short, it’s a really simple tradeoff: we trade portability (static linking to include all libraries) for smaller file sizes on our compiled binaries, and the ability to run code on many more systems (in terms of compatibility, but at a performance loss). Some may argue that dynamic linking is dangerous or less secure than static linking… to those people, I suggest reading this awesome rant on the subject.
From the ld.so
manpage:
The programs ld.so and ld-linux.so* find and load the shared objects (shared libraries) needed by a program, prepare the program to run, and then run it.
We can leverage this helpful debugging feature to load any ELF shared object prior to program execution.
WHY?
This isn’t just something an attacker can leverage, but we as incident responders can turn the tables on compiled malware to try and force behavior locked behind either C2 instruction, or logic checks. So pull a simple binary out, run strings
to see what function calls we can see and try breaking it!