本帖最后由 15788888 于 2024-11-30 19:26 编辑
本人刚刚入门汇编逆向破解分析,本帖将记录我对160个CrackMe程序中的第006程序的分析过程。在学习的过程中,我会边学边记录。如果有不正确之处,还请各位指正。
程序打开后有 有一个帮助按钮 有一个不可用的ok键 和一个清除键,点击完帮助按钮 会弹4个弹窗 大概意思就是说:输入正确的用户名和密码 让 ok键 从禁用状态变成可用状态 然后在让清除键和ok键隐藏把下面的logo完整显示出来。
查壳后是无壳 Delphi编写。
Delphi编写编写的程序 直接查一下都有哪些事件。
这个程序就不进行爆破了 我们来直接研究它的算法, 首先 OK按钮点不了 应该是在输入用户名和密码的时候 会进行验证 验证正确了OK按钮才能恢复正常。按照这个思路 先把 用户名和密码输入事件下上断点。
然后输入用户名和密码。 先看用户名输入事件的代码, 单步跟一下。
跟到这个位置发现 如果eax+0x47的地址值不为0 就不会跳转,通过我直接改标志位 让这个跳转不成立 进去后发下下面的CALL是恢复OK按钮使用。 也就是说 当 eax+0x47不等于0 就能恢复 OK按钮的使用。
继续往下跟会发现还有一个跳转 如果跳转不成立 那么0k按钮也会变成可用状态, 这个跳转上面有一个关键CALL决定这个跳转是成立还是不成立,这个CALL在上面 是一些取出用户名 密码 作为参数给这个关键CALL进行传参。 由此就可以看出这个关键CALL可能是算法CALL。
跟进这个关键CALL 我们发现 这有一个跳转,如果输入的用户名长度小于等于5 那就直接出CALL了 。
当用户名大于5个的时候 可以顺利走到这个位置 看到了一些计算操作 。
[Asm] 纯文本查看 复制代码 00442A93 | 8B4D FC | mov ecx,dword ptr ss:[ebp-4] | 取用户名给 ECX
00442A96 | 0FB64C01 FF | movzx ecx,byte ptr ds:[ecx+eax-1] | 把用户名第一个字节给ecx 每次循环都递增一个
00442A9B | 8B75 FC | mov esi,dword ptr ss:[ebp-4] | 取出用户名在给ESi
00442A9E | 0FB63406 | movzx esi,byte ptr ds:[esi+eax] | 把用户名第二个字节给ESI 每次循环都递增一个
00442AA2 | 0FAFCE | imul ecx,esi | ecx*esi
00442AA5 | 0FAFC8 | imul ecx,eax | ecx*eax
00442AA8 | 03D9 | add ebx,ecx | 最后算出来的结果 累加到ebx中
00442AAA | 40 | inc eax |
00442AAB | 4A | dec edx |
00442AAC | 75 E5 | jne along3x.1.442A93 |
通过这算法 我们可以看出 就是把用户名的第一个字节乘以第二个字节 再乘以当前循环的次数
正向代码:
[C++] 纯文本查看 复制代码 int ecx = 0;
int esi = 0;
int ebx = str.length();
for (int i = 0; i < str.length() - 1; i++)
{
ecx = str[i];
esi = str[i + 1];
ecx = ecx * esi * (i + 1);
ebx += ecx;
}
看下图 执行了一个 把密码转为16进制 再用之前算出来的值减去密码的16进制 如果等于0x29a;那么相当于OK按钮就可以恢复使用了。 这也就是说 我输入的用户名算出来的值减去0x29A 在转成十进制那就是对应的密码。
编写一下对应算法 在程序上面输入验证了 算法是正确的 OK键现在可以正常使用了。
现在我们在来点一下 OK按钮 发现 清空了密码输入内容(由于清空密码输入框改变了内容 导致密码不正确ok按钮又变回不可用状态)
目前我们知道了 刚才的算法只是能让OK恢复使用 并不能把它隐藏掉。 那我们继续去进入OK的点击事件里面找一下有没有对应的隐藏算法。
进入到OK按钮的点击事件当中 我们发现了有一个关键的比较 cmp byte ptr ds:[eax+47],1 如果等于0x1 那么直接就把OK按钮禁用了 然后一个跳转就出CALL了。 所以这个 eax+0x47是关键 必须要让他不等于0x1才可以。按钮点击事件 现在就不用看了 因为eax+0x47等于0x1那它就直接出CALL了 所以我们在看最后一个清除按钮的事件。
断下来后我们发现,有跳转 上面的CALL 里面传入了用户名进行运算决定跳转还是不跳转 推测也是一个算法 具体干什么用的还不清楚 跟进去看一下。
跟进去之后确实看到有一些计算的操作 就不一行一行注释了 大概的操作流程就是 先判断密码长度是否小于等于0x5 如果小于跳出 然后 取用户名的第四个字节和 0x7进行取模运算 得出来的结果在加0x2,计算出来的结果 经过下面的CALL进行阶乘。阶乘出来的结果 保存在esi中备用,然后 走到下面的循环 循环就是 用esi的值以此和用户名的每一个字符相乘的和值保存到ebx当中。 然后ebx的值减去密码的16进制 等于0x7A69 就算成立。
我们现在来写一下这个算法 输入到程序上面点击清除 结果发现清除按钮已经消失了 并且OK按钮变成了可用状态。
现在就差一个OK按钮的隐藏就可以了, 这个时候我们继续去看OK按钮事件,发现 eax+0x47已经可以正常跳转了。
继续往下走 我们发现有一个关键跳转, 我更改标志位让他不进行跳转发现 这个跳转里面的CALL就是隐藏ok按钮的CALL ,那这个跳转上面的CALL 就是关键的算法CALL了。
进入到这个CALL里面 第一个跳转是看密码的长度是否小于5 如果小于 直接跳转出CALL
继续往下走 又看到了一些计算的操作 不每行都注释了 具体的逻辑就是 传入的密码多长就对应循环几次, 每一次 取最后一个字节 依次递减 取出来的值做一个乘以自己本身(就是做平方) 然后得到的值在乘以密码的长度减去当前循环的次数 得出来的值在和0x19进行取模运算然后在加0x41。得到的值对应ascll的字符。
继续往下走有一个CALL传递了计算后的参数和用户名。 之前的运算相当于 把密码对应的算出来一个用户名, 通过这个用户名和你输入的用户名做对比 如果一致那么 ok按钮就会隐藏 。
继续写隐藏ok按钮算法。 算法写出来后 ok按钮也顺利隐藏 至此 整个分析过程就结束了。
注册机:
[C++] 纯文本查看 复制代码 #include <iostream>
#include <string>
using namespace std;
//可用ok按钮算法
void passWord1(string str)
{
int ecx = 0;
int esi = 0;
int ebx = str.length();
for (int i = 0; i < str.length() - 1; i++)
{
ecx = str[i];
esi = str[i + 1];
ecx = ecx * esi * (i + 1);
ebx += ecx;
}
int key = ebx - 0x29A;
cout<<"输入的用户名:"<< str << " 用户名算出来的值:" << hex << ebx << " 十进制密码:" << dec << key << endl;
}
//隐藏清除按钮算法
void passWord2(string str)
{
int len = str.length();
char eax = str[4];
int ecx = 0x7;
eax = eax % ecx + 0x2;
int esi = 1;
int ebx = 0;
for (int i = 0; i < (int)eax; i++)
{
esi *= ((int)eax - i);
}
for (int i = 0; i < len; i++)
{
ebx = ebx + esi * str[i];
}
int key = ebx - 0x7a69;
cout << hex << ebx << " " << dec << key << endl;
cout << "输入的用户名:" << str << " 用户名算出来的值:" << hex << ebx << " 十进制密码:" << dec << key << endl;
}
//隐藏ok键用户名算法
void passWord3(string str)
{
int len = str.length();
int eax = 0;
int edx = 0;
char* key = new char[len];
for (int i = 0; i < len; i++)
{
eax = str[len - i -1];
eax *= eax;
eax *= len - i;
edx = eax % 0x19 + 0x41;
key[len - i - 1] = (char)edx;
}
key[len] = '\0';
cout << "输入的密码:" << str << " 密码计算出来的用户名:" << hex << key << endl;
}
int main()
{
passWord1("123456");
passWord2("123456");
passWord3("123456");
return 0;
}
|