在各个语言中有数据结构这个东东,它是计算机存储 组织数据的方式.逆向分析时,确定了数据结构后,算法就容易得到了.有时候事情当然也会反着来,根据特定的算法来判断数据结构.
先看局部变量的
局部变量十亿个函数内部定义的变量,只有在函数内才能使用,如计数器,临时变量等.使用局部变量带来的好处,使程序模块化封装变的可能.从汇编语言角度看,局部变量就是在堆栈中进行分配,函数执行完毕后释放这些堆栈,或者直接把局部变量放在寄存器中,我在视频中也告诉各位把寄存器可以理解为变量.
第一种方法是用堆栈存放局部变量
局部变量在程序中用"sub esp,8"语句为局部变量分配空间,用[ebp-xxxx]寻址调用这些变量,而参数调用相对于ebp偏移量是正的,即[ebp+xxxx],因此在逆向时比较容易区分开.编译器在优化模式时,则通过esp寄存器直接对局部变量与参数寻址了.当函数退出时,用'add esp,8'平衡堆栈,以释放局部变量所占据的内存.有些编译器,如Delphi通过给esp加一个负值来进行内存分配.另外,编译器可能会用"push reg"指令来取代"sub esp,4"指令,以节省几个字节.局部变量分配堆栈一般有三种形式:
1.
sub esp,n
.....
add esp,n
2.
add esp,-n
....
sub esp,-n
3.
push reg
.....
pop reg
看看第三种形式的实例"push reg"指令来取代"sub esp,4"指令的.
先看C的代码:
- int add(int x,int y)
- int main(void)
- {
- int a=5,b=6; //声明局部变量
- add (a,b);
- return 0;
- }
- int add(int x,int y)
- {
- int z; //声明局部变量
- z=x+y;
- return (z);
- }
将他编译后反汇编.
- 00401000 /$ 55 push ebp
- 00401001 |. 8BEC mov ebp, esp
- 00401003 |. 83EC 08 sub esp, 8 ; 为局部变量分配内存
- 00401006 |. C745 FC 05000>mov dword ptr ss:[ebp-4], 5 ; 参数1放到局部变量[ebp-4]中
- 0040100D |. C745 F8 06000>mov dword ptr ss:[ebp-8], 6 ; 参数2放到局部变量[ebp-8]中
- 00401014 |. 8B45 F8 mov eax, dword ptr ss:[ebp-8]
- 00401017 |. 50 push eax
- 00401018 |. 8B4D FC mov ecx, dword ptr ss:[ebp-4]
- 0040101B |. 51 push ecx
- 0040101C |. E8 09000000 call local.0040102A
- 00401021 |. 83C4 08 add esp, 8
- 00401024 |. 33C0 xor eax, eax
- 00401026 |. 8BE5 mov esp, ebp
- 00401028 |. 5D pop ebp
- 00401029 /. C3 retn
;函数add (int x,int y)反汇编代码如下:
- 0040102A /$ 55 push ebp
- 0040102B |. 8BEC mov ebp, esp
- 0040102D |. 51 push ecx ; 为局部变量分配内存
- 0040102E |. 8B45 08 mov eax, dword ptr ss:[ebp+8] ; 取参数1
- 00401031 |. 0345 0C add eax, dword ptr ss:[ebp+C] ; 参数1+参数2
- 00401034 |. 8945 FC mov dword ptr ss:[ebp-4], eax ; 将相加的结果放到局部变量[ebp-04]中
- 00401037 |. 8B45 FC mov eax, dword ptr ss:[ebp-4] ; 将返回结果放到eax中
- 0040103A |. 8BE5 mov esp, ebp
- 0040103C |. 5D pop ebp
- 0040103D /. C3 retn
在函数add()里不存在"sub esp,n"这样的指令,程序是通过一句"push ecx"指令来开辟一块堆栈空间的,然后用[ebp-04]来访问这块空间
局部变量的起始值是随即的,是其他函数执行完后留在堆栈中的垃圾数据,因此需要对其初始化.初始化局部变量有两种方法:一种是通过mov 指令为变量赋值,如"mov [ebp-04],5";另一种是使用Push 指令直接将值压入堆栈,如push 15.
第二种是利用寄存器
除了堆栈占用了2个寄存器外,编译器会利用剩下的6个通用寄存器尽可能有效地存放局部变量,这样可以产生最小的代码,提高效率.如果寄存器不够用,编译器将会将变量放到堆栈中.逆向分析时要注意局部变量的生存周期比较短,必须及时确定当前寄存器的变量是那个变量.