好友
阅读权限10
听众
最后登录1970-1-1
|
女萝岩
发表于 2020-1-21 02:50
本帖最后由 女萝岩 于 2020-1-21 03:06 编辑
拿到crackme用peid的深度扫描发现是aspack2.x的壳,用Abstersiver把壳脱掉。运行脱壳后的程序,随便输入几组用户名和注册码,发现并没有任何提示信息。由此可以推测只有输入正确的name和serial程序才会给出提示信息。用OD加载起来程序,大概浏览一下代码,找到两处调用MessageBox的地方,很容易判断上面那个MessageBox是和验证注册码相关的。因为它上面有一个跳转,那个跳转是要跳转到返回的,所以这个跳转很关键,而决定要不要跳转的判断条件是eax的值,而eax的值是call 00401509的返回值。所以00401509这个call很关键,我们在这个地址处下一个断点重点分析一下。
下面就是00401509这个CALL的详细分析。Ctrl+F2重新启动被调试程序,F9让程序运行起来,我先输入一组name和serial,name是nvluoyan,serial是whaththefuck ,点击程序的check按钮,发现程序被断在了00401509。
总结一下就是获取用户输入的name并对name的长度进行判断。
总结一下就是对name的长度进行运算,确保name的长度必须在一个范围之内,即0x0190<=((0x02bc-5*(0x30-0x48/Lname))*0x6b)-0xcf6c<=0x2300解一下这个不等式。这个不等式我之后直接用16进制计算,发现得不到正确的结果,不知道为什么。
我们输入的nvluoyan是8位,正好在[3,9]之间。
对name的长度验证之后就进行了对serial的处理了。处理serial的函数需要三个参数,第一个是hwnd,和算法没关系,第二个是name的长度Lname,第三个是用户输入的用户名name
然后进入00401305这个call的内部来看看。
没什么重要的信息,就是一些字符串初始化操作,字符串的作用都在注释中写清楚了,就不多做解释了。
然后获取用户输入的Serial,把Serial存放到UserSerial中。
获取用户输入的Serial的第一个字符,用0x11cf对Serial的第一个字符取余,检测结果是不是0x17,如果是则继续流程,否则返回。可见这个crackme对Serial的第一个字符是有要求的,我们可以写一个小程序来输出一下,很简单的遍历输出即可。
输出结果是:
所以用户输入的Serial的第一个字符必须是$ * 6 8 ? H Q T l ~ 这10个中的其中一个。为了让流程继续下去,我们修改一下输入的Serial,从whatthefuck变为6hatthefuck
然后是取得用户的name,并把name中的每个字符按顺序加起来,最后的结果保存在sum中。00401305这个CALL有两个重要的参数一个是用户输入的name,一个是name的长度Lname,在这里用到了!
这部分就是就是关键了,详细的在注释中都说明了。
然后对SerialFirst数组格式化,就是在最前面加一个大写字母T,把结果存放到SerialFormatFirst数组中。
然后是对SerialFirst数组的第二次格式化,结果存放到SerialFormatSecond[256]中。
然后就是一个重要的call了,这个call有三个参数,其中两个是根据name生成并格式化过的Serial和它的长度,另外一个是用户输入的Serial。
程序并没有直接比较SerialFormatSecond和UserSerial中的每个字符,而是取得SerialFormatSecond中的每个字符,经过一定运算得到一个字符,如果这个字符和UserSerial中相应的某个字符比较是否相等,所以注册机就很好写了。值得注意的一点是,有一个代码是idiv ecx,是对0xA取余。取余这个操作很有意思,它的结果总是小于自己,对0x0A取余,那么结果一定小于0x0A,想一想为什么要对0x0A取余呢?因为它的结果一定是在0到0x0x0A-1这个范围里面,再加上0x30,那么这个范围就变成了0x30~0x3A-1,对应的ASCII码就是数字0到数字9,这样就保证用户的注册码只能包含数字来了。注册机源码本来想用python写的,可是对于位操作不太熟悉,所以就用C语言写了一个。
[C] 纯文本查看 复制代码 #include <iostream>
#include "string.h"
#include "stdio.h"
#include "stdlib.h"
int main()
{
char name[256] = {0};
char MagicNum[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
char GenSerial[256] = {0};
char FormatSerial[256 * 2] = { 0 };
int Lname =0;
int LMagicNum = strlen(MagicNum);
int LSerial = 0;
int sum = 0;
char cname = 0;
char cMagicNum=0;
char cGenSerial = 0;
int index = 0;
int xor1 = 0;
int xor2 = 0;
int sum2 = 0;
int FormatChar = 0;
int index2 = 0;
char CharComputSerial = 0;
int exit;
printf("please input your name :");
scanf_s("%s",name,256);
printf("\n");
printf("*******************************************************\n");
Lname = strlen(name);
//用户名长度必须大于等于3小于等于9
if (Lname < 3 || Lname>9)
{
printf("the length of user name shall range 3 to 9");
return 0;
}
//return 1;
for (int i = 0; i <= Lname; i++)
{
sum = sum + name[i];
}
//不支持ASCII码之外的字符 0x21~0x7E
//写一个循环检测name中的每个字符是否在0x21到0x7e中当然可行
//但我想用另外一种方法,既然name的长度为3位到9位,而且要保证
//每个字符都属于键盘输入的可见ASCII码,那么name中所有的字符的和
//一定有一个最小值,也一定有一个最大值
//最小值就是0x21*3,当name长度是3位的时候
//最大值就是0x7e*9,当name长度是9位的时候
if (sum < 0x21 * 3 || sum>0x7e * 9)
{
printf("illegle user name");
return 0;
}
do {
cname = name[index];
cMagicNum=MagicNum[3 * index - 1];
if (index == 0)
{
cMagicNum = 0x0;
}
xor1 = cname ^ cMagicNum;
xor2 = (sum * index - sum) ^ 0xffffffff;
sum2 = xor1 + xor2 + 0x014d;
cGenSerial = (((sum2 + Lname * (index + 3) * cname) % 0x0A) + 0x30)&0xff;
cGenSerial = ((((unsigned int)cGenSerial^ 0x0ADAC) * (index + 2)) % 0x0A + 0x030) & 0xff;
GenSerial[index] = cGenSerial;
index++;
} while (index < Lname);
FormatChar = ((Lname * sum) % 0x64)+0x30;
sprintf_s(FormatSerial, "%c%s-%d", 'T', GenSerial,FormatChar);
LSerial = strlen(FormatSerial);
index2 = 1;
do
{
CharComputSerial= FormatSerial[index2];
CharComputSerial ^= 0x20;
CharComputSerial %= 0x0A;
CharComputSerial += 0x30;
FormatSerial[index2] = CharComputSerial;
index2++;
} while(index2<LSerial);
printf("your name:%s\n",name);
printf("your code:%s\n",FormatSerial);
printf("*******************************************************\n");
scanf_s("%d",&exit);
return 1;
}
crackme6.zip
(5.2 KB, 下载次数: 26)
|
免费评分
-
查看全部评分
|
发帖前要善用【论坛搜索】功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。 |
|
|
|
|