CrackMe 是一種小程式,目的是在測試我們逆向工程的能力。這篇文章中的 Acid Burn 是非常適合初學者練習的 CrackMe,它沒有什麼特別的演算法或加密方式,可以幫助我們很快地建立對逆向流程的概念。CrackMe 通常具有與專有軟體中的保護方案相似的演算法,透過學習如何逆向破解,有助於提升我們未來在設計軟體保護方案的思路。
- 原始 CrackMe 檔案請自行搜尋下載
- SHA-256:
3d9e8f78e88ed0ea1eebbdd0ebca5addcdebfaf67068fb954f20fd6e91314e3d
# Serial
# 爆破
開啟 Acid burn.exe
後,先到 Serial
任意測試一下錯誤訊息為何。
發現錯誤訊息是 Try Again!!
,用 x32dbg 開始逆向。斷在 EnterPoint
後,右鍵 → [Search for] → [Current Module] → [String references]
在下方搜尋欄輸入 Try
關鍵字,可以找到 Try Again!!
的訊息(注意有兩個驚嘆號)
我們會發現,Try Again!!
的訊息上方有一個 jne
的跳轉,分別會跳到 Congratz!
的成功訊息與 Failed
的失敗訊息,確認它就是我們要找的關鍵跳轉!
看程式架構可以知道(圖片中最左邊的兩個箭頭也有清楚標示),如果此處跳轉,就會顯示失敗訊息;不跳轉則會顯示成功訊息。所以我們要讓它不跳轉,而最簡單的方式就是填充 nop
,意思是空指令。
0042F4D5 | 75 1A | jne acid burn.42F4F1 | 關鍵跳轉!
0042F4D7 | 6A 00 | push 0 |
0042F4D9 | B9 64F54200 | mov ecx,acid burn.42F564 | 42F564:"Congratz!"
0042F4DE | BA 70F54200 | mov edx,acid burn.42F570 | 42F570:"God Job dude !! =)"
0042F4E3 | A1 480A4300 | mov eax,dword ptr ds:[430A48] |
0042F4E8 | 8B00 | mov eax,dword ptr ds:[eax] |
0042F4EA | E8 81ACFFFF | call acid burn.42A170 |
0042F4EF | EB 18 | jmp acid burn.42F509 |
0042F4F1 | 6A 00 | push 0 |
0042F4F3 | B9 84F54200 | mov ecx,acid burn.42F584 | 42F584:"Failed!"
0042F4F8 | BA 8CF54200 | mov edx,acid burn.42F58C | 42F58C:"Try Again!!"
於是右鍵 → [Binary] → [Fill with NOPs]
此處不用調整,按下 [OK] 即可。
最後,我們便可以發現無論輸入什麼,結果都是 Congratz!
的成功訊息!
# Serial
透過追蹤剛剛 jne
跳轉前的程式可以發現,真正的註冊碼是固定的值 Hello Dude!
。這裡的邏輯不難,很單純地判斷輸入的字串是否等於 Hello Dude!
。嘗試追蹤執行幾次應該就能看出來,就請讀者當作練習囉!
# Serial / Name
若要用爆破的方式,則一樣找到關鍵跳轉並改成 nop
即可。這裡我們改用分析此 CrackMe 的驗證流程,並嘗試寫出註冊碼產生程式。
一樣透過跳出的訊息內容搜尋位置,在其前面下斷點。我們先隨便輸入 Name 和 Serial,看看有什麼效果。
在字串長度檢查之前,其實還有對使用者名稱做一些計算,不過那些是為了混淆我們逆向用的,最後實際產生註冊碼時並沒有用到,有興趣研究的讀者可以自己嘗試分析。
# 字串長度檢查
首先,程式會先檢查輸入的使用者名稱字串長度有沒有大於 4。我們可以發現它 call
的函式裡面有 repne scasb
,跟上下文合起來,一臉看起來就是在算字串長度。果真,我這邊輸入的是 Hello
共 5 個字元,拿到字串長度後,便會拿來跟 4 做 cmp
,如果字串長度小於 4 就會跳出錯誤訊息,符合條件就跳轉到下個階段。
如果您用的是 IDA Pro,相信一鍵 F5 會輕鬆很多。
# 註冊碼演算法
之後會擷取使用者名稱的第一個字元,例如我輸入的是 Hello
,就會擷出 H
。
然後將 H (0x48)
乘以 0x29
,後面有顯示 )
是因為 0x29
剛好對應到 ASCII 的右括號,但此題與它並不相關。
0x48 * 0x29
會得出 0xB88
,然後儲存起來。
這邊做 a = a + a
,就是將值乘以 2,即 0xB88 * 2 = 0x1710
0x1710
等於十進位的 5904
,前後加上 CW
跟 CRACKED
就是註冊碼了!
於是,我們便找到一組註冊碼:
- Name: Hello
- Serial: CW-5904-CRACKED
因為註冊碼的產生方式只有用到使用者名稱的第一個字元,也就是說,即便使用者名稱改成「Hmmm」也能成功!
# 註冊碼產生程式
根據我們上面逆向的思路,就可以寫一個通用的註冊碼產生器啦!
#include <bits/stdc++.h>
using namespace std;
int main()
{
string username;
while (cin >> username) {
if (username.size() < 4) {
cout << "Username must be longer than 4 characters." << endl;
continue;
}
cout << "CW-" << username[0] * 0x29 * 2 << "-CRACKED" << endl;
}
return 0;
}