文章目录

这道题算是这学期遇到的最经典的creackme了,很有必要仔细分析一下。它给出的是一个exe可执行文件和一份readme.text文件,首先我们看一下,它们的内容。一份readme描述了具体的问题,它也给出了半个用户名,然后提示暴力循环6分钟用i5处理器跑的话,但是到最后你会发现这明显有点扯淡,我们逆向分析出算法后给它1秒跑出来。

我们再尝试一下这个exe可执行文件,输入一个错误用户名,和序列号后提示错误。

了解了这些信息后我们开始对它开刀,首先用PEID查看,发现没有加壳。扔进OD后直接查找关键字符串,下好断点,就是下图中箭头所指的位置。

然后我们run一下断在了输用户名和序列号的位置,输入错误的用户名和正确的序列号,发现它在了Sorry~这个位置,我们看到了它的函数调用线,我们沿着这条线倒跟踪上去。

倒跟踪到函数调用线的初始位置后,我们在初始位置尽量上方的位置下一个断点,然后重新载入,回到这个断点处,我们单步跟踪。我们慢慢发现movsv eax,byte ptr ds:[esi+0x1]和movsv edx,byte ptr ds:[edx]这两条指令分别获取了我们输入的用户名的第一个字符和第二个字符,然后传入crackme.000B1F30这个函数。

我们进入这个函数后发现这个crackme.000B1F30就是计算serial序列号的关键函数。接下了就是对serial算法进行分析,首先这个函数把传进来的两个参数做各种运算然后生成了5个数字,具体看做的注释。


在这里我们可以看到程序已经在保存生成的数据了,而且每一个数据下面都有一个比较,如果不对就直接跳转到Sorry~。



接下来我们根据serial算法的汇编代码还原出C代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void decode(int forward, int back){
//int i;
int local1, local2, local3, local4, local5 = 0;//, eax, ecx, ebx;
int result[] = {0};
local3 = (forward & 1) + 6 + ((back >> 2 ) & 1);
result[0] = local3;
local4 = ((forward >> 3)& 1)+6+((back >> 3) & 1);
result[1] = local4;
local1 = ((forward >> 1) & 1)+6 + ((back >> 4) & 1);
result[2] = local1;
local2 = (back & 1 )+6+((forward>>2)&1);
result[3] = local2;
local5 = ((back >> 1) & 1)+6+((forward >> 4) & 1);
result[4] = local5;
printf("%d%d%d%d%d ", result[0], result[1], result[2], result[3], result[4]);
}

根据提示,其他位的范围为26个小写字母和下划线,我们写出爆破程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include"stdafx.h"
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include <ctype.h>
char result[5];
void decode(int forward, int back){
int local1, local2, local3, local4, local5 = 0;//, eax, ecx, ebx;
local3 = (forward & 1) + 6 + ((back >> 2) & 1);
itoa(local3, &result[0], 10);
local4 = ((forward >> 3) & 1) + 6 + ((back >> 3) & 1);
itoa(local4, &result[1], 10);
local1 = ((forward >> 1) & 1) + 6 + ((back >> 4) & 1);
itoa(local1, &result[2], 10);
local2 = (back & 1) + 6 + ((forward >> 2) & 1);
itoa(local2, &result[3], 10);
local5 = ((back >> 1) & 1) + 6 + ((forward >> 4) & 1);
itoa(local5, &result[4], 10);
}
int main()
{
char tag[] = "abcdefghijklmnopqrstuvwxyz_";
char flag[] = "{hdu*b***0_}";
int number[] = {4, 6, 7, 8 };
char fact[6][6] = { "87788", "66867", "66777", "86767"};
for (int i = 0; i < sizeof(number)/sizeof(number[0]); i++) {
for (int j = 0; j < strlen(tag); j++) {
int n = number[i];
flag[n] = tag[j];
decode(flag[n-1], flag[n]);
if (strcmp(result,fact[i]) == 0) {
printf("%s\n", flag);
break;
}
}
}
system("pause");
return 0;
}

跑出的结果为{hdu_bav0_}

我们在creakme中试一下结果正确。

好吧,既然注册算法都有了,那我们顺便把注册机也写出来吧,这样就可以拿着玩了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void decode(int forward, int back){
//int i;
int local1, local2, local3, local4, local5 = 0;//, eax, ecx, ebx;
int result[] = {0};
local3 = (forward & 1) + 6 + ((back >> 2 ) & 1);
result[0] = local3;
local4 = ((forward >> 3)& 1)+6+((back >> 3) & 1);
result[1] = local4;
local1 = ((forward >> 1) & 1)+6 + ((back >> 4) & 1);
result[2] = local1;
local2 = (back & 1 )+6+((forward>>2)&1);
result[3] = local2;
local5 = ((back >> 1) & 1)+6+((forward >> 4) & 1);
result[4] = local5;
printf("%d%d%d%d%d ", result[0], result[1], result[2], result[3], result[4]);
}
int main()
{
char str1[12];
int len;
printf("input your username:\n");
scanf("%s",&str1);
len=strlen(str1);
if(len>12)
{
exit(0);
}
int i=0;
int forward;
int back;
printf("serial:\n");
for(i;i<len-1;i++)
{
forward=str1[i];
back=str1[i+1];
decode(forward,back);
}
system("pause");
return 0;
}

测试一下,成功。想要学习的伙伴们可以在我的github中的CTF仓库中找到这个题。

文章目录