網(wǎng)絡(luò)安全編程:文件補丁及內(nèi)存補丁
微信公眾號:計算機與網(wǎng)絡(luò)安全
ID:Computer-network
有時破解一個程序后可能會將其發(fā)布,而往往被破解的程序只是修改了其中一個程序而已,無須將整個軟件都進行打包再次發(fā)布,只需要發(fā)布一個補丁程序即可。發(fā)布補丁常見的有三種情況,第一種情況是直接把修改后的文件發(fā)布出去,第二種情況是發(fā)布一個文件補丁,它去修改原始的待破解的程序,最后一種情況是發(fā)布一個內(nèi)存補丁,它不修改原始的文件,而是修改內(nèi)存中的指定部分。
3種情況各有好處。第一種情況將已經(jīng)修改后的程序發(fā)布出去,使用者只需要簡單進行替換就可以了。但是有個問題,如果程序的版本較多,直接替換可能就會導(dǎo)致替換后的程序無法使用。第二種方法是發(fā)布文件補丁,該方法需要編寫一個簡單的程序去修改待破解的程序,在破解以前可以先對文件的版本進行判斷,如果補丁和待破解程序的版本相同則進行破解,否則不進行破解。但是有時候修改了文件以后,程序可能無法運行,因為有的程序會對自身進行校驗和比較,當校驗和發(fā)生變化后,程序則無法運行。最后一種方式是內(nèi)存補丁,也需要自己動手寫程序,并且寫好的補丁程序需要和待破解的程序放在同一個目錄下,執(zhí)行待破解的程序時,需要執(zhí)行內(nèi)存補丁程序,內(nèi)存補丁程序會運行待破解的程序,然后比較補丁與程序的版本,最后進行破解。同樣,如果有內(nèi)存校驗的話,也會導(dǎo)致程序無法運行。不過,無論是文件校驗還是內(nèi)存校驗,都可以繼續(xù)對被校驗的部分進行打補丁來突破程序校驗的部分。本文編寫一個文件補丁程序和內(nèi)存補丁程序。
1. 文件補丁
用OD修改CrackMe是比較容易的,如果脫離OD該如何修改呢?其實在OD中修改反匯編的指令以后,對應(yīng)地,在文件中修改的是機器碼。只要在文件中能定位到指令對應(yīng)的機器碼的位置,那么直接修改機器碼就可以了。JNZ對應(yīng)的機器碼指令為0x75,JZ對應(yīng)的機器碼指令為0x74。也就是說,只要在文件中找到這個要修改的位置,用十六進制編輯器把0x75修改為0x74即可。如何能把這個內(nèi)存中的地址定位到文件地址呢?這就是PE文件結(jié)構(gòu)中把VA轉(zhuǎn)換為FileOffset的知識了。
具體的手動步驟,請大家自己嘗試,這里直接通過寫代碼進行修改。為了簡單起見,這里使用控制臺來編寫,而且直接對文件進行操作,省略中間的步驟。有了思路以后,就不是難事了。
關(guān)于文件補丁的代碼如下:
- #include <windows.h>
 - #include <stdio.h>
 - int main(int argc, char* argv[])
 - {
 - // VA = 00401EA8
 - // FileOffset = 00001EA8
 - DWORD dwFileOffset = 0x00001EA8;
 - BYTE bCode = 0;
 - DWORD dwReadNum = 0;
 - // 判斷參數(shù)
 - if ( argc != 2 )
 - {
 - printf("Please input two argument \r\n");
 - return -1;
 - }
 - // 打開文件
 - HANDLE hFile = CreateFile(argv[1],
 - GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,
 - NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL, NULL);
 - if ( hFile == INVALID_HANDLE_VALUE )
 - {
 - return -1;
 - }
 - SetFilePointer(hFile, dwFileOffset, 0, FILE_BEGIN);
 - ReadFile(hFile, (LPVOID)&bCode, sizeof(BYTE), &dwReadNum, NULL);
 - // 比較當前位置是否為 JNZ
 - if ( bCode != '\x75' )
 - {
 - printf("%02X \r\n", bCode);
 - CloseHandle(hFile);
 - return -1;
 - }
 - // 修改為 JZ
 - bCode = '\x74';
 - SetFilePointer(hFile, dwFileOffset, 0, FILE_BEGIN);
 - WriteFile(hFile, (LPVOID)&bCode, sizeof(BYTE), &dwReadNum, NULL);
 - printf("Write JZ is Successfully ! \r\n");
 - CloseHandle(hFile);
 - // 運行
 - WinExec(argv[1], SW_SHOW);
 - getchar();
 - return 0;
 - }
 
