反汇编分析--寄存器状态
一直对反汇编分析代码有兴趣,但是一直没静下心来研究一下,昨天腾讯笔试,有一题就是考察大端小端模式(见下面C代码段),涉及到函数调用,大端小端模式前些时间复习过,但是即便如此,做的时候还是感觉思路不清,回头想想,一是对函数调用中栈的变化过程不甚了解,一是对long long结构不了解,而如果我知道如何反汇编的话,可以直接在调试过程中解开疑虑,这样就不用盲目地在网上搜集了。因此我决定这些天弄一弄反汇编。
在VS2008中反汇编很容易,进入debug模式之后,在代码区域“右键->反汇编” 或者“调试->窗口->反汇编” 就可以看到程序对应的汇编代码,同样的方式“调试->窗口->寄存器” 就可以查看寄存器的状态。
/*-----------------lp横刀立马------------------------------ -----------------腾讯2013春季实习生笔试4.13--------------------- 题目描述:代码如下,win32 环境,CPU小端模式,参数用栈来传递,请问输出-- */ #include <stdio.h> int main() { long long a=1; long long b=2; long long c=3; printf("%d%d%d",a,b,c); }
VS2008反汇编代码如下:
#include <stdio.h> int main() { 00D813B0 push ebp 00D813B1 mov ebp,esp 00D813B3 sub esp,0F0h 00D813B9 push ebx 00D813BA push esi 00D813BB push edi 00D813BC lea edi,[ebp-0F0h] 00D813C2 mov ecx,3Ch 00D813C7 mov eax,0CCCCCCCCh 00D813CC rep stos dword ptr es:[edi]
/* 说明: rep 表示重复,重复的次数由ecx决定,在执行这一句话的过程中,ecx自动减小直至为零,这也是为什么ecx叫做计数器
另外,stos表示将edi指向的内容写成eax中的值,rep过程中edi自动增加,从这个行为也可以感受到为什么edi叫做目标变址寄存器*/
long long a=1; 00D813CE mov dword ptr [a],1 00D813D5 mov dword ptr [ebp-8],0
/* long long 类型占8个字节,而双字是4个字节,所以要分两次写入,可以看出,栈顶在低地址,栈底在高地址,push操作对应着esp的减小 */
long long b=2; 00D813DC mov dword ptr [b],2 00D813E3 mov dword ptr [ebp-18h],0
/*为啥这里是ebp-18h?不是只需要8个字节就够了吗?*/ long long c=3; 00D813EA mov dword ptr [c],3 00D813F1 mov dword ptr [ebp-28h],0 printf("%d%d%d",a,b,c); 00D813F8 mov esi,esp 00D813FA mov eax,dword ptr [ebp-28h] 00D813FD push eax 00D813FE mov ecx,dword ptr [c] 00D81401 push ecx 00D81402 mov edx,dword ptr [ebp-18h] 00D81405 push edx 00D81406 mov eax,dword ptr [b] 00D81409 push eax 00D8140A mov ecx,dword ptr [ebp-8] 00D8140D push ecx 00D8140E mov edx,dword ptr [a] 00D81411 push edx 00D81412 push offset string "%d%d%d" (0D8573Ch) 00D81417 call dword ptr [__imp__printf (0D882BCh)] 00D8141D add esp,1Ch 00D81420 cmp esi,esp 00D81422 call @ILT+310(__RTC_CheckEsp) (0D8113Bh) } 00D81427 xor eax,eax 00D81429 pop edi 00D8142A pop esi 00D8142B pop ebx 00D8142C add esp,0F0h 00D81432 cmp ebp,esp 00D81434 call @ILT+310(__RTC_CheckEsp) (0D8113Bh) 00D81439 mov esp,ebp 00D8143B pop ebp 00D8143C ret
反汇编的过程如果不懂每个寄存器分别是干啥的,无异于没学单词就开始念英文,那么今天就存寄存器开始吧。先进行一个简要的概述,然后挨个分析。
1.寄存器组概述
数据寄存器:保存操作数和运算结果等信息。
EAX:Accumulator。函数返回值。取低16位为AX,分割为8位寄存器AH-AL。指令ret返回用到。
EBX:Base Register。
ECX:Count Register。
EDX:Data Register。
指针寄存器:EBP,ESP可作为通用寄存器,即可存储算术逻辑运算的操作数和运算结果。
EBP:Base Pointer,基指针寄存器,直接访问栈中的数据。
ESP:Stack Pointer,栈指针寄存器,只可访问栈顶。指令pop/push时自动变。
EIP:Instruction Pointer,指令指针寄存器,存放下次将要执行的指令在代码段中的偏移量。每走一条指令自动变一次,如果希望跳转后能返回继续就需要跳转前把它放入栈中,返回时出栈。
变址寄存器:主要用于存放存储单元在段内的偏移量。
ESI: Source Index,源变址寄存器。EDS:ESI即源串段寄存器:源串变址,ESI在串操作中自动增减。
EDI:Destination Index,目标变址寄存器。EES:EDI即目标串段寄存器:目标串变址,EDI在串操作中自动增减。
段寄存器:内存分段。这里最多为6个内存段,不同的内存段放入不同的东西。
ECS:Code Segment Register,代码段寄存器。
EDS:Data Segment Register,数据段寄存器。
EES:Extra Segment Register,附加段寄存器。
ESS:Stack Segment Register,栈段寄存器。
EFS:Extra Segment Register,附加段寄存器。
EGS:Extra Segment Register,附加段寄存器。
2.EBP 和 ESP
最简单的说法是:EBP 指向栈底 ,ESP指向栈顶。
分析一段简短的c代码
void Layer02() { int b = 2; } void Layer01() { int a = 1; Layer02(); }
其反汇编代码如下:
void Layer02() { 00413700 push ebp 00413701 mov ebp,esp 00413703 sub esp,0CCh 00413709 push ebx 0041370A push esi 0041370B push edi 0041370C lea edi,[ebp-0CCh] 00413712 mov ecx,33h 00413717 mov eax,0CCCCCCCCh 0041371C rep stos dword ptr es:[edi] int b = 2; 0041371E mov dword ptr [b],2 } 00413725 pop edi 00413726 pop esi 00413727 pop ebx 00413728 mov esp,ebp 0041372A pop ebp 0041372B ret
如上,可以看到,这几段函数调用反汇编代码的头两句都是一样的,调用时先保存栈底指针,在将栈底指针设为栈顶指针,此时的栈底就为被调用函数的栈的栈底。
push ebp mov ebp ,esp
结尾也都是一样的,一旦调用完毕,栈底栈顶都恢复到调用之前的状态。
mov esp,ebp pop ebp ret
上面代码是参考solidMango的博客 ,里面的配图简单形象,可以参考。
3.EDI ,ESI
ESI/EDI 分别叫做"源/目标索引寄存器"(source/destination index),因为在很多字符串操作指令中, DS:ESI指向源串,而ES:EDI指向目标串.
4.EAX,EBX,ECX,EDX
EAX 是"累加器"(accumulator), 它是很多加法乘法指令的缺省寄存器。
EBX 是"基地址"(base)寄存器, 在内存寻址时存放基地址。
ECX 是计数器(counter), 是重复(REP)前缀指令和LOOP指令的内定计数器。
EDX 则总是被用来放整数除法产生的余数。
经过这么一分析,可以知道,这一题的结果实际为102