跟着视频学的,这里的例子并没有提供正向代码不过很简单不影响,重点是为了理解堆栈。
0040118E |. 6A 09 PUSH 9
00401190 |. 6A 0C PUSH 0C
00401192 |. 6A 05 PUSH 5
00401194 |. E8 85FEFFFF CALL HelloWor.0040101E
00401199 |. 83C4 0C ADD ESP,0C
00401100 /> \55 PUSH EBP
00401101 |. 8BEC MOV EBP,ESP
00401103 |. 83EC 40 SUB ESP,40
00401106 |. 53 PUSH EBX
00401107 |. 56 PUSH ESI
00401108 |. 57 PUSH EDI
00401109 |. 8D7D C0 LEA EDI,DWORD PTR SS:[EBP-40]
0040110C |. B9 10000000 MOV ECX,10
00401111 |. B8 CCCCCCCC MOV EAX,CCCCCCCC
00401116 |. F3:AB REP STOS DWORD PTR ES:[EDI]
00401118 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0040111B |. 3B45 0C CMP EAX,DWORD PTR SS:[EBP+C]
0040111E |. 7E 12 JLE SHORT HelloWor.00401132
00401120 |. 8B4D 08 MOV ECX,DWORD PTR SS:[EBP+8]
00401123 |. 3B4D 10 CMP ECX,DWORD PTR SS:[EBP+10]
00401126 |. 7E 05 JLE SHORT HelloWor.0040112D
00401128 |. 8B45 08 MOV EAX,DWORD PTR SS:[EBP+8]
0040112B |. EB 15 JMP SHORT HelloWor.00401142
0040112D |> 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10]
00401130 |. EB 10 JMP SHORT HelloWor.00401142
00401132 |> 8B55 0C MOV EDX,DWORD PTR SS:[EBP+C]
00401135 |. 3B55 10 CMP EDX,DWORD PTR SS:[EBP+10]
00401138 |. 7E 05 JLE SHORT HelloWor.0040113F
0040113A |. 8B45 0C MOV EAX,DWORD PTR SS:[EBP+C]
0040113D |. EB 03 JMP SHORT HelloWor.00401142
0040113F |> 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10]
00401142 |> 5F POP EDI
00401143 |. 5E POP ESI
00401144 |. 5B POP EBX
00401145 |. 8BE5 MOV ESP,EBP
00401147 |. 5D POP EBP
00401148 \. C3 RETN
原始ESP 0019FEF4
EBP 0019FF40
EBP作栈底,ESP为栈顶
①在传参之前有个ADD ESP,8 是保持原始的堆栈(上层传入两个参数)
②传参 c中参数在前的后push 顺序相反
push 9
push 0C
PUSH 5
实际为int 5, int 0c, int 9;
ESP-C = 0019FEE8
③CALL 函数
PUSH EIP
JMP
ESP-4 = 0019FEE4
④进入函数内部 PUSH EBP
ESP-4 = 0019FEE0
⑤MOV EBP,ESP
EBP = ESP = 0019FEE0
⑥开辟40h的空间作为函数临时内存
此时在子函数里EBP又变为当前函数栈底,ESP栈顶
SUP ESP,40
ESP-40 = 0019FEA0
⑧保留寄存器原始值 防止对原函数造成影响
PUSH EBX
PUSH ESI
PUSH EDI
ESP-C = 0019FE94
⑨初始化内存空间 防止不相干数据影响
LEA EDI,DWORD PTR SS:[EBP-40]
MOV ECX,10
MOV EAX,CCCCCCCC
REP STOS DWORD PTR ES:[EDI]
将开辟空间的栈顶值给EDI 利用STOS函数进行赋值
STOS DWORD PTR ES:[EDI] 默认将EAX中的值赋值给EDI代表的内存中
REP ECX作为循环次数
ps:在od中可以F7步进REP EDI会自增DWORD
⑩函数操作
伪代码
int x = 0x5, y = 0xc, z = 0x9;
void func(int x, int y, int z)
{
if(x < y)
if(y < z)
return z;
else
return y;
else if(x < z)
return z;
else
return x;
}
func(a,b,c);
⑪还原寄存器值
POP EDI
POP ESI
POP EBX
ESP+C = 0019FEA0
⑫还原被调用函数栈顶
MOV ESP,EBP
ESP = EBP = 0019FEE0
⑬还原调用函数栈底
POP EBP
⑭返回调用函数
RETN
POP EIP
JMP
ESP+4 = 0019FEA8
⑮还原调用函数栈顶
ADD ESP,0C
重点就是在ESP EBP每一步的含义,被调用CALL中的EBP+4 +8…是什么意思
汇编中一个完整CALL最精简的过程总结为
1 传参(与C中顺序相反,)
2 保留栈底push ebp
3 开辟空间
sub esp
mov ebp,esp
4 保护原有寄存器值
5 初始化内存
6 函数操作
7 还原寄存器
8 还原堆栈
其中在执行CALL的时候相当于PUSH EIP ,JMP
ESP先作为主函数的栈顶然后把值赋给EBP让其充当被调用的栈底说起来比较绕,务必理解图。
所以到了函数操作部分EBP作为子函数栈底(原本的ESP),EBP+4即主函数的EIP
EBP+8为子函数的第一个参数 EBP+C为第二个