【vc++精品源码栏目提醒】:网学会员在vc++精品源码频道为大家收集整理了“软件逆向工程教程 - 其它资料“提供大家参考,希望对大家有所帮助!
第一课 逆向分析基础知识1.1 调用约定在分析汇编代码时总是要遇到无数的 Call,对于这些 Call,尽量要根据 Call 之前传递的参数和 Call 的返回值来判断 Call 的功能。
传递参数的工作必须由函数调用者和函数本身来协调,
计算机提供了一种被称为栈的数据结构来支持参数传递。
当参数个数多于一个时,按照什么顺序把参数压入堆栈。
函数调用后,由谁来把堆栈恢复。
在高级语言中,通过函数调用约定来说明这两个问题。
常见的调用约定有:【例】按__stdcall 约定调用函数 test2Par1 Par2push par2 参数 2push par1 参数 1call test2push ebp 保护现场原先的 EBP 指针mov ebp esp 设置新的 EBP 指针,指向栈顶mov eax ebp0C 调用参数 2mov ebx ebp08 调用参数 1sub esp 8 若函数要用局部变量,则要在堆栈中留出点空间…add esp 8 释放局部变量占用的堆栈pop ebp 恢复现场的 ebp 指针ret 8 返回(相当于 ret add esp8)其堆栈调用示意图:资料函数调用堆栈变化分析1.2 局部变量在子程序内部说明的变量称为局部变量,局部变量的作用域是其所在的子程序。
从汇编角度来看,局部变量就是一个临时堆栈缓存,用完释放。
例如这个实例:附件:local.zip其反汇编代码如下(红体字为局部变量):00401000 gt/ 6A 04 push 4 /Arg2 0000000400401002 . 6A 03 push 3 Arg1 0000000300401004 . E8 16000000 call 0040101F Add.0040101F00401009 . 8BD8 mov ebx eax0040100B . 6A 00 push 0 /ExitCode 00040100D . FF15 00204000 call ltampKERNEL32.ExitProcessgt ExitProcess0040101F / 55 push ebp 保护现场原先的 EBP 指针00401020 . 8BEC mov ebp esp 设置新的 EBP 指针,指向栈顶00401022 . 83EC 04 sub esp 4 分配局部变量所有空间00401025 . 8B45 0C mov eax ebpC 调用参数 200401028 . 8B5D 08 mov ebx ebp8 调用参数 10040102B . 895D FC mov ebp-4 ebx 参数 1 放局部变量里0040102E . 0345 FC add eaxebp-4 参数 2 与局部变量相加00401031 . 83C4 04 add esp 4 释放局部变量所有空间00401034 . 5D pop ebp 恢复现场的 ebp 指针00401035 . C2 0800 retn 81.3 返回值在调试程序时,不要见 Call 就跟进,在 Call 之前所做的所有 PUSH 动作以及对寄存器的操作都可能是在给函数传递参数,而函数的返回值一般都放在 EAX 里面,当然这个值可能是一个指针,指向一个数据结构。
从汇编角度来看,主要有如下形式:1通过寄存器返回函数值;2通过参数按引用方式返回函数值;3通过全局变量返回函数值;4通过处理器标志返回函数值;一般情况下,由 retrun 操作符返回的值放在 EAX 寄存器之中,如果结果超过这个寄存器的位容量,那么该结果的高 32 位会加载到 EDX寄存器中。
如果返回一个含有几百个字节的结构或者一个近似大小的对象,编译器会在不告诉程序的情况下,给函数传递一个隐式参数,这个指针指向保存的返回结果。
1.4 启动函数在编写 Win32 应用程序时,都必须在源码里实现一个 WinMain 函数。
但 Windows 程序执行并不是从 WinMain 函数开始的,首先被执行的是启动函数相关代码,这段代码是编译器生成的。
启动代码完成初始化进程,再调用 WinMain。
标准编译器通常包含启动代码在内的库文件源码,例如 Visual C中,启动代码存放在 CRTSRCcrt0.c 文件中。
所有的 C/C运行时启动函数的作用基本都是相同的:检索指向新进程的命令行指针,检索指向新进程的环境变量指针,全局变量初始化,内存堆栈初始化等。
当所有的初始化操作完毕后,启动函数就调用应用程序的进入点函数。
调用 WinMain 如下所示:GetStartupInfo ampStartupInfoInt nMainRetVal WinMainGetModuleHandleNULLNULLpszCommandLineAnsiStartupInfo.dwFlagsampSTARTF_USESHOWWINDOWStartupInfo.wShowWindow:SW__SHOWDEFAULT当进入点返回时,启动函数便调用 C 运行库期的 exit 函数,将返回值(nMainRetVal)传递给它,进行一些必要处理,最后调用系统函数 ExitProcess 退出。
其他一些编译器,如
Delphi、BorLand C开发包中都有相应的启动代码。
在绝大数情况下,我们对启动代码并不需要关心。
对于逆向分析人员来说,首要的任务是找到 Winmain 函数。
WinMain 函数原型如下:int WINAPI WinMainHINSTANCE hInstance // 当前实例的句柄HINSTANCE hPrevInstance // 前一个实例的句柄LPSTR lpCmdLine // 命令行的指针int nCmdShow // 窗口的显示状态其中参数 hInstance 一般通过 GetModuleHandleA 函数进行获取的,这对识别 WinMain 函数有些帮助。
另外,对 WinMain 的调用通常放在启动函数代码结尾部分,后面通常跟着诸如 exit 或 XcptFilter 之内的两、三个函数。
例如下