设为首页 加入收藏

TOP

动态连接的诀窍:使用 LD_PRELOAD 去欺骗、注入特性和研究程序
2018-01-01 06:07:08 】 浏览:418
Tags:动态 连接 诀窍 使用 LD_PRELOAD 欺骗 注入 特性 研究 程序

动态连接的诀窍:使用 LD_PRELOAD 去欺骗、注入特性和研究程序


本文假设你具备基本的 C 技能


Linux 完全在你的控制之中。虽然从每个人的角度来看似乎并不总是这样,但是高级用户喜欢去控制它。我将向你展示一个基本的诀窍,在很大程度上你可以去影响大多数程序的行为,它并不仅是好玩,在有时候也很有用。


 


让我们以一个简单的示例开始。先乐趣,后科学。


random_num.c:


我相信,它足够简单吧。我不使用任何参数来编译它,如下所示:


我希望它输出的结果是明确的:从 0-99 中选择的十个随机数字,希望每次你运行这个程序时它的输出都不相同。


现在,让我们假装真的不知道这个可执行程序的出处。甚至将它的源文件删除,或者把它移动到别的地方 —— 我们已不再需要它了。我们将对这个程序的行为进行重大的修改,而你并不需要接触到它的源代码,也不需要重新编译它。


因此,让我们来创建另外一个简单的 C 文件:


unrandom.c:


我们将编译它进入一个共享库中。


因此,现在我们已经有了一个可以输出一些随机数的应用程序,和一个定制的库,它使用一个常数值 42 实现了一个 rand() 函数。现在 …… 就像运行 random_num 一样,然后再观察结果:


如果你想偷懒或者不想自动亲自动手(或者不知什么原因猜不出发生了什么),我来告诉你 —— 它输出了十次常数 42。


如果先这样执行


然后再以正常方式运行这个程序,这个结果也许会更让你吃惊:一个未被改变过的应用程序在一个正常的运行方式中,看上去受到了我们做的一个极小的库的影响 ……


等等,什么?刚刚发生了什么?


是的,你说对了,我们的程序生成随机数失败了,因为它并没有使用 “真正的” rand(),而是使用了我们提供的的那个 —— 它每次都返回 42


但是,我们告诉过它去使用真实的那个。我们编程让它去使用真实的那个。另外,在创建那个程序的时候,假冒的 rand() 甚至并不存在!


这句话并不完全正确。我们只能告诉它去使用 rand(),但是我们不能去选择哪个 rand() 是我们希望我们的程序去使用的。


当我们的程序启动后,(为程序提供所需要的函数的)某些库被加载。我们可以使用 ldd 去学习它是怎么工作的:


正如你看到的输出那样,它列出了被程序 random_nums 所需要的库的列表。这个列表是构建进可执行程序中的,并且它是在编译时决定的。在你的机器上的具体的输出可能与示例有所不同,但是,一个 libc.so 肯定是有的 —— 这个文件提供了核心的 C 函数。它包含了 “真正的” rand()


我使用下列的命令可以得到一个全部的函数列表,我们看一看 libc 提供了哪些函数:


这个 nm  命令列出了在一个二进制文件中找到的符号。-D 标志告诉它去查找动态符号,因为 libc.so.6 是一个动态库。这个输出是很长的,但它确实在列出的很多标准函数中包括了 rand()


现在,在我们设置了环境变量 LD_PRELOAD 后发生了什么?这个变量 为一个程序强制加载一些库。在我们的案例中,它为 random_num 加载了 unrandom.so,尽管程序本身并没有这样去要求它。下列的命令可以看得出来:


注意,它列出了我们当前的库。实际上这就是代码为什么得以运行的原因:random_num 调用了 rand(),但是,如果 unrandom.so 被加载,它调用的是我们所提供的实现了 rand() 的库。很清楚吧,不是吗?


 


这还不够。我可以用相似的方式注入一些代码到一个应用程序中,并且用这种方式它能够像个正常的函数一样工作。如果我们使用一个简单的 return 0 去实现 open() 你就明白了。我们看到这个应用程序就像发生了故障一样。这是 显而易见的, 真实地去调用原始的 open()


inspect_open.c:


嗯,不对。这将不会去调用 “原始的” open(...)。显然,这是一个无休止的递归调用。


怎么去访问这个 “真正的” open() 函数呢?它需要去使用程序接口进行动态链接。它比听起来更简单。我们来看一个完整的示例,然后,我将详细解释到底发生了什么:


inspect_open.c:


dlfcn.h 是我们后面用到的 dlsym 函数所需要的。那个奇怪的 #define 是命令编译器去允许一些非标准的东西,我们需要它来启用 dlfcn.h 中的 RTLD_NEXT。那个 typedef 只是创建了一个函数指针类型的别名,它的参数等同于原始的 open —— 它现在的别名是 orig_open_f_type,我们将在后面用到它。


我们定制的 open(...) 的主体是由一些代码构成。它的最后部分创建了一个新的函数指针 orig_open,它指向原始的 open(...) 函数。为了得到那个函数的地址,我们请求 dlsym 在动态库堆栈上为我们查找下一个 open() 函数。最后,我们调用了那个函数(传递了与我们的假冒 open() 一样的参数),并且返回它的返回值。


我使用下面的内容作为我的 “邪恶的注入代码”:


inspect_open.c (片段):


要编译它,我需要稍微调整一下编译参数:


我增加了 -ldl,因此,它将这个共享库链接到 libdl —— 它提供了 dlsym 函数。(不,我还没有创建一个假冒版的 dlsym ,虽然这样更有趣)


因此,结果是什么呢?一个实现了 open(...) 函数的共享库,除了它有 输出 文件路径的意外作用以外,其它的表现和真正的 open(...) 函数 一模一样。:-)


如果这个强大的诀窍还没有说服你,是时候去尝试下面的这个示例了:


我鼓励你去看看自己实验的结果,但是简单来说,它实时列出了这个应用程序可以访问到的每个文件。


我相信它并不难想像为什么这可以用于去调试或者研究未知的应用程序。请注意,这个特定诀窍并不完整,因为 open() 并不是唯一一个打开文件的函数 …… 例如,在标准库中也有一个 open64(),并且为了完整地研究,你也需要为它去创建一个假冒的。


 


如果你一直跟着我享受上面的过程,让我推荐一个使用这个诀窍能做什么的一大堆创意。记住,你可以在不损害原始应用程序的同时做任何你想做的事情!


这里仅是一些我想到的创意。我希望你能找到更多,如果你做到了 —— 通过下面的评论区共享出来吧!


via: https://rafalcieslak.wordpress.com/2013/04/02/dynamic-linker-tricks-using-ld_preload-to-cheat-inject-features-and-investigate-programs/


作者:Rafa? Cie?lak 译者:qhwdw 校对:wxy


本文由 LCTT 原创编译,Linux中国 荣誉推出


】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇使用 parallel 利用起你的所有 CP.. 下一篇Linux下C程序实现输出某进程内存..

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目