xz项目由Lasse Collin等人于2005年发起,因其优异的性能和压缩比逐渐成为了Linux Kernel,FreeBSD等开源软件的默认压缩方式,并且以liblzma
依赖库的方式被openssh-server
(部分发行版,如Debian)等关键程序引用,使用极为广泛。然而,早在2013年的文档中[1]就有提到:Lasse因“个人原因”导致项目更新缓慢。这样一个应用极为广泛但欠缺维护的开源软件无疑是攻击者的首选目标。
锁定目标后,攻击者有组织地展开行动,试图获取XZ仓库代码修改权限并植入后门。
从以上复盘可以看出,攻击者为了在XZ这样一个广泛使用的开源软件中植入后门而不被发现,在攻击的非技术层面做了精心设计和准备,包括:
可以看到,对以社区合作形式开展的开源项目的攻击,可能不仅仅局限在狭义的技术层面。无论是可重现构建还是零信任架构,我们不仅需要设计如何保护数据与系统,更需要思考人与人之间,人与技术之间,甚至技术与技术之间如何重新构建一种更有韧性,更可持续的信任体系。
时间过去9个月,网络上已经有大量深入的对XZ后门程序的逆向工程分析[4,5,6]。回过头来,我们想探究的问题是:攻击者耗费了2年时间精心隐藏植入的后门,为什么会在如此短的时间内就被发现?从攻击者的角度出发,这其中存在的缺陷是否还能够进一步优化? 从防御者角度出发,又如何更好的及时发现和定位类似的问题?
Andres Freund在社交媒体上的评论表示,他一开始只是发现有恶意攻击者在爆破服务器ssh密码,但是造成了异常高的CPU占用。随后他对sshd
进行了性能分析,发现更新过liblzma.so
后其处理每个ssh连接的时间从0.3秒增加到了0.8秒。联想到之前偶然看到的Valgrind报错,Andres对xz-5.6.1
中的liblzma.so
和源码构建脚本进行了深入分析并得出了其内部被植入后门的结论。
Andres对发现过程的描述
然而,根据先前研究人员逆向工程的结果[4],xz 5.6.1中后门程序的主要原理是拦截RSA解密函数后,根据ssh客户端发送证书中的字段进行解密,验签和执行命令操作。我们调试过程中发现,对于通过密码来登录的方式,并不会触发到后门代码的执行。也就是说按照现有的理解(或者说攻击者的设计思路),Andres应该无法从密码爆破这个现象观察到有明显的时间差异。
为了重现Andres的发现过程,我们使用perf
对sshd
进行了性能分析,显然带有后门的liblzma.so
明显消耗了异常的计算资源。
使用有(左)/无(右)后门的liblzma.so
的sshd
进程在处理ssh密码登录时的性能分析结果
经过进一步分析perf
报告的热点指令liblzma.so:0x2bbb0
处的代码,我们发现其位于后门代码中的一处x86_64指令反汇编器中。该反汇编器的主要作用是从给定地址处开始解析一条x86_64指令,配合其他上层函数实现基于汇编模式来匹配无符号的sshd
和ld
中的目标函数和结构体的偏移地址。
例如,在初始化阶段,XZ后门程序需要将dl_audit
全局变量改为伪造的audit_iface
结构体从而劫持symbind64
函数调用,进而劫持RSA_public_decrypt
等函数。为了找到dl_audit
的地址,后门程序使用反汇编器扫描ld
的内存,找到dl_main
函数中满足模式MOV reg,DWORD PTR [RIP+imm]
的指令,并进行解析其内存操作数。
后门通过解析指令获取dl_audit
全局变量地址
我们进一步分析发现,在重复调用反汇编器进行汇编模式搜索时,攻击者在一些上层函数中会让搜索的内存地址向后移动解析出来的指令长度,从而直接反汇编下一条指令。
然而,在反汇编器的内部实现中,解析出的“指令长度”却永远是0。这导致了后门代码事实上是在逐字节对每一个目标代码区间进行反汇编。此外,还有一些上层函数调用反汇编器时直接逐字节扫描目标内存:
一些上层函数调用反汇编器时直接逐字节扫描目标内存
这无疑为后门代码的运行带来了大量运行时开销。动态分析表明,该反汇编器函数的调用次数多达944万次。而每次调用中,仅仅是函数开头初始化解析结果结构体的liblzma.so:0x2bbb0: rep stosd
指令就会被重复执行22次,也就是说仅仅这一行代码就执行了超过2亿次,导致liblzma.so
在perf
的输出报告中鹤立鸡群。因此我们推定,该反汇编器和相关的汇编匹配函数的实现缺陷是导致这一后门程序在运行中消耗了大量资源的根本原因。我们大胆猜测,负责开发这部分代码的攻击者可能认为该反汇编器的大规模调用只会在ld
运行期间发生一次,因此只会一次性地影响sshd
服务初始化时的性能开销。然而,openssh-server
从3.9版本开始便默认采取了re-exec
模式,即每个fork
得到的子进程都会调用execv
来重新初始化进程地址空间[6]:
static int rexec_flag = 1;
...
/* This is the child processing a new connection. */
setproctitle("%s", "[accepted]");
/*
* Create a new session and process group since the 4.4BSD
* setlogin() affects the entire process group. We don't
* want the child to be able to affect the parent.
*/
...
if (rexec_flag) {
...
execv(rexec_argv[0], rexec_argv); // Make sshd(8) re-execute itself on accepting a new connection.
也就是说,每当有1个新的ssh连接, sshd
都会重新执行整个后门代码的初始化过程,消耗0.5s的CPU时间。我们在本地分别对带有后门和没有后门的sshd
进行了ssh爆破测试,发现后门版本的CPU使用率远远超过无后门版本(9.6% v.s. 0.7%):
因此当Andres在使用期间有另外一组攻击者试图爆破其ssh密码时,sshd
对于CPU资源的消耗便上升到了无法忽视的地步。
Andres本人对500ms meme的澄清
至此,我们才真正确认了XZ backdoor在短时间内被发现的根本原因。作为近年来最(接近)成功的开源供应链攻击,XZ后门的“翻车式”退场给所有潜在攻击者上了一课:后门代码植入,不光要藏得深,还得经得起“压力测试”。
深蓝洞察
2024年,除XZ backdoor外,CrowdStrike的全球蓝屏事故,黎巴嫩寻呼机爆炸等事件也向我们传统认知里系统安全的定义和边界发起了挑战。
与往年的永恒之蓝、Log4j等安全大事件相比,这些“后起之秀”们并非起源于技术层面上的软件漏洞,而是在信任传播的供应链中最薄弱的一环悄然发生,便能轻易撬动整个生态系统的安全性。
另一方面,内存破坏等传统二进制漏洞的利用由于各种软硬件安全机制的普及而变得愈发困难,也进一步加强了安全研究向新的领域和形态迁移的进程。
在敌暗我明,攻守易势的时代,如何继续实现安全,保护价值,已成为每位安全研究人员面临的共同机遇与挑战。
明日,请继续关注《深蓝洞察 | 2024 年度安全报告》第七篇。
推荐站内搜索:最好用的开发软件、免费开源系统、渗透测试工具云盘下载、最新渗透测试资料、最新黑客工具下载……
还没有评论,来说两句吧...