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,前後加上 CWCRACKED 就是註冊碼了!

於是,我們便找到一組註冊碼:

  • 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;
}