在前面的文章裡已經清楚計算機是隻認識0和1的,那平時編寫的程式到運作中間又經曆了什麼?
這個過程用下面一張圖就足以說明所有的問題了

稍微解釋一下其中的一些含義
目标檔案和可執行檔案都是由機器語言指令組成的
目标檔案隻包含你寫的代碼所翻譯的機器語言代碼
可執行檔案還包含你寫的代碼中使用的庫函數和啟動代碼的機器語言代碼(啟動代碼充當着程式和作業系統之間的接口)
編譯器到底生成了什麼
多說無益,這裡用一個空白的C語言函數來看看編譯器生成了哪些東西。
void test() {00ED1E70 push ebp 00ED1E71 mov ebp,esp 00ED1E73 sub esp,0C0h 00ED1E79 push ebx 00ED1E7A push esi 00ED1E7B push edi 00ED1E7C lea edi,[ebp-0C0h] 00ED1E82 mov ecx,30h 00ED1E87 mov eax,0CCCCCCCCh 00ED1E8C rep stos dword ptr es:[edi] 00ED1E8E mov ecx,offset _0BADB893_Source@cpp (0EDC000h) 00ED1E93 call @__CheckForDebuggerJustMyCode@4 (0ED1208h)
}00ED1E98 pop edi 00ED1E99 pop esi
}00ED1E9A pop ebx 00ED1E9B add esp,0C0h 00ED1EA1 cmp ebp,esp 00ED1EA3 call __RTC_CheckEsp (0ED1212h) 00ED1EA8 mov esp,ebp 00ED1EAA pop ebp 00ED1EAB ret
複制
中間的檢查堆棧平衡等函數我們可以省略,仔細看看其中的彙編代碼,很容易可以看出這其中所進行的操作就是上一篇文章所畫的堆棧圖,堆棧圖也是後面進行分析的關鍵,手寫這段程式的代碼也是一鍵很重要的事情,如果所有的操作都交給編譯器去做,那你所有的操作就都是很明确的,又怎麼去跟别人進行對抗。
手動編寫
這裡就需要引入
裸函數
的概念了,裸函數就是編譯器不幫你生成一行代碼,所有的代碼都必須你自己去手動編寫
void __declspec(naked) Function(){
}
複制
在正常情況下,我們寫一個空函數是不會出現報錯的情況的,但是裸函數則不然,直接用上面的方式寫,會跳到一個程式不認識的地方,如果對上一篇文章的堆棧圖足夠了解,就會知道造成這個情況的原因是什麼。
這是因為函數在彙編語言中是通過
call
來調用的,這個操作包含了兩個步驟,一步是把下一條指令的位址
push
到堆棧中,一步是跳轉到函數所要執行的位址,如果是一個空函數,它會再跳回到call指令的下一條位址,但是裸函數不會,因為編譯器沒有給我們生成任何一條指令,是以要想讓一個空的裸函數正常運作, 就需要我們手動添加一段指令,讓程式回到原來要執行的位置,那就是添加
ret
指令,是以可以運作的空的裸函數如下
void __declspec(naked) Function(){ __asm { ret }}
複制
對于手動編寫要特别注意對于相關資料的調用,需要明确它們所處的位置在哪裡,為了把所有的情況都包含在内,用了下面的這個例子作為說明
int plus(int x, int y, int z) { int a = 1; int b = 2; int c = 3; return x + y + z + a + b + c;}
複制
其中x、y、z和a、b、c在記憶體中所存在的位置是完全不同的,想要厘清楚這個内容,上一篇文章的堆棧圖就特别的關鍵了,不清楚的去看上一篇文章的說明。
下面直接給出最終的代碼,跟編譯器所生成的肯定是有差别的,但是在功能實作方面已經足夠了,想要看懂其中的含義,
堆棧圖是必須的,堆棧圖是必須的,堆棧圖是必須的
int plus(int x, int y, int z) { int a = 1; int b = 2; int c = 3; return x + y + z + a + b + c;}
int __declspec(naked) plus1(int x, int y, int z) { __asm { //儲存棧底 push ebp
//提升堆棧 mov ebp,esp sub esp,0x40
//保護現場 push ebx push esi push edi
//填充緩沖區 mov eax,0xCCCCCCCC mov ecx,0x10 lea edi,dword ptr ds:[ebp-0x40] rep stosd
//函數功能 mov dword ptr ds:[ebp-0x8],1 mov dword ptr ds:[ebp-0xC],2 mov dword ptr ds:[ebp-0x10],3
mov eax,dword ptr ds:[ebp+0x8] add eax,dword ptr ds:[ebp+0xC] add eax,dword ptr ds:[ebp+0x10] add eax,dword ptr ds:[ebp-0x8] add eax,dword ptr ds:[ebp-0xC] add eax,dword ptr ds:[ebp-0x10]
//恢複現場 push edi push esi push ebx
//恢複堆棧 mov esp,ebp pop ebp
//傳回 ret }}void test() {
}
int main(int argc, char* argv[]) { //test(); //plus(1, 2, 3); plus1(1, 2, 3); return 0;}
複制