1. LD_PRELOAD 实现注入的原理
LD_PRELOAD是linux系统中的一个环境变量,它可以指定优先加载某个动态库。当主程序中有相同的符号出现在不同的动态库中,会使用优先加载的动态库中的符号。根据这个原理,如果要被hook的函数在动态库a中,那么可以重载一个相同定义的函数并把它编译成动态库b,并使用LD_PRELOAD执行优先加载动态库b。当启动主程序的时候,主程序会调用动态库b中的函数。通过这个环境变量,我们可以在主程序和其动态链接库的中间加载别的动态链接库,去覆盖正常的函数库。
默认情况下linux的动态库链接顺序:
LD_PRELOAD --> LD_LIBRARY_PATH --> /lib --> /usr/lib
使用LD_PRELOAD替换rand函数
// main.cpp
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
int main(int argc, char** argv)
{
srand((unsigned)time(NULL));
int value = rand();
printf("value is %d \n", value);
return 0;
}
// hook.cpp
#include <stdlib.h>
#include <stdio.h>
// hock 函数
int rand (void)
{
printf("hock function \n");
return 100; // 返回一个固定值
}
[linux@ t0]~$tree -L 2
.
|-- a.out
|-- hook.cpp
|-- hook.o
|-- libhook.so
`-- main.cpp
// 编译
[linux@ t0]~$g++ main.cpp
[linux@ t0]~$g++ -Wall -fPIC -c hook.cpp -o hook.o
[linux@ t0]~$g++ -shared -o libhook.so hook.o
// 执行
[linux@ t0]~$LD_PRELOAD=./libhook.so ./a.out
hock function
value is 100
通过ldd可以查看依赖关系
[linux@ t0]~$ldd ./a.out
linux-vdso.so.1 (0x00007ffff7ffb000)
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007ffff7a3f000)
libm.so.6 => /lib64/libm.so.6 (0x00007ffff76bd000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007ffff74a5000)
libc.so.6 => /lib64/libc.so.6 (0x00007ffff70e3000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd4000)
[linux@ t0]~$LD_PRELOAD=./libhook.so ldd ./a.out
linux-vdso.so.1 (0x00007ffff7ffb000)
./libhook.so (0x00007ffff7ff1000) <----- 区别在这里
libstdc++.so.6 => /lib64/libstdc++.so.6 (0x00007ffff7a3f000)
libm.so.6 => /lib64/libm.so.6 (0x00007ffff76bd000)
libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007ffff74a5000)
libc.so.6 => /lib64/libc.so.6 (0x00007ffff70e3000)
/lib64/ld-linux-x86-64.so.2 (0x00007ffff7dd4000)
关于LD_PRELOAD 命令的一些操作
1. 通过命令 export LD_PRELOAD="库文件路径",设置要优先替换动态链接库
2. 如果找不替换库,可以通过 export LD_LIBRARY_PATH=库文件所在目录路径,设置系统查找库的目录
3. 替换结束,要还原函数调用关系,用命令unset LD_PRELOAD 解除
4. 想查询依赖关系,可以用ldd 程序名称
2. attribute(construct)
在加载动态库的时候,可以利用attribute关键字定义加载动态库的init函数。这是一个非常有用的特性。很多基于preload动态库的程序都会用到这个机制。
#include <stdlib.h>
#include <stdio.h>
// 这里定义了一个my_init_so函数
// 并通过attrbute关键字让它在成员加载这个动态库之后,执行的一个函数
void __attribute__ ((constructor)) my_init_so()
{
printf("init so\n");
}
int rand (void)
{
printf("hock function \n");
return 100;
}
attribute的具体作用这里先不说,可以先记住attribute可以定义一个加载动态库之后的一个初始化函数。
3. LD_PRELOAD hook malloc函数实现无侵入malloc插桩
利用这个原理,可以hook malloc函数,而且被监控的进程不需要改动任何代码。核心代码如下:
static void myInitHook(void)
{
Logout("setting up hooks...");
old_malloc_hook = __malloc_hook;
old_free_hook = __free_hook;
__malloc_hook = my_malloc_hook;
__free_hook = my_free_hook;
}
static void* myMallocHook(size_t size, const void* caller)
{
void* res;
restoreOldHooks();
res = malloc(size);
saveOldHooks();
// Do your memory statistics here...
snprintf(logBuff, sizeof(logBuff)
, "malloc (%u) returned @%p", (unsigned int) size, res);
Logout(logBuff);
// 打印调用栈
printBacktrace();
// Restore our own hooks
restoreMyHooks();
return res;
}
把这个小工具封装成了一个库,可以拆包使用(https://github.com/zhaozhengcoder/libmallocTrace )可以无侵入的获得目标程序调用malloc,free,new,delete的函数调用找。
示例如下:
// example.cpp
void leak(int num)
{
printf("%s [malloc] allocating %d bytes...\n", __PRETTY_FUNCTION__, num);
void * p1 = malloc(num);
memset(p1, 0, num);
printf("%s [new] allocating %d bytes...\n", __PRETTY_FUNCTION__, num);
char * p2 = new char[num];
memset(p2, 0, num);
return;
}
int main(int argc, char** argv)
{
srand((unsigned)time(NULL));
printf("%s: PID: %d\n", __PRETTY_FUNCTION__, getpid());
while (1)
{
leak(rand() % 1024);
notleak(rand() % 1024);
sleep(rand() % 10);
}
return 0;
}
// 不需要修改原程序的代码,只需要ld preload这个库
LD_PRELOAD=../libmTrace.so ./example
trace log输出效果
网友评论