Soft-ICE執行個體起步(Windows版)
● 文 / Jiang Hong
//蔣按:Soft-ice是進階開發必備工具,可惜很多人不知如何使用
// Jiang hong這篇還不錯,推薦給大家看看
--------------------------------------------------------------------------------
為了以後說話方便, 這裡把 Soft-ICE 的一些簡單使用方法說一下, 以免不通 E 文的同志們找不到中文的 Soft-ICE 說明而抓瞎.
Soft-ICE 由三部分 (以後說的 Soft-ICE, 如果不加特殊說明, 均指 Soft-ICE for Windows 95 的 2.0 版本以上) 組成: WINICE.EXE, WLDR.EXE (在 3.0 中這個檔案叫做 LOADER32.EXE) 和顯示驅動程式 SIWVID.386.
另外, Soft-ICE 在啟動的時候要裝入一些 DLL/EXE 的函數名資訊, 你必須手工指定這些 DLL, 按照:
exp=d:/path/name.ext
的格式寫在 WINICE.DAT 檔案裡. 本文附錄裡面有俺用的 WINICE.DAT, 你可以直接用起來, 省得自己寫那麼多行了. 注意, 一定要把下面幾行包括進去, 否則 WINICE 可能什麼東東也攔不到:
exp=c:/win95/system/kernel32.dll
exp=c:/win95/system/user32.dll
exp=c:/win95/system/gdi32.dll
exp=c:/win95/system/comctl32.dll
一般我們使用 WLDR (以後把 LOADER32 也稱為 WLDR) 來裝入一個 EXE 檔案或者一個 DLL 檔案, 大多數的時候, 我們也可以直接執行 EXE 檔案, 通過跟蹤它的各種訊息來找到它. 啟動 WINICE 的熱鍵是 Ctrl+D. 先介紹常規的辦法:
啟動 WLDR, 然後選擇你要跟的程式, 單擊 Load 按鈕, 螢幕上一陣亂閃後就進入了文字模式, 這就是 Soft-ICE 的跟蹤介面, 雖然簡單了點, 但是很友好. 可以像 DOSKEY 那樣用游標上下鍵重複上次輸入的內容, 也可以輸入上次輸入內容的一部分, 然後按游標上鍵, 上次輸入內容就完整地貼了出來.
一般情況下, 如果裝入的一個 NE 程式, WINICE 會直接找到它的進入點, 並且把當前的游標定在 EXE 的頭一條指令上; 如果是 PE 程式, WINICE 會停在一個 INVALID 區, 按下 F10 後可以到 EXE 頭部.
比較重要的功能鍵:
(1) F10 : 逐步執行; CALL, INT 會被跳過;
(2) F8 : 逐步執行; CALL, INT 會被切入;
(3) F4 : 查看程式畫面;
(4) F11 : 對於 CALL 形式的子程式, 直接執行完畢, 在 RET(F) 之後
回到 CALL 的下一條指令;
比較重要的幾條命令是:
(1) G: 執行程式, 後面如果加地址, 則執行到該地址為止, 比如:
2400:0480 MOV AH,30
2400:0482 INT 21
2400:0484 CMP AL,09
2400:0486 JZ 04F9
2400:0488 MOV AH,4C
2400:048A INT 21
假設當前 CS:IP (EIP) 為 2400:0480, 如果我們直接執行 G, 就會一 G 到底, 直接回到命令列, 原因在於 DOS 9.00 還沒出, 程式直接執行 DOS TERMINATE 功能了.
如果你把 DOS 版本號碼先改成 9.0, 然後執行 G 4F9, WINICE 會跑到 2400:04F9 處然後停下; 但是如果你不改版本號碼也這麼來一把, 就也會一 G 到底, 回到命令列了, 因為 2400:04F9 在這種情況下永遠不會被執行到.
(2) P: 逐步執行程式; 只執行 P 時, 相當於按下 F10 鍵; 如果後面加入 P RET 參數, 會執行到最近的一條 RET/RETF 處, 注意, IRET 會被忽略, 所以要小心;
(3) T: 相當於按下 F8;
(4) BPINT: 設定中斷斷點; 格式為 "BPINT 中斷號 [條件]"; 在 WINICE 2.0 裡面, 條件只能是下面三種之一:
bpint xx ah=ab
bpint xx al=cd
bpint xx ax=abcd
而在 WINICE 3.0 裡面, 條件的格式豐富得多:
bpint xx if ah==ab
bpint xx if al==cd
bpint xx if ax==abcd
bpint xx if dx->4==abcd (當 DS:DX+4 處的值為 0xabcd 的時候)
還有一些, 不是很常用, 等到用的時候再說吧.
(5) BPX: 設定執行地址斷點; 格式為 "BPX 地址", 還以上面的例子來說, 假如我們執行 bpx 486, 然後來一把 G 4F9 的話就不會一 G 到底了, 因為我們在那個判斷處設了斷點, WINICE 會執行到 2400:0486 然後停下;
BPX 的第二種用法是我們這個教程的關鍵, 格式為 "BPX 函數名". 這個函數名可以是任意一個 Windows API 函數, 虛擬機器指令, DLL 的引出函數等等. 功能強勁. 比如說, 我們先啟動 Notepad, 然後在裡面隨便敲些東東, 然後按 Ctrl+D, 執行:
:bpx messageboxa (不用區分大小寫)
:g
然後關閉 Notepad, 這時 WINICE 會被啟用, 原因是斷點條件已經符合了. Notepad 此時將彈出一個訊息框提醒你存檔, 這就進入了 MessageBox 函數, 後面加一個 A 是由於我們現在在 Windows 95 裡面, 函數是區分字元集的. A 表示 ANSI, W 表示 Wide, 即 Unicode (Wide character-set).
(6) BPM: 設定記憶體訪問斷點; 格式為 "BPM 地址"; 比如: BPM F000:E6F9 會把斷點設在記憶體中存放 BIOS 更新版本號碼的記憶體位置上, 只要有程式訪問這個地址, WINICE 就會啟用. 另外, 還可以單獨設定對地址的訪問條件: 讀(R)/寫(W)/執行(X). 只需要在後面把對應的字母加上就可以了. 以那段 "一 G 到底" 為例:
:bpm 2400:0486 x
:g
和 bpx 486 的效果完全一樣.
(7) BMSG: 跟蹤 Windows 訊息; 格式為 "BMSG 訊息名"; 比如我們執行 Notepad, 然後 Ctrl+D 啟用 WINICE, 輸入:
:bmsg wm_char
:g
然後回到 Notepad 中, 隨便按一個鍵, WINICE 就啟用了; 原因在於我們在按鍵訊息上設定了斷點.
(8) BL: 列出所有的斷點; 格式為 "BL"; 它會把所有斷點按從 0 開始的編號列出;
(9) BC: 清除斷點; 格式為 "BC 斷點編號", 這個編號就是 BL 列出的那個; 還有一種格式為 "BC *", 作用是清除所有的斷點.
(10) RFL: 改變標誌字; 格式為 "RFL 標誌位"; 比如當前 Z 標誌位 (零位) 為置位狀態, 執行 "rfl z" 之後會被清楚; 如果 C 標誌位為清除狀態, 那麼 "rfl c" 將使之置位; 比如:
:g 486
:rfl z
:g 4f9
這次就真可以 G 到 4F9 處了.
(11) A: 進入 WINICE 小彙編狀態; 格式為 "A 地址"; 也可以不加地址值, 直接在當前 CS:IP 處彙編; 舉例:
:g 486
:a 2400:0486
2400:0486 jnz 4f9
2400:0488 (按下 ENTER 鍵結束彙編)
:g 4f9
也可以 G 到 4F9 處.
(12) E: 進入 WINICE 記憶體修改狀態; 格式為 "E 地址"; 也可以不加地址值, 直接在 DS:DX 處修改; 比如:
:e 2400:0485
2400:0485 - 09 74 XX XX XX XX XX XX XX XX XX XX XX XX XX XX
(游標停在 09 下面, 我們輸入 09 EB, 然後按 ENTER 結束修改)
2400:0485 - 09 EB XX XX XX XX XX XX XX XX XX XX XX XX XX XX
然後 "g 4f9" 就可以到 4F9 啦; 我們輸入的 EB 是 JMP 的機器碼.
(13) U: 反組譯碼; 格式為 "U 地址", 也可以不加地址, 直接在當前 CS:IP (EIP) 往後反組譯碼 12 行 (行數由 CODE 欄的寬度而定).
(14) R: 更改寄存器的值; 格式為 "R 寄存器名=值", 也可以不加值, WINICE 會讓你在最上面的寄存器區直接修改, 用游標鍵可以在各個位之間移動. 比如:
:g 484
:r ax=0009
:g 4f9
就到了 4F9 處.
基本的指令就是這 14 條, 如果需要, 俺隨時補充就是了.
--- 如何拆解 ACDSee '95
ACDSee '95 是一個速度快, 功能強, 格式多, BUG 少的 ...... 圖形瀏覽程式 (不要想歪了哦! . 我們以 ACDSee '95 1.0 正式版為例來看看此類程式如何拆解.
首先是得到該程式, 該程式可以在:
http://www.acdsys.com/download.htm(l)
下獲得. 也可以在國內的許多 BBS 上得到. 注意: 我們需要它的正式版, 否則許多地址值是錯誤的, 但是原理是一樣的.
首先用用這個程式, 當你看了 30 張左右的圖片之後, 該程式就開始頻繁提醒你註冊了. 另外, 在程式的 About 對話方塊裡面也有 Register 按鈕讓你註冊. 我們來試試註冊會是什麼樣子. 在 Register 對話方塊裡面隨便輸入一個名字, 比如 "eGIS - pCE '97" 吧, 然後按 Tab 鍵跑到 Code 欄裡面輸入個數字, 比如 12345, 然後按斷行符號鍵.
啊! ACDSee '95 彈出一隻 Message Box, 告訴你輸入的號是無效的. :..(
讓我們來想想看這種判斷應該是如何工作的, 這不難猜:
(1) 取得使用者輸入的名字;
(2) 取得使用者輸入的註冊號;
(3) 使用某種演算法檢驗;
(4) 判斷是否合法;
(5) 如果合法就顯示註冊訊息;
(6) 如果非法就彈出一隻 Message Box.
好. 知道了這個就好辦. 我們不難看到, 拆解的關鍵在於上面的第四步, 如何令這個程式認為你輸入的號是正確的. 從那個 "一 G 到底" 程式裡面我們應該學到些簡單的拆解經驗, 在那個例子裡, 我們就是改動了一個 Z 標誌來達到可以 G 到 4F9 的目的的, 這個方法是 "普適" 的, 也適於這個比較複雜的例子.
現在我們開始拆. 首先我們猜想 ACDSEE 使用了 GetWindowTextA 函數來取得使用者輸入的資訊, 來試試:
:bpx getwindowtexta
:g
然後再按 ENTER 鍵, 結果 WINICE 沒有被啟用. 為什麼呢? 原因是很簡單的啦, 就是 ACDSEE 沒用這個函數. 那用的是什麼呢? 不難想到 GetDlgItemTextA 函數, 再跟一次看看:
:bc *
:bpx getdlgitemtexta
:g
按下斷行符號鍵. Bingo! WINICE 被啟用了! 好, 說明我們跟的函數是對的. 但是不要著急, 我們輸入了兩條資訊: 名字和註冊號, 因此這個函數會被執行兩次, 所以我們繼續 G 一下. 果然, WINICE 又攔到一個 GetDlgItemTextA 函數:
USER32! GetDlgItemTextA
--------------------------------------------------------------
0137:BFF61657 MOV CL,96
0137:BFF61659 PUSH EBP
0137:BFF6165A MOV EBP,ESP
0137:BFF6165C PUSH ECX
0137:BFF6165D SUB ESP,3C
0137:BFF61660 PUSH WORD PTR [EBP+08]
好了好了, 就 A 到這兒吧. 反正 WINICE 攔到了這個函數, 並且我們不關心它是如何得到那些資料的, 我們直接走完這個函數. 按下 F11 鍵就回到了調用它的地方:
0137:004016FB CALL EDI ; 我們就是從這裡回來的
0137:004016FD XOR DI,DI ; 當前 EIP
0137:00401700 LEA EBX,[ESP+18]
0137:00401704 CMP BYTE [ESP+18],0
0137:00401709 JZ 401723
0137:0040170B MOVSX EAX,BYTE PTR [EBX]
我們看到 EIP 下面兩條指令都和 [ESP+18] 有關. 究竟 [ESP+18] 處是什麼東東呢? 我們來 DUMP 一下它:
:d esp+18
哈哈! 原來 [ESP+18] 處存的是你輸入的姓名. 那麼下面一條 CMP 的作用很明顯了, 它是判斷你是不是什麼都沒有輸入, 我們既然輸入了姓名, 就可以狠光明正大地 G 到地址 40170B 去也.
接下來的一段是:
0137:0040170E PUSH EAX
0137:0040170F CALL 0045A230
0137:00401714 ADD ESP,04
0137:00401717 TEST EAX,EAX
0137:00401719 JZ 0040171D
0137:0040171B INC DI
0137:0040171D INC EBX
0137:0040171E CMP BYTE PTE [EBX],0
0137:00401721 JNZ 0040170B
0137:00401723 CMP DI,05
0137:00401727 JL 00401841
0137:0040172D LEA EAX,[ESP+38] ; 註冊號
0137:00401731 LEA EAX,[ESP+18] ; 名字
0137:00401735 PUSH EAX
0137:00401736 PUSH ECX
0137:00401737 PUSH 0047A128
0137:0040173C CALL 00403560
0137:00401741 ADD ESP,0C
0137:00401744 CMP EAX,01
0137:00401747 SBB EAX,EAX
0137:00401749 INC EAX
0137:0040174A TEST EAX,EAX
0137:0040174C JL 00401841
0137:00401752 LEA EAX,[ESP+14]
0137:00401756 LEA ECX,[ESP+0C]
0137:0040175A PUSH EAX
0137:0040175B PUSH ECX
A 了這麼長, 我們來看看這段代碼的用處吧. 首先我們看到在 40172D 這個地方用到了 [ESP+38] 和 [ESP+18], 經過 DUMP 知道, [ESP+38] 存放著我們輸入的序號, 那麼上面的一個迴圈就可以直接跳過來了. 輸入:
:g 40173c
然後有一個 CALL, 它前面的幾個壓棧函數壓進去的是名字和序號, 然後經過一個子程式, 然後下面又有判斷, 那麼這個 CALL 極有可能是判斷名字和序號是否符合的子程式, 我們來看看是不是這麼回事. 先 G 到下面的判斷處:
:g 401744
我們看到這時候 EAX 是 0, 然後繼續走到 40174C, 發現它是要跳到 401841 執行程式. 我們繼續走, 發現那個破框彈了出來. 之前的唯一一個分支就是在 40174C 這個地方了, 因此我們重複上面的步驟跟到 401744, 將 EAX 改成 1, 到 40174C 的時候繼續執行下一條指令. 我們來 G 一把.
Bingo!!! ACDSee '95 告訴我們它已經被註冊了. 但是別得意, 看看它的標題列吧, 還是 [unregistered] 的呢.
這是怎麼回事呢? 不難想到, 還有別的判斷. 是不是還要跟呢? 不用啦. 既然我們找到了判斷子程式, 就可以直接改它了. 根據我們上次的經驗, EAX 是 1 的時候表示名字和註冊號是相符的, 那麼我們把程式的傳回值 EAX 改成恒為 1 就可以了.
重新跟蹤, 到 40173C 這個地方按下 F8 開始. 然後進入程式, 我們用 Ctrl+游標下鍵行動程式碼, 到 4035FE 處:
0137:004035FE TEST EAX,EAX
0137:00403600 MOV EAX,00000001
0137:00403605 JZ 00403609
0137:00403607 XOR EAX,EAX
0137:00403609 POP EDI
0137:0040360A POP ESI
0137:0040360B POP EBX
0137:0040360C ADD ESP,4
0137:00403612 RET
如果你有一些反組譯碼 C++ 編譯出的 EXE 的經驗, 就可以看到這實際是:
return ( fValid ? 1 : 0 ) ;
的編譯結果. 可以看到, 403605 是一個關鍵的地方, 就是這條該死的指令把可愛的 EAX 弄成了 0. 知道這個就好辦啦. 我們把它跳過去:
:a 403605
0137:00403605 jmp 403609
0137:00403607 (按斷行符號結束彙編)
然後重新執行一次. HOHOHO! 這次標題列也變成註冊版的樣子啦. 拆解工作完畢!
回到 DOS 下, 把我們的成果寫回 EXE 裡面就可以了. 剛才我們改的是把 JZ 改成了 JMP, 也就是說, 把
B8 01 00 00 00 74 02 33 C0 5F 5E 5B
改成了:
B8 01 00 00 00 EB 02 33 C0 5F 5E 5B
(黑話叫做 "74 改 EB"
為什麼要把要尋找的串弄得這麼長呢? 直接把 "74 02" 替換成 "EB 02" 不就得了嗎? 不是這樣的. 在 EXE 裡面可能有許多個 "74 02", 因為 JMP $+2 是一條太普通不過的指令了, 而一個 EXE 裡面有多個
MOV EAX,00000001
JNZ @HERE+2
XOR EAX,EAX
POP EDI
POP ESI
POP EBX
的機會就少得多了, 一般情況下只有一處, 這就是我們要改的一處.
把特徵串取得太長了也沒什麼實際意義, 反而會浪費地球上有限的紙資源, 地球只有一個, 是我們的家, 我們要愛護它 ......
好, 用一個你喜愛的二進位檔案編輯器改掉它. 然後重新執行 ACDSee '95.
酷! 大功告成啦. 趕緊找個 MM 吹噓一把 ......
爽過以後總結一下經驗, 下次泡的時候就是老槍了:
經驗一: 你現在知道了註冊碼的判斷步驟;
經驗二: 你知道了程式可以用 GetDlgItemText 和 GetWindowText 取得
在 Edit Box 中使用者輸入的資料;
經驗三: 你知道了程式判斷註冊號是否合法的地方可能不止一處, 但是一
般都用同一個子程式來完成檢驗功能;
經驗四: 你知道了取得替換碼的一些原則: 即 --- 不要太長也不要太短.