本節書摘來自異步社群出版社《c++ 黑客程式設計揭秘與防範(第2版)》一書中的第6章,第6.5節,作者:冀雲,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
c++ 黑客程式設計揭秘與防範(第2版)
在介紹完pe檔案結構以後,接下來介紹調試api。調試api是系統留給使用者進行程式調試的接口,其功能非常強大。在介紹調試api以前,先來回顧一下od的使用。od是用來調試應用程式的強大的工具。第5章中對其進行了簡單的介紹,本章中将通過執行個體來回顧其強大功能。同樣,為了後續的部分較容易了解,這裡寫一個簡單程式,用od來進行調試。除了介紹調試api以外,還會介紹一些簡單的與破解相關的内容。當然,破解是一個技術的積累,也是要靠多方面技術的綜合應用,希望這些簡單的基礎知識能給讀者起到一個抛磚引玉的作用。
下面将寫一個crackme程式,crackme的意思是“來破解我”。這裡提到的破解是針對軟體方面來說的,不是網絡中的破解。對于軟體的破解來說,主要是解除軟體中的各種限制,比如時間限制、序列号限制……對于破解來說,無疑與逆向工程有着密切的關系,想要突破任何一種限制都要去了解該種限制的保護方式或保護機制。
破解别人的軟體屬于侵權行為,盡管有很多人在做這樣的事情,但是大多人數是為了進行學習研究,而非用于牟取商業利益。是以,為了尊重他人的勞動成果,也為了避免給自己帶來不必要的麻煩,讀者盡可能找一些crackme來進行學習和研究。
下面來寫一個非常簡單的crackme程式,并進行“破解”。自己寫crackme并破解,雖然這樣省去了很多問題的思考,但是對于初學者來說仍然是一件非常有趣的事情。
這個程式使用mfc來編寫,界面如圖6-48所示。

