返回介绍

第十五章 - 硬编码序列号寻踪-Part3

发布于 2025-01-31 21:06:55 字数 19109 浏览 0 评论 0 收藏 0

我们来接着完成上一章留下那个硬编码 CrackMe 的作业,名字叫”Splish”。

用 OD 加载它。

OD 加载后停在了入口点处。

我们通过在反汇编窗口中点击鼠标右键选择-Search for-Name(label)in current module 查看 API 函数列表。

我们可以看到当前模块使用了哪些 API 函数。

我们可以看到使用了 GetWindowTextA 来获取序列号,MessageBoxA 来提示序列号正确或者错误。我们可以给这两个 API 设置断点,这里我们先来看看该程序的字符串列表。

单击鼠标右键选择-Search for-All referenced text strings。

这里我们可以看到提示输入了正确硬编码序列号的字符串-”Gongratulations, you got the hard coded serial”。我们在这个字符串上面双击鼠标左键,就可以来到引用了该字符串的代码处。

我们可以看到来到了验证序列号的代码块了。

可以看到使用 GetWindowTextA 获取用户输入的序列号,然后使用 MessageBoxA 来提示用户输入的序列号正确与否。我们在获取用户输入的序列号 GetWindowTextA 的调用处设置一个断点。

按 F9 键运行 CrackMe。

为了找到上面的硬编码序列号,我们随便输入一个错误的序列号,然后单击 Check Hardcoded 按钮。

我们可以看到断在了之前设置的断点处。

我们先来看看堆栈中的情况,Buffer 参数指向 403215 地址开始的内存单元,用于保存用户输入的序列号。

我们在数据窗口中定位到该缓冲区

该缓冲区会保存用户输入的序列号。我们按 F8 键单步执行该 API 函数。

由于单击 F8 键,该 API 函数得以执行,所以序列号保存到了该缓冲区中。

下一条指令会将 401353 保存到 EAX 中(记住,LEA 指令并不是移动指定地址内存单元中的内容,而是移动方括号中的值,这里是 401353)。我们通过在该指令上面单击鼠标右键选择-Follow in Dump-Memory address 来在数据窗口中定位到 401353 这个地址。

401353 指向字符串”HardCoded”,按 F7 键单步执行 LEA 指令。

解释窗口中也提示 401353 这个地址指向字符串”HardCoded”,执行 LEA 指令后,EAX 保存了 401353 这个地址。

接下来的 LEA 指令,EBX 会保存 403215 这个地址。

按 F7 键单步,EBX 保存 403215。

OD 中显示,403215 这个地址指向字符串”98989898”,也就是我们之前输入的错误序列号,我们像之前一样在数据窗口中转到 403215 这个地址。

可以看到 403215 指向的内存单元中保存了我们输入的错误序列号。

下一条指令检查 EAX=401353 这个地址指向内存单元的第一个字节是否为零。

我们可以在数据窗口中转到 401353 这个地址,可以看到保存了字符串”HardCoded”。

这里第一个字节是 48,解释窗口中提示该 ASCII 码对应的字符是’H’,是”HardCoded”字符串的第一个字节,不为零。

由于’H’不等于零,所以零标志位置 0,JE 不会跳转(记住,JE 指令当零标志位 Z 置 1 的时候跳转)。

继续单击 F7 键单步。

可以清楚的看到该指令将 EAX 指向内存单元(即字符串”HardCoded”) 的第一个字节,这里是 48,保存到 CL 寄存器中,接下来一条指令将 EBX 指向的内存单元(即我们输入的错误序列号) 的第一个字节保存到 DL 寄存器中。接着比较这两个字节是否相等,如果不相等,就跳转到 4013D2 地址处,弹出消息框提示”Sorry, please try again.”。

下图中验证了这一点。

按 F7 键,CL 寄存器的值为 48。

接下来一行 DL 寄存器保存我们输入的错误序列号的第一个字节。

按下 F7 键,可以看到 DL 的值为 39。

现在比较 CL 和 DL 的值。

OD 解释窗口中,字符’9’对应的 ASCII 码值为 39,即我们输入的错误序列号的第一个字符与字符’H’对应的 ASCII 码值为 48,即硬编码序列号”HardCoded”的第一个字符,进行比较。

可以看到由于它们不相等,JNZ 指令就会跳转到提示错误信息的代码处。如果它们相等的话,跳转将不会发生,我们可以通过双击零标志位 Z 来修改其值,让跳转不发生。

现在零标志位 Z 置 1 了,表示比较的两个字节是相等的。

