轉載說明:不知道何原因 ,此係列的第6篇並沒有相關資料,如有讀過第6篇的網友請給我留言,謝謝
標 題: 【原創】OllyDBG 入門系列(七)-彙編功能
作 者: CCDebuger時 間: 2006-04-09,16:42:10
鏈 接: http://bbs.pediy.com/showthread.php?t=23873
OllyDBG 入門系列(七)-彙編功能
作者:CCDebuger
今天我們的目標程序是 MyUninstaller 1.34 版。這是一個非常小的程序卸載工具,VC6編寫,大小隻有61K。我拿到的這個是上次閃電狼兄弟給我的,附帶在裏面的簡體中文語言文件是由六芒星製作的。這個程序有個毛病:就是在列出的可卸載程序上雙擊查看屬性時,彈出的屬性窗口的字體非常難看,應該就是系統字體(SYSTEM_FONT):
我們今天的目標就是利用 OllyDBG 的彙編功能把上面顯示的字體改成我們常見的9號(小五)宋體。首先我們用 OllyDBG 載入程序,按 CTR+N 組合鍵查找一下有哪些 API 函數,只發現一個和設置字體相關的 CreateFontIndirectA。現在我們按鼠標右鍵,選擇“在每個參考上設置斷點”,關掉名稱對話框,F9運行,程序已經運行起來了。我們在程序的列表框中隨便找一項雙擊一下,很不幸,那個字體難看的界面又出現了,OllyDBG 沒有任何動作。可見創建這個窗口的時候根本沒調用 CreateFontIndirectA,問題現在就變得有點複雜了。先點確定把這個字體難看的對話框關閉,現在我們從另一個方面考慮:既然沒有調用設置字體的函數,那我們來看看這個窗口是如何創建的,跟蹤窗口創建過程可能會找到一些對我們有用的信息。現在我們再回到我們調試程序的領空,按 CTR+N 看一下,發現 CreateWindowExA 這個 API 函數比較可疑。我們在 CreateWindowExA 函數的每個參考上設上斷點,在 MyUninstaller 的列表框中再隨便找一項雙擊一下,被 OllyDBG 斷下:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \斷在這裏
上下翻看一下代碼:
00408F3B |. 50 |PUSH EAX ; |hInst
00408F3C |. 8B45 C0 |MOV EAX,DWORD PTR SS:[EBP-40] ; |
00408F3F |. 6A 00 |PUSH 0 ; |hMenu = NULL
00408F41 |. 03C6 |ADD EAX,ESI ; |
00408F43 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hParent
00408F46 |. FF75 D0 |PUSH DWORD PTR SS:[EBP-30] ; |Height
00408F49 |. 57 |PUSH EDI ; |Width
00408F4A |. 50 |PUSH EAX ; |Y
00408F4B |. FF75 BC |PUSH DWORD PTR SS:[EBP-44] ; |X
00408F4E |. FF75 EC |PUSH DWORD PTR SS:[EBP-14] ; |Style
00408F51 |. 68 80DE4000 |PUSH myuninst.0040DE80 ; |WindowName = ""
00408F56 |. 68 DCD94000 |PUSH myuninst.0040D9DC ; |Class = "STATIC"
00408F5B |. FF75 D4 |PUSH DWORD PTR SS:[EBP-2C] ; |ExtStyle
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \斷在這裏
00408F64 | 6A 00 |PUSH 0 ; 第一處要修改的地方
00408F66 | 8945 F4 |MOV DWORD PTR SS:[EBP-C],EAX
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
00408F6E |. 50 |PUSH EAX ; |hInst
00408F6F |. 8B45 DC |MOV EAX,DWORD PTR SS:[EBP-24] ; |
00408F72 |. 6A 00 |PUSH 0 ; |hMenu = NULL
00408F74 |. 03F0 |ADD ESI,EAX ; |
00408F76 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hParent
00408F79 |. FF75 CC |PUSH DWORD PTR SS:[EBP-34] ; |Height
00408F7C |. 53 |PUSH EBX ; |Width
00408F7D |. 56 |PUSH ESI ; |Y
00408F7E |. FF75 D8 |PUSH DWORD PTR SS:[EBP-28] ; |X
00408F81 |. FF75 E8 |PUSH DWORD PTR SS:[EBP-18] ; |Style
00408F84 |. 68 80DE4000 |PUSH myuninst.0040DE80 ; |WindowName = ""
00408F89 |. 68 D4D94000 |PUSH myuninst.0040D9D4 ; |Class = "EDIT"
00408F8E |. FF75 B8 |PUSH DWORD PTR SS:[EBP-48] ; |ExtStyle
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 | 8945 F0 |MOV DWORD PTR SS:[EBP-10],EAX ; 第二處要修改的地方
00408F9A | 8B45 F8 |MOV EAX,DWORD PTR SS:[EBP-8]
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |
00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format = "%s:"
00408FAA |. 50 |PUSH EAX ; |s
00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; \sprintf
00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA
00408FB7 |. 83C4 0C |ADD ESP,0C
00408FBA |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150]
00408FC0 |. 50 |PUSH EAX ; /Text
00408FC1 |. FF75 F4 |PUSH DWORD PTR SS:[EBP-C] ; |hWnd
00408FC4 |. FFD6 |CALL ESI ; \SetWindowTextA
00408FC6 |. 8D85 ACFAFFFF |LEA EAX,DWORD PTR SS:[EBP-554]
00408FCC |. 50 |PUSH EAX ; /Arg3
00408FCD |. FF75 FC |PUSH DWORD PTR SS:[EBP-4] ; |Arg2
00408FD0 |. FF35 00EF4000 |PUSH DWORD PTR DS:[40EF00] ; |Arg1 = 00BEADCC
00408FD6 |. E8 1884FFFF |CALL <myuninst.sub_4013F3> ; \sub_4013F3
00408FDB |. 83C4 0C |ADD ESP,0C
00408FDE |. 50 |PUSH EAX
00408FDF |. FF75 F0 |PUSH DWORD PTR SS:[EBP-10]
00408FE2 |. FFD6 |CALL ESI
00408FE4 |. FF45 FC |INC DWORD PTR SS:[EBP-4]
00408FE7 |. 8345 F8 14 |ADD DWORD PTR SS:[EBP-8],14
00408FEB |. 837D FC 0F |CMP DWORD PTR SS:[EBP-4],0F
00408FEF |.^ 0F8C 32FFFFFF \JL <myuninst.loc_408F27>
00408FF5 |. 5F POP EDI
00408FF6 |. 5E POP ESI
00408FF7 |. 5B POP EBX
00408FF8 |. C9 LEAVE
00408FF9 \. C3 RETN
我想上面的代碼我不需多做解釋,OllyDBG 自動給出的註釋已經夠清楚的了。我們雙擊 MyUninstaller 列表框中的的某項查看屬性時,彈出的屬性窗口上的 STATIC 控件和 EDIT 控件都是由 CreateWindowExA 函數創建的,然後再調用 SetWindowTextA 來設置文本,根本沒考慮控件上字體顯示的問題,所以我們看到的都是系統默認的字體。我們要設置控件上的字體,可以考慮在 CreateWindowExA 創建完控件後,在使用 SetWindowTextA 函數設置文本之前調用相關字體創建函數來選擇字體,再調用 SendMessageA 函數發送 WM_SETFONT 消息來設置控件字體。思路定下來後,我們就開始來實施。首先我們看一下這個程序中的導入函數,CreateFontIndirectA 這個字體創建函數已經有了,再看看 SendMessageA,呵呵,不錯,原程序也有這個函數。這樣我們就省事了。有人可能要問,如果原來並沒有這兩個導入函數,那怎麼辦呢?其實這也很簡單,我們可以直接用 LordPE 來在程序中添加我們需要的導入函數。我這裏用個很小的 PE 工具 zeroadd 來示範一下,這個程序裏面沒有 CreateFontIndirectA 和 SendMessageA 函數(這裏還有個問題說一下,其實我們編程時調用這兩個函數時都是直接寫 CreateFontIndirect 及 SendMessage,一般不需指定。但在程序中寫補丁代碼時我們要指定這是什麼類型的函數。這裏在函數後面加個“A”表示這是 ASCII 版本,同樣 UNICODE 版本在後面加個“W”,如 SendMessageW。在 Win9X 下我們一般都用 ASCII 版本的函數,UNICODE 版本的函數很多在 Win9X 下是不能運行的。而NT 系統如 WinXP 一般都是 UNICODE 版本的,但如果我們用了 ASCII 版本的函數,系統會自動轉換調用 UNICODE 版本。這樣我們寫補丁代碼的時候就可以直接指定爲 ASCII 版本的函數,可以兼容各個系統):我們用 LordPE 的 PE 編輯器載入 zeroadd 程序,選擇“目錄”,再在彈出的目錄表對話框中選擇輸入表後面的那個“...”按鈕,會彈出一個對話框:
因爲 SendMessageA 在 USER32.dll 中,我們在右鍵菜單中點擊按鈕“添加導入表”,來到下面:
按上面的提示完成後點“確定”,我們回到原先的那個“輸入表”對話框:
從上圖中我們可以看出多出了一個 USER32.dll,這就是我們添加 SendMessageA 的結果。這也是用工具添加的一個缺點。我們一般希望把添加的函數直接放到已存在的 DLL 中,而不是多出來一個,這樣顯得不好看。但用工具就沒辦法,LordPE 默認是建一個 1K 的新區段來保存添加後的結果,由此出現了上圖中的情況。如果你對 PE 結構比較熟悉的話,也可以直接用 16進制編輯工具來添加你需要的函數,這樣改出來的東西好看。如果想偷懶,就像我一樣用工具吧,呵呵。在上圖中我還標出了要注意 FirstThunk 及那個 ThunkRVA 的值,並且要把“總是查看 FirstThunk”那個選項選上。有人可能不理解其作用,我這裏也解釋一下:一般講述 PE 格式的文章中對 FirstThunk 的解釋是這樣的:FirstThunk 包含指向一個 IMAGE_THUNK_DATA 結構數組的 RVA 偏移地址,當把 PE 文件裝載到內存中時,PE裝載器將查找 IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 這些結構數組來決定導入函數的地址,隨後用導入函數真實地址來替代由 FirstThunk 指向的 IMAGE_THUNK_DATA 數組裏的元素值。這樣說起來還是讓人不明白,我舉個例子:比如你有個很要好的朋友,他是個大忙人,雖然你知道他的家庭住址,可他很少回家。如果你哪天想找他,直接去他家,很可能吃個閉門羹,找不到他人。怎麼辦?幸好你有他的手機號碼,你就給他撥了一個電話:“小子,你在哪呢?”,他告訴你:“我正在XXX飯店喝酒呢!”這時你怎麼辦?(當然是殺到他說的那家飯店去蹭飯了!^_^)這裏的 ThunkRVA 就相當於你朋友的手機號碼, SendMessageA 就相當於你那個朋友。而 FirstThunk 就是你手機裏的號碼分組。你把你的多個朋友都放在 FirstThunk 這樣的號碼分組裏,每個 ThunkRVA 就是你一個朋友的手機號碼。你要找他們,就是通過 ThunkRVA 這樣的手機號碼來和他們聯繫,直接去他家找他你很可能要碰壁。而移動或聯通就相當於操作系統,他們負責把你的手機號碼和你的朋友對應上。而 FirstThunk 這樣的號碼分組還有一個好處就是你可以不記你某個朋友的具體號碼,只要記得 FirstThunk 號碼分組的值,你的朋友會按順序在裏面排列。比如上圖中 USER32.dll 中的第一個函數是 SendMessageA,它的 ThunkRVA 值就是 FirstThunk 值。如果還有第二個函數,比如是 MessageBoxA,它的值就是 FirstThunk 值加上 4,其餘類推。你只要記住各個函數的位置,也可以通過 FirstThunk 加上位置對應值來找到它。當然這比不上直接看 ThunkRVA 來得方便。說了上面這些,我們就要考慮怎麼在程序中調用了。你可能會說,我在 OllyDBG 中直接在我們要修改的程序中這樣調用:CALL SendMessageA。哦,別這樣。這等於我上面說的都是廢話,會讓我感到傷心的。你這裏的 CALL SendMessageA 就相當於也不跟你朋友打個招呼就直接去他家找他,很有可能你會乘興而去,敗興而歸。別忘了他的手機號碼,我們只有通過號碼才知道他到底在什麼地方。我們應該這樣:CALL DWORD PTR [40B01A],這裏的 40B01A 就是上面的 SendMessageA 在程序載入後的所在的地方,由基址 00400000 加上 ThunkRVA 0000B01A 得到的。這就是你要找的人所在的地方,不管他跑到哪,你有他的手機號碼就能找到他。同樣道理,你只要記住了 ThunkRVA 值,就按這個來調用你需要的函數,在別的 Windows 系統下也是沒有問題的。系統會自動把你要找到函數和 ThunkRVA 值對應上。而你在 OllyDBG 中寫 CALL SendMessageA,可能你在你的系統上成功了,可放到別的系統下就要出錯了。爲什麼?因爲你找的人已經不在原來的位置了,他跑到別的地方去了。你還到老地方找他,當然看不見人了。說了這麼多廢話,也不知大家聽明白了沒有,別越聽越糊塗就行了。總之一句話,別像 CALL SendMessageA 這樣直接調用某個函數,而應該通過 ThunkRVA 值來調用它。下面我們回到我們要修改的 MyUninstaller 上來,先用 LordPE 打開看一下,呵呵,原來 CreateFontIndirectA 和 SendMessageA 原程序裏面都有了,省了我們不少事情。看一下這兩個函數的 ThunkRVA 值,CreateFontIndirectA 在 GDI32.dll 裏面,ThunkRVA 值是 0000B044,這樣我們就知道在程序中調用它的時候就是 CALL DWORD PTR [0040B044]。同樣,SendMessageA 的ThunkRVA 值是 0000B23C,調用時應該是這樣:CALL DWORD PTR [0040B23C]。瞭解了這些東西我們就來考慮怎麼寫代碼了。首先我們來看一下 CreateFontIndirectA 和 SendMessageA 這兩個函數的定義:
CreateFontIndirectA:
HFONT CreateFontIndirect(
CONST LOGFONT *lplf // pointer to logical font structure
);
CreateFontIndirect的返回值就是字體的句柄。
對於這個函數我們需要的參數就是給它一個 LOGFONT 的字體結構指針,我們只要在要修改程序的空白處建一個標準的9號(小五)宋體的 LOGFONT 字體結構,再把指針給 CreateFontIndirectA 就可以了。
SendMessageA:
LRESULT SendMessage(
HWND hWnd, // handle of destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
上面的第一個參數是窗口句柄,我們知道 CreateWindowExA 返回的就是窗口句柄,我們可以直接拿來用。第二個消息參數我們這裏是設置字體,選WM_SETFONT,這個值是 30H。第三個參數是字體句柄,可以由上面的 CreateFontIndirectA 獲得。第四個參數我們不需要,留空。現在我們準備開始寫代碼,首先我們要在程序中建一個標準9號宋體的 LOGFONT,以便於我們調用。對於 LOGFONT,我們再來看一下定義:
typedef struct tagLOGFONT { // lf
LONG lfHeight;
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;
這樣我們的標準9號宋體的 LOGFONT 值應該是32字節,16進制就像這樣:F4FFFFFF000000000000000000000000900100000000008600000000CBCECCE5。現在在程序中找個空地。我們用 PEiD 來幫助我們尋找,用 PEiD 打開程序,點 EP 段後面的那個 > 號,隨便選擇一個區段右擊,選“搜索全0處”(原版好像是cave什麼的):
我們看到 PEiD 把搜索到的空間都給我們列出來了:
現在我們用 WinHEX 打開我們要修改的程序,轉到偏移 9815 處,從 9815 處選擇 32 字節(16進制是0X20)的一個選塊,把光標定位到 9815 處,右鍵選擇菜單 剪貼板數據->寫入(從當前位置覆寫),隨後的格式選擇 ASCII Hex,把我們 LOGFONT 的 16 進制值
F4FFFFFF000000000000000000000000900100000000008600000000CBCECCE5
寫入保存。現在我們用 OllyDBG 載入已添加了 LOGFONT 數據的程序,先轉到 VA 40A415 處(從上圖中看到的)往下看一下:
因爲 SendMessageA 還要用到一個窗口句柄,我們可以通過前面的 CreateWindowExA 來獲得。現在我們就把前一張圖中的 .rdata 區段中的地址 0040C56E 作爲我們保存窗口句柄 HWND 值的臨時空間。一切就緒,開始寫代碼。先回顧一下我們最先說的那兩個要修改的地方:
第一個要改的地方:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F64 6A 00 PUSH 0 ; 修改前
00408F66 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
修改後:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F64 E9 D5140000 JMP myuninst.0040A43E ; 跳轉到我們的補丁代碼處
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
第二個要改的地方:
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX ; 改這裏
00408F9A 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |
00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format = "%s:"
00408FAA |. 50 |PUSH EAX ; |s
00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; \sprintf
00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA
修改後:
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 E9 D4140000 JMP myuninst.0040A470 ; 跳到我們的第二部分補丁代碼處
00408F9C 90 NOP
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |
00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format = "%s:"
00408FAA |. 50 |PUSH EAX ; |s
00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; \sprintf
00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA
這兩個地方的修改都是把原代碼改成跳轉,跳到我們的補丁代碼那繼續執行。在修改之前先把原代碼複製下來,以便恢復。我們在 OllyDBG 中按 CTR+G 組合鍵,來到 0040A43E 地址處,開始輸補丁代碼:
同樣,我們也在 0040A470 地址處輸入我們另一部分的補丁代碼。兩部分的補丁代碼分別如下:
補丁代碼1:
0040A43E 60 PUSHAD ; 保護現場
0040A43F A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX ; 保存窗口句柄
0040A444 68 15A44000 PUSH myuninst.0040A415 ; 傳遞字體句柄LOGFONT
0040A449 FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; GDI32.CreateFontIndirectA
0040A44F 6A 00 PUSH 0 ; lParam 參數留空
0040A451 50 PUSH EAX ; 字體句柄LOGFONT
0040A452 6A 30 PUSH 30 ; WM_SETFONT
0040A454 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; 窗口句柄送ECX
0040A45A 51 PUSH ECX ; 壓入窗口句柄參數
0040A45B FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; USER32.SendMessageA
0040A461 61 POPAD ; 恢復現場
0040A462 6A 00 PUSH 0 ; 恢復原代碼
0040A464 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
0040A467 ^ E9 FDEAFFFF JMP myuninst.00408F69 ; 返回
補丁代碼2:
0040A470 > \60 PUSHAD
0040A471 . A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX
0040A476 . 68 15A44000 PUSH myuninst.0040A415 ; /pLogfont = myuninst.0040A415
0040A47B . FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; \CreateFontIndirectA
0040A481 . 6A 00 PUSH 0 ; /lParam = 0
0040A483 . 50 PUSH EAX ; |wParam
0040A484 . 6A 30 PUSH 30 ; |Message = WM_SETFONT
0040A486 . 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; |
0040A48C . 51 PUSH ECX ; |hWnd => NULL
0040A48D . FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; \SendMessageA
0040A493 . 61 POPAD
0040A494 . 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
0040A497 . 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
0040A49A .^ E9 FEEAFFFF JMP myuninst.00408F9D
補丁代碼2因爲與補丁代碼1類似,我就不做詳細解釋了。現在我們的代碼都寫完了,現在我們開始保存我們的工作,選中我們修改的代碼,點擊鼠標右鍵,會出來一個菜單:
我們左鍵選所有修改(當然選它了,要不然只會保存我們選定的這一部分。關於這個地方還要說一下,有的時候我們修改完程序選“複製到可執行文件”時只有“選擇”菜單,沒有“所有修改”菜單項。按 OllyDBG 幫助裏關於備份功能的說法,好像是受內存塊限制的,補丁功能也同樣是這樣。對於備份及補丁功能我用的比較少,並不是很瞭解,這方面的內容還是大家自己去研究吧,有什麼好的心得也希望能共享一下。我遇到不能保存所有修改的情況就是先把補丁代碼全部複製下來,同時利用二進制功能複製代碼,先選一段補丁代碼保存爲文件,再用 OllyDBG 打開保存後的文件,轉到相應位置分別把我們複製下來的補丁二進制代碼粘貼上去後保存。純屬笨辦法,當然你也可以用 HexView 這樣的工具來修改代碼),隨後會出來一個“把選中的內容複製到可執行文件”的對話框,我們選“全部複製”,又出來一個對話框,我們在上面點右鍵,在彈出的菜單上選“保存文件”:
這時會出來一個另存文件的對話框,我們另選一個名字如 myuninst1.exe 來保存,不要直接覆蓋原文件 myuninst.exe,以便於出錯後好修改。現在關閉 OllyDBG,先不要急着運行剛剛修改過的文件,因爲我們還有個地方要改一下。大家還記得我們在 .rdata 中用了個地方作爲我們保存臨時變量的地方吧?原先的 .rdata 段屬性設置是不可寫的,現在我們寫入了數據,運行時是會出錯的。現在我們要修改一下 .rdata 段的屬性。用 LordPE 的 PE 編輯器打開我們修改後的程序,點“區段”按鈕,在彈出的對話框中點擊 .rdata 段,右鍵選擇彈出菜單中的“編輯區段”:
在彈出的對話框中選標誌後面那個“...”按鈕:
現在我們把區段標誌添加一個可寫入的屬性:
完成後按確定保存我們所做的工作,運行一下修改後的程序,呵呵,終於把字體改過來了:
如果你運行出錯也沒關係,用 OllyDBG 調試一下你修改後的程序,看看錯在什麼地方。這一般都是輸入補丁代碼時造成的,你只要看一下你補丁代碼運行的情況就可以了。到這裏我們的任務似乎也完成了,但細心的朋友可能會發現補丁代碼1和補丁代碼2前面的代碼基本上是相同的。一個兩個這樣的補丁還好,如果要是多的話,這樣重複就要浪費不少空間了,況且工作量也相應加大了。既然前面有很多代碼都是重複的,爲什麼我們不把這些重複的代碼做成一個子程序呢?這樣調用起來要方便的多。下面我們把前面的補丁代碼修改一下,我們先把補丁代碼1的代碼改成這樣:
0040A43E 60 PUSHAD ; 保護現場
0040A43F A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX ; 保存窗口句柄
0040A444 68 15A44000 PUSH myuninst.0040A415 ; 我們建的LOGFONT對應指針
0040A449 FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; GDI32.CreateFontIndirectA
0040A44F 6A 00 PUSH 0 ; lParam 參數留空
0040A451 50 PUSH EAX ; 字體句柄
0040A452 6A 30 PUSH 30 ; WM_SETFONT
0040A454 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; 窗口句柄
0040A45A 51 PUSH ECX ; 窗口句柄壓棧
0040A45B FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; USER32.SendMessageA
0040A461 61 POPAD ; 恢復現場
0040A462 C3 RETN ; 返回
這樣我們的子程序代碼就寫好了。現在我們再在子程序代碼後面寫上兩個補丁代碼,當然不要忘了改前面原程序中的跳轉:
修改後的補丁代碼1:
0040A467 E8 D2FFFFFF CALL myuninst.0040A43E ; 調用子程序
0040A46C 6A 00 PUSH 0 ; 恢復前面修改過的代碼
0040A46E 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
0040A471 ^ E9 F3EAFFFF JMP myuninst.00408F69 ; 返回繼續執行
修改後的補丁代碼2:
0040A47A E8 BFFFFFFF CALL myuninst.0040A43E
0040A47F 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
0040A482 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
0040A485 ^ E9 13EBFFFF JMP myuninst.00408F9D
我在每個補丁代碼片斷間留了4個字節來分隔。同樣,我們還要修改一下我們前面的跳轉:
第一個要修改跳轉的地方:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \斷在這裏
00408F64 E9 FE140000 JMP myuninst.0040A467 ; 跳到我們的第一部分補丁代碼處
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
第二個要修改跳轉的地方:
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 E9 DE140000 JMP myuninst.0040A47A ; 跳到我們的第二部分補丁代碼處
00408F9C 90 NOP
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
修改好後保存,同樣不要忘了再修改一下 .rdata 區段的屬性。運行一下,一切OK!
【版權聲明】 本文純屬技術交流, 轉載請註明作者並保持文章的完整, 謝謝!】OllyDBG 入門系列(七)-彙編功能作 者: CCDebuger
時 間: 2006-04-09,16:42:10
鏈 接: http://bbs.pediy.com/showthread.php?t=23873
OllyDBG 入門系列(七)-彙編功能
作者:CCDebuger
今天我們的目標程序是 MyUninstaller 1.34 版。這是一個非常小的程序卸載工具,VC6編寫,大小隻有61K。我拿到的這個是上次閃電狼兄弟給我的,附帶在裏面的簡體中文語言文件是由六芒星製作的。這個程序有個毛病:就是在列出的可卸載程序上雙擊查看屬性時,彈出的屬性窗口的字體非常難看,應該就是系統字體(SYSTEM_FONT):
我們今天的目標就是利用 OllyDBG 的彙編功能把上面顯示的字體改成我們常見的9號(小五)宋體。首先我們用 OllyDBG 載入程序,按 CTR+N 組合鍵查找一下有哪些 API 函數,只發現一個和設置字體相關的 CreateFontIndirectA。現在我們按鼠標右鍵,選擇“在每個參考上設置斷點”,關掉名稱對話框,F9運行,程序已經運行起來了。我們在程序的列表框中隨便找一項雙擊一下,很不幸,那個字體難看的界面又出現了,OllyDBG 沒有任何動作。可見創建這個窗口的時候根本沒調用 CreateFontIndirectA,問題現在就變得有點複雜了。先點確定把這個字體難看的對話框關閉,現在我們從另一個方面考慮:既然沒有調用設置字體的函數,那我們來看看這個窗口是如何創建的,跟蹤窗口創建過程可能會找到一些對我們有用的信息。現在我們再回到我們調試程序的領空,按 CTR+N 看一下,發現 CreateWindowExA 這個 API 函數比較可疑。我們在 CreateWindowExA 函數的每個參考上設上斷點,在 MyUninstaller 的列表框中再隨便找一項雙擊一下,被 OllyDBG 斷下:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \斷在這裏
上下翻看一下代碼:
00408F3B |. 50 |PUSH EAX ; |hInst
00408F3C |. 8B45 C0 |MOV EAX,DWORD PTR SS:[EBP-40] ; |
00408F3F |. 6A 00 |PUSH 0 ; |hMenu = NULL
00408F41 |. 03C6 |ADD EAX,ESI ; |
00408F43 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hParent
00408F46 |. FF75 D0 |PUSH DWORD PTR SS:[EBP-30] ; |Height
00408F49 |. 57 |PUSH EDI ; |Width
00408F4A |. 50 |PUSH EAX ; |Y
00408F4B |. FF75 BC |PUSH DWORD PTR SS:[EBP-44] ; |X
00408F4E |. FF75 EC |PUSH DWORD PTR SS:[EBP-14] ; |Style
00408F51 |. 68 80DE4000 |PUSH myuninst.0040DE80 ; |WindowName = ""
00408F56 |. 68 DCD94000 |PUSH myuninst.0040D9DC ; |Class = "STATIC"
00408F5B |. FF75 D4 |PUSH DWORD PTR SS:[EBP-2C] ; |ExtStyle
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \斷在這裏
00408F64 | 6A 00 |PUSH 0 ; 第一處要修改的地方
00408F66 | 8945 F4 |MOV DWORD PTR SS:[EBP-C],EAX
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
00408F6E |. 50 |PUSH EAX ; |hInst
00408F6F |. 8B45 DC |MOV EAX,DWORD PTR SS:[EBP-24] ; |
00408F72 |. 6A 00 |PUSH 0 ; |hMenu = NULL
00408F74 |. 03F0 |ADD ESI,EAX ; |
00408F76 |. FF75 08 |PUSH DWORD PTR SS:[EBP+8] ; |hParent
00408F79 |. FF75 CC |PUSH DWORD PTR SS:[EBP-34] ; |Height
00408F7C |. 53 |PUSH EBX ; |Width
00408F7D |. 56 |PUSH ESI ; |Y
00408F7E |. FF75 D8 |PUSH DWORD PTR SS:[EBP-28] ; |X
00408F81 |. FF75 E8 |PUSH DWORD PTR SS:[EBP-18] ; |Style
00408F84 |. 68 80DE4000 |PUSH myuninst.0040DE80 ; |WindowName = ""
00408F89 |. 68 D4D94000 |PUSH myuninst.0040D9D4 ; |Class = "EDIT"
00408F8E |. FF75 B8 |PUSH DWORD PTR SS:[EBP-48] ; |ExtStyle
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 | 8945 F0 |MOV DWORD PTR SS:[EBP-10],EAX ; 第二處要修改的地方
00408F9A | 8B45 F8 |MOV EAX,DWORD PTR SS:[EBP-8]
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |
00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format = "%s:"
00408FAA |. 50 |PUSH EAX ; |s
00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; \sprintf
00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA
00408FB7 |. 83C4 0C |ADD ESP,0C
00408FBA |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150]
00408FC0 |. 50 |PUSH EAX ; /Text
00408FC1 |. FF75 F4 |PUSH DWORD PTR SS:[EBP-C] ; |hWnd
00408FC4 |. FFD6 |CALL ESI ; \SetWindowTextA
00408FC6 |. 8D85 ACFAFFFF |LEA EAX,DWORD PTR SS:[EBP-554]
00408FCC |. 50 |PUSH EAX ; /Arg3
00408FCD |. FF75 FC |PUSH DWORD PTR SS:[EBP-4] ; |Arg2
00408FD0 |. FF35 00EF4000 |PUSH DWORD PTR DS:[40EF00] ; |Arg1 = 00BEADCC
00408FD6 |. E8 1884FFFF |CALL <myuninst.sub_4013F3> ; \sub_4013F3
00408FDB |. 83C4 0C |ADD ESP,0C
00408FDE |. 50 |PUSH EAX
00408FDF |. FF75 F0 |PUSH DWORD PTR SS:[EBP-10]
00408FE2 |. FFD6 |CALL ESI
00408FE4 |. FF45 FC |INC DWORD PTR SS:[EBP-4]
00408FE7 |. 8345 F8 14 |ADD DWORD PTR SS:[EBP-8],14
00408FEB |. 837D FC 0F |CMP DWORD PTR SS:[EBP-4],0F
00408FEF |.^ 0F8C 32FFFFFF \JL <myuninst.loc_408F27>
00408FF5 |. 5F POP EDI
00408FF6 |. 5E POP ESI
00408FF7 |. 5B POP EBX
00408FF8 |. C9 LEAVE
00408FF9 \. C3 RETN
我想上面的代碼我不需多做解釋,OllyDBG 自動給出的註釋已經夠清楚的了。我們雙擊 MyUninstaller 列表框中的的某項查看屬性時,彈出的屬性窗口上的 STATIC 控件和 EDIT 控件都是由 CreateWindowExA 函數創建的,然後再調用 SetWindowTextA 來設置文本,根本沒考慮控件上字體顯示的問題,所以我們看到的都是系統默認的字體。我們要設置控件上的字體,可以考慮在 CreateWindowExA 創建完控件後,在使用 SetWindowTextA 函數設置文本之前調用相關字體創建函數來選擇字體,再調用 SendMessageA 函數發送 WM_SETFONT 消息來設置控件字體。思路定下來後,我們就開始來實施。首先我們看一下這個程序中的導入函數,CreateFontIndirectA 這個字體創建函數已經有了,再看看 SendMessageA,呵呵,不錯,原程序也有這個函數。這樣我們就省事了。有人可能要問,如果原來並沒有這兩個導入函數,那怎麼辦呢?其實這也很簡單,我們可以直接用 LordPE 來在程序中添加我們需要的導入函數。我這裏用個很小的 PE 工具 zeroadd 來示範一下,這個程序裏面沒有 CreateFontIndirectA 和 SendMessageA 函數(這裏還有個問題說一下,其實我們編程時調用這兩個函數時都是直接寫 CreateFontIndirect 及 SendMessage,一般不需指定。但在程序中寫補丁代碼時我們要指定這是什麼類型的函數。這裏在函數後面加個“A”表示這是 ASCII 版本,同樣 UNICODE 版本在後面加個“W”,如 SendMessageW。在 Win9X 下我們一般都用 ASCII 版本的函數,UNICODE 版本的函數很多在 Win9X 下是不能運行的。而NT 系統如 WinXP 一般都是 UNICODE 版本的,但如果我們用了 ASCII 版本的函數,系統會自動轉換調用 UNICODE 版本。這樣我們寫補丁代碼的時候就可以直接指定爲 ASCII 版本的函數,可以兼容各個系統):我們用 LordPE 的 PE 編輯器載入 zeroadd 程序,選擇“目錄”,再在彈出的目錄表對話框中選擇輸入表後面的那個“...”按鈕,會彈出一個對話框:
因爲 SendMessageA 在 USER32.dll 中,我們在右鍵菜單中點擊按鈕“添加導入表”,來到下面:
按上面的提示完成後點“確定”,我們回到原先的那個“輸入表”對話框:
從上圖中我們可以看出多出了一個 USER32.dll,這就是我們添加 SendMessageA 的結果。這也是用工具添加的一個缺點。我們一般希望把添加的函數直接放到已存在的 DLL 中,而不是多出來一個,這樣顯得不好看。但用工具就沒辦法,LordPE 默認是建一個 1K 的新區段來保存添加後的結果,由此出現了上圖中的情況。如果你對 PE 結構比較熟悉的話,也可以直接用 16進制編輯工具來添加你需要的函數,這樣改出來的東西好看。如果想偷懶,就像我一樣用工具吧,呵呵。在上圖中我還標出了要注意 FirstThunk 及那個 ThunkRVA 的值,並且要把“總是查看 FirstThunk”那個選項選上。有人可能不理解其作用,我這裏也解釋一下:一般講述 PE 格式的文章中對 FirstThunk 的解釋是這樣的:FirstThunk 包含指向一個 IMAGE_THUNK_DATA 結構數組的 RVA 偏移地址,當把 PE 文件裝載到內存中時,PE裝載器將查找 IMAGE_THUNK_DATA 和 IMAGE_IMPORT_BY_NAME 這些結構數組來決定導入函數的地址,隨後用導入函數真實地址來替代由 FirstThunk 指向的 IMAGE_THUNK_DATA 數組裏的元素值。這樣說起來還是讓人不明白,我舉個例子:比如你有個很要好的朋友,他是個大忙人,雖然你知道他的家庭住址,可他很少回家。如果你哪天想找他,直接去他家,很可能吃個閉門羹,找不到他人。怎麼辦?幸好你有他的手機號碼,你就給他撥了一個電話:“小子,你在哪呢?”,他告訴你:“我正在XXX飯店喝酒呢!”這時你怎麼辦?(當然是殺到他說的那家飯店去蹭飯了!^_^)這裏的 ThunkRVA 就相當於你朋友的手機號碼, SendMessageA 就相當於你那個朋友。而 FirstThunk 就是你手機裏的號碼分組。你把你的多個朋友都放在 FirstThunk 這樣的號碼分組裏,每個 ThunkRVA 就是你一個朋友的手機號碼。你要找他們,就是通過 ThunkRVA 這樣的手機號碼來和他們聯繫,直接去他家找他你很可能要碰壁。而移動或聯通就相當於操作系統,他們負責把你的手機號碼和你的朋友對應上。而 FirstThunk 這樣的號碼分組還有一個好處就是你可以不記你某個朋友的具體號碼,只要記得 FirstThunk 號碼分組的值,你的朋友會按順序在裏面排列。比如上圖中 USER32.dll 中的第一個函數是 SendMessageA,它的 ThunkRVA 值就是 FirstThunk 值。如果還有第二個函數,比如是 MessageBoxA,它的值就是 FirstThunk 值加上 4,其餘類推。你只要記住各個函數的位置,也可以通過 FirstThunk 加上位置對應值來找到它。當然這比不上直接看 ThunkRVA 來得方便。說了上面這些,我們就要考慮怎麼在程序中調用了。你可能會說,我在 OllyDBG 中直接在我們要修改的程序中這樣調用:CALL SendMessageA。哦,別這樣。這等於我上面說的都是廢話,會讓我感到傷心的。你這裏的 CALL SendMessageA 就相當於也不跟你朋友打個招呼就直接去他家找他,很有可能你會乘興而去,敗興而歸。別忘了他的手機號碼,我們只有通過號碼才知道他到底在什麼地方。我們應該這樣:CALL DWORD PTR [40B01A],這裏的 40B01A 就是上面的 SendMessageA 在程序載入後的所在的地方,由基址 00400000 加上 ThunkRVA 0000B01A 得到的。這就是你要找的人所在的地方,不管他跑到哪,你有他的手機號碼就能找到他。同樣道理,你只要記住了 ThunkRVA 值,就按這個來調用你需要的函數,在別的 Windows 系統下也是沒有問題的。系統會自動把你要找到函數和 ThunkRVA 值對應上。而你在 OllyDBG 中寫 CALL SendMessageA,可能你在你的系統上成功了,可放到別的系統下就要出錯了。爲什麼?因爲你找的人已經不在原來的位置了,他跑到別的地方去了。你還到老地方找他,當然看不見人了。說了這麼多廢話,也不知大家聽明白了沒有,別越聽越糊塗就行了。總之一句話,別像 CALL SendMessageA 這樣直接調用某個函數,而應該通過 ThunkRVA 值來調用它。下面我們回到我們要修改的 MyUninstaller 上來,先用 LordPE 打開看一下,呵呵,原來 CreateFontIndirectA 和 SendMessageA 原程序裏面都有了,省了我們不少事情。看一下這兩個函數的 ThunkRVA 值,CreateFontIndirectA 在 GDI32.dll 裏面,ThunkRVA 值是 0000B044,這樣我們就知道在程序中調用它的時候就是 CALL DWORD PTR [0040B044]。同樣,SendMessageA 的ThunkRVA 值是 0000B23C,調用時應該是這樣:CALL DWORD PTR [0040B23C]。瞭解了這些東西我們就來考慮怎麼寫代碼了。首先我們來看一下 CreateFontIndirectA 和 SendMessageA 這兩個函數的定義:
CreateFontIndirectA:
HFONT CreateFontIndirect(
CONST LOGFONT *lplf // pointer to logical font structure
);
CreateFontIndirect的返回值就是字體的句柄。
對於這個函數我們需要的參數就是給它一個 LOGFONT 的字體結構指針,我們只要在要修改程序的空白處建一個標準的9號(小五)宋體的 LOGFONT 字體結構,再把指針給 CreateFontIndirectA 就可以了。
SendMessageA:
LRESULT SendMessage(
HWND hWnd, // handle of destination window
UINT Msg, // message to send
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
上面的第一個參數是窗口句柄,我們知道 CreateWindowExA 返回的就是窗口句柄,我們可以直接拿來用。第二個消息參數我們這裏是設置字體,選WM_SETFONT,這個值是 30H。第三個參數是字體句柄,可以由上面的 CreateFontIndirectA 獲得。第四個參數我們不需要,留空。現在我們準備開始寫代碼,首先我們要在程序中建一個標準9號宋體的 LOGFONT,以便於我們調用。對於 LOGFONT,我們再來看一下定義:
typedef struct tagLOGFONT { // lf
LONG lfHeight;
LONG lfWidth;
LONG lfEscapement;
LONG lfOrientation;
LONG lfWeight;
BYTE lfItalic;
BYTE lfUnderline;
BYTE lfStrikeOut;
BYTE lfCharSet;
BYTE lfOutPrecision;
BYTE lfClipPrecision;
BYTE lfQuality;
BYTE lfPitchAndFamily;
TCHAR lfFaceName[LF_FACESIZE];
} LOGFONT;
這樣我們的標準9號宋體的 LOGFONT 值應該是32字節,16進制就像這樣:F4FFFFFF000000000000000000000000900100000000008600000000CBCECCE5。現在在程序中找個空地。我們用 PEiD 來幫助我們尋找,用 PEiD 打開程序,點 EP 段後面的那個 > 號,隨便選擇一個區段右擊,選“搜索全0處”(原版好像是cave什麼的):
我們看到 PEiD 把搜索到的空間都給我們列出來了:
現在我們用 WinHEX 打開我們要修改的程序,轉到偏移 9815 處,從 9815 處選擇 32 字節(16進制是0X20)的一個選塊,把光標定位到 9815 處,右鍵選擇菜單 剪貼板數據->寫入(從當前位置覆寫),隨後的格式選擇 ASCII Hex,把我們 LOGFONT 的 16 進制值
F4FFFFFF000000000000000000000000900100000000008600000000CBCECCE5
寫入保存。現在我們用 OllyDBG 載入已添加了 LOGFONT 數據的程序,先轉到 VA 40A415 處(從上圖中看到的)往下看一下:
因爲 SendMessageA 還要用到一個窗口句柄,我們可以通過前面的 CreateWindowExA 來獲得。現在我們就把前一張圖中的 .rdata 區段中的地址 0040C56E 作爲我們保存窗口句柄 HWND 值的臨時空間。一切就緒,開始寫代碼。先回顧一下我們最先說的那兩個要修改的地方:
第一個要改的地方:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F64 6A 00 PUSH 0 ; 修改前
00408F66 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
修改後:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F64 E9 D5140000 JMP myuninst.0040A43E ; 跳轉到我們的補丁代碼處
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
第二個要改的地方:
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX ; 改這裏
00408F9A 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |
00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format = "%s:"
00408FAA |. 50 |PUSH EAX ; |s
00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; \sprintf
00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA
修改後:
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 E9 D4140000 JMP myuninst.0040A470 ; 跳到我們的第二部分補丁代碼處
00408F9C 90 NOP
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
00408F9F |. 8D85 B0FEFFFF |LEA EAX,DWORD PTR SS:[EBP-150] ; |
00408FA5 |. 68 D0D94000 |PUSH myuninst.0040D9D0 ; |format = "%s:"
00408FAA |. 50 |PUSH EAX ; |s
00408FAB |. FF15 90B14000 |CALL DWORD PTR DS:[<&MSVCRT.sprintf>] ; \sprintf
00408FB1 |. 8B35 84B24000 |MOV ESI,DWORD PTR DS:[<&USER32.SetWindowTextA>] ; USER32.SetWindowTextA
這兩個地方的修改都是把原代碼改成跳轉,跳到我們的補丁代碼那繼續執行。在修改之前先把原代碼複製下來,以便恢復。我們在 OllyDBG 中按 CTR+G 組合鍵,來到 0040A43E 地址處,開始輸補丁代碼:
同樣,我們也在 0040A470 地址處輸入我們另一部分的補丁代碼。兩部分的補丁代碼分別如下:
補丁代碼1:
0040A43E 60 PUSHAD ; 保護現場
0040A43F A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX ; 保存窗口句柄
0040A444 68 15A44000 PUSH myuninst.0040A415 ; 傳遞字體句柄LOGFONT
0040A449 FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; GDI32.CreateFontIndirectA
0040A44F 6A 00 PUSH 0 ; lParam 參數留空
0040A451 50 PUSH EAX ; 字體句柄LOGFONT
0040A452 6A 30 PUSH 30 ; WM_SETFONT
0040A454 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; 窗口句柄送ECX
0040A45A 51 PUSH ECX ; 壓入窗口句柄參數
0040A45B FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; USER32.SendMessageA
0040A461 61 POPAD ; 恢復現場
0040A462 6A 00 PUSH 0 ; 恢復原代碼
0040A464 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
0040A467 ^ E9 FDEAFFFF JMP myuninst.00408F69 ; 返回
補丁代碼2:
0040A470 > \60 PUSHAD
0040A471 . A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX
0040A476 . 68 15A44000 PUSH myuninst.0040A415 ; /pLogfont = myuninst.0040A415
0040A47B . FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; \CreateFontIndirectA
0040A481 . 6A 00 PUSH 0 ; /lParam = 0
0040A483 . 50 PUSH EAX ; |wParam
0040A484 . 6A 30 PUSH 30 ; |Message = WM_SETFONT
0040A486 . 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; |
0040A48C . 51 PUSH ECX ; |hWnd => NULL
0040A48D . FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; \SendMessageA
0040A493 . 61 POPAD
0040A494 . 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
0040A497 . 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
0040A49A .^ E9 FEEAFFFF JMP myuninst.00408F9D
補丁代碼2因爲與補丁代碼1類似,我就不做詳細解釋了。現在我們的代碼都寫完了,現在我們開始保存我們的工作,選中我們修改的代碼,點擊鼠標右鍵,會出來一個菜單:
我們左鍵選所有修改(當然選它了,要不然只會保存我們選定的這一部分。關於這個地方還要說一下,有的時候我們修改完程序選“複製到可執行文件”時只有“選擇”菜單,沒有“所有修改”菜單項。按 OllyDBG 幫助裏關於備份功能的說法,好像是受內存塊限制的,補丁功能也同樣是這樣。對於備份及補丁功能我用的比較少,並不是很瞭解,這方面的內容還是大家自己去研究吧,有什麼好的心得也希望能共享一下。我遇到不能保存所有修改的情況就是先把補丁代碼全部複製下來,同時利用二進制功能複製代碼,先選一段補丁代碼保存爲文件,再用 OllyDBG 打開保存後的文件,轉到相應位置分別把我們複製下來的補丁二進制代碼粘貼上去後保存。純屬笨辦法,當然你也可以用 HexView 這樣的工具來修改代碼),隨後會出來一個“把選中的內容複製到可執行文件”的對話框,我們選“全部複製”,又出來一個對話框,我們在上面點右鍵,在彈出的菜單上選“保存文件”:
這時會出來一個另存文件的對話框,我們另選一個名字如 myuninst1.exe 來保存,不要直接覆蓋原文件 myuninst.exe,以便於出錯後好修改。現在關閉 OllyDBG,先不要急着運行剛剛修改過的文件,因爲我們還有個地方要改一下。大家還記得我們在 .rdata 中用了個地方作爲我們保存臨時變量的地方吧?原先的 .rdata 段屬性設置是不可寫的,現在我們寫入了數據,運行時是會出錯的。現在我們要修改一下 .rdata 段的屬性。用 LordPE 的 PE 編輯器打開我們修改後的程序,點“區段”按鈕,在彈出的對話框中點擊 .rdata 段,右鍵選擇彈出菜單中的“編輯區段”:
在彈出的對話框中選標誌後面那個“...”按鈕:
現在我們把區段標誌添加一個可寫入的屬性:
完成後按確定保存我們所做的工作,運行一下修改後的程序,呵呵,終於把字體改過來了:
如果你運行出錯也沒關係,用 OllyDBG 調試一下你修改後的程序,看看錯在什麼地方。這一般都是輸入補丁代碼時造成的,你只要看一下你補丁代碼運行的情況就可以了。到這裏我們的任務似乎也完成了,但細心的朋友可能會發現補丁代碼1和補丁代碼2前面的代碼基本上是相同的。一個兩個這樣的補丁還好,如果要是多的話,這樣重複就要浪費不少空間了,況且工作量也相應加大了。既然前面有很多代碼都是重複的,爲什麼我們不把這些重複的代碼做成一個子程序呢?這樣調用起來要方便的多。下面我們把前面的補丁代碼修改一下,我們先把補丁代碼1的代碼改成這樣:
0040A43E 60 PUSHAD ; 保護現場
0040A43F A3 6EC54000 MOV DWORD PTR DS:[40C56E],EAX ; 保存窗口句柄
0040A444 68 15A44000 PUSH myuninst.0040A415 ; 我們建的LOGFONT對應指針
0040A449 FF15 44B04000 CALL DWORD PTR DS:[<&GDI32.CreateFontIndirectA>] ; GDI32.CreateFontIndirectA
0040A44F 6A 00 PUSH 0 ; lParam 參數留空
0040A451 50 PUSH EAX ; 字體句柄
0040A452 6A 30 PUSH 30 ; WM_SETFONT
0040A454 8B0D 6EC54000 MOV ECX,DWORD PTR DS:[40C56E] ; 窗口句柄
0040A45A 51 PUSH ECX ; 窗口句柄壓棧
0040A45B FF15 3CB24000 CALL DWORD PTR DS:[<&USER32.SendMessageA>] ; USER32.SendMessageA
0040A461 61 POPAD ; 恢復現場
0040A462 C3 RETN ; 返回
這樣我們的子程序代碼就寫好了。現在我們再在子程序代碼後面寫上兩個補丁代碼,當然不要忘了改前面原程序中的跳轉:
修改後的補丁代碼1:
0040A467 E8 D2FFFFFF CALL myuninst.0040A43E ; 調用子程序
0040A46C 6A 00 PUSH 0 ; 恢復前面修改過的代碼
0040A46E 8945 F4 MOV DWORD PTR SS:[EBP-C],EAX
0040A471 ^ E9 F3EAFFFF JMP myuninst.00408F69 ; 返回繼續執行
修改後的補丁代碼2:
0040A47A E8 BFFFFFFF CALL myuninst.0040A43E
0040A47F 8945 F0 MOV DWORD PTR SS:[EBP-10],EAX
0040A482 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-8]
0040A485 ^ E9 13EBFFFF JMP myuninst.00408F9D
我在每個補丁代碼片斷間留了4個字節來分隔。同樣,我們還要修改一下我們前面的跳轉:
第一個要修改跳轉的地方:
00408F5E |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \斷在這裏
00408F64 E9 FE140000 JMP myuninst.0040A467 ; 跳到我們的第一部分補丁代碼處
00408F69 |. E8 A098FFFF |CALL <myuninst.sub_40280E>
第二個要修改跳轉的地方:
00408F91 |. FF15 98B24000 |CALL DWORD PTR DS:[<&USER32.CreateWindowExA>] ; \CreateWindowExA
00408F97 E9 DE140000 JMP myuninst.0040A47A ; 跳到我們的第二部分補丁代碼處
00408F9C 90 NOP
00408F9D |. FF30 |PUSH DWORD PTR DS:[EAX] ; /<%s>
修改好後保存,同樣不要忘了再修改一下 .rdata 區段的屬性。運行一下,一切OK!
【版權聲明】 本文純屬技術交流, 轉載請註明作者並保持文章的完整, 謝謝!