看雪.WiFi萬能鑰匙 CTF 2017第十五題 點評及解題思路

Editor發表於2017-08-10

看雪CTF 2017 比賽進行至第十五題

截止至今天中午12點,第十五題破解人數為 9 人!

防守方 hsadkhk 依然位居首位~

今天是最後一題,攻擊方風間仁看雪.WiFi萬能鑰匙 CTF 2017第十五題 點評及解題思路

處於第一位~,kkHAIKE位列第二位。

看雪.WiFi萬能鑰匙 CTF 2017第十五題 點評及解題思路

比賽至此告一段落了,

比賽的最終結果,請大家持續關注!

接下來我們來回顧一下第十五題

看看 看雪評委和出題者是怎麼說的ヾ(๑╹◡╹)ノ"。


看雪評委 netwind 點評


題目中的VM是作者設計的一個全新的VM,體積雖小,五臟俱全。VM功能包括反除錯、反虛擬機器、反 API 斷點、匯入表保護、OEP 抽取,程式碼虛擬化、程式碼亂序、程式碼拆分、程式碼混淆;攻方需瞭解vm的基本知識,識別出各條handler,然後根據各handler序列分析出整個流程;作者同時採用lua指令碼來進行程式碼保護,攻方還需掌握lua反編譯知識;在演算法上採用了簡單的異或演算法。此題重在考察選手VM分析能力和LUA反編譯能力。

作者簡介

demoscene,反病毒工程師,反外掛工程師。


看雪 CTF 2017 第十五題設計思路


一、設計思路

一個專業實用的保護系統應該不僅能防 keygen 還應該能防爆破防程式碼分析

縱觀當前市面上最好的幾款保護,使用的技術皆為 RSA 演算法+程式碼虛擬化保護,這是當前最流行,最強的軟體保護技術。

題目設計主要考察分析vm的能力,所以不使用複雜的演算法,只進行了簡單的異或。

根據去年CTF時各位大牛寫的分析文章,對VM的弱點進行了修補,增強了VM的強度。

吸收去年CTF其它守護方的設計思路,結合lua指令碼,實現雙虛擬機器保護。

二、設計亮點

1、一個體積雖小,五臟俱全的新VM,非市面上已經被N多人分析過的VM,需要攻擊者重新分析,考察分析VM的基本功。

vm實現的功能:反除錯、反虛擬機器、反 API 斷點、匯入表保護、OEP 抽取,程式碼虛擬化、程式碼亂序、程式碼拆分、程式碼混淆。

2、結合 lua 指令碼,使用自寫虛擬機器+lua虛擬機器,實現雙虛擬機器保護。

       考察攻擊者的 lua 反編譯知識。

3、不用使複雜演算法,避免演算法分析。

三、破解思路

1、由於本題只是對輸入進行了簡單的異或判斷,所以無需費力氣分析演算法,只要把 vm 流程,各個 handler 分析清楚,找到異或的 key 即可。目前殼處理的指令還比較少,混淆模式也比較簡單,所以要理清楚並不難,只是由於大家都第一次接觸到此 VM,所以可能需要花點時間。

2、本題演算法較簡單,主要難點在分析VM和反編譯lua指令碼上

3、分析vm要求大家瞭解vm的基本知識,vm的整體框架,流程,然後區分出花掉令和正常的指令,去掉花指令,保留正常指令,識別出各條handler,然後根據各handler序列分析出整個流程

4、反編譯lua需要找到lua位元組碼並儲存下來,然後使用網上的方法,進行反編譯,這裡的難點在於找到 lua_loadbuffer的地址,在此函式裡把位元組碼dump下來即可

四、其它說明

1、CrackMe使用vs2008編譯,已在xp,win7 32、 win7 64系統上測試功能正常

2、CrackMe使用自己實現的虛擬機器BBProtect加密,之前未曾發在網上,複合題目要求

3、BBProtect 程式在已放到 BBProtect目錄裡

4、由於加了vm,所以殺軟軟體可能會報毒,cm沒有任何惡意功能,可放心執行


下面選取攻擊者 loudy 的破解分析


使用工具:IDA6.8(反彙編分析) python(程式設計解碼) OD(動態除錯)

步    驟:

一、定位關鍵程式碼

該程式有加殼,但對主要流程影響不大,難點在於分析luajit程式碼,定位主要流程。只要流程清晰了,該題沒有難度,只是簡單異或運算。

