天天看點

手動編寫C函數的彙編代碼

在前面的文章裡已經清楚計算機是隻認識0和1的,那平時編寫的程式到運作中間又經曆了什麼?

這個過程用下面一張圖就足以說明所有的問題了

手動編寫C函數的彙編代碼

稍微解釋一下其中的一些含義

目标檔案和可執行檔案都是由機器語言指令組成的

目标檔案隻包含你寫的代碼所翻譯的機器語言代碼

可執行檔案還包含你寫的代碼中使用的庫函數和啟動代碼的機器語言代碼(啟動代碼充當着程式和作業系統之間的接口)

編譯器到底生成了什麼

多說無益,這裡用一個空白的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;}           

複制