-
-
[原创]《加密与解密》的学习
-
发表于: 2022-9-16 00:22 6211
-
最近好迷茫,所以又斥巨资买了一本充满力量的书,那就是看雪的《加密与解密》,也来彻底地玩一玩逆向吧。
<!--more-->
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 2 3 4 5 6 7 8 9 10 11 12 13 14 | void CTrackMeDlg::OnBnClickedButton1() { / / TODO: 在此添加控件通知处理程序代码 HWND hWnd = AfxGetMainWnd() - >m_hWnd; QWORD Uaddr; BYTE Mark = 0 ; Uaddr = (QWORD)MessageBoxA; Mark = * (BYTE * )Uaddr; if (Mark = = 0xCC ) { MessageBoxA(hWnd, "be tracked" , "MessageBox" , 0 ); return ; } MessageBoxA(hWnd, "Very OK" , "MessageBox" , 0 ); } |
正常运行结果如图所示
如果放到调试器,对 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 2 3 4 5 6 7 8 9 10 | #include<bits/stdc++.h> int main(){ int sum = 0 ; system( "pause" ); for ( int i = 1 ;i< = 10000 ;i + + ){ sum + = i; printf( "%d\n" , sum ); } } |
我们可以很清晰看到这里的循环结构,然后我们看到循环变量存在 ebx
寄存器中,我们在循环体中下一个条件断点,观察程序的运行。
然后发现程序成功在我们指定的条件下面断住了。
静态调试
待更
赞赏
- [原创]强网杯S8初赛pwn writeup 1181
- [原创]KCTF2024 第八题 writeup 2700
- 腾讯游戏安全大赛2024决赛题解 11468
- [原创]腾讯游戏安全大赛2024初赛题解 10787
- 2022的腾讯游戏安全竞赛复盘 11600