接下来可以看到 EAX,EBX 的值递增 1,然后 JMP 指令又跳转回了循环的开始。

EAX 现在指向字符串”HardCoded”的第二个字节,我们可以看到会逐个字符依次比较,直到 EAX 指向的字节值为 0 为止(字符串结束标志为 0)。

EAX,EBX 分别加 1 以后将比较第二个字节,如果相等,则继续循环比较第 3 个字节,当”HardCoded”字符串都比较完毕了,CL 和 DL 还是相等的时候,就不会跳转到提示错误的消息框代码处。

由于现在比较完了”HardCoded”的所有字符,已经到了字符串的末尾,值为 0。

这里都为零,检测序列号结束。

接着 JZ 指令跳出循环。

现在我们到了显示正确消息框的代码块处。(由于我们修改零标志位 Z 的值)

每次 CL 与 DL 进行比较的时候,我们通过修改零标志位 Z 的值,让程序以为它们两个是相等的,从而提示正确的消息框。不管怎么说,我们现在已经知道了正确的序列号是”HardCoded”(要注意字母的大小写)

删除之前设置的所有断点,然后按下 Check Hardcoded 按钮。

弹出 Congratulations,you got the hard coded serial 正确序列号的消息框。

好了,现在来看最后一个硬编码序列号的 CrackMe 例子,第 16 章我们将探讨下一个话题。

这个 CrackMe 与之前稍稍有点不同,名字叫做 SamBo,我们用 OD 加载它。

这个窗口提示说“该程序入口点不在代码段,可能是自解压或者自修改文件,这样文件我们称之为被加壳或者被压缩。我们后面会深入探讨壳,这里我们先还是用 OD 加载它,尽管是加过壳的,我们还是可以尝试找到序列号的。

我们同意 OD 警告,将到达入口点处。

紧接着弹出一个窗口提示“代码段可能被压缩,加密,或者包含大量嵌入数据。代码分析可能是不可靠或者完全错误的。您仍要继续分析吗?”因为程序会在自己脱壳后继续运行原程序代码,所以我们选择 NO。

我们可以看到该 CrackMe 并没有像未加壳之前一样停在.text 节的 401000 处。

现在的入口点是 4D4001。

让我们来看看该程序的区段的情况,我们可以选择菜单项中的 View-Memory 或者单击工具栏中 M 按钮。

我们可以看到该程序入口点所属区段起始地址为 4D4000,大小为 2000(十六进制),入口点为 4D4001。

这也是为什么 OD 提示入口点在.text 节之外的缘故。

.text 区段开始于 401000,OD 提示包含 code,我们当前的入口点 4D4001 属于另外一个区段,OD 提示该程序可能包含大量嵌入数据。

从当前入口点开始解密其他区段,然后会跳转到真正的入口点处,开始执行原程序。

我们现在按 F9 键运行起来。

此时弹出了一个 CrackMe 窗口,等待用户输入序列号,我们知道该程序已经在内存中解密区段结束了,现在在执行代码段中的代码,我们在.text 区段上设置一个内存访问断点,让程序在执行代码段中的代码的中断下来。

当我们打开 CrackMe 窗口的时候,这个时候 OD 断在了代码段中。

我们可以看到程序正在解密内存中各种区段,我们来分析一下代码。单击鼠标右键选择-Analysis-Analyse code。

可以看到现在 OD 展示给我们的是完美分析后的代码。

现在我们位于代码段,我们查看当前模块使用了哪些 API 函数-但是我们在程序刚刚加载的时候不能这么做,因为那个时候我们查看当前模块使用的 API 的话是查看的壳所在模块使用的 API 函数,现在就不一样了。

比较糟糕的是 API 函数列表中显示是一些比较陌生的字符串。那我们就来查看一下字符串列表,尝试寻找一点蛛丝马迹。

我们又看到了一个糟糕的字符串列表。

我们可以看到”You did it!”-用于提示成功的字符串之一。

这里有一个比较指令和一个条件跳转指令跳转到提示成功的字符串代码块处,附近还有提示错误的代码块,但是并不是通过调用 MessageBoxA 函数来提示。

我们在该条件跳转指令上面设置一个断点以便来验证是不是一个关键的跳转,并且通过单击鼠标右键选择-Breakpoint-Remove memory breakpoint 来删除之前设置的内存断点。

接着运行程序。

我们随便输入一个错误的序列号,这里我们输入”Narvajita”,然后按下 Test-o-Doom 按钮。

看到跳转将会实现,我们直接按 F9 键。

