本節書摘來自異步社群出版社《c++ 黑客程式設計揭秘與防範(第2版)》一書中的第6章,第6.7節,作者:冀雲,更多章節内容可以通路雲栖社群“異步社群”公衆号檢視。
c++ 黑客程式設計揭秘與防範(第2版)
關于系統提供的調試api函數已經學習了不少,而且基本上常用到的函數都已學過。下面用調試api編寫一個能夠顯示密碼的程式。讀者别以為這裡寫的程式什麼密碼都能顯示,這是不可能的。下面針對前面的crackme來編寫一個顯示密碼的程式。
在編寫關于crackme的密碼顯示程式以前需要準備兩項工作,第一項工作是知道要在什麼地方合理地下斷點,第二項工作是從哪裡能讀取到密碼。帶着這兩個問題重新來思考一下。在這裡的程式中,要對兩個字元串進行比較,而比較的函數是strcmp(),該函數有兩個參數,分别是輸入的密碼和真正的密碼。也就是說,在調用strcmp()函數的位置下斷點,通過檢視它的參數是可以擷取到正确的密碼的。在調用strcmp()函數的位置設定int3斷點,也就是将0xcc機器碼寫入這個位址。用od看一下調用strcmp()函數的位址,如圖6-75所示。
圖6-75 調用strcmp()函數的位址
從圖6-75中可以看出,調用strcmp()函數的位址為00401e9e。有了這個位址,隻要找到該函數的兩個參數,就可以找到輸入的錯誤的密碼及正确的密碼。從圖6-75中可以看出,正确的密碼的起始位址儲存在edx中,錯誤的密碼的起始位址儲存在ecx中。隻要在00401e9e位址處下斷點,并通過線程環境讀取edx和ecx寄存器值就可以得到兩個密碼的起始位址。
進行準備的工作已經做好了,下面來寫一個控制台的程式。先定義兩個常量,一個是用來設定斷點的位址,另一個是int3指令的機器碼。定義如下:
// 啟動資訊
startupinfo si = { 0 };
si.cb = sizeof(startupinfo);
getstartupinfo(&si);
// 程序資訊
process_information pi = { 0 };
// 建立被調試程序
bool bret = createprocess(pszfilename,
null,
false,
debug_process | debug_only_this_process,
&si,
π);
if ( bret == false )
{
printf("createprocess error rn");
return -1;
}<code>`</code>
然後進入調試循環,要處理兩個調試事件,一個是createprocess_debug_event,另一個是exception_debug_event下的exception_breakpoint。處理create process_debug_event的代碼如下:
// 産生異常時的調試事件
case exception_debug_event:
{
// 判斷異常類型
switch ( de.u.exception.exceptionrecord.exceptioncode )
// int3類型的異常
case exception_breakpoint:
{
// 擷取線程環境
context.contextflags = context_full;
getthreadcontext(pi.hthread, &context);
// 判斷是否斷在設定的斷點位置處
if ( (bp_va + 1) == context.eip )
{
// 讀取正确的密碼
readprocessmemory(pi.hprocess,
(lpvoid)context.edx,
(lpvoid)pszpassword,
maxbyte,
&dwreadwritenum);
// 讀取錯誤密碼
(lpvoid)context.ecx,
(lpvoid)pszerrorpass,
printf("你輸入的密碼是: %s rn", pszerrorpass);
printf("正确的密碼是: %s rn", pszpassword);
//指令執行了int3而被中斷
// int3的機器指令長度為1位元組
// 是以需要将eip減一來修正eip
// eip是指令指針寄存器
// 其中儲存着下條要執行指令的位址
context.eip --;
// 修正原來該位址的機器碼
writeprocessmemory(pi.hprocess,
(lpvoid)bp_va,
(lpvoid)&boldbyte,
sizeof(byte),
&dwreadwritenum);
// 設定目前的線程環境
setthreadcontext(pi.hthread, &context);
}
break;
}
}
}<code>`</code>
對于調試事件的處理,應該放到調試循環中。上面的代碼給出的是對調試事件的處理,再來看一下調試循環的大體代碼: