【CTF大赛】第五届XMan选拔赛 ezCM Writeup
ezCM
直至比賽結束,這道題目都是 0 解題,一方面是因為比賽時間較短,另一方面還是因為這道題目較難,考察了不常見的橢圓曲線算法(ECC),大大增加了對做題者的要求。
題目信息
題目是使用 Golang 來編寫的一個 CrackMe 程序,程序內符號沒有被去除,所以這篇文章就不會講解如何恢復 Golang 程序符號,另外 IDA Pro 7.6 已經支持 Golang 程序分析,打開就可以直接恢復被去除的符號信息。
題目要求打開一個 KeyFile ,并且通過讀取其文件的內容來注冊程序,我們要做的就是通過分析程序驗證方式來編寫一個 KeyFile,使其可以通過程序注冊驗證,最終拿到 flag 數據。
前置知識
由于是 Golang 的題目,在一些數據結構和調用約定上和大多數語言都不一樣,所以一定不能過于的依賴偽代碼,在調試過程中最好能夠多關注匯編代碼,這樣在逆向過程中會快速掌握到核心。這部分內容參考學習了 panda0s – Golang underlying data representaion ,本來是不想把這部分內容放在這篇文章中的,但是由于關聯性過大,所以不得不拿來飽滿文章內容。
函數調用
在函數調用的過程中,無論是調用參數還是返回值都是通過棧來傳遞。
其傳參的特征是
rax、rcx,先把數據原來的儲存位置的數據賦值到這個寄存器上,然后再把這個寄存器的內容賦值給棧上數據,并且如果數據是 0x10
size 的結構體,就會借助 xmm 寄存器中轉來加速。
其返回值的特征是
rsp + 0x10,所以這里的返回值的地址就是在 rsp + 0x250 – 0x238 = rsp +
0x18,有多個返回值的情況也是類似。
方法調用
在上圖中,嚴格意義上并不是一次函數調用,而是一次方法調用。他是對 MyMainWindow 這個對象下的 Academy 方法進行了調用,這個傳入的參數就是這個對象的指針,像是 this 一樣。這個對象的指針就相對于函數調用的第一個參數。
String 字符串
String 結構
所以 Golang 程序在傳遞字符串的時候,同時也會在后續跟一個參數,這個參數指的就是字符串的長度。同時由于這樣的機制,使得字符串的內容在內存中分布不需要截止符’\x00’
Slice 切片
在其他語言中(例如 python),Slice 是一種切片的操作,切片之后可以返回一個新的數據對象,但是 Golang 中的 Slice 不僅僅是一種切片的操作,更像是一種靈活的數據結構。
了解 Slice 結構后,在 IDA 中修改對應的變量類型,可以大大加快分析速度。
Slice 結構
struct slice {dq Pointer;dq Length;dq Capacity; }Pointer:指向 Slice 底層數組的元素開始位置的指針
Length:Slice 的當前長度
Capacity:Slice 底層數組的最大長度,超過此長度會自動擴展
初始化 Slice
這表示先聲明一個長度為 5、數據類型為 int 的底層數組,然后從這個底層數組中從前向后取 3 個元素作為 slice 的結構(length = 3,cap = 5)
make 最底層調用 runtime_makeslice 分配空間,這個函數返回的是指向內部數組的指針
訪問 Slice
在訪問 Slice 中元素時,會檢測是否越界如果越界則調用 runtime_panicIndex
append / copy
當 Length 已經等于 Capacity 的時候,再使用 append 給 slice 追加元素,會調用 runtime_growslice 進行擴容。
在代碼中的表現是在 append / copy 的時候會檢測,slice.len + 1 與 slice1.cap 的大小關系
如圖在把 slice 轉換為字符串的過程前,由于將要 copy slice,所以會對傳參 len 進行檢測。
切片截取
myvar := slice1[a:b]myvar 是新一個新的切片結構
dataPtr = &slice1.dataPtr[1],相當于給了一個底層數組的指針
len = b – a
cap = slice1.cap – a
尋找關鍵函數
對于這種有界面的程序,和常規的只有一個控制臺的題目不同的是,題目的關鍵信息不是直接存在于 main 函數中,所以我們首先要做的就是定位到題目的關鍵位置。
而在這道題里,我們的突破口就是在沒有選擇文件的情況下,點擊“Register”就會彈出的信息框“Cannot open target file.”
我們可以利用 IDA 的 Shift + F12 熱鍵調出 Strings 窗口來查找“Cannot open target file.”字符串
搜索字符串后,我們再雙擊進入,在前面自動生成的名稱處按下 X 熱鍵查找交叉引用
對于每一處引用我們都前去查看,最終找到了關鍵的代碼位置(截圖僅截取部分代碼)
我們接下來對函數內容進行分步驟的解析
KeyFile 格式解析
特征格式
這部分內容雖然偽代碼看起來混亂,但是大概可以猜測是開頭和結尾的特征字符
通過這兩個特征讀取出關鍵的秘鑰信息 xxx,然后傳入到后續函數,這樣的標記格式在其他地方也很常見,所以這里不著重分析。
解密核心數據
在后續函數中的對秘鑰核心數據做了一個解碼
這里上述代碼中可以看出,程序先通過一個 base64 解密對中間部分內容進行解碼,然后以兩個字節為一個單位進行解密,對第一字節異或 0xAA 得到數據,并且對第二字節異或 0x55,與第一字節的內容進行比對,如果不同則直接退出。
數據結構格式
解密后的數據是如何在存放的,分別又代表著那些信息?想要知道這些就要分析接下來所做的代碼。
代碼中由于對切片做了很多索引操作,所以有各種各樣的越界檢測,我們拋開這部分代碼來看,就可以看出 Username 和 Organization 的儲存結構 —— 第一個字節存放字符串長度,后續跟字符數據。
根據前置知識中切片的相關知識,這里調用 math_big_nat_setBytes 的切片內容我們可以大致還原,主要就是根據切片的 len 和 cap 來確定,
切片左邊的值:由來源切片的 cap 減去的內容
切片右邊的值:由來源切片左邊的值 + 新切片的 len
所以 main_a 和 main_b 的內容來源于新的切片內容分別是
a[org_len + name_len + 2:org_len + name_len + 2 + 8] a[org_len + name_len + 10:org_len + name_len + 10 + 8]這部分內容可以結合上面的偽代碼結合得出,也就是 Username 和 Organization 后的 8 字節是 main_a 的內容,再后 8 字節是 main_b 的內容,最后 4 字節是 main_expire 的內容。
這里需要注意的是,main_a 和 main_b 的內容都是以字節的形式直接轉換為大數類型,而 main_expire 是以 WORD 的形式讀取(使用 2 字節),這兩種讀取方式的字節序不同。
數據表
綜合上面所說的,可以得出以下表格來記錄 KeyFile 文件內加密數據格式
驗證邏輯
約束條件
了解了程序如何解析 KeyFile 后,接下來才是本文最關鍵的地方,也就是程序的驗證方法。
首先會把 main_a 和 main_b 的內容相加,然后與 13417336609348053335(0xba33f48ee008e957)進行比對,如果相同則進入后續的判定,這就是對 a 和 b 之間關系的一個約束。
初始化大數
接下來又對三個大數進行了初始化,我把這幾個常量去 Google 搜索了一下,發現 main_p 的值是在 GF§ 上的橢圓曲線中常用的一種取值,這對于我們了解接下來的代碼的大致內容有所幫助。
這樣根據常量來猜測程序意義的方法也是常用的,這里就是借助了橢圓曲線中常見的 p。
驗證代碼
最終的檢測代碼就是判斷題目 public_key 是否在橢圓曲線(y^2 = x^3 + ax + b)上,其中 a 和 b 是用戶可控的值,我們現在有一個在橢圓曲線上的點 生成元 G ,那么我們就可以根據這個 G 點的值和 a + b 的約束來反推 a 和 b 的值。
推導過程只是我粗淺的理解,所以可能不是很規范,但是表明了如何推出 a 的值,有了 a 的值后,我們直接相減就可以計算得到 b 的值。
注冊機編寫
通過上述的邏輯和整理,我們可以快速的編寫出一個 Keygen,我的代碼如下
接下來把 KeyFile 拖入程序,點擊 Register,即可成功通過驗證得到 Flag
可以發現 flag 的值其實就是 main_a 和 main_b 的 hex 編碼后的值,這樣可以保證 flag 的唯一性。
總結
在這之前其實也遇到過一些 Golang 的題目,但是因為 Golang 難以分析,所以這些題目的核心算法相對來說都比較簡單,都是一些比較簡單的邏輯問題。這道題雖然分析過程看似簡單輕松,但是實際上我對其內涵的原理和程序的用法進行了深入的研究,消耗了大量的時間和精力。雖然本題最終展現的并不是一個 ECC 難題,但是在逆向分析的過程中,我也學習到了一些 ECC 的內涵和代碼實現。希望各位師傅可以借此題來開篇學習 Golang 逆向和 ECC 算法。
最后
我整理了相關的資料,有需要的朋友可以私我哦!!!
【資料】
總結
以上是生活随笔為你收集整理的【CTF大赛】第五届XMan选拔赛 ezCM Writeup的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 你们应该听说过”w8ay“这个ID吧!一
- 下一篇: 【域渗透】教你怎么利用DCSync导出域