开篇总要说点什么,我也避免不了这种套俗.老生常谈还是那些东西.谁让我们现在的机子大部分都是32位的系统呢.当然也有不少人使用64的了.既然大部分都在32的机子,那我们还是说32咯.
在编写32位机子的应用程序的时候,都有一个启动函数WinMain,不管是C或D还是E,但是Windows程序执行并不是从这个函数开始执行的,首先被执行的是启动函数的相关代码,这段代码是由IDE生成的,也就是编译器.首先由启动代码完成一些初始化的进程,再调用WinMain函数.
对已C/C++的程序来说,它调用的事C/C++运行时的启动函数,这个函数负责对C/C++运行库进行初始化.所有的C/C++运行时启动函数的作用都是相同的:检索指向新进程的命令行指针,检索指向新进程的环境变量指针,全局变量初始化,内存堆栈初始化等.当所有的初始化操作完毕后,启动函数就调用应用程序的进入点函数.
在我们E中为了省事,避免重复造轮子的这种繁琐无聊工作,可以新建一些子程序,作为一个长吁的集块,用来实现一个特定的功能.在C和D中当然也有的.在D中又按有无返回值称为函数和过程,而在C/C++中称为函数,下面我们就说说函数这个东西.
在研究逆向分析/找CALL的时候,重点就是放在函数的识别以及参数的传递上这是明智的做法,这样可以在成千上万行代码中将我们的精力集中放在某一段代码上.一个函数有如下几部分组成:函数名 入口参数 返回值函数功能.程序中通过调用程序调用函数,而函数执行完之后又会返回调用程序继续执行函数是怎么知道要返回的地址呢?实际上调用函数的代码保存了一个返回地址,并连同参数一起传递给被调用的函数.有许多中的方法可以实现这个功能的,在大多数情况下,编译器都使用CALL和RET指令来调用函数与返回调用位置.所以我们在找CALL的时候,就是在OD里面找程序调用的一个关键函数(子程序).
CALL指令与跳转指令功能类似,所不同的是,CALL指令保存返回的信息,也就是将其之后的指令地址压入堆栈的顶部,当遇到RET指令结束函数的时候来执行(但是要注意哦,并不是所有的RET指令都标识函数的结束).这一机制使得很容易的把函数调用和其他跳转指令区别开来.
- 004011DF |. 59 pop ecx
- 004011E0 |. 85C0 test eax, eax
- 004011E2 |. 75 08 jnz short ReverseM.004011EC
- 004011E4 |. 6A 1C push 1C
- 004011E6 |. E8 B0000000 call ReverseM.0040129B
- 004011EB |. 59 pop ecx
- 004011EC |> 8975 FC mov dword ptr ss:[ebp-4], esi
- 004011EF |. E8 E1070000 call ReverseM.004019D5
- 004011F4 |. FF15 3C404000 call near dword ptr ds:[<&KERNEL32.GetCommandLineA>; [GetCommandLineA
- 004011FA |. A3 D8594000 mov dword ptr ds:[4059D8], eax
比如说这个CALL,004011EF |. E8 E1070000 call ReverseM.004019D5,它上面没有PUSH入栈的命令,但是实际上却是PUSH 004011F4,然后 JMP ReverseM.004019D5,这样的操作.于是呢就可以通过定位CALL机器指令或利用RET指令结束的标识来识别函数.CALL指令的操作数就是所调用函数的首地址.先看一个简单的例子.
- int Add(int x,int y)
- main()
- {
- int a=3,b=4;
- Add(a,b);
- return 0;
- }
- Add (int x,int y)
- {
- return (x+y);
- }
很简单,先定义两个int类型的变量a和b,然后调用函数Add,函数Add在下面的声明是返回输入的两个参数的值,用E写呢大体就是下面这个样子:
启动窗口创建完毕
全局变量 a 整数型
全局变量 b 整数型
a=3,b=4
相加子程序(a,b)
返回
相加子程序
局部变量 x 整数型
局部变量 y 整数型
返回 x+y
而把他们反汇编的样子呢就是下面这样了
- push 4
- push 3
- call 00401010 ;这个地方是调用"相加子程序",这个子程序在内存中的地址是00401010
- add esp,8
- xor eax,eax
- retn
- nop
- 00401010 mov eax,dword ptr [esp+8] ;一般[esp+*]这种格式的就表示是变量
- 00401014 mov ecx,dword ptr [esp+4]
- add eax,ecx
- retn
这是函数直接调用方式,使得程序很简单,所幸的是大部分的情况都是这样的.当然也有一些情况不是这样,程序调用函数是间接调用的,也就是通过寄存器传递函数地址或动态计算函数地址的,比如:
CALL [4*eax+10h]