下載文件后,file命令看一下是ELF32程序,strings命令發現程序被UPX加殼了。
1 upx -d snake-final.exe
脫殼后扔到IDA里面分析,主函數發現調用signal注冊了若干個回調函數:
特別是幾個38h、32h、34h、36h。實際上是定義了4個控制貪吃蛇行動的游戲按鍵。但顯然就這個程序,按對應的鍵是產生不出對應的signal信號的。(有隊伍使用向進程發送對應signal的方式,間接操控貪吃蛇,游戲成功可以獲得flag)。經過進一步分析,發現最重要的回調函數是handler,同時在handler中存在"int 3"反調試,正常執行的時候會產生signal==5的信號,這里的處理函數為nullsub_1。實際上就是忽略,所以我們也可以直接 nop 掉 int 3。當然完全按照本文的靜態分析方法,而不使用調試,完全可以忽略 int 3。
進一步分析handler,發現是一個極其復雜的函數。沒心思看,找找有沒有別的入口。
strings窗口發現"Mission Complete",xref發現這個字符串引用位置sub_80492E0+14C。
分析sub_80492E0:
這個函數確實是判斷游戲成功與否的標志,成功的話,還需要判斷一個ebx == 3,這里的ebx實際上是函數的傳入參數。但問題是,找到成功的標志,但是后面沒有跟著輸出flag的地方,而僅僅是調用settimer重置了新的信號量。這樣程序的執行流又回到handler里面。
根據目前的信息,尋找帶參數3調用sub_80492E0的地方,xref只發現兩處8049941、8049FF1。實際上這兩處的代碼是一樣的(經過最后的分析實際上這里往后的代碼就是輸出flag的地方,但我在這里迷失了很久)。比較奇怪的是這兩處都不在IDA識別的handle函數的作用域中。原因是IDA識別出了handler函數的Canary保護機制,以此作為handler函數的開始和結束。
進一步分析handler發現:
原來成功吃到30個食物成功后,是通過C++異常處理機制來調用sub_80492E0( 3 )的。
通過上面查找xref的兩個地方,隨便選取第二個,看到調用代碼:
由于sub_80492E0里面沒有顯示flag的地方,只是重置settimer,那么猜測很有可能flag也是通過異常處理鏈來打印的。上面的代碼發現,調用sub_80492E0后,重新拋出了一個異常。所以繼續觀察下面的代碼:
這里注意804A039處的一個比較,這是在打印flag前的異常處理鏈中的最后一道門檻。也是調試時候為什么sub_80492E0已經運行到"Mission Complete",但還是沒有出現flag的原因。
注意這里的一個循環,實際上就是printf "[ebp-50+i] xor 2Ah"。(2Ah是ascii的'*')
1 import sys
2 a='x7fx1ax647fx78x44x5ex50x67x7dx4ex5f'
3 for i in range(0, len(a)):
4 sys.stdout.write(chr(ord(a[i]) ^ ord('*')))
得到:U0N-LRntzMWdu
這個還沒有完,繼續往下看:
可見flag還有兩部分,其中[ebp-84h]就是handler函數開始處的值(說明異常處理實際上還是應該在handler當中的,只是IDA分析的使用因為canary的原因,被排除了):
最后一部分的數據是804C0C0處的全局數據,三部分的算法是一樣的,最后解碼得到:
U0NURntzMWduNGxfMXNfZnVubnk6KX0=
1 echo
2 U0NURntzMWduNGxfMXNfZnVubnk6KX0= | base64 -d
得到flag。
后記:
此題看360首發的sctf writeup上,只講了"Mission Complete"和sub_80492E0,然后就直接base64字符串了,不知道那位大神是如何“看”出來的。
C++的throw–catch的逆向分析是頭一回遇到,還是應該寫一個簡單的C++相應程序,然后逆向研究一下它的結構。這樣才知道handler里面canary和異常處理是人為的有意為之,還是編譯器本來就是這樣處理的。
由于信號量的處理有線程再入的特點,異常也會涉及到跨線程的問題,所以程序流程要完全研究透徹,需要理解的內容非常多。有待進一步學習。
這個題目如果調試,如果跟蹤到異常處理的代碼,還請大牛不吝賜教。