圖6-48 自己編寫的crackme程式界面
從圖6-48中可以看出,整個程式隻有兩個可以輸入内容的文本編輯框和兩個可以單擊的按鈕,除此之外什麼都沒有,更不會有什麼提示。基本上這就是一個crackme的樣子。不過有的人習慣在crackme中添加一個美女的照片,讓界面顯得美觀誘惑,有的人喜歡給crackme加層殼來增加破解的難度,不過這些不重要,關鍵是要進行學習。在界面上輸入一個賬号和一個密碼,當單擊“确定”按鈕後,該按鈕會執行以下代碼:
// 把生成的密碼和輸入的密碼進行比對
if ( strcmp(sztmppassword, szpassword) == 0 )
{
messagebox("密碼正确");
}
else
messagebox("密碼錯誤");
}<code>`</code>!
strcmp()是字元串比較函數。如果兩個字元串相等,也就是說,輸入的密碼與正确的密碼比對,則執行“密碼正确”流程;否則反之。修改一下比較的條件,也就是說,兩個密碼比對不成功,使其執行“密碼成功”的流程。這樣,輸入錯誤的密碼也會提示“密碼正确”。在c語言中的修改很簡單,隻要修改為如下代碼即可:
`
if ( strcmp(sztmppassword, szpassword) != 0 )`
但是對于反彙編應該如何做呢?其實非常簡單,再看一下圖6-57中的那幾條反彙編代碼。想要修改其判斷條件,隻要修改00401ea8處的指令代碼jnz short 00401ebd即可。該指令的意思是如果比較結果不為0,則跳轉到00401ebd處執行。jnz指令是結果不為0則跳轉,隻要把jnz修改為jz即可,jz的意思剛好與jnz相反。修改方法很簡單,選中00401ea8位址所在的行,按下空格鍵即可進行編輯,如圖6-58所示。
圖6-58 在od中修改反彙編代碼
單擊視窗上的“彙編”按鈕,然後按f9鍵運作,随便輸入一個長度大于7位的賬号,再輸入一個密碼,然後單擊“确定”按鈕,會提示“密碼正确”,如圖6-59所示。
圖6-59 修改指令後的流程
關掉od和crackme,然後直接運作crackme,随便輸入賬号和密碼,單擊“确定”按鈕後提示密碼錯誤。為什麼呢?因為剛才隻是在記憶體中進行了修改,需要對修改後的檔案進行存盤,這樣在以後運作時,該修改才有效。修改後的存盤方法為:選中修改的反彙編代碼(可以多選幾行,隻要修改的那行被選中即可),然後單擊右鍵,在彈出的菜單中選擇“複制到可執行檔案”->“標明内容”指令,會出現“檔案”對話框,如圖6-60所示。
圖6-60 “檔案”對話框
在這個對話框中單擊滑鼠右鍵,在彈出的菜單中選擇“儲存檔案”指令,然後進行儲存。這樣修改就存盤了。下次在執行該程式時,随便輸入大于7位的賬号和密碼,都會提示“密碼正确”。如果輸入了正确的密碼,那麼會提示“密碼錯誤”。
上面就是兩種破解crackme的方法,這兩種方法都是極其簡單的方法,現在可能已經很不實用了。這裡是為了學習,提高動手能力,而采用了這兩種方法。
有時破解一個程式後可能會将其釋出,而往往被破解的程式隻是修改了其中一個程式而已,無須将整個軟體都進行打包再次釋出,隻需要釋出一個更新檔程式即可。釋出更新檔常見的有三種情況,第一種情況是直接把修改後的檔案釋出出去,第二種情況是釋出一個檔案更新檔,它去修改原始的待破解的程式,最後一種情況是釋出一個記憶體更新檔,它不修改原始的檔案,而是修改記憶體中的指定部分。
3種情況各有好處。第一種情況将已經修改後的程式釋出出去,使用者隻需要簡單進行替換就可以了。但是有個問題,如果程式的版本較多,直接替換可能就會導緻替換後的程式無法使用。第二種方法是釋出檔案更新檔,該方法需要編寫一個簡單的程式去修改待破解的程式,在破解以前可以先對檔案的版本進行判斷,如果更新檔和待破解程式的版本相同則進行破解,否則不進行破解。但是有時候修改了檔案以後,程式可能無法運作,因為有的程式會對自身進行校驗和比較,當校驗和發生變化後,程式則無法運作。最後一種方式是記憶體更新檔,也需要自己動手寫程式,并且寫好的更新檔程式需要和待破解的程式放在同一個目錄下,執行待破解的程式時,需要執行記憶體更新檔程式,記憶體更新檔程式會運作待破解的程式,然後比較更新檔與程式的版本,最後進行破解。同樣,如果有記憶體校驗的話,也會導緻程式無法運作。不過,無論是檔案校驗還是記憶體校驗,都可以繼續對被校驗的部分進行打更新檔來突破程式校驗的部分。不過這不是本部分的重點,這裡的重點是編寫一個針對上一節程式的檔案更新檔程式和記憶體更新檔程式。
1.檔案更新檔
用od修改crackme是比較容易的,如果脫離od該如何修改呢?其實在od中修改反彙編的指令以後,對應地,在檔案中修改的是機器碼。隻要在檔案中能定位到指令對應的機器碼的位置,那麼直接修改機器碼就可以了。jnz對應的機器碼指令為0x75,jz對應的機器碼指令為0x74。也就是說,隻要在檔案中找到這個要修改的位置,用十六進制編輯器把0x75修改為0x74即可。如何能把這個記憶體中的位址定位到檔案位址呢?這就是前面介紹的pe檔案結構中把va轉換為fileoffset的知識了。
具體的手動步驟,請讀者自己嘗試,這裡直接通過寫代碼進行修改。為了簡單起見,這裡使用控制台來編寫,而且直接對檔案進行操作,省略中間的步驟。想必有了思路以後,對于讀者來說就不是難事。
關于檔案更新檔的代碼如下:
int main(int argc, char* argv[])
{
// va = 004024d8
dword dwvaddress = 0x00401ea8;
byte bcode = 0;
dword dwreadnum = 0;
// 判斷參數數量
if ( argc != 2 )
printf("please input two argument rn");
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,
false,
create_suspended, // 将子程序暫停
&si,
π);
if ( bret == false )
printf("createprocess error ! rn");
readprocessmemory(pi.hprocess,
(lpvoid)dwvaddress,
(lpvoid)&bcode,
sizeof(byte),
&dwreadnum);
// 判斷是否為jnz
if ( bcode != 'x75' )
printf("%02x rn", bcode);
closehandle(pi.hthread);
closehandle(pi.hprocess);
// 将jnz修改為jz
bcode = 'x74';
writeprocessmemory(pi.hprocess,
resumethread(pi.hthread);
closehandle(pi.hthread);
closehandle(pi.hprocess);
printf("write jz is successfully ! rn");
getchar();
return 0;
}<code>`</code>
代碼中的注釋也比較詳細,代碼的關鍵是要進行比較,否則會造成程式的運作崩潰。在
進行記憶體更新檔前需要将線程暫停,這樣做的好處是有些情況下可能沒有機會進行更新檔就已經執行完需要打更新檔的地方了。當打完更新檔以後,再恢複線程繼續運作就可以了。
檔案更新檔與記憶體更新檔已經介紹完了。這兩種更新檔,都是通過前面學到的知識來完成的,可見前面的基礎知識的用處還是非常廣泛的。用了這麼多的篇幅來介紹使用od破解crackme,也介紹了檔案更新檔和記憶體更新檔,那麼,接下來就開始學習調試api。掌握調試api以後,就可以打造一個類似于od的應用程式調試器,下面來一步一步學習。