I'll explain:
Let's say I'm interested in replacing the rand()
function used by a certain application.
So I attach gdb to this process and make it load my custom shared library (which has a customized rand()
function):
call (int) dlopen("path_to_library/asdf.so")
This would place the customized rand()
function inside the process' memory. However, at this point the symbol rand will still point to the default rand()
function. Is there a way to make gdb point the symbol to the new rand()
function, forcing the process to use my version?
I must say I'm also not allowed to use the LD_PRELOAD
(linux) nor DYLD_INSERT_LIBRARIES
(mac os x) methods for this, because they allow code injection only in the beginning of the program execution.
The application that I would like to replace rand()
, starts several threads and some of them start new processes, and I'm interested in injecting code on one of these new processes. As I mentioned above, GDB is great for this purpose because it allows code injection into a specific process.
The only things you can do with a function pointer are read its value, assign its value, or call the function that it points toward. You cannot increment or decrement the address stored in a function pointer or perform any other arithmetic operations.
To execute one line of code, type "step" or "s". If the line to be executed is a function call, gdb will step into that function and start executing its code one line at a time. If you want to execute the entire function with one keypress, type "next" or "n".
I followed this post and this presentation and came up with the following set of gdb commands for OSX with x86-64 executable, which can be loaded with -x
option when attaching to the process:
set $s = dyld_stub_rand
set $p = ($s+6+*(int*)($s+2))
call (void*)dlsym((void*)dlopen("myrand.dylib"), "my_rand")
set *(void**)$p = my_rand
c
The magic is in set $p = ...
command. dyld_stub_rand
is a 6-byte jump instruction. Jump offset is at dyld_stub_rand+2
(4 bytes). This is a $rip
-relative jump, so add offset to what $rip
would be at this point (right after the instruction, dyld_stub_rand+6
).
This points to a symbol table entry, which should be either real rand
or dynamic linker routine to load it (if it was never called). It is then replaced by my_rand
.
Sometimes gdb will pick up dyld_stub_rand
from libSystem or another shared library, if that happens, unload them first with remove-symbol-file
before running other commands.
I have a new solution, based on the new original constraints. (I am not deleting my first answer, as others may find it useful.)
I have been doing a bunch of research, and I think it would work with a bit more fiddling.
info functions
to find the address of rand
in the symbol tableUse dlopen then dlsym to load the function into memory and get its address
call (int) dlopen("my_rand.so", 1)
-> -val-
call (unsigned int) dlsym(-val-, "my_rand")
-> my_rand_addr
jumpq 0x*my_rand_addr*
instructionset {int}*rand_addr* = *my_rand_addr*
to change symbol table instructionContinue
execution: now whenever rand
is called, it will jump to my_rand
insteadThis is a bit complicated, and very round-about, but I'm pretty sure it would work. The only thing I haven't accomplished yet is creating the jumpq
instruction code. Everything up until that point works fine.
I'm not sure how to do this in a running program, but perhaps LD_PRELOAD
will work for you. If you set this environment variable to a list of shared objects, the runtime loader will load the shared object early in the process and allow the functions in it to take precedence over others.
LD_PRELOAD=path_to_library/asdf.so path/to/prog
You do have to do this before you start the process but you don't have to rebuild the program.
Several of the answers here and the code injection article you linked to in your answer cover chunks of what I consider the optimal gdb
-oriented solution, but none of them pull it all together or cover all the points. The code-expression of the solution is a bit long, so here's a summary of the important steps:
dlopen()
in the inferior process to link in a shared library containing the injected code. In the article you linked to the author instead loaded a relocatable object file and hand-linked it against the inferior. This is quite frankly insane -- relocatable objects are not "ready-to-run" and include relocations even for internal references. And hand-linking is tedious and error-prone -- far simpler to let the real runtime dynamic linker do the work. This does mean getting libdl
into the process in the first place, but there are many options for doing that.dlsym
-- can circumvent the GOT and provide direct access to the function of interest. The only way to be certain of intercepting all calls to a particular function is overwrite the initial instructions of that function's code in-memory to create a "detour" redirecting execution to your injected function.gdb
has included the ability to write new commands in Python. This support can be used to implement a turn-key solution for injecting and detouring code in/to the inferior process.Here's an example. I have the same a
and b
executables as before and an inject2.so
created from the following code:
#include <unistd.h>
#include <stdio.h>
int (*rand__)(void) = NULL;
int
rand(void)
{
int result = rand__();
printf("rand invoked! result = %d\n", result);
return result % 47;
}
I can then place my Python detour
command in detour.py
and have the following gdb
session:
(gdb) source detour.py (gdb) exec-file a (gdb) set follow-fork-mode child (gdb) catch exec Catchpoint 1 (exec) (gdb) run Starting program: /home/llasram/ws/detour/a a: 1933263113 a: 831502921 [New process 8500] b: 918844931 process 8500 is executing new program: /home/llasram/ws/detour/b [Switching to process 8500] Catchpoint 1 (exec'd /home/llasram/ws/detour/b), 0x00007ffff7ddfaf0 in _start () from /lib64/ld-linux-x86-64.so.2 (gdb) break main Breakpoint 2 at 0x4005d0: file b.c, line 7. (gdb) cont Continuing. Breakpoint 2, main (argc=1, argv=0x7fffffffdd68) at b.c:7 7 { (gdb) detour libc.so.6:rand inject2.so:rand inject2.so:rand__ (gdb) cont Continuing. rand invoked! result = 392103444 b: 22 Program exited normally.
In the child process, I create a detour from the rand()
function in libc.so.6
to the rand()
function in inject2.so
and store a pointer to a trampoline for the original rand()
in the rand__
variable of inject2.so
. And as expected, the injected code calls the original, displays the full result, and returns that result modulo 47.
Due to length, I'm just linking to a pastie containing the code for my detour
command. This is a fairly superficial implementation (especially in terms of the trampoline generation), but it should work well in a large percentage of cases. I've tested it with gdb
7.2 (most recently released version) on Linux with both 32-bit and 64-bit executables. I haven't tested it on OS X, but any differences should be relatively minor.
For executables you can easily find the address where the function pointer is stored by using objdump. For example:
objdump -R /bin/bash | grep write
00000000006db558 R_X86_64_JUMP_SLOT fwrite
00000000006db5a0 R_X86_64_JUMP_SLOT write
Therefore, 0x6db5a0 is the adress of the pointer for write
. If you change it, calls to write will be redirected to your chosen function. Loading new libraries in gdb and getting function pointers has been covered in earlier posts. The executable and every library have their own pointers. Replacing affects only the module whose pointer was changed.
For libraries, you need to find the base address of the library and add it to the address given by objdump. In Linux, /proc/<pid>/maps
gives it out. I don't know whether position-independent executables with address randomization would work. maps
-information might be unavailable in such cases.
As long as the function you want to replace is in a shared library, you can redirect calls to that function at runtime (during debugging) by poking at the PLT. Here is an article that might be helpful:
Shared library call redirection using ELF PLT infection
It's written from the standpoint of malware modifying a program, but a much easier procedure is adaptable to live use in the debugger. Basically you just need to find the function's entry in the PLT and overwrite the address with the address of the function you want to replace it with.
Googling for "PLT" along with terms like "ELF", "shared library", "dynamic linking", "PIC", etc. might find you more details on the subject.
You can still us LD_PRELOAD
if you make the preloaded function understand the situations it's getting used in. Here is an example that will use the rand()
as normal, except inside a forked process when it will always return 42. I use the dl routines to load the standard library's rand()
function into a function pointer for use by the hijacked rand()
.
// -*- compile-command: "gcc -Wall -fPIC -shared my_rand.c -o my_rand.so -ldl"; -*-
//my_rand.c
#include <sys/types.h>
#include <unistd.h>
#include <dlfcn.h>
int pid = 0;
int (*real_rand)(void) = NULL;
void f(void) __attribute__ ((constructor));
void f(void) {
pid = getpid();
void* dl = dlopen("libc.so.6", RTLD_LAZY);
if(dl) {
real_rand = dlsym(dl, "rand");
}
}
int rand(void)
{
if(pid == getpid() && real_rand)
return real_rand();
else
return 42;
}
//test.c
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char** argv)
{
printf("Super random number: %d\n", rand());
if(fork()) {
printf("original process rand: %d\n", rand());
} else {
printf("forked process rand: %d\n", rand());
}
return 0;
}
jdizzle@pudding:~$ ./test
Super random number: 1804289383
original process rand: 846930886
forked process rand: 846930886
jdizzle@pudding:~$ LD_PRELOAD="/lib/ld-linux.so.2 ./my_rand.so" ./test
Super random number: 1804289383
original process rand: 846930886
forked process rand: 42
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With