/lib64/ld-linux-x86-64.so.2(0x00007f48c07e3000)
正如你看到的输出那样,它列出了被程序 random_nums 所需要的库的列表。这个列表是构建进可执行程序中的,并且它是在编译时决定的。在你的机器上的具体的输出可能与示例有所不同,但是,一个 libc.so 肯定是有的 —— 这个文件提供了核心的 C 函数。它包含了 “真正的” rand()。
我使用下列的命令可以得到一个全部的函数列表,我们看一看 libc 提供了哪些函数:
nm-D /lib/libc.so.6
这个 nm 命令列出了在一个二进制文件中找到的符号。-D 标志告诉它去查找动态符号,因为 libc.so.6 是一个动态库。这个输出是很长的,但它确实在列出的很多标准函数中包括了 rand()。
现在,在我们设置了环境变量 LD_PRELOAD 后发生了什么?这个变量 为一个程序强制加载一些库。在我们的案例中,它为 random_num 加载了 unrandom.so,尽管程序本身并没有这样去要求它。下列的命令可以看得出来:
$ LD_PRELOAD=$PWD/unrandom.so ldd random_nums
linux-vdso.so.1=>(0x00007fff369dc000)
/some/path/to/unrandom.so (0x00007f262b439000)
libc.so.6=>/lib/x86_64-linux-gnu/libc.so.6(0x00007f262b044000)
/lib64/ld-linux-x86-64.so.2(0x00007f262b63d000)
注意,它列出了我们当前的库。实际上这就是代码为什么得以运行的原因:random_num 调用了 rand(),但是,如果 unrandom.so 被加载,它调用的是我们所提供的实现了 rand() 的库。很清楚吧,不是吗?
更清楚地了解这还不够。我可以用相似的方式注入一些代码到一个应用程序中,并且用这种方式它能够像个正常的函数一样工作。如果我们使用一个简单的 return 0 去实现 open() 你就明白了。我们看到这个应用程序就像发生了故障一样。这是 显而易见的, 真实地去调用原始的 open():
inspect_open.c:
int open(constchar*pathname,int flags){
/* Some evil injected code goes here. */
return open(pathname,flags);// Here we call the "real" open function, that is provided to us by libc.so
}
嗯,不对。这将不会去调用 “原始的” open(...)。显然,这是一个无休止的递归调用。
怎么去访问这个 “真正的” open() 函数呢?它需要去使用程序接口进行动态链接。它比听起来更简单。我们来看一个完整的示例,然后,我将详细解释到底发生了什么:
inspect_open.c:
#define _GNU_SOURCE
#include<dlfcn.h>
typedefint(*orig_open_f_type)(constchar*pathname,int flags);
int open(constchar*pathname,int flags,...)
{
/* Some evil injected code goes here. */
orig_open_f_type orig_open;
orig_open =(orig_open_f_type)dlsym(RTLD_NEXT,"open");
return orig_open(pathname,flags);
}