现象:采集数百台服务器的/bin、/sbin、/usr/bin、/usr/sbin下的文件md5sum加以对比,发现多个相同的文件名的md5sum竟然不同。
思考方向1:是否系统版本、内核版本不一致?
通过查找相同系统版本、内核版本的相同可执行程序,也发现不同。
思考方向2:是否rpm包不同?
通过rpm -qi查看,安装的rpm一模一样。
结论:原来是系统上的prelink导致的!
通过查看# cat /etc/cron.daily/prelink可知,每天会对/etc/prelink.conf中指定目录中的so文件、二进制文件进行预链接导致文件内容变化。
只要删除这个定时任务,并用prelink -au取消预链接即可保证一致了。
为了搞清楚prelink,特翻译下文,对其工作原理和可能的影响做个基本的介绍。
原文地址:https://lwn.net/Articles/190139/
标题:预链接和地址空间随机化
预链接(prelink)是一个流行的工具,它用于缩短程序加载时间、减少系统启动时间并让应用启动的更快。预链接是由红帽公司的Jakob Jelinek开发出来的,它重定位(relocate)磁盘上的库来节省动态链接时间。
当动态链接器加载一个已动态链接的可执行链接格式(Executable and Linkable Format,ELF)二进制的时候,在执行程序的进入点(即_main())之前,它也必须加载和链接所有的库。这个过程包含重定位库,也就是改变在库中引用的所有地址以反映内存中的实际地址。重定位包括迭代库中的每个地址并且把它替换成真实地址,这个地址是由进程虚拟地址空间中的库位置所决定的。大部分重定位发生在符号表(symbol table)和过程链接表(Procedure Linkage Table,PLT);但是在极少数情况下,也有.text重定位,这要求在一个稍微更慢一些的过程中打上固定位置可执行代码的补丁。
重定位过程将减慢一个应用的启动。为了加速该过程,预链接提前重定位库。通过扫描每个要预链接的可执行程序、生成要与其他库同时加载的库的图谱、然后为每个库计算目标地址(在这样的地址上,这个库不会和其他的库在相同的地址上加载)来完成了预链接。这些偏移然后存储在共享对象文件本身,符号表和段地址全部被调整以反映基于被选定的基地址的地址。
一旦预链接完成了,动态链接器再也没有必要过问重定向的事了。库被加载到库头部指定的地址上,符号表已然是正确的了。如果任何东西强制库被加载到一个不同的地址上,那么库像往常一样被恰当的重定位;否则,我们可以向重定向库的加载时间的开销说再见了。
为库提供地址空间布局随机化的内核工具不能和预链接一起用;这样做会需要重定位库,使得预链接的目的失效。地址空间随机化是安全系统的一项核心特性,这样的安全系统例如OpenBSD、Adamantix、Hardened Gentoo、Fedora Core以及Red Hat Enterprise Linux。它已经作为PaX的一个部分出现,也作为Ingo Molnar的Exec Shield的一个部分出现,而且已经由Arjan van de Ven在2.6.12提交后被接受进入了主线内核中(http://lwn.net/Articles/121845/)。
地址空间随机化的单纯目的是,通过改变用于攻击的重要段被加载的位置来使其更难以执行特定种类的攻击。如果一个攻击者希望执行已注入的shell代码或者操纵程序使其不正常执行,很明显,他必须知道那个代码在哪里。通过把内存段打乱顺序,那些攻击变得相当困难;在Pax文档(http://pax.grsecurity.net/docs/aslr.txt)和维基百科(http://en.wikipedia.org/wiki/Address_space_layout_randomization)上数学化的描述了成功攻击的可能性。
为了恢复地址空间随机化的一些好处,预链接能够随机性的选择用于预链接的地址。这使其更难以在一个系统上执行特定的攻击,因为使用的地址是唯一于那个系统的。然而,这种方式比每个进程的随机化更低效,因为直到预链接再次运行之前,地址都是不变的。
要看看预链接的另一个影响。为了理解这种影响,首先让我们通过检查2个进程中C标准库的加载地址来检视下预链接的一个特性:用于所属的’cat’和root所属的’bash’。C标准库是很有意思的,因为,在实践中,几乎所有的return-to-libc无一例外的利用了这一点。
user@icebox:~$ cat /proc/self/maps | grep libc | grep r-xp
4df2e000-4e053000 r-xp 00000000 08:07 81197 /lib/tls/i686/cmov/libc-2.3.6.so
user@icebox:~$ sudo -s
root@icebox:/home/user# cat /proc/$$/maps | grep libc | grep r-xp
4df2e000-4e053000 r-xp 00000000 08:07 81197 /lib/tls/i686/cmov/libc-2.3.6.so
仔细的检查这些输出快速的验证了,在这2个进程中,glibc的可执行代码地址是相同的;这和预链接的行为一致。因为库本身是提前重定位的,对动态链接库来说优先在那个地址上加载库。检查libc本身产生了如下的输出。
user@icebox:~$ readelf -S /lib/tls/i686/cmov/libc-2.3.6.so | head -n 6
There are 64 section headers, starting at offset 0x12d114:
Section Headers:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .note.ABI-tag NOTE 4df2e154 000154 000020 00 A 0 0 4
计算4df2e154 - 154,也就是从任何给定的非NULL段取出的地址和偏移量,结果是4df2e000,也就是libc的基地址。这是合理的;预链接基于一个特定的加载地址来为库重写段和符号地址,动态链接器在那个地址上加载库以避免重定位库。进一步说,任何与libc链接的程序都必须能够读到libc,因此能够获取到相同的信息。
所有这些都意味着,在系统上使用任何预链接的库的任何程序都能够泄露有关使用那个相同库的更高权限的任务的信息。这使得任何攻击者都能够获得任何形式的本地访问—或者更直接一点儿,获得读取libc的能力—来获得有关更高权限进程的地址空间布局的信息。正如我们所知道的,这种信息对于想漏洞利用一个高权限进程而不用暴力破解库加载地址的攻击者来说是相当有价值的。
这种脆弱性仅仅适用于具有本地访问的攻击者;但是这不是空穴来风的需求。很多站点托管公司给予本地shell访问或者允许PHP;这2者都可以被用于远程获取libc的副本。因为动态链接器的本性和合情合理的安全设计,动态链接器与其启动的进程具有相同的权限;因此,即使系统上最严格的强制访问策略,例如SELinux、grsecurity、AppArmor都不能阻止这种攻击。
除了避免预链接,有另一种方式可以阻止这种信息泄露被利用。所有链接到一个预链接库的进程需要访问库文件并且在相同的地址上加载那个库;暴露点(point of exposure)是使用库的相同副本。为了阻止信息泄露,那么对在任何2个你不希望相互泄露信息的程序之间共同的每个库,你都必须有单独的副本。这可以用Xen、chroot范围、UML或者简单的隔离机器来完成,只要目录层级是使用预链接随机化来各个预链接的即可。每个系统都将和在这种模式下的其他系统有不同的地址集合。这当然需要更多硬件、更多的磁盘空间、更多的管理、更多的内存以及更多的工作。
这种信息泄露的直接影响取决于你的安全顾虑到底是什么。例如,考虑到确实丢失地址空间随机化带来的好处,一个站点托管公司可能不希望在其服务器上运行预链接。而另一方面,一个家庭台式电脑,可能仅仅必要担心使用这种信息泄露的木马来对一个系统服务(例如cups和dbus)实施攻击,并且可能应该首先担心 /proc/PID/maps。尽管这2者本质上都是关心一个具有本地访问的攻击者,但是攻击的可能性和潜在破坏的价值是不同的。
预链接工具有效的缩短了程序加载时间,可以帮助用户利用其需要更快的运行的桌面和程序。然而,预链接工具有些不幸的后果,这些后果必须被检视到,特别是在依赖于地址空间随机化的安全敏感的环境中。
译者:胥峰
新钛云服首席解决方案架构师,十二年运维经验,曾长期在盛大游戏担任运维架构师,参与盛大游戏多款大型端游和手游的上线运维。资深Linux专家,拥有工信部高级信息系统项目管理师资格,著有畅销书《Linux运维最佳实践》,译著《DevOps:软件架构师行动指南》是DevOps领域的经典著作。
版权声明:本文为新钛云服原创编译,谢绝转载,否则将追究法律责任!