前言
作者:
浪子花梦
,一个有趣的程序员 ~
时间:2020.8.9,一直想学逆向工程,刚好最近时间比较充足,每天除了刷算法题也可以学逆向了 . . .
此系列文章都是一些基础的文章,通过几个小例子快速的了解 Win32反汇编与OD的使用,在此作个笔记
如若对您有帮助,记得三连哟 ~
文章目录
OD介绍
在研究反汇编之前,我们先来了解一下以后会一直用的神器:
Ollydbg
1. Ollydbg 简介
Ollydbg 用作动态调试,一般用于逆向一些游戏,而对于软件的逆向一般是使用 IDA,以后我应该会学的,Ollydbg
集成了许多的脚本与插件,非常牛X 的一个逆向工具 . . .界面如下所示:
整个界面被分为五个区域,每一个区域都显示非常重要的数据集,对应如下所示:
- 反汇编窗口:显示被调试程序的反汇编代码(地址栏、HEX数据栏、汇编指令栏、注释栏)
- 寄存器窗口:显示当前所选线程的CPU寄存器的内容
- 信息窗口:显示反汇编窗口中选中的第一个命令的参数及一些跳转的目标地址、字符串等
- 数据窗口:显示内存或文件的内容
- 堆栈窗口:显示当前线程的堆栈
我们会在下面的示例中可以观察到上面对应的数据 . . .
2. 调试方式
- 用 OD调试器直接打开进程
- 附加到已经打开的进程(暂时不学习)
3.定位地址
Ctrl + G 输入 Win32 API函数名(如:MessageBoxW、printf)
4. 断点
- 直接在选中的行上按下 F2
- bp 指令地址(Win32 API 函数名或者地址)
5. 调试中经常用到的几个快捷键
- F2:设置断点
- F8:单步步过
- F7:单步步入
- F9:运行程序
- Ctrl + F9 执行到返回
- Alt + F9 执行到用户代码
- Ctrl + G 查找表达式(定位)
- Alt + B 查看断点
对于OD 就介绍到这里,以后遇到再说 . . .
.
全局变量赋值的反汇编形式
首先,我们准备需要逆向的程序,此例子是和全局变量有关的,所以我们准备的代码如下所示:
// 全局变量反汇编演示
#include <Windows.h>
#include <iostream>
using namespace std;
int num = 1;
int main()
{
MessageBox(nullptr, nullptr, nullptr, MB_OK);
num = 0x666666;
__asm mov num, 0x999999
return 0;
}
其中调用了一个 Win32 的API,消息盒子,并且我们在其中嵌入了一条汇编代码 . . . 下面我们来通过 od对这个程序进行反汇编 . . .
如下所示,我们将程序直接插进 od中即可:
下面我们来研究上面所写的 cpp 反汇编代码:
首先我们进行 OD界面,按 Ctrl + G 搜索 MessageBoxW表达式进行定位,如下所示:
红色框的部分就是 MessageBoxW内部的汇编指令,我们按F9 将程序执行到红色断点处,然后按F8 单步执行到如下的指令出,如下所示:
可见消息盒子已经弹出来了,由此发现, MessageBoxW的内部调用了 MessageBoxTimeoutW的方法 . . .
因为 MessageBoxW 方法是在 main主函数中调用的,所以我们按下快捷键 Alt + F9 一定会返回到 main方法中,效果如下所示:
红色框是 main内部的汇编指令,箭头所指的是上一个方法返回的地方,我们发现返回到了 MessageBoxW 的下一条语句 . . .
现在我们把 OD 与 VS 相对看一下,如下所示,我后将OD中加入了注释(红色框):
对应的汇编代码中,有压栈平衡的知识,以后的文章中会讲到,这些指令学过 8086的应该都能看懂,所以在此就不多讲了,下面我们看其它的例子吧 . . .
.
.
函数调用的反汇编形式
上面的例子中,我们已经演示了如何去调用 MessageBoxW这个方法,下面我们自己实现一个函数,按F7单步步入执行,体验一下数据世界的变化吧 . . .
C/C++ 函数调用翻译成汇编指令相当于 Call 一个 子程序调用
. . .
首先,我们准备需要逆向的程序,此例子是和函数调用有关的,所以我们准备的代码如下所示(比较简单的函数):
#include <Windows.h>
#include <iostream>
using namespace std;
int add(int n1, int n2) {
return n1 + n2;
}
int main() {
MessageBox(0, nullptr, nullptr, MB_OK);
int sum = add(0x11, 0x22);
printf("0x%06x\n", sum);
return 0;
}
因为调用函数的地方用的都是常量参数,所以VS会自动优化成 int sum = 0x33; 所以我们需要将VS优化进行设置,方式如下所示:
设置为 已禁用即可 . . .
按下来,我们就可以使用 OD 来调试这个程序了,我们快速的定位到 main函数中,然后在 MessageBoxW的下面按F2 设置断点,将程序快速的执行到这边,这样我们就可以研究 cpp文件中的代码执行数据的变化,如下所示:
我们在观看反汇编窗口的同时,一定要留意其它窗口中的数据,这样对于我们研究是非常有好处的 . . .
下面我们按 F7 单步进入 call add 指令中,发现add函数的指令集如下所示:
OD 的功能是非常强大的,它已经把子程序块用 {} 给括起来了,使我们更容易接收 . . .
在这个子程序块中,访问了两个内存单元:
dword ptr [ebp + 8]
dword ptr [ebp + c]
我们执行完这个子程序块后,eax的值是0x33,单步执行到下面的指令处,将eax给到一个内存单元,我们就可以通过 dd命令查看这个内存单元中的数据,如下所示:
最后,我们在main函数的返回处设置断点,然后快速的执行到这个地方,发现其中 call了 printf函数,所以在控制台中打印出了一个数据,如下所示:
函数调用的简单使用就介绍到这里,下面再介绍一点其它的吧 . . .
.
.
加法计算与内存单元长度的修饰
我们来研究一下加法计算在汇编中的表示,并且我们来了解内存单元长度的不同,造成结果的不同 . . .
我们来看一下下面准备的代码,其中包含指针变量,汇编指令,是一个比较杂的程序,也是对我们了解反汇编有一个很大的帮助,如下所示:
#include <Windows.h>
#include <iostream>
using namespace std;
int main() {
int i = 1;
int* p = &i;
i = i + 0x100;
__asm {
mov eax, p
add [eax], 0x22222220
}
i = i + 0x200;
printf("0x%08x\n", i);
return 0;
}
首先, i = 1,然后 i 加上 0x100,然后通过指针变量间接的又加上了 0x22222220,最后加上了 0x200,所以不出意外的话结果应该是:
0x22222521
当我们执行这个程序时,输出如下的结果:
0x00000321
这就有疑惑了,这是为什么呢? 下面就让我们通过OD 对这个程序进行反汇编,看看它的底层究竟发生了什么事儿 . . .
我们直接定位到main的程序中,其中一些汇编指令对应的代码我已经写好注释了,如下所示:
我们来看 mov eax, p 这条指令的下一条指令,如下所示:
我们将 add byte ptr [eax], 20 与 add [eax], 0x22222220 进行对比一下,发现 22222220 变成了 20,这是为什么呢?因为 add byte ptr [eax], 20 前面用 byte ptr 进行了修饰,只取 22222220 的一个字节8位,所以只取了 20,在 cpp 代码中,不指定修饰内存单元长度,则默认为一个字节,下面我们修改代码如下所示:
#include <Windows.h>
#include <iostream>
using namespace std;
int main() {
int i = 1;
int* p = &i;
i = i + 0x100;
__asm {
mov eax, p
add dword ptr [eax], 0x22222220
}
i = i + 0x200;
printf("0x%08x\n", i);
return 0;
}
在嵌入的汇编代码中, 加入了 dword ptr 长度修饰,这下子就能完全保留后面数的每一位了,OD 中的调试如下所示:
输出的结果为:
各长度的区别:
- 字节(1字节):BYTE类型 (unsigned char) 0 ~ 255 表示成十六进制 0 ~ 0xFF
- 字(2字节):WORD类型 (unsigned short)0 ~ 65535 表示成十六进制 0 ~ 0xFFFF
- 双字 (4字节):DWORD类型 (unsigned int)0 ~ 4294967295 表示成十六进制 0 ~ 0xFFFFFFFF
.