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

Editor發表於2017-06-30



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

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

防守方 hsadkhk 依然位居首位~

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

此題過後,風間仁依然處於第一位~,kkHAIKE位列第二位。

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

比賽明天就要結束了!

結局初步已定了,提前恭喜各位啦~

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

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


看雪評委 netwind 點評


該題採用了加密殼進行保護,程式中所有字串都被加密隱藏,校驗流程被隱藏在著名的Brainfuck虛擬機器中,序號產生器制採用LFSR(線性反饋移位暫存器)模型,題目難度非常大;需要對程式脫殼後,對Brainfuck虛擬機器程式碼還原,再破解LFSR演算法才能解此題。

作者簡介


半盲道人,在下合肥某科大天文系學子,畢業方十日,前程漫漫。書蠹詩魔,興趣廣泛。文理情兼具,儒玄道兩全。上測星軌,下卜大衍。然裸眼視力0.1,其父嘲之曰半盲,因以為號焉。

反正啦,就是啥都會一點,深度學習啊,高效能運算啦,逆向其實還是剛剛接觸,這次希望能在看雪結識大牛,也能學習到不少東西,也希望能給諸君帶來有趣的破解體驗,做了一點微小的工作,謝謝大家。

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


去年看了看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水平。但是這個程式我故意寫的比較慢,所以想直接窮舉的話不太可能。


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


隨緣做題 2333

程式帶殼,用查殼工具沒能查出來是什麼殼,那這個應該是作者原創的,只能手工脫殼了,可是......我不會脫殼,瞎搗騰一番,毫無結果。

在最後快要放棄的時候,嘗試了一次投機的方法,執行程式,用OD附加,轉到401000,然後dump,發現能dump!!!

這樣就得到了疑似脫殼後的程式碼了,然後是字串搜尋,定位到sub_40679A()

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

其中sub_4074A3()如下

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

所以v0上的資料就是'Please enter the key: '

sub_40752A()的功能不清楚,不過看起來那麼複雜,應該是庫函式吧,根據實際就暫時把他當做printf之類的函式吧

找到呼叫sub_40679A的函式sub_407165()

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

這裡的過程看上很怪。。。不過裡面的函式有點特別,比如sub_40818E,sub_407918,sub_407E2F,他們的引數裡都有一個函式地址,跟進分析這些函式地址,根據其程式碼,分析猜測其功能,然後推測出其驗證過程:輸入長度為12的字串交由bf_vm驗證。

bf_vm函式如下

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

這裡輸入的字串會轉成bit,然後程式根據預先準備好的bf程式碼生成位元組碼,然後再開始執行vm

bf_func函式如下

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

這裡的功能跟bf的並沒多大不同,只是case 5和case 6需要注意,case 5如下

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

case 6如下

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

試著把bf_data上的資料累加,發現和剛好為3600,那麼綜合分析可以知道,case 5的驗證必須成功,而且次數剛好是bf_data的長度,不過當bit_add達到3600時,get_bf_input返回的資料有點變化,但現在還不知道有什麼用,接下來就是對bf程式碼轉義了

轉義結果可以根據自己的語言習慣,我比較喜歡用Python,就按照python的格式來轉義了

首先是常見的轉義,程式碼如下

def cal(s):
    if '+' in s:
        return '+= %d'%len(s)
    else:
        return '-= %d'%len(s)
...
...
...
        pattern = re.compile(r'>+')
        match = pattern.match(s[p:])
        if match:
            p += len(match.group())
            ptr += len(match.group())
            continue
        pattern = re.compile(r'' in s:
        return len(s)
    else:
        return -len(s)
...
...
...
        pattern = re.compile(r'([><]+)')
        match = pattern.match(s[p:])
        if match:
            p += len(match.group())
            groups = match.groups()
            ptr1 = ptr + sym2cal(groups[0])
            ptr2 = ptr1 + sym2cal(groups[1])
            ptr3 = ptr2 + sym2cal(groups[2])
            # print tab+'mov mem[%d] mem[%d]'%(ptr1,ptr3)
            print tab+'mem[%d] = mem[%d]'%(ptr1,ptr3)
            for v in groups:
                ptr += sym2cal(v)
            continue
             
        pattern = re.compile(r'\[-\]')
        match = pattern.match(s[p:])
        if match:
            p += len(match.group())
            # print tab+'mov mem[%d] 0'%(ptr)
            print tab+'mem[%d] = 0'%(ptr)
            continue

這樣得到的轉義程式碼就好讀多了,然後是演算法分析,轉義程式碼裡有一段這樣的程式碼

mem[51] = 0
mem[51] += 2
mem[21] = mem[55]
mem[20] = mem[51]
mem[50] = mem[21]
while mem[21]:
    mem[21] -= 1
    mem[20] -= 1
    mem[19] = mem[20]
    mem[18] += 1
    while mem[19]:
        mem[19] = 0
        mem[18] -= 1
    while mem[18]:
        mem[18] -= 1
        mem[50] = mem[21]
        mem[20] = mem[51]
mem[55] = mem[50]
cmp mem[55] data[data_ptr]

其實這個就是mem[55] %= 2,既然結果mod2,那麼前面的一些操作都可以簡化為mod2上的操作,比如加可以簡化為異或,乘可以簡化為與,這樣分析出其驗證過程,然後是用z3來求解了,求解程式碼見附件。

得到 flag:

ToBeOrNot2Be



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

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

比心 ❤


贊助商

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


相關文章