《加密与解密》的学习
最近好迷茫,所以又斥巨资买了一本充满力量的书,那就是看雪的《加密与解密》,也来彻底地玩一玩逆向吧。
Windows操作系统
曾经的我,认为 Linux
天下第一好用,(虽然现在我也那么认为)但是 windows
作为受众很广的操作系统,很多 windows
的程序也是有必要去学习一下的。
win32API
当今大部分 windows
程序都是 GUI
,GUI
是通过一系列 API
来完成的,具体帮助手册自行下载。
我们只需要知道一部分的,并且用到什么就学什么好了,比如常见的从控件中获取输入值,用接口 GetDlgItemTextA
,弹窗反馈 MessageBoxA
,当然这是比较常见的,具体调试的时候还得具体情况具体分析。
windows
程序运行比较依赖动态链接库(dll),可以理解为 Linux
下的 .so
文件。windows
比较内核的三个动态链接库为
kernel(kernel32.dll)
:提供操作系统的核心服务。user(user32.dll)
:提供用户输入和输出的接口。GDI(GDI32.dll)
:提供图形设备接口。
这里需要注意一点,就是很多接口的后缀 A
或者 W
表示它处理字符的一个字符集, A
表示 ASCII
码,w
表示 unicode
码。
动态调试器
这本书介绍了很多的动态调试器,这里我还是比较喜欢 x32dbg
,所以其它的我也不一一解读了,但是相同的特性也能拿来讲一讲记一记。
直接调试
打开调试器,在文件 ->
打开 中选择自己要调试的文件,然后载入。
附加调试
打开调试器,在文件 ->
附加 中选择自己要附加调试的进程,然后载入。
感觉附加调试会好一点吧,毕竟能先让程序运行到自己想调试的点然后再去调试,这样省了前面入口点的一些操作,但是我们要调试关键信息,一定是要下断点(break point)的。
这里我们直接用 chap02\OllyDbg调试器\2.1.4 基本操作\bin\ASCII版\TraceMe.exe
来试试手。
调试器整体呈这样,最上面一行肯定是标签栏,工具栏,选项卡。剩余窗口大致分为四块。CPU
选项卡中的窗口表现出来就是代码窗口,最右边的窗口时寄存器窗口,左下角的窗口是内存窗口,右下角的窗口是栈窗口,最下面一行的文本框可以用于打命令使用。
CPU窗口
大概分了五列,如下图所示。
- 对于第一列,更多的是标识跳转的作用,以及设置断点。
- 第二列标识了当前内存地址的地址,双击可以显示改行的相对地址偏移,再次双击恢复。
- 第三列标识了该地址的内存字节,双击可以下断点。
- 第四列标识了该字节码的反汇编代码,选中使用 空格 键可以修改汇编代码。
- 第五列提供了一些内存地址或者是寄存器的值,也可以用
;
键去添加注释。
断点
断点大致分为以下几种类型
INT 3
断点- 硬件断点
- 内存断点
- 消息断点
- 条件断点
INT 3断点
INT 3
是一条汇编指令,其机器码是 0xCC
所以也叫 CC
指令。执行这个指令的时候,会抛出一个 break point exception
异常,这个异常会被调试器捕获到,因此能达到断点的目的。在打断点的时候,会把这个地址设置为 0xCC
,也就是 INT 3
的机器码。优点是可以设置很多个断点,因为我们只要想,可以在任意地址把值改成 0xCC
以此达到断点的目的。但是带来的缺点就是会修改程序的内存,改变了原机器码,可能会被程序检测到。
比如这样的一个 MFC
程序,附件下载
source:
1 | void CTrackMeDlg::OnBnClickedButton1() |
正常运行结果如图所示
如果放到调试器,对 MessageBoxA
这个函数下断点的话,那么就会导致出现另一个不同的运行结果。
因为我们断点设置在 MessageBoxA
上,而程序判断了 MessageBoxA
调用地址是否为 INT 3
指令(0xCC
),有的话就直接输出 be tracked
说明检测到这里下了 INT 3
断点。
如果我们要绕过检测同时又要求能断下来,那么我们可以在函数调用中间或者是末尾下 INT 3
断点。
硬件断点
硬件断点主要是通过 DRx
寄存器实现的,DR0~DR3
分别用于保存硬件断点的地址,那我们也可以看出来它最多能同时存在四个硬件断点。DR4-DR5
未公开具体作用, DR6
用于保存寄存器组状态, DR7
用于保存寄存器组控制。硬件断点不会改变程序字节码,因此它更难被检测,在断点选项中可以设置硬件执行断点,同样,对于一般内存来说,我们可以设置硬件访问断点。
设置完成之后我们在寄存器窗口拉到最下面,可以看到 DR
寄存器,我们可以从前四个寄存器中找到我们下的硬件断点的位置。硬件断点的优势劣势与前面的 INT 3
断点相对,硬件断点不能同时大量设置,但是它不会更改进程代码段的内存。
内存断点
内存(读/写/执行)断点是通过将某一块内存页标记为对应的不可(读/写/执行),当程序尝试对该内存(读/写/执行)的时候就会抛出异常,转而给调试器进行异常处理,若发现访问内存刚好是断点位置时,程序断住,否则正常进行(读/写/执行)操作。由于在执行的时候,会有一个读取命令的操作,因此我们如果在代码断设置了内存访问断点,执行到指定位置时同样会被断住。
比如这个程序,我们在某一条指令上下内存读取断点
我们再次按 F9
继续执行可以发现程序停在了我们下的断点位置。
不知为何在经过一番激烈的讨论之后,认定 x32dbg
应该是这里的技术细节没有实现,所以导致它只能在一个内存页设置,不能保证在指定位置断住,因此在一个内存页中可能多次被这个点断住,因为它可能没有比较访存位置与内存断点位置。
内存断点分持久的和一次性断点,一次性断点在断住之后即被删除。
消息断点
略
条件断点
我们如果希望在一个地方满足一定条件才断下来,这个时候我们可以 shift+F2
设置条件断点,比如我想在一个 10000
次的循环当中,看第 5000
次的执行结果,那么我们正常操作就是给循环体一个断点,然后 F9
5000
次,显然这么做会很麻烦,那么我们可以设置一个条件,让它在指定条件才断住,加入循环变量存储在 rcx
当中,那么我们可以设置 rcx==5000
。
source:
1 |
|
我们可以很清晰看到这里的循环结构,然后我们看到循环变量存在 ebx
寄存器中,我们在循环体中下一个条件断点,观察程序的运行。
然后发现程序成功在我们指定的条件下面断住了。
静态调试
待更