用32位匯編語言寫一個內(nèi)存補丁的例子
今天博客就是復(fù)習(xí)一下之前看的內(nèi)容。
現(xiàn)在網(wǎng)絡(luò)上有很多破解軟件,一個本來要錢才能用得軟件經(jīng)過大佬破解之后就變得可以不用錢了。這個破解的過程肯定是非常的辛苦,一些脫殼逆向的過程就不用多說了。而這里我只是寫了一個hello worid級別的程序,然后主要是介紹怎么用ReadProcessMemory函數(shù)和WriteProcessMemory函數(shù)來對一個進程的內(nèi)存地址空間進行讀和寫的操作。
要對一個進程進行各種操作,那么最基本的肯定就是要先獲得進程的句柄了。獲取進程的句柄的方式有很多,可以通過CreateProcess函數(shù),也可以通過目標進程所打開的窗口的類名和標題獲得窗口的句柄,再通過窗口的句柄獲得打開該窗口的進程的ID,再通過該ID獲得進程的句柄。(這個好麻煩啊)還有一種辦法是通過快照函數(shù)獲取一個當(dāng)前系統(tǒng)的進程的快照,然后再找出目標進程。
這上面三種方法,第一種是我們在自己的進程中用這個函數(shù)打開目標進程,然后函數(shù)會返回目標進程的句柄。也就是說那個目標進程現(xiàn)在變成了我們自己的進程的子進程。后面兩種方法都是進程已經(jīng)存在了,然后我們再去獲得句柄。但是結(jié)合我們是想模擬一個破解軟件的亞子。我們想的應(yīng)該就是打開我們的補丁程序,然后就程序自動就幫我們打好了補丁,然后再打開目標程序。如果說進程已經(jīng)存在了,那么我們再去打補丁可能就麻煩了,因為要打補丁的部分可能已經(jīng)運行過去了,并且本來就一般都是先打好了再運行的嘛。而CreateProcess函數(shù)里面的參數(shù)可以指定創(chuàng)建進程后,進程的主線程處于掛起狀態(tài),然后這個時候我們就可以對它進行打補丁的操作了。所以我們選用CreateProcess函數(shù)來創(chuàng)建目標進程。
這里先給出這個Hello world級別的程序的源代碼
1 .386 2 .model flat,stdcall 3 option casemap:none 4 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 5 include windows.inc 6 include user32.inc 7 includelib user32.lib 8 include kernel32.inc 9 includelib kernel32.lib 10 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 11 .const 12 szErr db '對不起,你使用的是盜版軟件',0 13 szOK db '感謝您使用正版軟件',0 14 szCaption db '謝謝',0 15 .code 16 start: 17 xor eax,eax 18 or eax,eax 19 je @F 20 invoke MessageBox,NULL,addr szOK,\ 21 addr szCaption,MB_OK 22 jmp loc_1 23 @@: 24 invoke MessageBox,NULL,addr szErr,NULL,\ 25 MB_OK or MB_ICONSTOP 26 loc_1: 27 invoke ExitProcess,NULL 28 end start
該程序?qū)AX清0后判斷EAX是否是0,如果是0就顯示“使用盜版”的窗口,不是就顯示使用正版的窗口。所以這里是一定會顯示盜版窗口的。然后我們要做的就是利用之前說的那兩個讀寫進程地址空間的函數(shù)把這個進程的地址空間中的代碼改一改,讓它顯示正版窗口。
現(xiàn)在要確定的就是改哪里的代碼。因為那兩個讀寫函數(shù)是要根據(jù)目標進程中要進行讀寫的指令的地址來使用的,所以我們還得知道代碼在目標進程中的地址。
修改哪個地方的代碼,這個好確定(對于這個程序來說)。我們可以直接把第19行那個跳轉(zhuǎn)改為跳到下面執(zhí)行正版窗口的地方,或者是直接把這一條指令去掉,也就是用nop替換。或者用其他方法也行,這里就選擇用nop填充,因為這個比較簡單。
那么,現(xiàn)在確定了修改哪一條代碼,剩下的就是確定要修改的代碼的地址了。所以我們將改程序反匯編一下:

