去年看了看CTF 2016,躍躍欲試之下準備參加今年的CTF。沒想到竟然提前了。花了十天時間趕快寫完,順便專門寫了一個殼。用了一些比較學術的黑科技,希望能給破解者帶來不一樣的體驗。原始碼和具體說明都放在壓縮包裡了。
參賽程式是CTF.exe,支援Windows 7 32/64位到Windows 10 Build 15063.250。XP未測試,應該也能執行。
為了保持簡單與可移植性,此程式無圖形介面,不需要任何額外的執行庫。
如果key輸入正確,會列印字串“Yeah! You've got it”並自動退出。
防毒軟體可能會報毒,現在的殺軟看到殼就報毒......這個問題在下也很頭疼。有時候剛執行就被Windows Defender刪掉了.......具體報毒情況參見:
https://www.virustotal.com/en/file/b5b1fb5642d811c0a0059ba41398ddac2716057ff01b6531dd814fb927d16369/analysis/1493728563/
http://r.virscan.org/report/e02d220087c3b8bc2cdc4e16f7f72bbc
正確的金鑰是 ToBeOrNot2Be
CrackMe 目錄下是主程式的程式碼,以及 VS2015 的工程檔案。
PEZEncrypt 目錄下是在下為本次比賽隨手寫的一個簡單的殼,更通用的程式碼已經被在下放在https://github.com/emc2314/PEZEncrypt。
References目錄下是參考文獻。即這個程式用到的一些技術來源。
Brainfuck目錄下是與Brainfuck虛擬機器相關的檔案。
Bin
目錄下是一些手工處理的結果。libbf.exe是libbf的編譯結果。CrackMe.exe是CrackMe工程編譯所得,CrackMe-stolen是手工將程式入口點的幾條指令去除後的結果。Project1.exe是PEZEncrypt的編譯結果。將CrackMe-stolen.exe拖到Project1.exe上即為最終結果。
CTF.exe.checksum是CTF.exe的雜湊,CTF.exe.checksum.asc是用在下的GPG私鑰對CTF.exe.checksum的簽名,均為ASCII檔案。
程式大概分四個層次:
1.
加殼。加殼很簡單,用XTEA演算法對.text和.data區段進行加密,然後執行的時候自動解密。對輸入表無處理。殼的程式碼中使用了SEH來混淆程式流程,並且檢測CONTEXT中的硬體斷點。為了防止ESP定律,殼的開頭首先是一個虛假的pushad,然後把popad藏在花指令裡,再sub
esp, a_certain_value,然後才是真的pushad,在最後的跳轉前add esp,
a_certain_value。且將SEH藏在這個pushad的結構裡,使得硬體斷點(如果有的話)提前多次觸發。為防止記憶體斷點,在執行過程中將棧所在的頁設為可讀可寫,覆蓋可能的NO_ACCESS屬性。在SEH處理中設定SetUnhandledExceptionFilter,以此檢測偵錯程式。並且執行中有兩次記憶體校驗,防止0xCC。
2. CrackMe。這個程式中的字串都被異或了一個隨機值(編譯時),並在每次呼叫重要函式的時候使用模板超程式設計構成一個有限狀態機,在狀態轉化的時候檢測偵錯程式,如果沒有才會完成呼叫
3.
Brainfuck。真正的校驗流程被隱藏在著名的Brainfuck虛擬機器中。這個語言只有[
] + - < > . ,
8條指令,CrackMe中實現了一個虛擬機器,BF的程式碼被放在.rsrc段中(為了降低難度,並未加密字串),註冊流程大概是如果輸入字串長度為12,就將12個字元的ASCII碼(96個bit)輸入VM中執行(見CrackMe\lfsr.cpp)。然後VM會輸出一個結果,CrackMe會將這個結果與預設的一個陣列比較,如果相同就認為正確。
4. LFSR。序號產生器制是一個線性反饋移位暫存器(LFSR),這個是原來生成流密碼的基本模型。這個模型存在很多問題所以現在漸漸被淘汰。本程式利用了其中一個漏洞:代數攻擊(見References\lfsr
algebraic attack.pdf)。具體的漏洞利用程式碼尚未放在資料夾中,如果此程式被本次比賽選中,在下再行公開。
破解思路:
說實話自己寫的程式自己都覺得難破解。。。看著自己的原始碼大概有這幾個步驟:
1. 脫殼,找到OEP直接dump,怎麼找OEP?慢慢跟蹤唄。幾個反除錯需要外掛才能過,畢竟很小的殼,耐心一點就好了
2. 識別主函式中的函式呼叫,找到幾個重要函式(比如BF的VM相關程式碼)。這個主程式很小,也不難。
3. 最難的就是弄清楚BF要做什麼。這個先將BF還原為C程式碼(非常簡單),然後透過窺孔最佳化等技術簡化這個流程(其實最簡單的方法就是透過各種最佳化能力強的編譯器比如gcc,
intel c++ compiler編譯這個C程式碼,再反彙編回來)。然後在程式中匯出那個與VM輸出想對比的陣列
4. 最後就是破解lfsr了,這個透過論文中的方法,解一個3570(即12*7+(12*7)*(12*7-1)/2)元的線性方程組就行了。高斯消元法(或者用SAT的演算法複雜度更低)的複雜度是N^3,大概幾十秒就跑出來了。
最後,窮舉的空間不大,只有10^26水平。但是這個程式我故意寫的比較慢,所以想直接窮舉的話不太可能。