Cast-128 密碼編譯演算法和 MyPassWord 的破解
作者: 一塊三毛錢
1. Cast-128 密碼編譯演算法概述
Cast-128 密碼編譯演算法是一種類似於DES的置換組合網路(Substitution-Permutation Network,SPN)加密系統,對於微分密碼分析、線性密碼分析、密碼相關分析具有較好的抵抗力。這種加密還有其他的幾個理想的特點,包括雪崩、嚴格的雪崩標準(SAC)、位獨立標準(BIC)、沒有互補屬性也不存在軟弱或者半軟弱的密鑰。因此對於整個Internet社區——要求密碼強壯、容易擷取的密碼編譯演算法——而言,這是一種能夠滿足一般應用的很好的選擇。
整個加密過程分為密鑰預先處理和資訊加密兩個過程。密鑰預先處理過程是根據輸入的 128 位密鑰,通過一系列的S-盒置換產生 16 對子密鑰。資訊加密是把輸入的 64 位元資訊分為左右兩半 L0 和 R0,通過一系列的迴圈變換產生 L16 和 R16,然後串連 L16 和 R16 就是輸出的密文。對 Cast-128 演算法解密也要分為密鑰預先處理和資訊解密兩個過程。密鑰預先處理和加密的時候完全相同,輸入的密鑰表也一樣。而資訊解密是把加密時的一系列迴圈變換倒過來,按相反的順序從 L16 和 R16 計算出 L0 和 R0。(Cast-128 演算法的具體描述請參考 RFC2144)
所以要想破解採用 Cast128 演算法的軟體,關鍵是找到密鑰預先處理時用到的密鑰表,有了密鑰表就可以根據密文解密出明文,一般而言也就是我們要找的註冊碼。而且要寫一個註冊機也不是難事。
2. 執行個體分析:MyPassWord 的破解
MyPassWord 是一款私人密碼管理軟體,可以協助大家管理各種各樣的密碼,在《黑防》2004 年 1 月刊中有介紹。軟體管理帳戶資訊應該是採用了 MD5 演算法進行驗證,如果跳過帳戶驗證開啟別人的密碼檔案得到的只是一些看不懂的亂碼。軟體沒註冊會在每次開啟的時候提示使用了多少次,提醒大家註冊。
先用 PEiD 查看一下加了殼沒有,加了殼就先脫殼,沒加殼就看看採用了什麼密碼編譯演算法。檢測一下發現 ASPack 2.12 -> Alexey Solodovnikov,既然是 ASPack 的殼那就不客氣了,利用 stripper v2.07 脫殼機很方便的就可以把殼脫掉。脫殼後再次用 PEiD 檢測一下,發現軟體是用 Delphi6.0 編寫的,採用了 Cast 和 MD5 密碼編譯演算法。
這裡要說一句,每次拿到一個軟體先用 PEiD 和 FileInfo 等工具探測一番不失為一個好的習慣,可以節省時間對症下藥(什麼殼要脫,什麼語言用什麼工具對付,什麼演算法要不要複習看看書)。好,知道了是 Delphi 編寫的軟體,當然是先用 DeDe 反組譯碼尋找關鍵的代碼。反組譯碼後找到“註冊”對話方塊中的“註冊”按紐的單擊響應代碼位置在 0x004F0B60,“註冊”對話方塊的初始化代碼在 0x004F0D18 處。
DeDe 的工作做完後再開啟 Ida Pro 反組譯碼,這個工具不單是靜態反組譯碼之王,現在加入了動態調試的功能更是讓人愛不釋手。不但可以把變數改成容易理解的名字,還可以把分析過的代碼寫上注釋,碰到一個過程可以先進去看看再決定是需要跟進還是直接帶過,關鍵的過程可以知道被幾個地方調用了,識別出了大部分的庫函數使得代碼更容易理解和更容易調試(不需要跟進該函數就能知道函數實現的功能),不過反組譯碼的時間有一點點長。
反組譯碼結束後,在 0x004F0B60 處按 F2 下斷點,按 F9 運行軟體。在“註冊”對話方塊中會發現“序號”欄已經填寫了一些字元,現在先不管它,在“註冊碼”欄輸入 123456789 然後按“註冊”按紐。程式中斷在 0x004F0B60 處,跟蹤一下發現輸入的長度有要求,在“註冊碼”欄重新輸入 1234567890abcdef 然後按“註冊”按紐。中斷後跟蹤一下會來到如下代碼處:
CODE:004F0BBB mov eax, [ebp+var_10] ;[eax]="1234567890abcdef"
CODE:004F0BBE lea ecx, [ebp+var_C]
CODE:004F0BC1 mov edx, ds:dword_55E6D8
CODE:004F0BC7 call sub_54B2F8
CODE:004F0BCC mov eax, [ebp+var_C] ;[eax]==0D4h,15h,6Dh,0D8h,3Ch,0B7h,0F8h
CODE:004F0BCF mov edx, ds:dword_55E4C4
CODE:004F0BD5 mov edx, [edx] ;[edx]=="49988800"
CODE:004F0BD7 call @@LStrCmp ;比較 eax 和 edx 所指向的內容是否相同
CODE:004F0BDC jz short loc_4F0BEF ;如果更改這個跳轉會發現彈出訊息框說註冊成功
所以 call sub_54B2F8 肯定是關鍵的過程,跟進。又是經過一番細細的跟蹤來到如下代碼處:
CODE:0054B39C lea ecx, [ebp+var_20]
CODE:0054B39F mov edx, edi
CODE:0054B3A1 mov eax, [ebp+var_10] ;[eax]="1234567890abcdef"
CODE:0054B3A4 call sub_54B1B0
CODE:0054B3A9 mov edx, [ebp+var_20] ;[edx]==0D4h,15h,6Dh,0D8h,3Ch,0B7h,0F8h
跟進 call sub_54B1B0,馬上就會看到這樣一個過程 CODE:0054B1F3 call sub_4BD5A8。如果先進去看看會發現很像 Cast-128 演算法的密鑰預先處理過程,首先是在代碼的注釋(這個是 Ida Pro 自動產生的)中居然看到如下字元:
CODE:004BD5C4 mov ecx, offset aCast128KeyMust ; "Cast128: Key must be between 1 and 16 b"...
CODE:004BD5C9 mov dl, 1
CODE:004BD5CB mov eax, ds:dword_40881C
CODE:004BD5D0 call CreateExcept
看到沒,Cast128: Key must ... ,就是它,明明白白的指出了這是 Cast-128 的密鑰預先處理過程。我記得上次跟蹤資料收藏大師 v3.73時也看到一樣的字元,看來它們都採用了同一個人寫的 Cast-128 演算法模組。初步斷定了這是 Cast-128 的密鑰預先處理過程,但也不能排除軟體作者迷惑我們的可能性。繼續往下看:
CODE:004BD61E cmp edi, 0Ah
CODE:004BD621 jg short loc_4BD62F
CODE:004BD623 mov dword ptr [ebx+90h], 0Ch
CODE:004BD62D jmp short loc_4BD639
CODE:004BD62F ; ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
CODE:004BD62F
CODE:004BD62F loc_4BD62F: ; CODE XREF: sub_4BD5A8+79j
CODE:004BD62F mov dword ptr [ebx+90h], 10h
上面這段代碼就是根據密鑰表的長度設定資訊加密時是採用 12 迴圈還是 16 迴圈。
CODE:004BD713 mov edx, [esi+0Ch]
CODE:004BD716 mov ecx, edx
CODE:004BD718 shr ecx, 10h
CODE:004BD71B and ecx, 0FFh
CODE:004BD721 mov ecx, ds:sbox5[ecx*4]
CODE:004BD728 xor ecx, [esi]
CODE:004BD72A mov edi, edx
CODE:004BD72C and edi, 0FFh
CODE:004BD732 xor ecx, ds:sbox6[edi*4]
CODE:004BD739 mov edi, edx
CODE:004BD73B shr edi, 18h
CODE:004BD73E xor ecx, ds:sbox7[edi*4]
CODE:004BD745 shr edx, 8
CODE:004BD748 and edx, 0FFh
CODE:004BD74E xor ecx, ds:sbox8[edx*4]
CODE:004BD755 mov edx, [esi+8]
CODE:004BD758 shr edx, 18h
CODE:004BD75B xor ecx, ds:sbox7[edx*4]
CODE:004BD762 mov [ebp+var_34], ecx
......
上面的代碼(省略了大部分)就是用來計運算元密鑰的。知道了 sub_54B1B0 就是 Cast-128 的密鑰預先處理過程,把 sub_54B1B0 改名為 Cast128_InitKey 方便後面跟蹤。現在就是找出密鑰預先處理過程用到的密鑰表:
CODE:0054B1E6 lea eax, [ebp+TCastData] ;[eax]=TCastData
CODE:0054B1EC mov ecx, 10h ;ecx=密鑰長度
CODE:0054B1F1 mov edx, ebx ;[edx]=密鑰
CODE:0054B1F3 call Cast128_InitKey
來到上面的代碼處就可以從 edx 中得到密鑰的位置找出密鑰 01h,23h,27h,67h,12h,74h,42h,78h,23h,52h,57h,35h,34h,56h,78h,9Ah 。把密鑰記為 key2,我們寫註冊機的時候要用到。密鑰預先處理完後就是資訊加密,繼續跟蹤程式來到下面的代碼處:
CODE:0054B25E lea ecx, [ebp+var_18]
CODE:0054B261 lea edx, [ebp+var_10] ;[edx]=12h,34h,56h,78h,90h,0ABh,0CDh,0EFh 也就是明文
CODE:0054B264 lea eax, [ebp+TCastData]
CODE:0054B26A call sub_4BE41C
CODE:0054B26F lea eax, [ebp+var_1C]
CODE:0054B272 call @@LStrClr
CODE:0054B277 mov ebx, 8
CODE:0054B27C lea esi, [ebp+var_18] ;[esi]=0D4h,15h,6Dh,0D8h,3Ch,0B7h,0F8h 也就是密文
可以看出 sub_4BE41C 過程就是把 edx 中的明文變換成 esi 中的密文,不過 sub_4BE41C 是對應 Cast-128 中的資訊加密過程還是資訊解密過程呢,我們需要繼續分析。跟進 sub_4BE41C 會在開頭的一段代碼中發現如下的代碼:
CODE:004BE4BB cmp dword ptr [ebx+90h], 0Ch
CODE:004BE4C2 jle loc_4BE5F0
這裡是測試迴圈的次數是 12 次還是 16 次,如果在開頭部分發現這段代碼,那麼就是資訊解密過程,如果是在過程結尾部分發現這段代碼,那麼就是資訊加密過程。因為加密代碼和解密代碼很相似,只不過是迴圈的順序不一樣,不容易區分。另外也可以自己寫段程式測試一下,看看是加密過程輸出的結果與解密過程輸出的結果哪一個和跟蹤程式時得出的結果一樣。
知道了 sub_4BE41C 過程就是 Cast-128 演算法的解密過程,可以把 sub_4BE41C 改名為 Cast128_Decode。到這裡我們可以知道,軟體在註冊時把我們輸入的註冊碼通過 Cast-128 解密,然後再把解密後的位元組串與 49988800 作比較。如果一樣,則是合法的註冊碼,註冊成功。如果不一樣,註冊失敗。那麼 49988800 又是如何得出來的呢?我們需要繼續跟蹤剖析器。在兩處調用 Cast128_InitKey 的地方下斷點,重新運行程式,會中斷在如下代碼處:
CODE:0054B459 lea eax, [ebp+TCastData] ;[eax]=TCastData
CODE:0054B45F mov ecx, 10h ;ecx=密鑰長度
CODE:0054B464 mov edx, ebx ;[edx]=密鑰
CODE:0054B466 call Cast128_InitKey
這裡的密鑰和上面的密鑰不同,記為 key1:01h,23h,28h,67h,12h,78h,56h,78h,23h,50h,67h,89h,34h,56h,78h,9Ah。繼續運行,會來到如下代碼處:
CODE:0054B54E lea ecx, [ebp+var_18]
CODE:0054B551 lea edx, [ebp+var_18] ;[edx]=0B4h,46h,6Bh,62h,0E4h,0FCh,53h,13h 也就是序號
CODE:0054B554 lea eax, [ebp+TCastData]
CODE:0054B55A call Cast128_Decode
CODE:0054B55F lea eax, [ebp+var_20]
CODE:0054B562 call @@LStrClr
CODE:0054B567 mov ebx, 8
CODE:0054B56C lea esi, [ebp+var_18] ;[esi]="49988800"
上面這段代碼把序號解密得到 49988800。現在我們就可以知道如何註冊:利用 key1 預先處理密鑰,然後解密序號,得到中間變數,再用 key2 預先處理密鑰,然後加密中間變數就可以得到註冊碼。註冊機關鍵代碼如下:
invoke cast_setkey, addr key, addr key1, 16
invoke RtlZeroMemory, addr tmp, sizeof tmp
invoke cast_decrypt, addr key, addr buf, addr tmp
invoke cast_setkey, addr key, addr key2, 16
invoke RtlZeroMemory, addr buf, sizeof buf
invoke cast_encrypt, addr key, addr tmp, addr buf