160CM-033
1. 爆破
首先拖入PE看一下,应该是个汇编程序(ExeInfoPE检测为unknown,DetectItEasy检测为delphi(delhi反汇编工具无法反汇编),PEID检测为汇编)。直接拖进OD中,查找关键字符串也可以找到。这里我们用F12暂停法,打开注册界面,输入用户名和密码,然后确定,弹窗错误提示对话框后,在OD中按F12暂停,然后Alt+K进入堆栈调用列表中,再最后一个调用处双击,就找到了弹窗的调用程序,然后F8单步跟踪到返回,就可以到401245位置,该处调用就是错误弹窗函数。
往上看,可以看到一个比较和一个判断跳转,这个跳转就是关键跳转,将je
该为jmp
,爆破就完成了。
2. 算法分析
从图1中的代码可以看出,程序验证过程一目了然:先获取用户名字符串,然后调用校验算法1计算得到结果1;再获取密码字符串,然后调用校验算法2计算得到结果2,最后比较结果1和结果2是否相等,相等则提示成功,否则提示失败。
先分析用户名校验算法,用IDA打开程序,找到算法子程序40137E
,按F5得到伪代码,如下图:
从伪代码中可以看出,算法先一次判断用户名字符串中的字符是否在A~Z之间,小于A,则弹窗报错,大于Z,将其ASCII值减32(即转换成大写字母),全部转换成大写字母后,再调用子程序4013C2
,将所有字母的ASCII码值相加,最后在和0x5678
进行异或运算(上述过程如果功力不够静态分析不大确定,在OD中跟踪一下程序执行过程也就很容易确定下来)。
接着分析密码校验算法,同样用IDA打开程序,找到算法子程序4013D8
,按F5得到伪代码,如下图:
从伪代码中可以发现,IDA生成的伪代码有点问题,漏掉了与0x1234
进行异或的计算过程。*i-48
也就是字符的ASCII码减去数字0的ASCII码值,也即得到输入数字所代表的数值,v2=(*i-48)+10*v2
,整个过程起始就是将数字字符串转化为其对应的数值。最后再与0x1234
进行异或计算。
将上述校验过程编写成校验算法如下,输入数据与程序本身的计算结果进行验证,可以证明算法无误。
// 160CrackMe-033.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <math.h>
int _tmain(int argc, _TCHAR* argv[])
{
char name[20];
char code[20];
int i,j;
int k;
printf("请输入用户名(长度≤20):");
scanf_s("%s", name, 20);
printf("请输入密码(长度≤20):");
scanf_s("%s", code, 20);
//未做用户名输入校验,只允许输入字母
for (i = 0; i<strlen(name); i++)
{
name[i] = toupper(name[i]);
}
k = 0;
for (i = 0; i<strlen(name); i++)
{
k = k + name[i];
}
printf("用户名字符和:%d \n",k);
k = k ^ 0x5678;
printf("用户名校验输出:%x \n", k);
j = 0;
for (i = 0; i<strlen(code); i++)
{
j = (code[i]-48) + 10 * j;
}
printf("密码异或前的结果:%d \n", j);
j = j ^ 0x1234;
printf("密码校验输出:%x \n", j);
system("pause");
return 0;
}
至于注册机的算法,由于密码的校验算法相当于输入数字字符串的数值与0x1234
的异或结果,因此注册码的求解过程可以直接用用户名校验算法得到的结果,与0x1234
进行异或,得到的数值就是注册码。具体算法程序如下:
// 160CrackMe-033.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <Windows.h>
#include <stdio.h>
#include <math.h>
int _tmain(int argc, _TCHAR* argv[])
{
char name[20];
char code[20];
int i,j;
int k;
printf("请输入用户名(长度≤20):");
scanf_s("%s", name, 20);
//未做用户名输入校验,只允许输入字母
for (i = 0; i<strlen(name); i++)
{
name[i] = toupper(name[i]);
}
k = 0;
for (i = 0; i<strlen(name); i++)
{
k = k + name[i];
}
k = k ^ 0x5678;
k = k ^ 0x1234;
printf("注册码为:%d \n", k);
system("pause");
return 0;
}
输入用户名abc
,可以计算得到注册码为17546
,输入程序中,验证成功。
3. 总结
这道题验证过程清晰简单,算法也不难,借助IDA、OD这两个软件,分析起来还是比较顺利的。
其中关于注册码的计算,默认注册码为数字字符串的话,可以如上直接异或后得出,但由于程序没有限制只能输入数字字符串,因此将其中的数字用字母代替,也是可以算得注册码的,比如最后一个数字用字母h
替代,则可以得到1749h
这样一个注册码,有很多个解。