I am messing with shared library injection and managed to make it work. But it didn't take long until the first issue showed up. If you take a look at the following code blocks, you'll see that the only difference between them is that on the first one, the value returned from the "test" function is assigned to a local variable, while in the second code block, it is assigned to a global variable.
//main.cpp
#include <iostream>
#include "mem/mem.hpp"
#define PROCESS_MOD_NAME MEM_STR("/target\n")
#define LIBC_MOD_NAME MEM_STR("/libc.so")
mem::moduleinfo_t test()
{
mem::moduleinfo_t _modinfo;
_modinfo.name = "testing";
_modinfo.path = "testing";
_modinfo.base = (mem::voidptr_t)10;
_modinfo.end = (mem::voidptr_t)20;
_modinfo.size = 10;
return _modinfo;
}
__attribute__((constructor))
void libmain()
{
mem::moduleinfo_t modinfo = test(); //local variable
std::cout << "name: " << modinfo.name << std::endl;
std::cout << "path: " << modinfo.path << std::endl;
std::cout << "base: " << modinfo.base << std::endl;
std::cout << "size: " << modinfo.size << std::endl;
std::cout << "end: " << modinfo.end << std::endl;
}
This one works, no crash.
//main.cpp
#include <iostream>
#include "mem/mem.hpp"
#define PROCESS_MOD_NAME MEM_STR("/target\n")
#define LIBC_MOD_NAME MEM_STR("/libc.so")
mem::moduleinfo_t test()
{
mem::moduleinfo_t _modinfo;
_modinfo.name = "testing";
_modinfo.path = "testing";
_modinfo.base = (mem::voidptr_t)10;
_modinfo.end = (mem::voidptr_t)20;
_modinfo.size = 10;
return _modinfo;
}
mem::moduleinfo_t modinfo; //global variable
__attribute__((constructor))
void libmain()
{
modinfo = test();
std::cout << "name: " << modinfo.name << std::endl;
std::cout << "path: " << modinfo.path << std::endl;
std::cout << "base: " << modinfo.base << std::endl;
std::cout << "size: " << modinfo.size << std::endl;
std::cout << "end: " << modinfo.end << std::endl;
}
On this block of code, the target program simply crashes. When calling "dlopen" on GDB, the output is:
Program received signal SIGSEGV, Segmentation fault.
0xf7c19404 in __memcpy_sse2_unaligned () from /usr/lib32/libc.so.6
The program being debugged was signaled while in a function called from GDB.
GDB remains in the frame where the signal was received.
To change this behavior use "set unwindonsignal on".
Evaluation of the expression containing the function
(dlopen) will be abandoned.
The target program code is:
//target.cpp
#include <iostream>
#include <dlfcn.h>
int main()
{
while(true);
return 0;
}
Command line to compile this code:
clang++ -m32 target.cpp -o build/target -ldl
clang++ -m32 -shared -fPIC main.cpp mem/mem.cpp -o build/libinject.so -ldl
Not being able to use global variables is a big deal, so what could be causing this crash and how do I fix it?
As pointed by yugr, "C++ global constructors run after C constructors (i.e. functions marked with attribute((constructor))". With that in mind, I thought about a way around this: creating a C thread that would run on the background AFTER the C++ stuff loaded using PThread. Then I did this to test out:
//main.cpp (shared library)
#include <iostream>
#include <pthread.h>
typedef struct
{
std::string name;
std::string path;
void* base;
void* end;
}example_t;
example_t test()
{
example_t example;
example.name = "hello";
example.path = "test";
example.base = (void*)0;
example.end = (void*)0;
return example;
}
example_t global_var;
void* thread_init(void* _args)
{
global_var = test();
std::cout << "global_var name: " << global_var.name << std::endl;
std::cout << "global_var path: " << global_var.path << std::endl;
std::cout << "global_var base: " << global_var.base << std::endl;
std::cout << "global_var end: " << global_var.end << std::endl;
return (void*)NULL;
}
__attribute__((constructor))
void libmain()
{
pthread_t thread;
pthread_create(&thread, NULL, (void*(*)(void*))thread_init, (void*)NULL);
}
//target.cpp (target program)
#include <iostream>
#include <dlfcn.h>
int main()
{
while(true);
return 0;
}
... using the following script to compile:
if [ ! -d build ]; then
mkdir build
fi
clang++ -g -m32 -shared -fPIC main.cpp -o build/libinject.so -pthread
clang++ -g -m32 target.cpp -o build/target -ldl
... and this one to inject the shared library:
#/bin/bash
if [ "$EUID" -ne 0 ]; then
echo "[!] Run as root"
exit 0
fi
if [ ! command -v gdb &> /dev/null ]; then
echo "[!] Unable to find GDB, make sure you have it installed"
exit 0
fi
proc_name="target"
proc_id=""
libpath="$(pwd)/build/libinject.so"
if ! proc_id=$(pgrep $proc_name) > /dev/null 2>&1; then
echo "[!] The target process is not running"
exit 0
fi
if [ ! -f $libpath ]; then
echo "[!] Invalid shared library file"
fi
gdb -n -q -batch \
-ex "attach $proc_id" \
-ex "set \$dlopen = (void*(*)(char*, int)) dlopen" \
-ex "call \$dlopen(\"$libpath\", 1)" \
-ex "detach" \
-ex "quit" > /dev/null 2>&1
echo "[*] done"
exit 1
guess what? worked just fine