首先需要了解的两个概念。
静态分析法是在不执行代码文件的情形下,对代码进行静态分析的一种方法。静态分析时并不执行代码,而是观察代码文件的外部特征,获取文件的类型(EXE、DLI、DOC、ZIP等)、大小、PE头信息、Import/ExportAPI、内部字符串、是否运行时解压缩、注册信息、调试信息、数字证书等多种信息。此外,使用反汇编工具(Disassembler)查看内部代码、分析代码结构也属于静态分析的范畴。我们通过静态分析方法会获得多种信息,这些信息是进行动态分析的重要参考资料,对代码的动态分析非常有帮助。
动态分析法是在程序文件的执行过程中对代码进行动态分析的一种方法,它通过调试来分析代码流,获得内存的状态等。通过动态分析法,我们可以在观察文件、注册表(Registry)、网络等的同时分析软件程序的行为。此外,动态分析中还常常使用调试器(Debugger)分析程序的内部结构与动作原理。在逆向分析代码时,通常先采用静态分析法收集代码相关信息,然后通过收集到的信息推测程序的结构与行为机制。这些推测对动态分析具有非常高的参考价值,能为动态分析提供很多创意。对程序代码进行逆向分析的过程中,灵活使用静态与动态两种分析方法,动静结合,可以极大地提高代码的分析效率,增加分析的准确性,缩短代码分析时间。
了解完上述概念后,我们尝试进行代码调试。源文件附文末。
#include "windows.h"
#include "tchar.h"
int _tmain(int argc, TCHAR *argv[]){
MessageBox(NULL,
"Hello Word!",
"bylibrary!",
MB_OK
);
return 0;
}
首先使用OllyDbg打开Hello world.exe程序
代码窗口默认用于显示反汇编代码,还用于显示各种注释、标签,分析代码时显示循环、跳转位置等信息
寄存器窗口实时显示CPU寄存器的值,可用于修改特定的寄存器
数据窗口以Hex/ASCII/Unicode值的形式显示进程的内存地址,也可在此修改内存地址
栈窗口实时显示ESP寄存器指向的进程栈内存,并允许修改
调试器停止的地点即为HelloWorld.exe执行的起始地址(4011A0),它是一段EP(EntryPoint,入口点)代码,如下所示。(建议初学者不要自己build exe文件.....会晕的.......)
地址进程的虚拟内存地址(Virtual Address,VA)
指令IA32(或x86)CPU指令
反汇编代码将OPcode转换为便于查看的汇编指令
注释调试器添加的注释(根据选项不同,显示的注释略有不同)
接下来继续调试,请记住,我们的目标是在mainO函数中找出调用MessageBox0函数的代码。EP(EntryPoint,入口点)EP是Windows可执行文件(EXE、DLL、SYS等)的代码入口点,是执行应用程序时最先执行的代码的起始位置,它依赖于CPU。
OllyDbg基本指令
指令 | 快捷键 | 含义 |
---|---|---|
Restart | Ctrl+F2 | 重新开始调试(终止正在调试的进程后再次运行) |
Step Into | F7 | 执行一句OPcode(操作码),若遇到调用命令(CALL),将进入函数代码内部 |
Step Over | F8 | 执行一句OP code(操作码),若遇到调用命令(CALL),仅执行函数自身,不跟随进入 |
Execute till Return | Ctrl+F9 | 一直在函数代码内部运行,直到遇到RETN命令,跳出函数 |
在EP代码的地址处使用Step Into(F7)指令,进入40270C函数 。
不理解上述的汇编代码没有关系,因为我们尚未掌握汇编语言。我们继续往下看,我们的目标是main()函数
4027A1地址处有一条RETN指令,它用于返回到函数调用者的下一条指令,一般是被调用函数的最后一句。
我们不停的按F7执行,在4027A1地址处的RETN指令上执行Step over(F8)或Execute till Return(Ctrl+F9)命令,继续操作。按F7/F8执行RETN指令,程序会跳转到4011A5地址处。
执行4011A5处的跳转命令JMP 0040104F,跳转至0040104F,结果如图所示:
代码看上去比较复杂,他是visual C++的启动函数,跟踪这些代码,就能发现我们要查找的main函数。
PS:第一次接触上述代码,你可能会觉得比较陌生,甚至分不清他们是用户代码,还是启动函数。但是反复调试代码的过程中你会发现,由visual C++编写的可执行文件大都与上述形式类似。熟悉了这些启动函数后,再调试代码时就能很快识别并跳过。不同的开发工具生成的启动函数不同,即使同一种开发工具,产生的启动函数也随版本不同而不同。
从上图的40104F地址处,每执行1次Step Info(F7)命令就下移1行代码,移动到401056地址处的CALL402524函数调用指令时,执行Step Info(F7)命令,进入402524函数。
正如上图所示,我们很难把402524函数称为main()函数,因为在它的代码中并未发现调用MessageBox() API的代码。执行Execute till Return(ctrl+F9),跳转到402568处的RETN指令,然后执行F7或F8执行RETN指令,跳出402524函数,返回至40105B处。
同样,在40104F地址处执行F7命令调试,遇到函数调用就进入函数查看代码(F7),确认是否为main()函数。若不是main()函数,则使用ctrl+F9命令跳出相关函数,继续以相同方式调试。
调试过程中会遇到以下代码,4010E4处的call <jmp.&KERNEL32.GetCommandLineW>指令是调用Win32 API的代码。现在,我们还不需要进入被调用的函数,直接使用F8跳过。
PS: 4010EE地址处是调用00401C5A函数的指令,执行后进入函数,再按ctrl+F9跳出函数,由于00401C5A函数中含有循环语句,所以跳出函数时需要花费一些时间。
如果调试一切正常,你会看到以下代码
401144地址处有一条CALL 401000指令,用于调用401000函数,使用Step Into(F7)命令进入401000函数,如下图
401000函数内部出现了调用MessageBoxW()API的代码,该API函数的参数为“www.reversecore.com”与“Hello World!”两个字符串。这与我们前面的源代码内容一致,由此可以断定,401000函数就是我们一直在查找的main()函数。