代碼給出了詳細的注釋,只需要把CrackMe文件拖放到文件補丁上或者在命令行下輸入命令即可,如圖1所示。
圖1 對CrackMe進行文件補丁
通常,在做文件補丁以前一定要對打算進行修改的位置進行比較,以免產(chǎn)生錯誤的修改。程序使用的方法是將要修改的部分讀出來,看是否與用OD調(diào)試時的值相同,如果相同則打補丁。由于這里只是介紹編程知識,針對的是一個CrackMe。如果對某個軟件進行了破解,自己做了一個文件補丁發(fā)布出去給別人使用,不進行相應(yīng)的判斷就直接進行修改,很有可能導(dǎo)致軟件不能使用,因為對外發(fā)布以后不能確認別人所使用的軟件的版本等因素。因此,在進行文件補丁時最好判斷一下,或者是用CopyFile()對文件進行備份。
2. 內(nèi)存補丁
相對文件補丁來說,還有一種補丁是內(nèi)存補丁。這種補丁是把程序加載到內(nèi)存中以后對其進行修改,也就是說,本身是不對文件進行修改的。要將CrackMe載入內(nèi)存中,載入內(nèi)存可以調(diào)用CreateProcess()函數(shù)來完成,這個函數(shù)參數(shù)眾多,功能強大。使用CreateProcess()創(chuàng)建一個子進程,并且在創(chuàng)建的過程中將該子進程暫停,那么就可以安全地使用WriteProcessMemory()函數(shù)來對CrackMe進行修改了。整個過程也比較簡單,下面直接來閱讀源代碼:
- #include <Windows.h>
 - #include <stdio.h>
 - int main(int argc, char* argv[])
 - {
 - // VA = 004024D8
 - DWORD dwVAddress = 0x00401EA8;
 - BYTE bCode = 0;
 - DWORD dwReadNum = 0;
 - // 判斷參數(shù)數(shù)量
 - if ( argc != 2 )
 - {
 - printf("Please input two argument \r\n");
 - return -1;
 - }
 - STARTUPINFO si = { 0 };
 - si.cb = sizeof(STARTUPINFO);
 - si.wShowWindow = SW_SHOW;
 - si.dwFlags = STARTF_USESHOWWINDOW;
 - PROCESS_INFORMATION pi = { 0 };
 - BOOL bRet = CreateProcess(argv[1],
 - NULL,NULL,NULL,FALSE,
 - CREATE_SUSPENDED, // 將子進程暫停
 - NULL,NULL,&si,&pi);
 - if ( bRet == FALSE )
 - {
 - printf("CreateProcess Error ! \r\n");
 - return -1;
 - }
 - ReadProcessMemory(pi.hProcess,
 - (LPVOID)dwVAddress,(LPVOID)&bCode,
 - sizeof(BYTE),&dwReadNum);
 - // 判斷是否為 JNZ
 - if ( bCode != '\x75' )
 - {
 - printf("%02X \r\n", bCode);
 - CloseHandle(pi.hThread);
 - CloseHandle(pi.hProcess);
 - return -1;
 - }
 - // 將 JNZ 修改為 JZ
 - bCode = '\x74';
 - WriteProcessMemory(pi.hProcess,
 - (LPVOID)dwVAddress,(LPVOID)&bCode,
 - sizeof(BYTE),&dwReadNum);
 - ResumeThread(pi.hThread);
 - CloseHandle(pi.hThread);
 - CloseHandle(pi.hProcess);
 - printf("Write JZ is Successfully ! \r\n");
 - getchar();
 - return 0;
 - }
 
代碼中的注釋也比較詳細,代碼的關(guān)鍵是要進行比較,否則會造成程序的運行崩潰。在進行內(nèi)存補丁前需要將線程暫停,這樣做的好處是有些情況下可能沒有機會進行補丁就已經(jīng)執(zhí)行完需要打補丁的地方了。當打完補丁以后,再恢復(fù)線程繼續(xù)運行就可以了。
參考文獻:C++ 黑客編程揭秘與防范(第3版)
















 
 
 
 
 
 
 