共计 937 个字符,预计需要花费 3 分钟才能阅读完成。
Crackme019 的逆向分析
1. 程序观察
可以看到,程序要求用户名至少要 5 位。
2. 简单查壳
无壳。
3. 程序分析
OD 载入程序,搜索字符串。
可以看到,字符串上方不远处有一个跳转语句。
在 JNZ 语句处下断点,运行程序,中断在了断点处
修改 ZF 标志位
因为 eax 的值是 JNZ 语句上面的函数返回的
所以我们进入这个函数里面看一看
可以看到,004018C0 这个函数在 004018D1 处调用了一个函数,参数有两个,其中一个是我们输入的假码,另一个可能是真码,我们试一下
那 004018D1 处的这个函数有可能就是比较函数了,我们进入这个函数内部看一下
这个函数又调用了 cmp 函数进行比较
看来,004018C0 函数就是用来比较注册码是否正确的,正确 eax 返回 0,不正确返回非 0。
下面分析程序的算法
程序首先求得用户名和注册码的长度,如果用户名长度小于 5 就会报错。
然后程序建立循环,循环次数为用户名的长度
- 取用户名一个字符 name[n],n 为循环次数
- 让一个十六进制的默认值 0x81276345 加上 name[n]
- 取循环次数 n,将 n 左移 8 位
- 步骤 2 的结果与 步骤 3 的结果进行异或运算
- 取循环次数加一
- 让用户名长度乘以循环次数,然后按位取反
- 5 和 6 的结果相乘
- 4 的结果再和 7 的结果相乘
以上就是循环的内容,如下图
循环完成之后,程序将结果转化为 lu 类型的,也就是无符号长整形整数
这就是最终的注册码啦!
4. 久违的注册机环节
#include <stdio.h>
#include <string.h>
#include <Windows.h>
int Key()
{char szName[20] = {0};
int NameLen = 0;
int code = 0x81276345;
printf("请输入用户名:");
scanf_s("%s", szName, 20);
NameLen = strlen(szName);
for (int i = 0; i < NameLen; i++)
{code += szName[i];
code = code ^ (i << 8);
code = code * ((~(NameLen * i)) * (i + 1));
}
printf("%lu", code);
return 0;
}
int main(int argc, char* argv[])
{Key();
return 0;
}
相关文件在我的 Github
正文完