(1)脫殼

其實也不算真正脫殼,其實就是等程式執行起來後,dump記憶體映象。

這樣在IDA中可以分析解密後的函式和解密後的字串,如下圖

(2)定位關鍵點

從IDA的字串視窗可以看到該程式使用了LuaJIT 2.1.0-beta3 且Lua版本號為Lua 5.1。

通過“main”(呼叫Lua的main函式)關鍵字在IDA中定位到如下位置。

其上面從0040103D開始的一大段mov指令實際是在棧中生成了一個luajit的bytecode。

二、解密

function randomFunction by (INPUT_VAR_0_,INPUT_VAR_1_)
     var_0_4 = INPUT_VAR_0_
     var_0_5 = INPUT_VAR_1_
     var_0_6 = INPUT_VAR_1_
     string.byte( string.sub(var_0_4, var_0_5, var_0_6) )
end
  
function randomFunction main (INPUT_VAR_0_)
     var_1_2 = INPUT_VAR_0_
     var_1_1 = string.len(var_1_2)
     if var_1_1 ~= 0 then
         --jump to 0009 (if previous if statement is false) --0009 JMP-JMP
         var_1_1 = 0 --var_1_1 NUMBER-NUMBER
         return var_1_1
     end
     var_1_3 = INPUT_VAR_0_                                      key[0]
     var_1_4 = 1 --var_1_4 NUMBER-NUMBER  
     var_1_2 = by(var_1_3, var_1_4)                     key[1]
     var_1_3 = 112 --var_1_3 NUMBER-NUMBER  
     var_1_1 = bit.bxor(var_1_2, var_1_3)      key[1]^112
     var_1_4 = INPUT_VAR_0_
     var_1_5 = 2 --var_1_5 NUMBER-NUMBER
     var_1_3 = by(var_1_4, var_1_5)                     key[2]
     var_1_4 = 101 --var_1_4 NUMBER-NUMBER
     var_1_2 = bit.bxor(var_1_3, var_1_4)      key[2]^101
     var_1_5 = INPUT_VAR_0_
     var_1_6 = 3 --var_1_6 NUMBER-NUMBER
     var_1_4 = by(var_1_5, var_1_6)                     key[3]
     var_1_5 = 100 --var_1_5 NUMBER-NUMBER
     var_1_3 = bit.bxor(var_1_4, var_1_5)      key[3]^100
     var_1_6 = INPUT_VAR_0_
     var_1_7 = 4 --var_1_7 NUMBER-NUMBER
     var_1_5 = by(var_1_6, var_1_7)                     key[4]
     var_1_6 = 105 --var_1_6 NUMBER-NUMBER
     var_1_4 = bit.bxor(var_1_5, var_1_6)      key[4]^105
     var_1_7 = INPUT_VAR_0_
     var_1_8 = 5 --var_1_8 NUMBER-NUMBER
     var_1_6 = by(var_1_7, var_1_8)                     key[5]
     var_1_7 = 121 --var_1_7 NUMBER-NUMBER     
     var_1_5 = bit.bxor(var_1_6, var_1_7)      key[5]^121
     var_1_8 = INPUT_VAR_0_
     var_1_9 = 6 --var_1_9 NUMBER-NUMBER
     var_1_7 = by(var_1_8, var_1_9)                     key[6]
     var_1_8 = 49 --var_1_8 NUMBER-NUMBER
     var_1_6 = bit.bxor(var_1_7, var_1_8)      key[6]^49
     var_1_9 = INPUT_VAR_0_
     var_1_10 = 7 --var_1_10 NUMBER-NUMBER
     var_1_8 = by(var_1_9, var_1_10)                    key[7]
     var_1_9 = 50 --var_1_9 NUMBER-NUMBER
     var_1_7 = bit.bxor(var_1_8, var_1_9)      key[7]^50
     var_1_10 = INPUT_VAR_0_
     var_1_11 = 8 --var_1_11 NUMBER-NUMBER 
     var_1_9 = by(var_1_10, var_1_11)               key[8]
     var_1_10 = 7 --var_1_10 NUMBER-NUMBER
     var_1_8 = by(var_1_9, var_1_10)           key[8]^51
     var_1_11 = INPUT_VAR_0_
     var_1_12 = 9 --var_1_12 NUMBER-NUMBER
     var_1_10 = by(var_1_11, var_1_12)                  key[9]
     var_1_11 = 52 --var_1_11 NUMBER-NUMBER
     var_1_9 = bit.bxor(var_1_10, var_1_11)    key[9]^52
     var_1_12 = INPUT_VAR_0_
     var_1_13 = 10 --var_1_13 NUMBER-NUMBER
     var_1_11 = by(var_1_12, var_1_13)                  key[10]
     var_1_12 = 53 --var_1_12 NUMBER-NUMBER
     var_1_10 = bit.bxor(var_1_11, var_1_12)   key[10]^53
     var_1_13 = INPUT_VAR_0_
     var_1_14 = 11 --var_1_14 NUMBER-NUMBER
     var_1_12 = by(var_1_13, var_1_14)                  key[11]
     var_1_13 = 54 --var_1_13 NUMBER-NUMBER
     var_1_11 = bit.bxor(var_1_12, var_1_13)   key[11]^54
     var_1_14 = INPUT_VAR_0_
     var_1_15 = 12 --var_1_15 NUMBER-NUMBER
     var_1_13 = by(var_1_14, var_1_15)                  key[12]
     var_1_14 = 55 --var_1_14 NUMBER-NUMBER
     var_1_12 = bit.bxor(var_1_13, var_1_14)   key[12]^55
     var_1_13 = var_1_1
     var_1_14 = var_1_2
     var_1_15 = var_1_3
     var_1_16 = var_1_4
     var_1_17 = var_1_5
     var_1_18 = var_1_6
     var_1_19 = var_1_7
     var_1_20 = var_1_8
     var_1_21 = var_1_9
     var_1_22 = var_1_10
     var_1_23 = var_1_11
     var_1_24 = var_1_12
     return var_1_13, var_1_14, var_1_15, var_1_16, var_1_17, var_1_18, var_1_19, var_1_20, var_1_21, var_1_22, var_1_23, var_1_24
