吾爱2022春节红包题目

本文最后更新于:2022-02-16 中午

前言

今年来做一下吾爱论坛的红包题目,之前就知道有这个活动,但是从来没参与过,今年参与一下。

第一题送分题就不说了,因为也不会安卓和web,所以第四题第五题都没做,只做了二三题,两道windows的题目。

好久没写过东西了,感觉写的时候,突然有点不熟悉了,思路啥的也表达不明白了…

Windows初级题

这个题是一个明码比较,比较简单。

上来输入之后,先是对输入的长度进行了限制,必须是23位的pasawd。

sub_402B70是比较函数,失败的话,JNZ跳向失败的地方。

比较时看到比较的passwd是2022HappyNewYear52PoJie,这就是flag了。

Windows中级题

打开程序,看到要输入一个UID,还有一个KEY,那么应该是通过UID计算出来KEY

查壳,看到是UPX的壳

脱壳,过程就不再赘述了,我直接手动脱了。

首先对UID进行了一个限制,输入的UID是不能大于2000000的。

然后是三个对于输入的UID进行处理的函数

sub_401100主要就是返回UID % 25

sub_401080定义了一个数组,返回table[UID % 0xC]

sub_401110函数还挺复杂的,我刚开始的也没有仔细的分析这个函数,其实不分析这个也是可以算出来的KEY的,先不说这个函数,最后再说它。

继续往下跟,可以看到有一个跳转会跳向失败,那么前面的函数就是KEY的生成加比较了。

进入sub_401520,前面这些就是会生成一个字符串

生成的字符串flag{Happy_New_Year_52Pojie_2022}

生成这个字符串之后,就进入了计算和比较的地方。

首先会对输入的KEY逐位判断,当前这一位是否是字母,如果这个字符的ASCII码小于A,或大于z,或大于Z小于a,那么它就不是一个字母,然后就不会对其进行处理。

如果是字母的话,分为大写字母和小写字母的情况,不过算法都是一样的,区别是大写字母的计算参与计算的是大写字母A,小写则是a

这里用小写字母来说明,其中V10就是sub_401110的结果,V11sub_401100的结果。这两个值,在计算的时候,可以通过动调直接看结果,当然V11本来计算就很简单,不需要看也可以,主要是V10也就是sub_401110函数中的返回值,这个稍微复杂一些,如果不写通用注册机,可以输入一个UID,然后动调查看结果,直接进行计算。所以我刚才说可以暂时不用分析这个函数,不过我们最后再说这个函数。

1
2
3
4
5
6
7
8
9
00401400    0FBE07          movsx eax,byte ptr ds:[edi]     
00401403 2BC5 sub eax,ebp
00401405 BF 1A000000 mov edi,0x1A
0040140A 83E8 61 sub eax,0x61
0040140D 0FAFC3 imul eax,ebx
00401410 99 cdq
00401411 F7FF idiv edi
00401413 80C2 61 add dl,0x61
00401416 8811 mov byte ptr ds:[ecx],dl

如果(flag[i] - V11 - 0x61) * V10) < 0,那么还需要再给上一步计算后的结果加上0x1A。

1
2
3
4
5
6
0040143F    0FBE09          movsx ecx,byte ptr ds:[ecx]
00401442 2BCD sub ecx,ebp
00401444 83E9 61 sub ecx,0x61
00401447 0FAFCB imul ecx,ebx
0040144A 85C9 test ecx,ecx
0040144C 7D 76 jge short 3.004014C4
1
2
3
4
5
6
00401488    8B5424 1C       mov edx,dword ptr ss:[esp+0x1C]
0040148C 8A0F mov cl,byte ptr ds:[edi]
0040148E 80C1 1A add cl,0x1A
00401491 8D0432 lea eax,dword ptr ds:[edx+esi]
00401494 8808 mov byte ptr ds:[eax],cl
00401496 EB 28 jmp short 3.004014C0

最后就是和flag{Happy_New_Year_52Pojie_2022}进行比较。

现在就可以开始尝试算出我们的KEY了,根据上面分析的算法,我们将V11定义为parm1V10定义为parm2,首先拿我自己的UID来做示例,我的UID计算出来的parm1是12,parm2是25,可以直接代入进行计算。

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
void CalcKey(int parm1, int parm2) {
char key[34] = { 0 };
char* flag = "flag{Happy_New_Year_52Pojie_2022}";

for (int i = 0; i < strlen(flag); i++) {
//除字母以外的字符不进行处理
if (flag[i] < 'A' || flag[i]>'z' || (flag[i] > 'Z' && flag[i] < 'a')) {
key[i] = flag[i];
continue;
}
//对字母进行处理
for (int j = 'A'; j <= 'z'; j++) {
int tmp = 0;

//对大写字母的处理
if (j <= 'Z' && j>= 'A') {
tmp = ((j - parm1 - 0x41) * parm2) % 0x1A + 0x41;
if (((j - parm1 - 0x41) * parm2) < 0) {
tmp += 0x1A;
}
}
//小写字母处理
if (j <= 'z' && j>='a') {
tmp = ((j - parm1 - 0x61) * parm2) % 0x1A + 0x61;
if (((j - parm1 - 0x61) * parm2) < 0) {
tmp += 0x1A;
}
}

if (tmp == flag[i]) {
key[i] = j;
break;
}
}
}
printf("your key is :\n%s\n", key);
}

可以求出当前的flag

现在来说一下关于parm2的计算,也就是sub_401110函数。

