堆栈平衡 | |
---|---|
指令解析 | push register:将寄存器内存储的值压入栈中; pop register:将栈顶地址存储的值传递给寄存器; |
开辟栈帧 | 函数内使用的局部变量都是临时存储的,如果每次调用都要往内存中存数据并且不去删除,就会造成很大的浪费 解决方法就是给他一块临时的空间,用完之后就覆盖掉,这就是开辟堆栈 标志:从 push ebp 开始,到下一个push ebp 结束 |
堆栈平衡 作用 | 函数的调用,要时刻的保持堆栈平衡。不平衡就会造成地址错位,从而程序代码就回不到调用函数的位置了。 |
平衡方式 | 1. cdecl 调用约定:栈外(函数外)平衡,平衡方式:add 8。 2. fastcall,thiscall,stdcall 调用约定:是栈内(函数内)平衡,平衡方式:ret 8 (相当与将retn 和 add 8 合并成一条指令)。 3. delphi call: 前三个参数使用 1.eax,2.edx,3.ecx 传递,后面使用栈传递 |
堆栈平衡中 寄存器 |
EBP——在程序的运行过程中有着特定的作用,保存数据的基址,根据这个特性,EBP一般不会被更改。 但是在汇编中,函数需要用到的数据都会在运行前入栈,函数可以通过栈顶找到这些数据,所以在函数运行时,EBP就暂时失去了意义,所以我们就用它来临时保存开辟的栈底。 EIP——即返回地址,在堆栈中的位置,ebp+4 。ret命令 执行,返回有调用函数指令指向的下一条指令。备注:如果调用约定是_cdecl,应为平衡堆栈 add x ESP——在调用函数前后,ESP的值应该不变的。 |
脱壳中的ESP平衡 | - pushad 的作用是把通用寄存器压栈。寄存器的入栈顺序依次是:EAX,ECX,EDX,EBX,ESP(初始值),EBP,ESI,EDI. - 在ESP寄存器保存的的初始值(即开辟堆栈的栈底)下硬件断点,只有当堆栈快执行完毕,popad时才会再次访问堆栈的栈底,则此时触发硬件断点。 |
汇编调用CALL 地址的计算 | |
E8 Call Offset(调用普通函数)(Offset 是一段偏移) FF 15 Call Dword ptr [ 地址 ] (调用IAT)([地址]内 IAT表中函数的地址) E9 JMP Offset 跳到一个地址(可手动计算出) FF25 JMP Dword ptr [ 地址 ] (跳到IAT地址,不可手动算出) |
一个函数的解析
//主函数
int main()
{
int x= AFunc(5, 6);
return 0;
}
00D317B0 > 55 PUSH EBP ; 保存语句执行前的EBP
00D317B1 8BEC MOV EBP,ESP ; 将当前堆栈的ESP传给EBP
00D317B3 81EC CC000000 SUB ESP,0xCC ; 为函数的局部变量申请一段空间
00D317B9 53 PUSH EBX
00D317BA 56 PUSH ESI ; 保存入栈前的寄存器值
00D317BB 57 PUSH EDI
00D317BC 8D7D F4 LEA EDI,DWORD PTR SS:[EBP-0xC] ; 将局部变量的堆栈中开始地址值保存到EDI寄存器中
00D317BF B9 03000000 MOV ECX,0x3 ; ECX=3;计数器
00D317C4 B8 CCCCCCCC MOV EAX,0xCCCCCCCC ; 初始化 EAX
00D317C9 F3:AB REP STOS DWORD PTR ES:[EDI] ; 用EAX的值填充到EDI指向的地址,循环次数ECX
00D317CB B9 03C0D300 MOV ECX,堆栈平衡.00D3C003 ; 给ECX赋值
00D317D0 E8 32FBFFFF CALL 堆栈平衡.00D31307 ; CheckForDebuggerJustMyCode
00D317D5 6A 06 PUSH 0x6 ; 参数2=6
00D317D7 6A 05 PUSH 0x5 ; 参数1=5
00D317D9 E8 33FBFFFF CALL 堆栈平衡.00D31311 ; 调用 AFunc(int a,int b)函数
00D317DE 83C4 08 ADD ESP,0x8 ; 平衡堆栈
00D317E1 8945 F8 MOV DWORD PTR SS:[EBP-0x8],EAX ; 函数返回值EAX传递给局部变量x
00D317E4 33C0 XOR EAX,EAX ; EAX=0; return 0
00D317E6 5F POP EDI
00D317E7 5E POP ESI ; 寄存器出栈,恢复寄存器的值
00D317E8 5B POP EBX
00D317E9 81C4 CC000000 ADD ESP,0xCC ; 释放局部变量空间,平衡堆栈
00D317EF 3BEC CMP EBP,ESP ; 检查堆栈平衡
00D317F1 E8 3AFAFFFF CALL 堆栈平衡.00D31230 ; CheckESP
00D317F6 8BE5 MOV ESP,EBP ; ESP=EBP,还原调用函数前
00D317F8 5D POP EBP ; ESP=EBP,还原调用函数栈底 EBP
00D317F9 C3 RETN ; 返回调用函数指令的下一条指令 EIP
00D31311 /E9 2A040000 JMP 堆栈平衡.AFunc
00D31740= 00D31311+ 0000042A+ 5
int AFunc(int a, int b)
{
int n =a+b;
return n;
}
-
00D31740 > 55 PUSH EBP ; 保存语句执行前的EBP
00D31741 8BEC MOV EBP,ESP ; 将当前堆栈的ESP传给EBP
00D31743 81EC CC000000 SUB ESP,0xCC ; 为函数的局部变量申请一块内存空间
00D31749 53 PU
00D3174A 56 PUSH ESI ; 保存寄存器环境
00D3174B 57 PU
00D3174C 8D7D F4 LEA EDI,DWORD PTR SS:[EBP-0xC] ; 将局部变量的堆栈中开始地址值保存到EDI寄存器中
00D3174F B9 03000000 MOV ECX,0x3 ; ECX=3
00D31754 B8 CCCCCCCC MOV EAX,0xCCCCCCCC ; 初始化 EAX
00D31759 F3:AB REP STOS DWORD PTR ES:[EDI] ; 用EAX的值填充到EDI指向的地址,循环次数ECX
00D3175B B9 03C0D300 MOV ECX,堆栈平衡.00D3C003 ; 给ECX赋值
00D31760 E8 A2FBFFFF CALL 堆栈平衡.00D31307 ; CheckForDebuggerJustMyCode
00D31765 8B45 08 MOV EAX,DWORD PTR SS:[EBP+0x8] ; EAX=参数1
00D31768 0345 0C ADD EAX,DWORD PTR SS:[EBP+0xC] ; EAX=参数2+参数1
00D3176B 8945 F8 MOV DWORD PTR SS:[EBP-0x8],EAX ; 局部变量var=EAX
00D3176E 8B45 F8 MOV EAX,DWORD PTR SS:[EBP-0x8] ; return eax
00D31771 5F P
00D31772 5E POP ESI ; 恢复寄存器环境
00D31773 5B P
00D31774 81C4 CC000000 ADD ESP,0xCC ; 释放局部变量空间,平衡堆栈
00D3177A 3BEC CMP EBP,ESP ; 检查堆栈平衡
00D3177C E8 AFFAFFFF CALL 堆栈平衡.00D31230 ; CheckESP
00D31781 8BE5 MOV ESP,EBP ; ESP=EBP
00D31783 5D POP EBP ; 还原调用函数栈底 EBP
00D31784 C3 RETN ; 返回调用函数指令的下一条指令 EIP