之前写过一篇文章,主要是使用OllyDbg逆向微信寻找HOOK地址。由于微信复杂很多,这里我们就自己简单写了一个C语言程序,通过使用OllyDbg逆向这个C语言函数,从而一步一步地介绍如何使用OllyDbg分析和寻找HOOK地址。另外需要注意的是,在第一次接触逆向过程中网上很多案例中使用OD(OllyDbg),所以本文也是以OD作为样例的。目前原版OD不支持调试64代码。在stackoverflow上有网友推荐x64dbg,有兴趣的朋友可以试试。
开发环境
简单介绍以下开发环境,主要是包括以下几个工具:
- Windows 10
- VS Code with C/C++ Extension
- Mingw-w64(注意安装686版本,编译出来的是32位可执行文件;86_64版本编译出来的是64位)
- OllyDbg 1.10
C语言介绍
首先我们先简单介绍一下我们这个C语言程序。这个程序内部定义了三个函数,主要是为了方便后续我们使用OllyDbg逆向查看汇编语言。下面先简单介绍一下:
无参数无返回值
void g() { printf("This is g(),No parameter No return!\n"); }
这个函数就是简单打印一个字符串,没有任何参数和返回值。
一个参数无返回值
void f(int n) { printf ("This is f(), one parameter is No return!, Number: %d\n", n); }
这个函数有一个参数,但是没有返回值。
两个参数有返回值
int h(int m, int n) { printf("This is h(), two parameter is %d and %d\n", m, n); return m + n; }
这个函数有一个参数,但是没有返回值。
主函数
在主函数中,我们先把这三个函数的地址打印出来,然后等待用户输入一个数字,根据不同的输入调用不同的函数。内容非常简单,大家看一下代码就懂了:
//主函数 int main (int argc, char * argv[]) { int i = 0; int j = 0; int l = 0; int num = 0 ; printf("g(No parameter No return ) is at %p\n", g); printf ("f(One parameter One return)) is at %p\n", f); printf("h(Two parameter One return) is at %p\n", h); printf("-------------------------------------------------------------------------\n"); printf("please input one number in [1,3],Then press \"Enter\":\n"); while (1) { scanf("%d", &num); if (num == 1) { int n = i++; f(n); } else if (num ==2) { int n = j++; int m = l++; h(n, m); } else if (num == 3) { g(); } else { printf("Please input one number in [1,3],Then press \"Enter\":\n"); } Sleep (1000); } }
接下来我们看看这个程序的运行结果:
当这个代码运行时,会把三个函数的内存地址打印出来,方便我们在OD中做验证。
使用OllyDbg逆向查找HOOK地址
本节我们将使用OllyDbg逆向上节中编译的exe可执行文件,并且通过分析查找HOOK地址。由于程序比较简单,很容易就在汇编主窗口就好到了三个函数的入口地址。如图所示:
图中三个函数的地址分别是0x004015C0,0x004015D5,和0x004015FF。与代码中输出的函数地址一样:
知道以上三个函数的内存地址以后,我们需要寻找程序/模块的基地址,通过函数地址和基地址计算地址偏移。后期在写HOOK脚本和调用脚本时,我们是需要使用地址偏移实时计算HOOK地址的。在OD中可以查看详细的程序/模块的基地址,通过地址范围可以判断每一个函数具体在哪个程序/模块范围内。如图所示:
如图我们可以判断三个函数在hello.exe的内存地址段内,基地址是0x00400000。因此对应的地址偏移分别是0x15C0,0x15D5,和0x15FF。接下来,我们分别在三个入口地址上设置断点。然后通过函数调用,单步调试汇编代码并且观察相关寄存器的变化情况。
使用OllyDbg设置断点并单步调试观察寄存器变化
通过在OD中设置断点我们可以单步调试汇编指令并观察每一个寄存器的数据变化情况和内存地址变化情况。如图所示,目前OD断点暂停在0x004015FF位置,通过查看调用堆栈的信息,可以知道这个调用是从地址0x004016CE调用的。我们可以点击调用堆栈的”Call from”地址跳转到调用的地方。
这个函数有一个输入参数,目前中断的地方并不能发现这个输入参数是放在哪个寄存器中的。在调用前寄存器中已经设置好了该函数的输入参数,因此我们跳转到调用前的位置,再次设置一个断点,然后看看寄存器的情况。
恢复上次断点后再次在函数的调用地址设置断点并调试程序。如上图所示,函数的唯一一个参数被放在EAX寄存器中,并且在函数调用前把EAX的地址存入ESP寄存器(ESP是一个调用栈顶指针,相对于EBP调用栈底指针)。因为这个函数只有一个输入,相对简单。下面我们对于有两个输入参数的函数h()再调试看看寄存器的情况。
使用OllyDbg观察多输入参数的寄存器变化情况
根据上节中的步骤,我们首先找到函数h()的地址,通过单步调试和调用堆栈找到Call from的位置,随后在Call from地址上设置断点并再次调试程序。如图所示:
上图中的汇编指令是一个非常典型的案例,对应于C语言代码中的自加、赋值、传值操作。对应与下图中的C语言代码:
int n = j++; int m = l++; h(n, m);
到目前为止,一顿猛如虎的汇编指令执行完毕以后,函数h()的两个参数分别被压入了ESP寄存器中。我们可以在数据窗口中查看ESP寄存器的值。如图所示:
总结
本文中通过OD(OllyDbg)逆向了一个简单的C语言程序,并且通过设置断点和单步调试观察各个寄存器的变化情况。学习了调用函数前如何处理函数的输入参数和变量自加的汇编指令。本文分别演示了具有一个输入参数和具有两个输入参数的函数在调用前,汇编指令是如何准备输入参数和使用寄存器保存参数的。下一步,我将根据本文中涉及到的函数、调用地址和寄存器操作,使用Frida框架,编写一个简单的内存注入HOOK脚本和原生应用方法调用教程。
相关阅读:
《使用Frida编写HOOK实现内存注入》
《使用Frida实现内存注入主动调用函数》
扫码联系船长