總是試圖想把想表達(dá)的東西表達(dá)清楚,但總是發(fā)現(xiàn)表達(dá)的不夠清楚
_BEGIN(廢話)
一直很奇怪為什么VC中,要把堆棧的內(nèi)容全初始化為 0xCC
今天才突然想起其中的原因,
原來 0xCC 翻譯成匯編代碼就是 int3 ;(斷點)
當(dāng)[指今執(zhí)行越界]時,就會產(chǎn)生中斷
證實方法: 可以在程序中加上 _asm int3
然后設(shè)置斷點,查看指令的地址,再查看地址的內(nèi)存,你會發(fā)現(xiàn)是 0xCC
_END(廢話)
第1集,第2集說過的東西,由于記性衰退的緣故,已經(jīng)忘記了!!!!!!!!
為了更簡單,把代碼進(jìn)行改動,把第1集中的代碼作了刪減,
Make it sample!!!!!
Make it sample!!
Make it sample!
一切為了弄懂 Win32 中函數(shù)調(diào)用做了什么.
void func1(int input1, int input2)
{
int i, j;
char c;
i = input1;
j = input2;
c = 0;
}
int main()
{
int i, j;
i=2;
j=3;
func1(i,j);
return 0;
}
讓一切回到調(diào)用 func1() 函數(shù)這前
_BEGIN(廢話)
本文所列出的 004010XX 地址,會因為機(jī)器的配置不同而有所差異,
請不要太在意
_END(廢話)
------------------------------------------------------------
13: i=2; <--- 很黃很暴力的“斷點”在此
00401078 mov dword ptr [ebp-4],2
14: j=3;
0040107F mov dword ptr [ebp-8],3
15:
16: func1(i,j);
00401086 mov eax,dword ptr [ebp-8]
00401089 push eax
0040108A mov ecx,dword ptr [ebp-4]
0040108D push ecx
0040108E call @ILT+0(func1) (00401005)
00401093 add esp,8
------------------------------------------------------------
此時寄存器的狀態(tài)
EAX = CCCCCCCC EBX = 7FFDE000 ECX = 00000000
EDX = 00370D78 ESI = 00000000 EDI = 0012FF80
EIP = 00401078 ESP = 0012FF2C EBP = 0012FF80
EFL = 00000212
------------------------------------------------------------
內(nèi)存的內(nèi)容
0012FF6B CC CC CC CC CC CC CC 燙燙燙.
0012FF72 CC CC CC CC CC CC CC 燙燙燙.
0012FF79 CC CC CC CC CC CC CC 燙燙燙.
0012FF80 C0 FF 12 00 E9 11 40 ......@
0012FF87 00 01 00 00 00 00 0D .......
0012FF8E 37 00 78 0D 37 00 00 7.x.7..
_BEGIN(廢話)
注:像 CC 表示一個字節(jié)的內(nèi)容,一行共 7 個字節(jié)
_END(廢話)
------------------------------------------------------------
函數(shù)內(nèi)聲明的變量,需要一塊空間去保存它們,
這塊空間,是以 ebp寄存器 指向的地址 的一塊內(nèi)存空間,
請看證據(jù)~
14: i=2;
00401078 mov dword ptr [ebp-4],2 <--- i 存放在 ebp寄存器 指向的地址 減去4 的地方
15: j=3;
0040107F mov dword ptr [ebp-8],3 <--- j 存放在 ebp寄存器 指向的地址 減去8 的地方,
因為i 占用了 4個字節(jié), 所以 4 + 4 = 8 了
所以不難想象出下面這個圖
| …… |
0012FF80: |--------| <--- [ebp]寄存器里 存放著 0012FF80
| |
0012FF7C: |--------|
| |
0012FF78: |--------|
| |
|--------|
| |
|--------|
按了下 F10,
14: i=2;
00401078 mov dword ptr [ebp-4],2
15: j=3; <--- 斷點來到這兒了
0040107F mov dword ptr [ebp-8],3
寄存器變化
-----------------------------------------------------------
EAX = CCCCCCCC EBX = 7FFD9000 ECX = 00000000
EDX = 00370F58 ESI = 00000000 EDI = 0012FF80
EIP = 0040107F ESP = 0012FF2C EBP = 0012FF80
EFL = 00000212
-----------------------------------------------------------
內(nèi)存的變化
-----------------------------------------------------------
0012FF72 CC CC CC CC CC CC CC 燙燙燙.
0012FF79 CC CC CC 02 00 00 00 燙.....
0012FF80 C0 FF 12 00 D9 12 40 ......@
-----------------------------------------------------------
_BEGIN(廢話)
注:在VC中,執(zhí)行一條指令后,寄存器的值或內(nèi)存的值有變化,變化的部份會顯示為紅色
_END(廢話)
再下 F10,
寄存器變化
-----------------------------------------------------------
EAX = CCCCCCCC EBX = 7FFD9000 ECX = 00000000
EDX = 00370F58 ESI = 00000000 EDI = 0012FF80
EIP = 00401086 ESP = 0012FF2C EBP = 0012FF80
EFL = 00000212
-----------------------------------------------------------
內(nèi)存的變化
-----------------------------------------------------------
0012FF72 CC CC CC CC CC CC 03 燙燙燙.
0012FF79 00 00 00 02 00 00 00 .......
0012FF80 C0 FF 12 00 D9 12 40 ......@
-----------------------------------------------------------
再想象一下
| …… |
0012FF80: |--------| <--- [ebp]寄存器里 存放著 0012FF80
| |
0012FF7C: |--------|
| 2 | <--- 存放著 i
0012FF78: |--------|
| 3 | <--- 存放著 j
|--------|
| |
|--------|
調(diào)用 func1() 函數(shù)前需要做什么? 看下編譯器編譯后的匯編就知道了~
17: func1(i,j);
00401086 mov eax,dword ptr [ebp-8] <---- 斷點在此
00401089 push eax
0040108A mov ecx,dword ptr [ebp-4]
0040108D push ecx
0040108E call @ILT+0(func1) (00401005)
00401093 add esp,8
地址 0x00401086 至 地址 0x00401093 的指令,
做的事情就是 把調(diào)用 func1() 函數(shù)時所需要的參數(shù)分別入棧,push
00401086 mov eax,dword ptr [ebp-8] <--- 看回前面 j = 3; 就知道 這里 j 是先入棧的
00401089 push eax
0040108A mov ecx,dword ptr [ebp-4]
0040108D push ecx
為什么讓 j 先入棧,i 比 j 可愛多了~
因為參數(shù)入棧的順序是有規(guī)定的!
修飾函數(shù)參數(shù)的入棧順序的關(guān)鍵字有
1. __cdecl
C/C++與MFC默認(rèn)的約定
參數(shù)從右至左順序入棧 并且由[調(diào)用者]負(fù)責(zé)把參數(shù) pop出 堆棧
2. __stdcall
WIN API 采用的約定
參數(shù)從右至左順序入棧,被調(diào)用的函數(shù)在返回前清理堆棧的的內(nèi)容,所以函數(shù)的參數(shù)個數(shù)需要是固定個數(shù)
3. __fastcall
用于對性能要求非常高的場合
參數(shù)從左邊開始的兩個不大于4字節(jié)(DWORD)的參數(shù)分別放在ECX和EDX寄存器,
其余的參數(shù)仍舊自右身左壓棧,被調(diào)用的函數(shù)在返回前負(fù)責(zé)清理傳送參數(shù)的堆棧
MSDN有云:
Keyword
|
Stack cleanup
|
Parameter passing
|
__cdecl
|
Caller
|
Pushes parameters on the stack, in reverse order (right to left)
|
__stdcall
|
Callee
|
Pushes parameters on the stack, in reverse order (right to left)
|
__fastcall
|
Callee
|
Stored in registers, then pushed on stack
|
thiscall (not a keyword)
|
Callee
|
Pushed on stack; this pointer stored in ECX
|
Obsolete Calling Conventions
Microsoft Specific
The __pascal, __fortran, and __syscall calling conventions are no longer supported. You can emulate their functionality by using one of the supported calling conventions and appropriate linker options.
WINDOWS.H now supports the WINAPI macro, which translates to the appropriate calling convention for the target. Use WINAPI where you previously used PASCAL or __far __pascal.
END Microsoft Specific
原來如此,這下明白了~
00401086 mov eax,dword ptr [ebp-8]
00401089 push eax
0040108A mov ecx,dword ptr [ebp-4]
0040108D push ecx
0040108E call @ILT+0(func1) (00401005) <---- 斷點來到這里
00401093 add esp,8
此時的寄存器狀態(tài)
EAX = 00000003 EBX = 7FFDF000 ECX = 00000002
EDX = 00030EA8 ESI = 00000000 EDI = 0013FF80
EIP = 0040108E ESP = 0013FF24 EBP = 0013FF80
EFL = 00000212
按F11
它跳轉(zhuǎn)到這里
@ILT+0(?func1@@YAXHH@Z):
00401005 jmp func1 (00401020) <---- 斷點在此
此時的寄存器狀態(tài)
EAX = 00000003 EBX = 7FFDF000 ECX = 00000002
EDX = 00030EA8 ESI = 00000000 EDI = 0013FF80
EIP = 00401005 ESP = 0012FF20 EBP = 0013FF80
EFL = 00000212
此時的 ESP 寄存器的值為什么變了,為什么???
_BEGIN(廢話)
SP(Stack Pointer),ESP 是堆棧指針寄存器,
與SS(Stack Segment,堆棧段寄存器) 相配合,指向指向堆棧的位置
它存放的地址 始終 指向堆頂
當(dāng)執(zhí)行 push, pop 操作 SP 的值都會作相應(yīng)的改變
不妨看下以下代碼
void main()
{
_asm
{
push 0xAABBCCDD <---- 斷點在此
pop eax
}
}
此時,寄存器狀態(tài)
EAX = CCCCCCCC EBX = 7FFD4000 ECX = 00000000
EDX = 00030EA8 ESI = 00000000 EDI = 0013FF80
EIP = 00401038 ESP = 0013FF34 EBP = 0013FF80
EFL = 00000202
ESP 寄存器指向的地址是棧頂?shù)牡刂罚?/span>0013FF34
0013FF10 30 FF 13 00 1F 3F 40 00 00 0....?@..
0013FF19 08 00 00 00 00 00 00 02 00 .........
0013FF22 00 00 30 2F 42 00 83 00 00 ..0/B....
0013FF2B 00 A8 1E 03 00 54 FF 13 00 .....T...
0013FF34 00 00 00 00 00 00 00 00 00 ......... <---- 在此
0013FF3D 40 FD 7F CC CC CC CC CC CC @?燙燙燙
執(zhí)行 push 0xEEEEEEEE 后寄存器的狀態(tài)
EAX = CCCCCCCC EBX = 7FFDF000 ECX = 00000000
EDX = 00030EA8 ESI = 00000000 EDI = 0013FF80
EIP = 0040103D ESP = 0013FF30 EBP = 0013FF80
EFL = 00000202
內(nèi)存狀態(tài)
0013FF22 00 00 30 2F 42 00 83 00 00 ..0/B....
0013FF2B 00 A8 1E 03 00 DD CC BB AA .....萏華
0013FF34 00 00 00 00 00 00 00 00 00 .........
不難看出 push 0xAABBCCDD 指令就是將
AABBCCDD 復(fù)制到 0013FF33, 0013FF32, 0013FF31, 0013FF30 這四個內(nèi)存單元,每個單元一字節(jié)
并且把原來 ESP 的值 0013FF34 減去 4, 得到 0013FF30
可見 棧頂 是向低地址 前進(jìn)的
再按 F10
5: push 0xAABBCCDD
00401038 push 0AABBCCDDh
6: pop eax
0040103D pop ax
7: }
8: }
0040103F pop edi <---- 斷點在此
此時寄存器狀態(tài)
EAX = AABBCCDD EBX = 7FFD6000 ECX = 00000000
EDX = 00030EA8 ESI = 00000000 EDI = 0013FF80
EIP = 0040103E ESP = 0013FF34 EBP = 0013FF80
EFL = 00000202 MM0 = 0000000000000000
內(nèi)存
0013FF22 00 00 30 2F 42 00 83 00 00 ..0/B....
0013FF2B 00 A8 1E 03 00 DD CC BB AA .....萏華
0013FF34 00 00 00 00 00 00 00 00 00 .........
0013FF3D 60 FD 7F CC CC CC CC CC CC `?燙燙燙
可見, pop eax 指令做的事情,就是
把 0013FF33, 0013FF32, 0013FF31, 0013FF30 四個字節(jié)的內(nèi)存單元 里面的內(nèi)容 復(fù)制到 eax 寄存器
同時把 ESP 寄存器 加 4,得到 0013FF34
換句話說,ESP 寄存器 的值改變了,意味了執(zhí)行過 push 或 pop 操作
地址向低地址偏移,說明進(jìn)行了 push 操作
地址向高地址偏移,說明時行了 pop 操作
_END(廢話)
說完廢話后,看回 call 指令
@ILT+0(?func1@@YAXHH@Z):
00401005 jmp func1 (00401020) <---- 斷點在此
此時的寄存器狀態(tài)
EAX = 00000003 EBX = 7FFDF000 ECX = 00000002
EDX = 00030EA8 ESI = 00000000 EDI = 0013FF80
EIP = 00401005 ESP = 0012FF20 EBP = 0013FF80
EFL = 00000212
在執(zhí)行 call 指令前的 寄存器狀態(tài)
此時的寄存器狀態(tài)
EAX = 00000003 EBX = 7FFDF000 ECX = 00000002
EDX = 00030EA8 ESI = 00000000 EDI = 0013FF80
EIP = 0040108E ESP = 0012FF24 EBP = 0013FF80
EFL = 00000212
很明顯,ESP 向低地址偏移了四個字節(jié),說它 call 指令執(zhí)行了 push 操作,入棧
它把什么東西入棧了????
看下 ESP = 0012FF20 指向的內(nèi)存地址的內(nèi)容先
0013FF0E 03 00 30 FF 13 00 1F 3F 40 ..0....?@
0013FF17 00 00 08 00 00 00 00 00 00 .........
0013FF20 83 10 40 00 02 00 00 00 03 ..@......
0013FF29 00 00 00 00 00 00 00 00 00 .........
從內(nèi)存中看出, 00401083 就是剛剛 push 進(jìn)去的內(nèi)容了
再看回前面的 call 指令
18: func1(i,j);
00401076 mov eax,dword ptr [ebp-8]
00401079 push eax
0040107A mov ecx,dword ptr [ebp-4]
0040107D push ecx
0040107E call @ILT+10(func1) (00401020)
00401093 add esp,8 <---- 請注意,這條指令的地址
原來 00401093 是一個地址,
那么可以肯定地說
call @ILT+10(func1) (00401020) 指令做的事情就是
先將 下一條指令 的地址 push 進(jìn)去堆棧,然后再無條件跳轉(zhuǎn)到 (00401020) 這個地址
奇怪,為什么在
call 指令將 下一條指令的地址 push 進(jìn)去 堆棧后,為什么不直接跳轉(zhuǎn)到 func1 (00401020)
而是 先跳轉(zhuǎn)到 00401005 呢?????
why? why?? why???
還是一個 @ILT+0(?func1@@YAXHH@Z) 的東東是什么?
在DEBUG版本中,VC匯編程序會產(chǎn)生一個函數(shù)跳轉(zhuǎn)指令表,
該表的每個表項存放一個函數(shù)的跳轉(zhuǎn)指令。
程序中的函數(shù)調(diào)用就是利用這個表來實現(xiàn)跳轉(zhuǎn)到相應(yīng)函數(shù)的入口地址。
ILT就是函數(shù)跳轉(zhuǎn)指令表的名稱,是Import Lookup Table的縮寫;
@ILT就是函數(shù)跳轉(zhuǎn)指令表的首地址。
在DEBUG版本中增加函數(shù)跳轉(zhuǎn)指令表,其目的是加快編譯速度,當(dāng)某函數(shù)的地址發(fā)生變化時,只需要修改ILT相應(yīng)表項即可,而不需要修改該函數(shù)的每一處引用。
注意:在RELEASE版本中,不會生成ILT,也就是說call指令的操作數(shù)直接是函數(shù)的入口地址,例如在本例中是這樣的:call 00401020
經(jīng)過千親萬苦,終于進(jìn)入 func1() 函數(shù)了
1: void func1(int input1, int input2)
2: {
00401020 push ebp <----------- 這兩個 經(jīng)典語句 在后面再研究它們的作用,
00401021 mov ebp,esp <---------- 先從第三個語句看起
00401023 sub esp,4Ch
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-4Ch]
0040102C mov ecx,13h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
00401023 sub esp,4Ch
然后,堆棧寄存器 ESP 向低地址偏移76(0x4C)字節(jié)。這里相當(dāng)于為 func1()函數(shù)層 分配了棧內(nèi)存 76個字節(jié)。
76個字節(jié),是編譯器根據(jù)你定義的變量的個數(shù)和大小,算出來的!
接著又把 ebx, esi, edi 分別入棧, 目的是為了保存 main 函數(shù)層的相關(guān)內(nèi)容
00401026 push ebx
00401027 push esi
00401028 push edi
ebx, esi, edi 分別保存了什么內(nèi)容? 為什么要把它們分別壓入堆棧????
再接著,
用0xCC初始化上述為func1()函數(shù)層所分配的棧內(nèi)存的每個字節(jié)。這里每一步用F11單步跟蹤,棧內(nèi)存的變化你會看得更清楚。
00401029 lea edi,[ebp-4Ch] ; 將有效的地址 [ebp-0x4Ch] 賦值到 edi,
; [ebp-0x4Ch] 的值正是為 func1() 函數(shù)分配的 76個字節(jié)內(nèi)存塊的 初始地址
0040102C mov ecx,13h ;
00401031 mov eax,0CCCCCCCCh ;
00401036 rep stos dword ptr [edi] ;
stos指令:
字符串存儲指令 STOS
格式: STOS OPRD
其中OPRD為目的串符號地址.
功能: 把AL(字節(jié))或AX(字)中的數(shù)據(jù)存儲到DI為目的串地址指針?biāo)鶎ぶ返拇鎯ζ鲉卧腥?/span>.指針DI將根據(jù)DF的值進(jìn)行自動調(diào)整.
由于上面的指令是 dword ptr 類型
dword 表示雙字 ptr 表示取首地址
那么 stos dword ptr [edi] 執(zhí)行的操作就是
將 ES:[DI]←AX,DI←DI±4 (DI 加或減是由 DF 標(biāo)志位確定的)
如果是 那么 stos word ptr [edi] 的話那么就是
將 ES:[DI]←AL,DI←DI±2 (DI 加或減是由 DF 標(biāo)志位確定的)
不然推出 stos BYTE ptr [edi]
注:
DF:方向標(biāo)志DF位用來決定在串操作指令執(zhí)行時有關(guān)指針寄存器發(fā)生調(diào)整的方向。
重復(fù)前綴
格式: REP ;CX<>0 重復(fù)執(zhí)行字符串指令
REP 每執(zhí)行一次后面的字符串指令后, cx減1, 直至 cx 為0
在本例中, 每次拷貝 sizeof(DWORD) 四個字節(jié), 而堆棧大小是 76(0x4C) 個字節(jié), 故 只需要重復(fù)執(zhí)行 76 / 4 = 19 (0x13) 次就可以了
故
0040102C mov ecx,13h ;
現(xiàn)在終于清楚
00401029 lea edi,[ebp-4Ch] ; 將有效的地址 [ebp-0x4Ch] 賦值到 edi
0040102C mov ecx,13h ;
00401031 mov eax,0CCCCCCCCh ;
00401036 rep stos dword ptr [edi] ;
的作用就是把堆棧的數(shù)據(jù)置為 0xCC;
6: i = input1;
00401038 mov eax,dword ptr [ebp+8] <---- 斷點現(xiàn)在在這里
0040103B mov dword ptr [ebp-4],eax
這兩句語句,讀取了 輸入的第一個參數(shù),并將它賦給了 i
在上面的
12: int i, j;
13: i=2;
00401078 mov dword ptr [ebp-4],2
14: j=3;
0040107F mov dword ptr [ebp-8],3
15:
16: func1(i,j); <-- 斷點在此,EBP = 0012FF80
00401086 mov eax,dword ptr [ebp-8]
00401089 push eax
0040108A mov ecx,dword ptr [ebp-4]
0040108D push ecx
--------------------------------------------------
12: int i, j;
13: i=2;
00401078 mov dword ptr [ebp-4],2 <-- 在 main 的函數(shù)層也像 func1() 函數(shù)層一樣
<-- 在函數(shù)內(nèi)變量 存放在 EBP 指向的一塊內(nèi)存空間
然后,j,i 分別入棧
1: void func1(int input1, int input2)
2: {
00401020 push ebp
00401021 mov ebp,esp
EBP 原來是 0012FF80,現(xiàn)在 變成了 0012FF1C
在進(jìn)入 func1 前,
ESP 存放著此時的堆棧地址
00401023 sub esp,4Ch
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-4Ch]
0040102C mov ecx,13h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
00401023 sub esp,4Ch
把 ESP 向低地址偏移了 4CH,~~~ 然后,肚子好餓,需要先去吃飯~~