然后弹出一个提示成功的窗口,我们单击接受按钮。

接着弹出一个消息框提示”开个玩笑,序列号错误”。我们单击接受按钮又回到了等待用户输入序列号的窗口。我们再次按下 Test-o-Doom 按钮。

我们修改影响跳转标志位,看看会不会弹出提示序列号正确的消息框。

我们在零标志位 Z 上面双击改变其值,然后运行起来。

弹出提示序列号正确的消息框,那么这个跳转就是决定序列号正确与否的关键跳转。我们再来看看这个关键跳转。

这里的 TEST CL,CL 指令判断 CL 是否等于零,在判断之前还一个 CALL 指令,我们在这个 CALL 指令处设置一个断点。

我们把程序运行起来,然后来到主窗口再次按下 Test-o-Doom 按钮,断在了 CALL 指令处。

我们看看堆栈中的参数情况。

堆栈中我们可以看到我们刚刚输入的错误序列号,我们通过单击鼠标右键选择-Follow in Dump 在数据窗口中定位到这个字符串。

我们可以看到一个字符串”1556555”,可能是正确的序列号,我们来验证一下它到底是不是正确的序列号。

我们对这个错误序列号设置一个内存访问断点,如果这个 CALL 中将我们输入的错误序列号与正确的序列号进行比较的话,就会断下来。

我们将光标拖选中错误的序列号,然后单击鼠标右键选择-Breakpoint-Memory,on access。

此时,如果我们运行起来,如果这个 CALL 中访问我们错误序列号的话,OD 将中断下来。

我们按下 F9 键。

我们可以看到并没有中断下来,说明比较还在前面,所以我们可以考虑反复通过上面的方式在还要靠前的地方设置断点,或者我们可以以程序获取用户输入的序列号作为入手点,但是这里并没有使用 GetWindowTextA 函数,但是我们知道有将虚拟键消息转换为字符消息的函数。

我们删除设置的内存断点。

我们通过在命令栏中输入 BP TranslateMessage 给 TranslateMessage 函数设置一个断点。

运行起来。

断在了该 API 函数入口处,我们在断在这一行上面单击鼠标选择-Breakpoint-Conditional log

我们设置条件为 MSG == 202 即 WM_LBUTTONUP。

并且设置中断程序方式为条件中断,以及总是记录表达式的值以及函数的参数值。

这里,可以看到一个粉红色的条件断点,我们运行起来。

我们来到主窗口,由于之前的序列号还在内存中,为了不和之前的混淆,我们输入一个不同的序列号。

我们通过单击按钮触发条件断点。

当 WM_LBUTTONUP 消息到达的时候,断了下来。

我们通过选择菜单项中 Debug-Execute till return 执行到返回。

我们单击 F7 键单步返回到主程序模块中。

现在我们有两种选择,第一种-我们对.text 节设置内存访问断点,然后 F9 键运行起来看看哪里在进行序列号的比较,这对于我们来说是比较困难的,我们可以看到,代码量有点大。

第二种选择-就是在内存搜索一下我们输入的序列号,我们单击工具栏中的 M 按钮打开内存窗口。

这里有在对应内存块中搜索的功能,我们在当前内存块上面单击鼠标右键选择-Search。

在弹出的窗口的 ASCII 编辑框中输入我们的错误序列号。

我们勾选上 Entire block(这个内存块),然后按下 OK。

我们还可以使用 CTRL + L 来看看有没有其他内存区域有该错误序列号,我们发现当前区段中没有,其他区段中也没有找到错误序列号了。

我们对错误序列号设置内存访问断点,当程序拿错误序列号进行比较的时候就会断下来。

我们按下 F9 键运行起来。

这里首先将错误序列号拷贝一块内存区域,然后紧接着有一个 CALL。

我们知道 REP MOVS 指令会将 ESI 指向内存单元的内容拷贝到 EDI 指向的内存单元中,所以我们通过在 EDI 寄存器上面单击鼠标右键选择-Follow in Dump 在数据窗口中定位到 EDI 指向的内存单元。

这里 REP MOVS 将进行序列号拷贝,我们按下 F8 执行该指令。

当我们一直 F8 单步执行到下面的 CALL 指令处时,错误序列号拷贝完毕,我们继续在该错误序列号上面设置内存访问断点。

我们运行起来,断了下来。

正在进行比较,嘿嘿。

ESI 指向我们输入的错误序列号,EDI 指向正确的序列号”1556555”,现在我们清楚所有断点。

弹出提示序列号正确的消息框。

下一章我们将分析用户名和序列号生成算法。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文