了解eax, esp, ebp的作用(x86上是exx, x64上是rxx)
1. eax -- 数据寄存器,用来存储/读取数据,做累加的操作
2. esp 指针寄存器- 堆栈指针,里面寸的都是指针,需要调用dd来查看内容
3. ebp 指针寄存器 - 基址寄存器,每段函数进入后都会有如下指令集:
00414330 push ebp <-- 存当前函数上层函数的基指针
00414331 mov ebp,esp <-- 存当前的堆栈指针, 传入的参数都都在这个esp里,由于下面还会有push操作,所以把esp存入ebp中,ebp成为当前函数的基指针,非常重要,此时ebp以上,为传入的参数,以下为临时变量的指针
00414333 sub esp,0D8h <--开辟一段内存,用于储存临时/局部变量
00414339 push ebx <--几个寄存器值先入站
0041433A push esi
0041433B push edi
0041433C lea edi,[ebp-0D8h] <--循环
00414342 mov ecx,36h
00414347 mov eax,0CCCCCCCCh <--debug版本将栈上所有的数据置为该值,用于检查当前变量是否初始化
首先调用kn来查看当前call stack和frame
0:000> kn
*** Stack trace for last set context - .thread/.cxr resets it
# ChildEBP RetAddr
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0012fc3c 102afdd2 kernel32!RaiseException+0x52
01 0012fc7c 00414385 msvcr90d!CxxThrowException+0x52
02 0012fd70 004143f0 ConsoleCpp1!Test22+0x55 [f:\codes\cpp\consolecpp1\consolecpp1\consolecpp1.cpp @ 669] <--准备查看此stack上传入的参数以及临时变量
03 0012fe58 004144ee ConsoleCpp1!Test11+0x30 [f:\codes\cpp\consolecpp1\consolecpp1\consolecpp1.cpp @ 674]
04 0012ff68 00415268 ConsoleCpp1!wmain+0xae [f:\codes\cpp\consolecpp1\consolecpp1\consolecpp1.cpp @ 684]
05 0012ffb8 004150af ConsoleCpp1!__tmainCRTStartup+0x1a8 [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 583]
06 0012ffc0 7c81776f ConsoleCpp1!wmainCRTStartup+0xf [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 403]
07 0012fff0 00000000 kernel32!RegisterWaitForInputIdle+0x49
使用.frame /r #来查看frame上的register的情况
0:000> .frame /r 2
02 0012fd70 004143f0 ConsoleCpp1!Test22+0x55 [f:\codes\cpp\consolecpp1\consolecpp1\consolecpp1.cpp @ 669]
eax=0012fbec ebx=7ffd6000 ecx=00000000 edx=00000003 esi=0012fc7c edi=0012fc7c
eip=00414385 esp=0012fc84 ebp=0012fd70iopl=0 nv up ei pl nz na pe nc
cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00000206
ConsoleCpp1!Test22+0x55:
00414385 8b45f8 mov eax,dword ptr [ebp-8] ss:0023:0012fd68=00008de6
查看当前ebp的值,来查找传入的参数args->arg1和args->arg2,还有results(此处我也不是很确定,传说参数是从ebp+8开始是第一个参数,因为ebp+4存的是返回地址,ebp存的是上层ebp的值),此处查询得之两个参数为0x6554, 0x2892, 注意这两个数都是16进制
0:000> dd 0012fd70 + 8
0012fd78 00006554 00002892 0012ff5c 0012fe64
0012fd88 7ffd6000 cccccccc cccccccc cccccccc
0012fd98 cccccccc cccccccc cccccccc cccccccc
0012fda8 cccccccc cccccccc cccccccc cccccccc
0012fdb8 cccccccc cccccccc cccccccc cccccccc
0012fdc8 cccccccc cccccccc cccccccc cccccccc
0012fdd8 cccccccc cccccccc cccccccc cccccccc
0012fde8 cccccccc cccccccc cccccccc cccccccc
接下来寻找当前frame上的局部变量result,此处不知为何多出一个值为cccccccc的临时变量,查到第二个局部变量的时候才找到results的值0x6554 + 0x2892 = 0x8de6
0:000> dd 0012fd70 -4
0012fd6c cccccccc 0012fe58 004143f0 00006554
0012fd7c 00002892 0012ff5c 0012fe64 7ffd6000
0012fd8c cccccccc cccccccc cccccccc cccccccc
0012fd9c cccccccc cccccccc cccccccc cccccccc
0012fdac cccccccc cccccccc cccccccc cccccccc
0012fdbc cccccccc cccccccc cccccccc cccccccc
0012fdcc cccccccc cccccccc cccccccc cccccccc
0012fddc cccccccc cccccccc cccccccc cccccccc
0:000> dd 0012fd70 - 8
0012fd68 00008de6 cccccccc 0012fe58 004143f0
0012fd78 00006554 00002892 0012ff5c 0012fe64
0012fd88 7ffd6000 cccccccc cccccccc cccccccc
0012fd98 cccccccc cccccccc cccccccc cccccccc
0012fda8 cccccccc cccccccc cccccccc cccccccc
0012fdb8 cccccccc cccccccc cccccccc cccccccc
0012fdc8 cccccccc cccccccc cccccccc cccccccc
0012fdd8 cccccccc cccccccc cccccccc cccccccc
从此处dump文件的寄存器使用情况可以得知,vs2008编译器的局部变量存储位置就是ebp-8, ebp - 14, ebp-20以此类推,每次用8 byte的数据,这是dbg版本为了在变量前后都加上前后标志用以检查越界,这就可以解释为什么ebp-4是cccccccc了
CrashCPP!Test:
012b1c80 55 push ebp
012b1c81 8bec mov ebp,esp
012b1c83 81ece4000000 sub esp,0E4h
012b1c89 53 push ebx
012b1c8a 56 push esi
012b1c8b 57 push edi
012b1c8c 8dbd1cffffff lea edi,[ebp-0E4h]
012b1c92 b939000000 mov ecx,39h
012b1c97 b8cccccccc mov eax,0CCCCCCCCh
012b1c9c f3ab rep stos dword ptr es:[edi]
012b1c9e 8b4508 mov eax,dword ptr [ebp+8]
012b1ca1 8b08 mov ecx,dword ptr [eax]
012b1ca3 894df8 mov dword ptr [ebp-8],ecx
012b1ca6 8b4508 mov eax,dword ptr [ebp+8]
012b1ca9 8b4804 mov ecx,dword ptr [eax+4]
012b1cac 894dec mov dword ptr [ebp-14h],ecx
012b1caf 8b45f8 mov eax,dword ptr [ebp-8]
012b1cb2 0345ec add eax,dword ptr [ebp-14h]
012b1cb5 8945e0 mov dword ptr [ebp-20h],eax
012b1cb8 837df800 cmp dword ptr [ebp-8],0
012b1cbc 752b jne CrashCPP!Test+0x69 (012b1ce9)
012b1cbe 8bf4 mov esi,esp
012b1cc0 ff15d4922b01 call dword ptr [CrashCPP!_imp__getchar (012b92d4)]
012b1cc6 3bf4 cmp esi,esp
012b1cc8 e88cf4ffff call CrashCPP!ILT+340(__RTC_CheckEsp) (012b1159)
012b1ccd 8bf4 mov esi,esp
012b1ccf 6a00 push 0
012b1cd1 6a00 push 0
012b1cd3 6a00 push 0
012b1cd5 6803000080 push 80000003h
012b1cda ff15fc912b01 call dword ptr [CrashCPP!_imp__RaiseException (012b91fc)]
012b1ce0 3bf4 cmp esi,esp
到此,本轮的debug完成,下面是完整的c++代码和汇编代码,另外附上资料原文和连接
/*
* main
*/
class MyArgs
{
public:
int arg1;
int arg2;
MyArgs()
{
srand(time(NULL));
arg1 = rand();
arg2 = rand();
}
};
int Test22(int a, int b)
{
int result = a+b;
if(result%2==0)
{
throw 1;
}
return result;
}
void Test11(MyArgs* arg)
{
int result = Test22(arg->arg1, arg->arg2);
printf("Result: %d", result);
}
int _tmain(int argc, _TCHAR* argv[])
{
getchar();
MyArgs* arg = new MyArgs();
Test11(arg);
return 0;
}
int Test22(int a, int b)
{
00414330 push ebp
00414331 mov ebp,esp
00414333 sub esp,0D8h
00414339 push ebx
0041433A push esi
0041433B push edi
0041433C lea edi,[ebp-0D8h]
00414342 mov ecx,36h
00414347 mov eax,0CCCCCCCCh
0041434C rep stos dword ptr es:[edi]
int result = a+b;
0041434E mov eax,dword ptr [a]
00414351 add eax,dword ptr [b]
00414354 mov dword ptr [result],eax
if(result%2==0)
00414357 mov eax,dword ptr [result]
0041435A and eax,80000001h
0041435F jns Test22+36h (414366h)
00414361 dec eax
00414362 or eax,0FFFFFFFEh
00414365 inc eax
00414366 test eax,eax
00414368 jne Test22+55h (414385h)
{
throw 1;
0041436A mov dword ptr [ebp-0D4h],1
00414374 push offset __TI1H (41C320h)
00414379 lea eax,[ebp-0D4h]
0041437F push eax
00414380 call @ILT+415(__CxxThrowException@8) (4111A4h)
}
return result;
00414385 mov eax,dword ptr [result]
}
00414388 pop edi
00414389 pop esi
0041438A pop ebx
0041438B add esp,0D8h
00414391 cmp ebp,esp
00414393 call @ILT+575(__RTC_CheckEsp) (411244h)
00414398 mov esp,ebp
0041439A pop ebp
0041439B ret
void Test11(MyArgs* arg)
{
004143C0 push ebp
004143C1 mov ebp,esp
004143C3 sub esp,0CCh
004143C9 push ebx
004143CA push esi
004143CB push edi
004143CC lea edi,[ebp-0CCh]
004143D2 mov ecx,33h
004143D7 mov eax,0CCCCCCCCh
004143DC rep stos dword ptr es:[edi]
int result = Test22(arg->arg1, arg->arg2);
004143DE mov eax,dword ptr [arg]
004143E1 mov ecx,dword ptr [eax+4]
004143E4 push ecx
004143E5 mov edx,dword ptr [arg]
004143E8 mov eax,dword ptr [edx]
004143EA push eax
004143EB call Test22 (41127Bh)
004143F0 add esp,8
004143F3 mov dword ptr [result],eax
printf("Result: %d", result);
004143F6 mov esi,esp
004143F8 mov eax,dword ptr [result]
004143FB push eax
004143FC push offset string "Result: %d" (41AD14h)
00414401 call dword ptr [__imp__printf (41E324h)]
00414407 add esp,8
0041440A cmp esi,esp
0041440C call @ILT+575(__RTC_CheckEsp) (411244h)
}
00414411 pop edi
00414412 pop esi
00414413 pop ebx
00414414 add esp,0CCh
0041441A cmp ebp,esp
0041441C call @ILT+575(__RTC_CheckEsp) (411244h)
00414421 mov esp,ebp
00414423 pop ebp
00414424 ret
int _tmain(int argc, _TCHAR* argv[])
{
00414440 push ebp
00414441 mov ebp,esp
00414443 push 0FFFFFFFFh
00414445 push offset __ehhandler$_wmain (41808Eh)
0041444A mov eax,dword ptr fs:[00000000h]
00414450 push eax
00414451 sub esp,0E8h
00414457 push ebx
00414458 push esi
00414459 push edi
0041445A lea edi,[ebp-0F4h]
00414460 mov ecx,3Ah
00414465 mov eax,0CCCCCCCCh
0041446A rep stos dword ptr es:[edi]
0041446C mov eax,dword ptr [___security_cookie (41D04Ch)]
00414471 xor eax,ebp
00414473 push eax
00414474 lea eax,[ebp-0Ch]
00414477 mov dword ptr fs:[00000000h],eax
getchar();
0041447D mov esi,esp
0041447F call dword ptr [MSVCR90D_NULL_THUNK_DATA (41E328h)]
00414485 cmp esi,esp
00414487 call @ILT+575(__RTC_CheckEsp) (411244h)
MyArgs* arg = new MyArgs();
0041448C push 8
0041448E call operator new (4112C1h)
00414493 add esp,4
00414496 mov dword ptr [ebp-0E0h],eax
0041449C mov dword ptr [ebp-4],0
004144A3 cmp dword ptr [ebp-0E0h],0
004144AA je wmain+7Fh (4144BFh)
004144AC mov ecx,dword ptr [ebp-0E0h]
004144B2 call MyArgs::MyArgs (4110F0h)
004144B7 mov dword ptr [ebp-0F4h],eax
004144BD jmp wmain+89h (4144C9h)
004144BF mov dword ptr [ebp-0F4h],0
004144C9 mov eax,dword ptr [ebp-0F4h]
004144CF mov dword ptr [ebp-0ECh],eax
004144D5 mov dword ptr [ebp-4],0FFFFFFFFh
004144DC mov ecx,dword ptr [ebp-0ECh]
004144E2 mov dword ptr [ebp-14h],ecx
Test11(arg);
004144E5 mov eax,dword ptr [ebp-14h]
004144E8 push eax
004144E9 call Test11 (41128Fh)
004144EE add esp,4
return 0;
004144F1 xor eax,eax
}
004144F3 mov ecx,dword ptr [ebp-0Ch]
004144F6 mov dword ptr fs:[0],ecx
004144FD pop ecx
004144FE pop edi
004144FF pop esi
00414500 pop ebx
00414501 add esp,0F4h
00414507 cmp ebp,esp
00414509 call @ILT+575(__RTC_CheckEsp) (411244h)
0041450E mov esp,ebp
00414510 pop ebp
00414511 ret
http://shitouer.cn/2012/12/method-called/
代码如下:
#include “stdlib.h”
int sum(int a,int b,int m,int n)
{
return a+b;
}
void main()
{
int result = sum(1,2,3,4);
system(“pause”);
}
有四个参数的sum函数,接着在main方法中调用sum函数。在debug环境下,单步调试如下:
11: void main()
12: {
00401060 push ebp
;保存ebp,执行这句之前,ESP = 0012FF4C EBP = 0012FF88
;执行后,ESP = 0012FF48 EBP = 0012FF88,ESP减小,EBP不变
00401061 mov ebp,esp
;将esp放入ebp中,此时ebp和esp相同,即执行后ESP = 0012FF48 EBP = 0012FF48
;原EBP值已经被压栈(位于栈顶),而新的EBP又恰恰指向栈顶。
;此时EBP寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),
;从该地址为基准,向上(栈底方向)能获取返回地址、参数值(假如main中有参数,“获取参数值”会比较容易理解,
;不过在看下边的sum函数调用时会有体会的),向下(栈顶方向)能获取函数局部变量值,
;而该地址处又存储着上一层函数调用时的EBP值!
00401063 sub esp,44h
;把esp往上移动一个范围
;等于在栈中空出一片空间来存局部变量
;执行这句后ESP = 0012FF04 EBP = 0012FF48
00401066 push ebx
00401067 push esi
00401068 push edi
;保存三个寄存器的值
00401069 lea edi,[ebp-44h]
;把ebp-44h加载到edi中,目的是保存局部变量的区域
0040106C mov ecx,11h
00401071 mov eax,0CCCCCCCCh
00401076 rep stos dword ptr [edi]
;从ebp-44h开始的区域初始化成全部0CCCCCCCCh,就是int3断点,初始化局部变量空间
;REP ;CX不等于0 ,则重复执行字符串指令
;格式: STOS OPRD
;功能: 把AL(字节)或AX(字)中的数据存储到DI为目的串地址指针所寻址的存储器单元中去.指针DI将根据DF的值进行自动
;调整. 其中OPRD为目的串符号地址.
;以上的语句就是在栈中开辟一块空间放局部变量
;然后把这块空间都初始化为0CCCCCCCCh,就是int3断点,一个中断指令。
;因为局部变量不可能被执行,执行了就会出错,这时候发生中断提示开发者。
13: int result = sum(1,2,3,4);
00401078 push 4
0040107A push 3
0040107C push 2
0040107E push 1
;各个参数入栈,注意查看寄存器ESP值的变化
;亦可以看到参数入栈的顺序,从右到左
;变化为:ESP = 0012FEF8–>ESP = 0012FEF4–>ESP = 0012FEF0–>ESP = 0012FEEC–>ESP = 0012FEE8
00401080 call @ILT+15(boxer) (00401014)
;调用sum函数,可以按F11跟进
;注:f10(step over),单步调试,遇到函数调用,直接执行,不会进入函数内部
;f11(step into),单步调试,遇到函数调用,会进入函数内部
;shift+f11(step out),进入函数内部后,想从函数内部跳出,用此快捷方式
;ctrl+f10(run to cursor),呵呵,看英语注释就应该知道是什么意思了,不再解释
00401085 add esp,10h
;调用完函数后恢复/释放栈,执行后ESP = 0012FEF8,与sum函数的参数入栈前的数值一致
00401088 mov dword ptr [ebp-4],eax
;将结果存放在result中,原因详看最后有关ss的注释
14: system(“pause”);
0040108B push offset string “pause” (00422f6c)
00401090 call system (0040eed0)
00401095 add esp ,4
;有关system(“pause”)的处理,此处不讨论
15: }
00401098 pop edi
00401099 pop esi
0040109A pop ebx
;恢复原来寄存器的值,怎么“吃”进去,怎么“吐”出来
0040109B add esp,44h
;恢复ESP,对应上边的sub esp,44h
0040109E cmp ebp,esp
;检查esp是否正常,不正常就进入下边的call里面debug
004010A0 call __chkesp (004010b0)
;处理可能出现的堆栈异常,如果有的话,就会陷入debug
004010A5 mov esp,ebp
004010A7 pop ebp
;恢复原来的esp和ebp,让上一个调用函数正常使用
004010A8 ret
;将返回地址存入eip,转移流程
;如果函数有返回值,返回值将放在eax返回(这就是很多软件给秒杀爆破的原因了,因为eax的返回值是可以改的)
—————————————————————————————————————————–
;以上即是主函数调用的反汇编过程,下边来看调用sum函数的过程:
;上边有说在00401080 call @ILT+15(boxer) (00401014)这一句处,用f11单步调试,f11后如下句:
00401014 jmp sum (00401020)
;即跳转到sum函数的代码段中,再f11如下:
6: int sum(int a,int b,int m,int n)
7: {
00401020 push ebp
00401021 mov ebp,esp
00401023 sub esp,40h
00401026 push ebx
00401027 push esi
00401028 push edi
00401029 lea edi,[ebp-40h]
0040102C mov ecx,10h
00401031 mov eax,0CCCCCCCCh
00401036 rep stos dword ptr [edi]
;可见,上边几乎与主函数调用相同,每一步不再赘述,可对照上边主函数调用的注释
8: return a+b;
00401038 mov eax,dword ptr [ebp+8]
;取第一个参数放在eax
0040103B add eax,dword ptr [ebp+0Ch]
;取第二个参数,与eax中的数值相加并存在eax中
9: }
0040103E pop edi
0040103F pop esi
00401040 pop ebx
00401041 mov esp,ebp
00401043 pop ebp
00401044 ret
;收尾操作,比前边只是少了检查esp操作罢了
有关ss部分的注释:
;一般而言,ss:[ebp+4]处为返回地址
;ss:[ebp+8]处为第一个参数值(这里是a),ss:[ebp+0Ch]处为第二个参数(这里是b,这里8+4=12=0Ch)
;ss:[ebp-4]处为第一个局部变量(如main中的result),ss:[ebp]处为上一层EBP值
;ebp和函数返回值是32位,所以占4个字节