对于这块呢,我的理解就是一个数组的计算,定义一个大的数组,然后通过计算给它赋值,结束循环后,通过循环次数在里面找特定的一个值。这块主要就是通过分析,重现一个它的操作,直接看代码吧。

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
49
50
51
52
53
54
55
56
57
58
00401110    81EC 80000000   sub esp,0x80
00401116 53 push ebx
00401117 55 push ebp
00401118 56 push esi
00401119 8BB424 90000000 mov esi,dword ptr ss:[esp+0x90] ; kernel32.77E2ED6C
00401120 57 push edi
00401121 B9 1F000000 mov ecx,0x1F
00401126 33C0 xor eax,eax
00401128 8D7C24 14 lea edi,dword ptr ss:[esp+0x14]
0040112C F3:AB rep stos dword ptr es:[edi]
0040112E B8 01000000 mov eax,0x1
00401133 8D4C24 14 lea ecx,dword ptr ss:[esp+0x14]
00401137 33ED xor ebp,ebp
00401139 894424 10 mov dword ptr ss:[esp+0x10],eax
0040113D 894424 1C mov dword ptr ss:[esp+0x1C],eax
00401141 BF 1A000000 mov edi,0x1A
00401146 83E9 10 sub ecx,0x10
00401149 8BC7 mov eax,edi
0040114B 83C1 10 add ecx,0x10
0040114E 99 cdq
0040114F F7FE idiv esi
00401151 8BC7 mov eax,edi
00401153 8BDA mov ebx,edx
00401155 99 cdq
00401156 F7FE idiv esi
00401158 8B11 mov edx,dword ptr ds:[ecx]
0040115A 45 inc ebp
0040115B 8951 0C mov dword ptr ds:[ecx+0xC],edx
0040115E 8B51 08 mov edx,dword ptr ds:[ecx+0x8]
00401161 8951 14 mov dword ptr ds:[ecx+0x14],edx
00401164 8B11 mov edx,dword ptr ds:[ecx]
00401166 8B79 FC mov edi,dword ptr ds:[ecx-0x4]
00401169 0FAFD0 imul edx,eax
0040116C 2BFA sub edi,edx
0040116E 8979 10 mov dword ptr ds:[ecx+0x10],edi
00401171 8B51 08 mov edx,dword ptr ds:[ecx+0x8]
00401174 0FAFD0 imul edx,eax
00401177 8B41 04 mov eax,dword ptr ds:[ecx+0x4]
0040117A 8BFE mov edi,esi
0040117C 2BC2 sub eax,edx
0040117E 8BF3 mov esi,ebx
00401180 85DB test ebx,ebx
00401182 8941 18 mov dword ptr ds:[ecx+0x18],eax
00401185 ^ 75 C2 jnz short 3.00401149
00401187 C1E5 04 shl ebp,0x4
0040118A 5F pop edi ; 3.00402319
0040118B 5E pop esi ; 3.00402319
0040118C 8B4C2C 04 mov ecx,dword ptr ss:[esp+ebp+0x4]
00401190 8D442C 04 lea eax,dword ptr ss:[esp+ebp+0x4]
00401194 5D pop ebp ; 3.00402319
00401195 5B pop ebx ; 3.00402319
00401196 85C9 test ecx,ecx
00401198 7D 05 jge short 3.0040119F
0040119A 83C1 1A add ecx,0x1A
0040119D 8908 mov dword ptr ds:[eax],ecx
0040119F 8B00 mov eax,dword ptr ds:[eax]
004011A1 81C4 80000000 add esp,0x80
004011A7 C3 retn

我这里定义了一个32的数组,在程序中是一个31的数组(0x7c/4=31)。所以我在每一个运算的时候,都加了1,在原来的401166处的ecx-4就是我的table[0]。然后加i是每次循环都要加4,对应40114B处的add ecx,0x10count就是ebp的值,然后最后在取数的时候这样table[(count / 4) + 4 - 2],是因为在40118C 处的 mov ecx,dword ptr ss:[esp+ebp+0x4],这里的基地址esp实际上要比程序中定义的数组开头地址要小12,比如最开始循环时ecx值是80,这里的esp值就是68,然后刚才也说了,我定义的数组是32的,所以我这里减去2就可以了。

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
int CalcParm2(int parm2) {
int table[32] = { 0 };
table[0] = 1;
table[3] = 1;
int x, y, z;
x = 0x1A;
z = parm2;
int count = 0, i = 0;
do {
count++;
y = x % z;
int tmp = x / z;
x = z;
z = y;
table[i + 4] = table[i + 1];
table[i + 6] = table[i + 3];
table[i + 5] = table[i] - table[i + 1] * tmp;
table[i + 7] = table[i + 2] - table[i + 3] * tmp;
i += 4;
} while (y);
count = count << 4;
int ret = table[(count / 4) + 4 - 2];
if (ret < 0) {
ret += 0x1A;
}
return ret;
}

主函数

1
2
3
4
5
6
7
8
9
10
11
int main() {
int parm1, parm2;
int uid;
int table[] = { 1,3,5,7,9,11,15,17,19,21,23,25 };
printf("please input your uid:");
scanf("%d", &uid);
parm1 = uid % 25; //sub_401100的结果
parm2 = CalcParm2(table[uid % 0xC]); //sub_401080与sub_401110的结果
CalcKey(parm1, parm2);
return 0;
}

完整的注册机就写完了,这里算一下UID为123456的KEY