其實 wiki 上對 reverse engineering (逆向工程,文章後面簡稱 reverse) 已經有很明確的定義:
逆向工程,又稱反向工程,是一種技術過程,即對一專案標產品進行逆向分析及研究,從而演繹並得出該產品的處理流程、組織結構、功能效能規格等設計要素,以製作出功能相近,但又不完全一樣的產品。逆向工程源於商業及軍事領域中的硬體分析。其主要目的是,在無法輕易獲得必要的生產資訊下,直接從成品的分析,推導產品的設計原理。
在這邊我想談的是自己對 reverse 的一些感想,逆向工程其實又可以分為 軟體 和 硬體 兩個層面:
但無論是硬體或軟體的逆向工程,本質其實都是 Code Review,透過閱讀被轉化過的 byte code 或 assembly,再加上一點腦補和猜想 (所以是 具現化系 :p),嘗試分析出原本 source code 是如何撰寫,也因為這樣,要進行逆向工程其實必須要有一定的 coding 能力,不然就算把 source code 直接攤在眼前,也沒辦法讀懂程式行為,反過來 reverse 看得多了 (尤其是逆過許多在 high quaulity 的程式之後),也可以提升自己的 coding 能力
由於 CTF 比賽性質的限制,reverse 題目其實相對 real world 的情境來說簡單很多,原因有兩點:
雖然上面說了一些 CTF 的 reverse 題的缺點,但解 CTF 還是一個非常好的練習,尤其可以避免在還不熟悉的情況下直接逆向 real world 的 malware,讓電腦成為 botnet 之一,或是重要資料受到損壞,另外透過解 CTF 的題目也可以在賽後了解出題者或其他參賽者做 reverse 的思維,從中偷師學習
另外無論是不是從事 security 行業,懂一些軟體的 reverse 對一般 RD 來說也很有幫助,尤其是遇到有些 compile level 形成的 bug 時,source code 看再多也找不出問題,但看 assembly 就一目了然了
今天要介紹的是 2015 年 CSAW CTF 出的一題 500 分 Reverse,是那場最難的 reverse 題,是 CTF reverse 題中很常見的註冊機題型,要求輸入 secret,會根據輸入結果印出 failed 或 success 的相關訊息,但這題公開解法之後,大家就都知道透過 side channel 的方式爆破 flag,之後的 reverse 題目就會特別注意是否能被直接爆破出 flag 了
題目執行起來長以下這樣:
+-----------------------+
| Welcome Hero |
+-----------------------+
[!] Quest: there is a dragon prowling the domain.
brute strength and magic is our only hope. Test your skill.
Enter the dragon's secret:ggwp
[-] You have failed. The dragon's power, speed and intelligence was greater.
程式是用 C++ 撰寫,在比對 flag 的地方有進行一些 bitwise 運算和混淆,即使透過 ida pro 將 assembly 轉回 pseudo code 也很難看懂程式邏輯,更別說反推 secret
if ( v7 )
{
if ( y26 >= 10 && (((_BYTE)x25 - 1) * (_BYTE)x25 & 1) != 0 )
goto LABEL_14;
while ( 1 )
{
*v9 = legend >> 2;
if ( y26 < 10 || (((_BYTE)x25 - 1) * (_BYTE)x25 & 1) == 0 )
break;
LABEL_14:
*v9 = legend >> 2;
}
}
else
{
if ( y26 >= 10 && (((_BYTE)x25 - 1) * (_BYTE)x25 & 1) != 0 )
goto LABEL_15;
while ( 1 )
{
v1 = v12;
std::string::string(v8, v12);
if ( y26 < 10 || (((_BYTE)x25 - 1) * (_BYTE)x25 & 1) == 0 )
break;
LABEL_15:
std::string::string(v8, v12);
}
v6 = sanitize_input(v8, v1);
if ( y26 >= 10 && (((_BYTE)x25 - 1) * (_BYTE)x25 & 1) != 0 )
goto LABEL_16;
while ( 1 )
{
*v9 = v6;
std::string::~string(v8);
if ( y26 < 10 || (((_BYTE)x25 - 1) * (_BYTE)x25 & 1) == 0 )
break;
LABEL_16:
*v9 = v6;
std::string::~string(v8);
}
}
do
v5 = *v9;
while ( y26 >= 10 && (((_BYTE)x25 - 1) * (_BYTE)x25 & 1) != 0 );
但我們只需要想辦法知道 secert 是什麼就好,所以不用看懂整段邏輯也沒關係,我們只需要看懂兩件事:
滿足以上兩個條件,我們就可以直接對程式進行 brute force 來猜 secert 是多少,每次 input 給不同的 char,再透過工具觀察這次程式執行了多少的 instruction,就可以知道這次給的 char 是不是和 secret 相同
inscount0.so
模組具體步驟如下:
先知道失敗情況會執行多少個 instruction
base_cnt = run("echo '' | pin -t ./inscount0.so -- ./wyvern; cat ./inscount.out")
開始嘗試可能的字元,如果發現某次執行的的 instruction 比較多,就代表猜中 secert 的某個 char 了
flag = ''
for c in chars:
_ = flag + c
cnt = run("echo '%s' | pin -t ./inscount0.so -- ./wyvern; cat ./inscount.out" % _)
if cnt > base_cnt:
flag += c
base_cnt = cnt
重複以上行為,直到印出 success message,就代表猜中 secret 了
最後執行結果是:
+-----------------------+
| Welcome Hero |
+-----------------------+
[!] Quest: there is a dragon prowling the domain.
brute strength and magic is our only hope. Test your skill.
Enter the dragon's secret:dr4g0n_or_p4tric1an_it5_LLVM
success
[+] A great success! Here is a flag{dr4g0n_or_p4tric1an_it5_LLVM}