end
  
function someFunc2()
     require("bit")
     local randomFunction0 = function() end -- starts at  test.lua:0
     by = randomFunction0
     local randomFunction1 = function() end -- starts at  test.lua:0
     main = randomFunction1
     return
end


我們主要關注其中的main函式,上面已經用c語法註釋出來。其實就是對輸入分別與下面這一串異或,返回結果。

[112,101,100,105,121,49,50,51,52,53,54,55]

接下來0040222C處的程式碼取得lua輸出的每一個值分別與下面這一串值異或。

[0x5,0x12,0xa,0x29,0x42,0x41,0x75,0x61,0x35,0x83,0x55,0x94]

接下來004022F0處,異或後的值分別與下面著串值比較,相等則註冊成功。

[0x18,0x16,0x1e,0x2f,0x48,0x11,0x21,0x37,0x33,0x86,0x52,0x94]

那麼python編寫如下程式碼,即可得到真正註冊碼“maposafe2017”。

a = '000000000000'
b = [112,101,100,105,121,49,50,51,52,53,54,55]
c = [0x5,0x12,0xa,0x29,0x42,0x41,0x75,0x61,0x35,0x83,0x55,0x94]
d = [0x18,0x16,0x1e,0x2f,0x48,0x11,0x21,0x37,0x33,0x86,0x52,0x94]
al = len(a)
a1 = list(a)
for i in range(al):
    a1[i] = chr(b[i]^c[i]^d[i])
print ''.join(a1)


全文完,註冊碼“maposafe2017”。


最後感謝 WiFi 萬能鑰匙安全應急響應中心的贊助支援,

接下來的比賽大家一定要使出洪荒之力哦!↖(^ω^)↗

比心 ❤


贊助商

上海連尚網路科技有限公司成立於 2013 年,是一家專注於提供免費上網和內容服務的移動網際網路企業。連尚網路自主研發的核心產品 WiFi 萬能鑰匙,以分享經濟的模式,通過雲端計算和大資料技術,利用熱點主人分享的閒置WiFi資源,為使用者提供免費、穩定、安全的上網服務,以幫助更多的人上網,找到屬於他們的機會,改變自己的命運。


往期熱門內容推薦



更多比賽詳情,長按下方二維碼,“關注看雪學院公眾號”檢視!

看雪.WiFi萬能鑰匙 CTF 2017第十五題 點評及解題思路

相關文章