這個就是上面那個程序反匯編后的樣子。可以看到我們要修改的指令的地址是:00401004,并且這個指令是16位的,而nop是8位(nop的機器碼就是90h),所以我們呢要填充兩個nop。這里要注意一下,這個地址是線性地址,并不是程序真正所在的內(nèi)存地址,每次程序執(zhí)行它都是這個線性地址,所以不用擔(dān)心地址會變的問題。但是如果是DLL就不一樣了。因為一個程序,它可以裝入很多個DLL,可能這個DLL的建議裝入地址是這個,但是它裝入的時候發(fā)現(xiàn)另一個DLL已經(jīng)被裝入到這了,那它就只能改變地址了。這個時候可以可以通過查看PE文件的導(dǎo)入表來查看DLL的真正裝入位置,不過其實光查看還不行,這樣只能獲得DLL里面的函數(shù)的地址,還得在這個函數(shù)的附近掃描DLL的頭,也是一個PE文件頭,然后才能確定位置。
我們那兩個函數(shù)用的就是線性地址,所以用反匯編顯示的地址是沒問題的。然后就介紹一下這兩個函數(shù)的用法。
invoke ReadProcessMemory,hProcess,lpBaseAddress,lpBuffer,dwSize,lpNumberOfBytesRead
invoke WriteProcessMemory,hProcess,lpBaseAddress,lpBuffer,dwSize,lpNumberOfBytesRead
該函數(shù)讀進程的內(nèi)存。函數(shù)執(zhí)行成功返回非0值,參數(shù)定義如下:
- hProcess:該參數(shù)指定目標進程的句柄。
- lpBaseAddress:在目標進程中要讀寫的起始線性地址(對于ReadProcessMemory)或者在目標進程中要寫入的起始線程地址(對于WriteProcessMemory)。
- lpBuffer:該參數(shù)指向一個緩沖區(qū),用來接收讀取的目標進程中的數(shù)據(jù)(對于ReadProcessMemory)。指向一個緩沖區(qū),緩沖區(qū)里面是要寫入的數(shù)據(jù)(對于WriteProcessMemory)。
- dwSize:要讀或?qū)懙淖止?jié)數(shù)。
- lpNumberOfBytesRead:該參數(shù)指向一個雙字變量,函數(shù)將返回實際讀寫的字節(jié)數(shù)在里面,可以指定為NULL如果不關(guān)心這個數(shù)據(jù)的話。
然后要注意的就是我們在用CreateProcess函數(shù)創(chuàng)建進程的時候,要指定PROCESS_VM_READ和PROCESS_VM_WRITE參數(shù),這樣我們才能對進程地址空間進行讀寫。
做完這些準備工作后,要寫一個針對上面那個簡單程序就容易多了。程序大概流程就是先用CreateProcess函數(shù)創(chuàng)建我們的目標進程,并且參數(shù)里面要指定dwFlags參數(shù)進程剛創(chuàng)建就要將主線程掛起,然后再用ReadProcessMemory函數(shù)讀取數(shù)據(jù),然后再檢測一下數(shù)據(jù)是不是我們要改的那個,這個是為了防止出現(xiàn)程序版本不一樣的情況,當(dāng)然這里這個程序不會出現(xiàn)這種問題。最后如果沒有錯誤,就用WriteProcessMemory函數(shù)將數(shù)據(jù)寫入,然后恢復(fù)進程主線程。否則就關(guān)閉進程并顯示錯誤信息。
下面是這個簡單程序的簡單補丁程序的代碼:
1 .386 2 .model flat,stdcall 3 option casemap:none 4 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 5 ;Include 6 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 7 include windows.inc 8 include user32.inc 9 includelib user32.lib 10 include kernel32.inc 11 includelib kernel32.lib 12 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 13 ;數(shù)據(jù)段 14 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 15 PATCH_POSITION equ 00401004h 16 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 17 .data? 18 dbOldBytes db 2 dup (?) ;讀取的數(shù)據(jù)存放的緩沖區(qū) 19 stStartUp STARTUPINFO <?> 20 stProcInfo PROCESS_INFORMATION <?> 21 .const 22 dbPatch db 74h,15h 23 dbPatched db 90h,90h 24 szExecFilename db 'test.exe',0 25 szErrExec db '無法裝載執(zhí)行文件!',0 26 szErrVersion db '執(zhí)行文件的版本不正確,無法修正!',0 27 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 28 .code 29 start: 30 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 31 ;創(chuàng)建進程 32 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 33 invoke GetStartupInfo,addr stStartUp ;獲取當(dāng)前進程的窗口信息 34 invoke CreateProcess,offset szExecFilename,NULL,NULL,NULL,\ ;創(chuàng)建進程,進程的主線程一開始就為掛起狀態(tài),防止補丁程序在修改代碼時被WINDOWS打斷 35 NULL,NORMAL_PRIORITY_CLASS or CREATE_SUSPENDED,0,0,\ 36 offset stStartUp,offset stProcInfo 37 .if eax 38 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 39 ;讀進程內(nèi)存并驗證內(nèi)容是否正確 40 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> 41 invoke ReadProcessMemory,stProcInfo.hProcess,\ ;讀取從程序入口開始4個字節(jié)的位置,也就是跳轉(zhuǎn)命令的地方 42 PATCH_POSITION,addr dbOldBytes,2,NULL 43 .if eax 44 mov ax,word ptr dbOldBytes 45 .if ax == word ptr dbPatch ;驗證版本信息 46 invoke WriteProcessMemory,stProcInfo.hProcess,\ 47 PATCH_POSITION,addr dbPatched,2,NULL ;將要修改的代碼寫入 48 invoke ResumeThread,stProcInfo.hThread ;恢復(fù)進程中的主線程 49 .else 50 invoke TerminateProcess,stProcInfo.hProcess,-1 ;如果版本不正確則終止進程 51 invoke MessageBox,NULL,addr szErrVersion,\ 52 NULL,MB_OK or MB_ICONSTOP 53 .endif 54 .endif 55 ;********************************************************************************************************** 56 invoke CloseHandle,stProcInfo.hProcess 57 invoke CloseHandle,stProcInfo.hThread ;關(guān)閉打開的進程句柄和它的線程句柄 58 .else 59 invoke MessageBox,NULL,addr szErrExec,NULL,\ 60 MB_OK or MB_ICONSTOP 61 .endif 62 invoke ExitProcess,NULL 63 64 end start
把兩個程序都編譯鏈接然后先運行目標程序:

果不其然,顯示盜版信息。然后我們再直接運行補丁程序:

OK,成功。但是要注意我們修改的是文件在內(nèi)存中的數(shù)據(jù),并沒有修改文件本身的數(shù)據(jù),所以下一次直接運行還是會出現(xiàn)盜版窗口。
浙公網(wǎng)安備 33010602011771號