導語
這道題的比賽過程可謂是精彩紛呈,十分有看點了。
群裡的討論十分火爆~大家都對這道題表達了自己的見解~
精彩的第五題結束,第五題出題者birchfire以13人的成績,一舉登上防守方第一位。
第五題過後,很多黑馬讓大家眼前一亮。
前十名的排名再次發生了較大的變動。poyoten升至第一名。
cqccqc衝進第四名,raycp、qwertyaa、acdxvfsvd,也是黑馬,緊隨其後,衝進了前十位。
第五題過去後,比賽的最終結果又開始變得撲朔迷離。
老將能否捍衛自己的寶座,而新將又是否能夠一舉奪魁,給我們帶來新的驚喜呢?
接下來讓我們一起來看看第五題的點評、出題思路和解析。
看雪評委netwind點評
本題構思精細、耐人尋味,是一道非常有趣的題目。首先作者透過int
2d 指令觸發異常來進行反除錯,然後採用魔方轉動變換的方式對原始資料進行加密。而魔方旋轉變換可以用矩陣乘法實現,透過乘以一個 48*48
的單位矩陣實現魔方的旋轉變換,破解者需要分析加密過程,然後逆序操作進行解密。
第五題作者簡介
brichfire,看雪論壇資深粉絲,喜歡程式逆向分析,長期從事基於汙點傳播方法的二進位制程式碼分析和漏洞挖掘方法研究,特別感謝看雪論壇提供這麼好的學習平臺,喜歡的書有《0Day漏洞》、《Android軟體安全與逆向分析》、《軟體安全分析與應用》等。
第五題設計思路
演算法思想
1、 本題採用魔方轉動變換的方式對原始資料進行加密,破解者需要分析加密過程,然後逆序操作進行解密。
2、 魔方操作的數學建模
魔方初始狀態圖,中心 a,b,c,d,e,f 是 6 個面的中心,
順時針旋轉 a 面 90°得到如下圖:紅色的數字是發生了置換的位置。
順時針旋轉 b 面 90°得到如下圖:
順時針旋轉 c 面 90°得到如下圖:
順時針旋轉 d 面 90°得到如下圖:
順時針旋轉 e 面 90°得到如下圖:
順時針旋轉 f 面 90°得到如下圖:
無論怎麼旋轉,魔方中心不會改變位置,每個面有 8 個值,上圖中數為 8 進
制,6
個面共 48 個數,可以構成一個 48*1 的矩陣。而魔方旋轉變換可以用矩陣乘法實現,透過乘以一個 48*48 的單位矩陣實現魔方的旋轉變換。
6 個面用 6 個矩陣分別表示:如果 a 面旋轉 180°就乘以 2 次 MATRIX_a, 270°就乘以 3 次。 因此,每個面的操作有 3
種, 6 個面共計 18 種。為了將輸入對映到操作,可以考慮 18 進位制,而輸入範圍包括數字和大小寫字母,可以考慮用 62
進位制,兩個結合到一塊就是 62 進位制到 18 進位制的轉換。
int MATRIX_a[48][48];
int MATRIX_b[48][48];
int MATRIX_c[48][48];
int MATRIX_d[48][48];
int MATRIX_e[48][48];
int MATRIX_f[48][48];
3、加密金鑰:KanXueCrackMe2017,題目將其視為
62 進位制數(左邊是低位,右邊是高位),因為合法輸入包含 26*2 個大小寫字母和10 個數字共計 62 個字元,加密前先轉換成 18
進位制數,EDAHE450C741GH441E11BH84(左邊是低位,右邊是高位), 每一位作為轉動魔方的一個操作碼 V,用 V/3
表示要操作的面(魔方 6 個面,編號 0~5),用 V%3 表示要轉動的角度, 0, 1, 2 分別表 示 順 時 針 轉 動 90 ° ,
180 ° , 270 ° , 初 始 化 時 依 次 按
照EDAHE450C741GH441E11BH84 對應的動作轉動魔方,得到一個混亂狀態的魔方。
4、 驗證過程是使用者輸入驗證碼,數字和大小寫字母組合成一個 62 進位制的數,
然後轉換成 18 進位制的資料,從低位到高位作為魔方轉動的操作對魔方進行
變換,結束後檢驗魔方是否還原回來。
5、 從原理上,我們首先得到各個操作碼的逆操作碼,即操作完 V 之後操作~V
能夠回到原始狀態。如下表所示:這些操作碼三個一組,分管一個面的操作:
6、
得到解密還原的操作碼序列如下:46F911C144FG147E234CFADC,另外我們知道如果連續的 2 個操作碼是操作同一個面,那這 2
個操作碼其實可以進行運算消除合併, 46F911C144FG147E234CFADC 中的11 操作是第零個面連續轉了 2 個
180°,可以消除捨去, 44 操作也是第一個面連續轉了 2 個 180°,消除捨去, FG 操作是第五個面轉了 180°後再轉
270°,等價於轉了 90°,化簡為 H, 34 操作化簡為 5 操作, DC 操作化簡為 E
操作,化簡合併之後得到最短的還原操作步驟如下:46F9C1H147E25CFAE,將這個值當成 18 進位制(左低位右高位)轉換成 62
進位制(左低位右高位)得到口令:CcLaoE37J45Y
7、 魔方還原的操作步法不唯一,本題透過後面的最後一步的校驗保證其唯一性,解出此題的一個關鍵是發現化簡環節。工作流程圖如下所示:
反除錯技巧
本題的反除錯使用了一個小技巧,
int 2d 指令觸發一個異常訊號,在除錯和跟蹤環境下則不會觸發該異常,會走到一個迭代 1000 次 base64 編碼的編碼過
程,然後將輸入與字串“ Vm0wd2QyUXlVWGw==”比較,這個是永遠不會成立的,因為正確的編碼結果是“
Vm0wd2QyUXlVWGw=”,對應的輸入是“ ImBeDebuged” ,而比較的字串多了一個 ” =“號。
此處容易誘導解題者進入解題誤區。
破解過程
1、 定位到 0x406FC3 位置的校驗函式;
2、 分析 0x40700B 位置呼叫魔方初始化函式和 0x407030 位置呼叫的魔方打
亂函式,該函式位於 0x4084A3 位置,根據操作步驟呼叫 0x4080DF 處的函
數,根據引數旋轉指定的面和轉動角度。
引數 KanXueCrackMe2017,打亂步驟 EDAHE450C741GH441E11BH84
3、 分析 0x40718B 位置呼叫的魔方還原函式,與打亂函式相同,引數是使用者
的輸入,對應還原步驟。按照打亂步驟的逆序和逆操作碼,得到一個還原
序列46F911C144FG147E234CFADC,得到對應的輸入ieunV2phk6yPywNtJ,測試得到如下結果:
給出了提示,不是失敗,而是接近成功,說明思路是對的。
另外從下面的程式碼看出輸入字串長度為 0xC,即 12 個字元,小於上述
逆推出來的 17 個字元。
4、 最後一 個坑。
序列 46F911C144FG147E234CFADC 是可以化簡的,
46F911C144FG147E234CFADC
中的 11 操作是第 0 面連續轉了 2 個 180°,可以消除捨去, 44 操作也是第 1 面連續轉了 2 個 180°,消除捨去, FG
操作是第 5 面轉了 180°後再轉 270°,等價於轉了 90°,化簡為 H, 34 操作化簡為 5 操作, DC 操作化簡為 E
操作,化簡合併之後得到更短的還原操作步驟如下:46F9C1H147E25CFAE,將這個值當成 18 進位制(左低位右高位)轉換成 62
進位制(左低位右高位)得到口令:CcLaoE37J45Y, 剛好 12個字元, 驗證成功。
下面選取攻擊者 jackandkx 的破解分析
相關工具:IDA、OD、python
一、按鈕事件處理函式
由程式圖示得知這是一個MFC程式。開啟程式,隨便輸入sn,彈出失敗對話方塊。
用OD開啟CM,bp MessageBoxW,執行,輸入sn,點選確定,觸發MessageBoxW斷點。棧回溯確定呼叫源0x407245,隨即找到按鈕處理函式0x4071fd。
主函式邏輯很簡單,判斷sub_406fc3的返回值。0彈出“失敗”,1彈出“成功”,2彈出“你就差一點啦”
二、int 2d反除錯
跟進0x406fc3函式,在正常流程中發現int 2d指令
int 2d反除錯原理很簡單,正常執行時int 2d觸發異常,進入程式的異常處理函式。而當除錯執行時,OD會處理該異常,將eip+1繼續執行。
下圖是CM設定的異常處理函式
patch掉int 2d,修改為jmp 0x40717d即可正常除錯。
三、抽絲剝繭,理清程式脈絡
異常處理函式中有3個關鍵判斷
判斷1:(失敗返回0,成功則進入判斷2)
判斷2:(失敗返回2,成功則進入判斷3)
判斷3:(失敗返回2,成功則返回1,整個CM校驗成功)
判斷1是CM最重要的部分,下面我會詳細分析判斷1。
作者設定判斷2是為了防止程式多解。事實上,如果在分析理清了作者的演算法思路,那麼逆推得出的sn會自然的pass掉判斷2。
判斷3的邏輯一目瞭然,eax指向sn字串,判斷條件:strlen(sn)=0xc。同判斷2,這也是一個防止多解的判斷。按作者思路逆推的sn也會pass掉判斷3。
下面是判斷 1 的詳細分析:
判斷 1 的成功條件:
為了便於描述,設var_d9f4開始,大小為0x30的位元組流為BLOCK1。var_d934開始,大小為0x30的位元組流為BLOCK2。
成功條件就是:BLOCK1和BLOCK2全等。
四、BLOCK1 和BLOCK2 的初始化
在0x40718b處下斷,除錯發現BLOCK1和BLOCK2都被初始化過。BLOCK1被初始化為0x00-0x2F依次遞增排列,BLOCK2被初始化為0x00-0x2F的亂序排列。
往前翻程式碼,發現如下程式碼:
除錯發現,sub_4084A3就是初始化BLOCK1和BLOCK2的函式。初始化時,sub_4084A3被呼叫(傳入了字串引數”KanXueCrackMe2017“,傳入了BLOCK1的地址)(事實上,BLOCK1和BLOCK2在記憶體中是緊挨著的,實際上做變換的是BLOCK2),判斷1前,sub_4084A3再次被呼叫(傳入了我們的sn字串,傳入了BLOCK1的地址)。
為了使判斷1成功,要讓BLOCK2和BLOCK1全等。除錯發現,sub_4084A3並不會打亂BLOCK1,只會打亂BLOCK2。(BLOCK1一直為0x00-0x2F的遞增排列)
所以我們要找的sn可以說是字串”KanXueCrackMe2017“在函式sub_4084A3作用下的”逆“。 ”KanXueCrackMe2017“ 負責打亂遞增排列的BLOCK2,而我們的SN則負責還原BLOCK2。
五、SN變換
在打亂BLOCK之前,SN會經歷4步變換。
SN的第一次變換:
進入sub_4084A3
進入sub_40829c
對sn中的每一個char做判斷:
在'0'~'9' 之間則減去'0'。結果範圍[0,9]
在'A'~'Z'之間則減去'7'。結果範圍[10,35]
在'a'~'z'之間則減去'='。結果範圍[36,61]
變換1:由字元向[0,61]區間做1對1對映。
SN的第二次變換:
同樣是在sub_40829c中
第一個call sub_403641作用是初始化變數。
第二個call sub_4038E1讀取向[0,61]區間對映過的sn,按位權重乘以相應個數的62。
最後兩個call將這些按62位權重的數求和。
變換2:轉換為62進位制數字
SN的第三次變換:
變換3:62進位制轉換為18進位制
SN的第四次變換:
變換4:[0,17]向[0-9,'A'-'H']做1對1對映
驗證程式碼:
為了確認這4次SN的變換,透過除錯,得到輸入字串” ”KanXueCrackMe2017 “,得到輸出字串"EDAHE450C741GH441E11BH84"
驗證程式碼如下(python實現):
輸出如下:
驗證透過
六、BLOCK 變換
在之前的程式碼中,我們的SN被變換為SN1。下面我們來看SN1是如何引導BLOCK的變換的。
SN1變換後作為sub_4080DF的引數:
SN1又被轉換為了[0,17]的數字序列,每個數字除以3的商和餘數作為引數傳遞給sub_4080DF
進入sub_4080DF(函式較長,僅擷取一部分):
這種函式中就呼叫了一個函式sub_40727D,餘數決定了呼叫的次數,商決定了傳給該函式的01塊的地址。
餘數的範圍[0,2],呼叫次數為餘數+1,即[1,3]。[0,17]除以3的範圍是[0,5],相對應,也有6個不同的01塊。
進入sub_4072:
除錯發現sub_40727d就是BLOCK變換的執行者。下文簡稱基函式。上圖基函式有兩個變數,傳遞進來的都是BLOCK2的地址。
但事實上,最關鍵的一個引數IDA並沒有識別出來,就是圖中的v2,透過ecx傳遞進來的。這個v2是一個大小為0x2400的由0和1的DWORD值組成的塊。
觀察上圖程式碼,基函式透過區域性變數v10儲存資料,退出時,用v10覆蓋BLOCK2。v10中的每個值都是由01塊v2和原BLOCK2對應求積再求和而來,這0x30個積中,01塊有且僅有一個為1。每次算出一個值之後,會對01塊進行的錯位。透過這種方式來保證每個BLOCK2中的值都能透過01塊對映到v10中。就這樣,基變換完成了對BLOCK2的一次置換。
01塊的初始化:
01塊的初始化在sub_407307中:
上圖只是一個01塊的初始化(一共有6個01塊)
6個01塊,對應著6種基函式。把置換表提取出來:
七、撥雲見日,演算法模型浮出水面
提取出置換表之後,觀察置換表特點
1、有一行置換後不變
2、有一行發生了偏移為2的移位。
3,、其餘4行都只有3個位置產生了置換,剩餘5個位置不變。
然後就。。。。
3X3魔方6個面對應6個置換表。旋轉一個面時,對立面不變,旋轉面偏移2移位,其餘4面變換3位。
去除中心節點,魔方共有6X(9-1)=48=0x30個小方塊。完美契合CM中的置換函式。
八、建立模型,還原魔方求SN
還是用python:
結果:
SN:CcLaoE37J45Y