最近在研究一些经典的 CrackMe 练习,遇到了 [crackme]019-CrackMe3 这一款。它是一个比较简单的 Windows 逆向工程挑战,主要目的是考察我们对汇编代码的阅读能力,以及能否通过分析算法,最终找到正确的注册码。这里记录一下我的分析过程和心得,希望能帮助到其他正在学习逆向工程的同学。
问题场景重现
首先,我们运行 CrackMe3.exe。程序会要求我们输入用户名和注册码。如果我们输入错误的注册码,程序会提示“注册失败”。我们的目标就是找到正确的注册码,让程序显示“注册成功”。
底层原理深度剖析
为了分析 CrackMe3.exe,我们需要使用一些反汇编工具,例如 OllyDbg 或 IDA Pro。我个人比较喜欢用 IDA Pro,因为它提供了更强大的分析功能。使用 IDA Pro 打开 CrackMe3.exe 后,我们需要找到程序中判断注册码是否正确的关键代码段。
找到关键代码段
通常,我们可以通过字符串搜索来定位关键代码段。在 IDA Pro 中,我们可以搜索程序中用到的字符串,例如“注册失败”或“注册成功”。找到这些字符串后,我们可以查看引用这些字符串的代码,从而找到判断注册码是否正确的代码段。
在 CrackMe3.exe 中,我们很容易找到一个比较可疑的函数,它会获取我们输入的用户名和注册码,然后进行一些计算,最终返回一个值。这个返回值会决定程序是否显示“注册成功”。
算法分析
接下来,我们需要分析这个函数的算法。这个函数的核心逻辑大致如下:
- 获取用户名的长度。
- 将用户名的每个字符的 ASCII 码相加。
- 将注册码的每个字符的 ASCII 码相加。
- 将用户名字符 ASCII 码之和乘以一个常量(例如 0x1234)。
- 将注册码字符 ASCII 码之和与上一步的结果进行比较。如果相等,则注册成功;否则,注册失败。
这个算法非常简单,我们可以很容易地用 C++ 代码来实现:
#include <iostream>
#include <string>
using namespace std;
int main() {
string username;
string regcode;
cout << "请输入用户名:";
cin >> username;
cout << "请输入注册码:";
cin >> regcode;
int username_sum = 0;
for (char c : username) {
username_sum += (int)c;
}
int regcode_sum = 0;
for (char c : regcode) {
regcode_sum += (int)c;
}
int magic_number = 0x1234; // 假设的 magic number,需要根据实际情况调整
if (username_sum * magic_number == regcode_sum) {
cout << "注册成功!" << endl;
} else {
cout << "注册失败!" << endl;
}
return 0;
}
寻找 Magic Number
现在,我们需要找到算法中的那个常量(magic number)。我们可以通过调试 CrackMe3.exe 来找到这个常量。在 OllyDbg 或 IDA Pro 中,我们可以设置断点在进行比较的关键代码处,然后查看寄存器的值。通过观察寄存器的值,我们可以推断出 magic number 的值。
具体的代码/配置解决方案
假设我们通过分析发现 magic number 是 0x1234。现在,我们可以根据用户名来生成注册码了。例如,如果用户名为 “test”,那么用户名字符 ASCII 码之和为 116 + 101 + 115 + 116 = 448。那么,注册码字符 ASCII 码之和应该等于 448 * 0x1234 = 1133824。我们可以选择一个字符串,它的每个字符的 ASCII 码之和等于 1133824。例如,我们可以选择一个长度为 10 的字符串,每个字符的 ASCII 码都等于 113382.4。由于 ASCII 码只能是整数,我们需要进行一些调整,使每个字符的 ASCII 码尽可能接近 113382.4,并且总和等于 1133824。实际上,寻找一个符合条件的字符串比较困难,因为注册码的长度有限制。
实际上,这个算法是存在溢出的,username_sum * magic_number 的结果可能会溢出,导致比较结果出错。因此,我们需要考虑溢出的情况。一种简单的解决办法是,我们直接修改程序的汇编代码,跳过注册码的判断逻辑,直接显示“注册成功”。
在 IDA Pro 中,我们可以找到判断注册码是否正确的代码段,然后使用 Patch 功能,将跳转指令修改为无条件跳转,从而跳过注册码的判断逻辑。
; 原始代码
00401020 JE SHORT 00401030 ; 如果注册码正确,则跳转到注册成功的代码
; 修改后的代码
00401020 JMP SHORT 00401030 ; 无条件跳转到注册成功的代码
实战避坑经验总结
- 在逆向分析时,一定要仔细阅读汇编代码,理解程序的逻辑。
- 可以使用调试器来动态分析程序,观察程序的运行状态。
- 要注意算法中的溢出问题。
- 可以使用 Patch 功能来修改程序的代码,从而绕过一些限制。
- 如果遇到困难,可以参考一些相关的资料或教程,与其他逆向工程爱好者交流。
Crackme 分析和 Web 安全测试有很多共通之处,比如都需要熟悉各种编码、加密算法。如果网站后台使用了类似的简单校验方式,容易被绕过。实际 Web 项目中,我们应该使用更安全的身份验证方式,例如使用 JWT、OAuth 等协议,并且对密码进行加盐哈希存储。在服务器端,我们可以使用 Nginx 作为反向代理,配置合理的负载均衡策略,提高系统的可用性和并发处理能力。对于一些高并发的 API 接口,可以考虑使用 Redis 进行缓存,减少数据库的压力。
冠军资讯
键盘上的咸鱼