逆向脫殼分析基礎學習筆記九 C語言內聯彙編和三種呼叫協定 裸函式
本文為本人在 大神論壇 學習逆向破解脫殼學習筆記之一,為本人對以往所學的回顧和總結,可能會有謬誤之處,歡迎大家指出。 陸續將不斷有筆記放出,希望能對想要入門的萌新有所幫助,一起進步
C語言內聯彙編和呼叫協定
前面我們通過分析反彙編分析了C語言,現在我們來探究如何在C語言裡直接自寫彙編函式
這裡就要引入C語言中裸函式的概念
裸函式
宣告裸函式
裸函式與普通函式的區別在於在函式前多聲明瞭
__declspec (naked)
以此來表面該函式是一個裸函式
裸函式作用
要講裸函式的作用,就不得不提到裸函式與普通函式的區別
裸函式與普通函式區別
前面反彙編C語言的筆記裡,我們可以得知一個普通空函式的反彙編程式碼並不少,保護現場、恢復現場等等都有,那麼這些反彙編程式碼是如何產生的呢?
答案是:編譯器
編譯器會為我們產生這些反彙編程式碼
相比之下,只要普通函式加上裸函式字首轉化為裸函式,編譯器就會知道這個函式無需額外生成上面所說的保護現場、恢復現場等反彙編程式碼,函式執行所需的反彙編程式碼由我們自己來實現
於是裸函式的作用呼之欲出:
當我們不希望編譯器為我們生成函式裡的彙編程式碼,而是想要自己實現函式內部的彙編程式碼時,就可以使用裸函式來告訴編譯器不要去額外生成彙編程式碼
最簡單的裸函式
void __declspec (naked) function(){
__asm{
ret
}
}
上面是一個最簡單的裸函式,反彙編程式碼只有一行ret
與普通空函式相比,同樣是什麼都沒做,但卻要加上ret,為什麼?
我們可以來看看這個最簡單的裸函式的執行流程,並注意與普通空函式對比
分析最簡單的裸函式
完整程式碼
先給出完整的程式碼
#include "stdafx.h"
void __declspec (naked) function(){
__asm{
ret
}
}
int main(int argc, char* argv[])
{
function();
return 0;
}
接下來進入反彙編的世界
函式外部
13: function();
00401078 call @ILT+10(function) (0040100f)
14: return 0;
0040107D xor eax,eax
15: }
0040107F pop edi
00401080 pop esi
00401081 pop ebx
00401082 add esp,40h
00401085 cmp ebp,esp
00401087 call __chkesp (004010e0)
0040108C mov esp,ebp
0040108E pop ebp
0040108F ret
函式內部
6: void __declspec (naked) function(){
00401030 ret
開始分析
00401078 call @ILT+10(function) (0040100f)
我們可以發現裸函式的呼叫和普通函式的呼叫並沒有什麼區別,都是call 函式地址
但接著進入函式內部就可以看到
00401030 ret
函式的內部有且僅有我們程式碼中用__asm所寫的ret語句,沒有任何其餘的程式碼
執行完ret語句後,函式正常返回,接下來就和普通的空函式沒有區別了
得出結論
於是我們可以知道,裸函式的內部彙編程式碼完全由我們自己來實現,之所以要寫上一個ret也是為了讓函式能夠正常地返回
再來看看不新增ret語句時的反彙編程式碼情況
我們將裸函式內部的__asm刪除
void __declspec (naked) function(){
}
然後再觀察函式內部的彙編程式碼
我們可以看到函式內部為一堆 int 3,也就是CC即初始化堆疊的內容
程式執行到這裡就會產生中斷,無法正常執行,所以我們才要加上一條彙編ret語句,讓函式能夠正常執行返回
大致瞭解了裸函式後,我們就來使用內聯彙編自己實現加法函式
內聯彙編實現加法函式
自寫加法函式
#include "stdafx.h"
int __declspec (naked) Plus(int x,int y){
__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 eax,dword ptr ds:[ebp+8]
//引數相加
add eax,dword ptr ds:[ebp+0xC]
//恢復現場
pop edi
pop esi
pop ebx
//降低堆疊
mov esp,ebp
pop ebp
//返回
ret
}
}
int main(int argc, char* argv[])
{
Plus(1,2);
return 0;
}
不難發現,其實我們自己實現的加法函式就是模擬了編譯器為我們做的事情,此時進到函式內部也會看到
函式內部
6: int __declspec (naked) Plus(int x,int y){
00401030 push ebp
7: __asm{
8: //保留呼叫前堆疊
9: push ebp
10: //提升堆疊
11: mov ebp,esp
00401031 mov ebp,esp
12: sub esp,0x40
00401033 sub esp,40h
13: //保護現場
14: push ebx
00401036 push ebx
15: push esi
00401037 push esi
16: push edi
00401038 push edi
17: //初始化提升的堆疊,填充緩衝區
18: mov eax,0xCCCCCCCC
00401039 mov eax,0CCCCCCCCh
19: mov ecx,0x10
0040103E mov ecx,10h
20: lea edi,dword ptr ds:[ebp-0x40]
00401043 lea edi,ds:[ebp-40h]
21: rep stosd
00401047 rep stos dword ptr [edi]
22: //函式核心功能
23:
24: //取出引數
25: mov eax,dword ptr ds:[ebp+8]
00401049 mov eax,dword ptr ds:[ebp+8]
26: //引數相加
27: add eax,dword ptr ds:[ebp+0xC]
0040104D add eax,dword ptr ds:[ebp+0Ch]
28:
29:
30: //恢復現場
31: pop edi
00401051 pop edi
32: pop esi
00401052 pop esi
33: pop esi
00401053 pop esi
34:
35: //降低堆疊
36: mov esp,ebp
00401054 mov esp,ebp
37: pop ebp
00401056 pop ebp
38:
39: //返回
40: ret
執行的就是我們自己所寫的程式碼,而非編譯器所生成的,並且也能夠實現加法函式的功能
函式返回後
我們可以發現函式返回後和普通函式並無差異
00401081 add esp,8
都有這一行平衡堆疊的語句,也就是堆疊外平衡,但如果我們想要在函式內部就平衡堆疊,也就是實現堆疊內平衡,也就是希望函式返回後沒有這個外部的堆疊平衡語句,讓堆疊的平衡工作由我們自己來處理,該如何做到?
這裡就要引入C語言的呼叫協定這個概念了
呼叫協定
常見的幾種呼叫協定:
其中__cdecl為C語言預設呼叫協定
接下來我們來比較一下這三種呼叫協定
int __cdecl Plus1(int x,int y){
return x+y;
}
int __stdcall Plus2(int x,int y){
return x+y;
}
int __fastcall Plus3(int x,int y){
return x+y;
}
同樣都是一個簡單的加法函式,分別採用了三種不同的呼叫協定,我們來用匯編來一察他們的區別
__cdecl
首先是我們最熟悉的__cdecl協定,和我們上一個筆記分析的簡單加法函式不無區別
觀察反彙編:
函式外部
函式內部
我們這裡主要是關注三個地方:引數壓棧、函式返回值、返回後執行語句
引數壓棧:先push 2再 push 1
函式返回值:ret
返回後執行語句:add esp,8
__stdcall
函式外部
函式內部
接著關注三個地方:引數壓棧、函式返回值、返回後執行語句
引數壓棧:先push 2再 push 1
函式返回值:ret 8
返回後執行語句:xor eax,eax
__fastcall
函式外部
函式內部
依舊關注三個地方:引數壓棧、函式返回值、返回後執行語句
引數壓棧:先move edx,2再mov ecx,1
函式返回值:ret
返回後執行語句:xor eax,eax
對比三種協定
我們可以得出結論:
__cdecl是將引數壓入棧中,然後在函式執行返回後再平衡堆疊,也就是堆疊外平衡
__stdcall也是將引數壓入棧中,但是是在函式內部通過ret xxx來平衡堆疊,也就是堆疊內平衡
__fastcall則是在引數個數小於等於2時直接使用edx和ecx作為引數傳遞的載體,沒用使用到堆疊,自然也就無須平衡堆疊,但是當引數個數大於2時,則多出來的那幾個引數則按stdcall的方式來處理,也是採用堆疊內平衡
接下來再談談__stdcall中返回值的問題
我們可以看到,我們在上面的加法函式中push了兩個立即數2和1,返回值是8
這是不是意味著ret xxxx中xxxx=引數個數*4?
並不是!!!這裡ret xxxx裡的xxxx和壓入引數的資料寬度有關
我們這裡壓入的兩個立即數的資料寬度都是4個位元組=32bit,因此我們這裡是ret 4+4=8
如果改成push ax,也就是壓入2個位元組=16bit時則應該ret 2
這裡可以參考我之前發表的堆疊相關彙編指令的push指令
瞭解了以上呼叫協定後,我們就可以修改之前的簡單加法裸函式,將其改為堆疊內平衡
堆疊內平衡加法函式
__declspec (naked) __stdcall int Plus(int x,int y){
__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 eax,dword ptr ds:[ebp+8]
//引數相加
add eax,dword ptr ds:[ebp+0xC]
//恢復現場
pop edi
pop esi
pop ebx
//降低堆疊
mov esp,ebp
pop ebp
//返回
ret 8
}
}
int main(int argc, char* argv[])
{
Plus(1,2);
return 0;
}
與前面相比,修改ret 為ret 8,自己在函式內實現了堆疊內平衡
本系列逆向脫殼基礎學習都在下方連結中,歡迎下載並交流溝通
大神論壇 逆向脫殼分析基礎學習筆記九 C語言內聯彙編和... - 『學習資料區』 - 大神論壇 |脫殼破解|易語言|病毒分析|www.dslt.tech
版權宣告:本文由 lyl610abc 原創,歡迎分享本文,轉載請保留出處
- 逆向脫殼分析基礎學習筆記十二 彙編 全域性和區域性變數
- 逆向脫殼分析基礎學習筆記十一 彙編C語言基本型別
- 逆向脫殼分析基礎學習筆記十 彙編尋找C程式入口
- 逆向脫殼分析基礎學習筆記九 C語言內聯彙編和三種呼叫協定 裸函式
- 逆向脫殼分析基礎學習筆記八 反彙編分析C語言
- 逆向脫殼分析基礎學習筆記六 彙編跳轉和比較指令
- 逆向脫殼分析基礎學習筆記五 標誌暫存器
- 大神論壇 逆向脫殼分析基礎學習筆記四 堆疊篇
- 大神論壇 逆向脫殼分析基礎學習筆記三 通用暫存器和記憶體讀寫
- 大神論壇 逆向脫殼分析基礎學習筆記一 資料寬度和邏輯運算
- 大神論壇 UEditor 富文字web編輯器最新漏洞版XML檔案上傳導致儲存型XSS
- 大神論壇 內網滲透之向日葵幫我幹掉了殺軟 實現內網穿透
- 大神論壇 逆向分析 Internet Download Manager 序列號演算法 附IDM註冊機完整原始碼
- 【資料合集】HarmonyOS從入門到大神資料下載合集
- 大神論壇 Android多層鎖機樣本逆向脫殼分析與解鎖 (附樣本原始檔)
- 大神論壇 逆向脫殼之保護模式學習六 程式碼跨段跳轉
- 逆向脫殼之保護模式學習三 段描述符和段選擇子
- 逆向破解入門之VM下浮點演算法提取(附練習程式和原始碼)
- 遊戲熱血江湖 滿線自動查詢器製作遊戲分析脫殼與查詢器原始碼分享
- 大神論壇 史上最全植物大戰殭屍分析及遊戲輔助Python實現