There are a lot of concepts (specially in the world of computer security) which look very mystic and dll injection is, without any doubt, one of those. I’ll just post here a proof of concept because… well, because I think it’s interesting shit!
I’ll do it on Linux because I feel more comfortable in this OS but the concept is similar in MS Windows. First of all, an excerpt from an online program library howto:
Shared libraries are libraries that are loaded by programs when they start. When a shared library is installed properly, all programs that start afterwards automatically use the new shared library. It’s actually much more flexible and sophisticated than this, because the approach used by Linux permits you to:
- update libraries and still support programs that want to use older, non-backward-compatible versions of those libraries;
- override specific libraries or even specific functions in a library when executing a particular program.
- do all this while programs are running using existing libraries.
The second point is the interesting one for us, since it will allow us to “impersonate” some of the functions the program calls that are situated inside dynamic link libraries. Several examples crossed my mind right now, all of them containing functions with names like checkPassword() or something similar
Instead of giving a boring lecture I’ll try to explain every point on the way. Let’s say our target program is this one (core.c):
#include <stdio.h>
#include <stdint.h>
#include <time.h>
int
main(int argc, char **argv)
{
time_t result;
printf(“I’m about to call time() in time.h\n”);
result = time(NULL);
printf(“The time is %ju secs\n”, (uintmax_t)result);
return(0);
}
It just get the system time using the time() function in glibc 2.0 and prints it to the console.
carlos@pattern:~/Projects/dll_injection$ ./core
I’m about to call time() in time.h
The time is 1253876880 secs
carlos@pattern:~/Projects/dll_injection$ ./core
I’m about to call time() in time.h
The time is 1253876888 secs
Until here, nothing really interesting so let’s get a bit more deep. In order to get the system time, core.c uses a function which code is not in my program but inside a shared library. Our core.c program publishes, after compiling, a series of symbols, that is, information about where should these functions code be inserted. This can be inspected with nm:
carlos@pattern:~/Projects/dll_injection$ nm core
08049f20 d _DYNAMIC
08049ff4 d _GLOBAL_OFFSET_TABLE_
0804853c R _IO_stdin_used
[...]
0804a014 W data_start
0804a020 b dtor_idx.6637
08048400 t frame_dummy
08048424 T main
U printf@@GLIBC_2.0
U puts@@GLIBC_2.0
U time@@GLIBC_2.0
Notice how time and printf are listed here. We can see as well what libraries are linked at runtime with ldd:
carlos@pattern:~/Projects/dll_injection$ ldd core
linux-gate.so.1 => (0xb8058000)
libc.so.6 => /lib/tls/i686/cmov/libc.so.6 (0xb7eda000)
/lib/ld-linux.so.2 (0xb8059000)
As expected, the libc is listed here, along with a very special one ld-linux.so.2, the dynamic linker/loader.
From the man page:
ld.so loads the shared libraries needed by a program, prepares the program to run, and then runs it. Unless explicitly specified via the -static option to ld during compilation, all Linux programs are incomplete and require further linking at run time.
[SuperNerd parenthesis]
GCC inserts at compile time an ELF header specifying which dynamic loader will be used at runtime:
carlos@pattern:~/Projects/dll_injection$ readelf -l core
Elf file type is EXEC (Executable file)
Entry point 0×8048370
There are 8 program headers, starting at offset 52
Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0×000034 0×08048034 0×08048034 0×00100 0×00100 R E 0×4
INTERP 0×000134 0×08048134 0×08048134 0×00013 0×00013 R 0×1
[Requesting program interpreter: /lib/ld-linux.so.2]
LOAD 0×000000 0×08048000 0×08048000 0×00580 0×00580 R E 0×1000
LOAD 0x000f0c 0x08049f0c 0x08049f0c 0×00110 0×00118 RW 0×1000
DYNAMIC 0x000f20 0x08049f20 0x08049f20 0x000d0 0x000d0 RW 0×4
NOTE 0×000148 0×08048148 0×08048148 0×00020 0×00020 R 0×4
[...]
That instructs the OS to pass the control of the program to ld-linux.so.2 instead of the normal entry point of the application.
[End of SuperNerd parenthesis]
ld-linux.so.2 checks the symbols (the way nm does), searches for the library files and loads the corresponding code into the process memory.
There’s a way, however, of preloading a shared object (or dll) at run time giving it precedence over this process. Here is where the technical info gets obscure but I guess this lib becomes a preferred target for symbol retrieval, so if it contains a symbol with a matching name, its code will be loaded and this dependency marked as satisfied.
That’s all we need to know. Let’s create our shared object then with a function called… yes, time()
carlos@pattern:~/Projects/dll_injection$ cat lib_evil.c
/* The evil library.
It exports the symbol time()
and overrides glibc
*/
#include <stdio.h>
int time()
{
printf(“FAKE TIME FUNCTION says: No you didn’t!\n”);
return(1234);
}
and compile it as shared object:
To create the object file:
$ gcc -c -fPIC -ggdb -Wall -o lib_evil.o lib_evil.c
To create the shared object (library .so):
$ ld -shared lib_evil.o -o lib_evil.so
Finally, let’s try our cool DLL injection, preloading our evil shared object.
Normal operation
carlos@pattern:~/Projects/dll_injection$ ./core
I’m about to call time() in time.h
The time is 1253881612 secs
DLL injection
carlos@pattern:~/Projects/dll_injection$ LD_PRELOAD=”./lib_evil.so” ./core
I’m about to call time() in time.h
FAKE TIME FUNCTION says: No you didn’t!
The time is 1234 secs
Now find your favourite software and try to bypass authentication following this pseudocode:
function checkPassword
return EverythingCool