长城杯2024决赛RE

写在前面

长城杯决赛只看了CTF的RE题目,题目挺常规,坐牢8小时拿下3.5道题,勉强混个逆向专项奖。

mapamp

迷宫题,输入wasd控制移动方向,x == 38, y == 38出口,迷宫没有直接存在内存中,需要每步计算判断是否die

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
__int64 sub_404340()
{
unsigned __int64 v0; // rsi
unsigned __int64 v1; // rdx
__int64 v2; // rcx
__int64 v3; // r12
unsigned __int64 v4; // r14
int y; // ebx
int x; // r13d
char v7; // al
__int64 v8; // rbp
__int64 v9; // r15
__int64 v10; // rax
__int64 v11; // rax
__int64 v12; // rdx
_BOOL8 v13; // rdi
unsigned __int64 v14; // rdi
__int64 *v15; // rax
unsigned __int64 v16; // r12
__int64 v17; // rax
__int64 v19; // [rsp+0h] [rbp-78h]
__int64 v20; // [rsp+0h] [rbp-78h]
__int64 *v21; // [rsp+10h] [rbp-68h] BYREF
unsigned __int64 len; // [rsp+18h] [rbp-60h]
__int64 v23[3]; // [rsp+20h] [rbp-58h] BYREF
unsigned __int64 v24; // [rsp+38h] [rbp-40h]

v24 = __readfsqword(0x28u);
sub_404C00();
v0 = (unsigned __int64)&v21;
len = 0LL;
v21 = v23;
LOBYTE(v23[0]) = 0;
scanf(&unk_648560, &v21);
v3 = qword_647530;
if ( len != 140 )
die();
v4 = 0LL;
y = 0;
x = 0;
do
{
v7 = *((_BYTE *)v21 + v4);
if ( v7 == 'w' )
{
v8 = *(_QWORD *)(v3 + 16);
--y;
v9 = v3 + 8;
if ( !v8 )
goto LABEL_40;
}
else
{
switch ( v7 )
{
case 's':
++y;
break;
case 'a':
--x;
break;
case 'd':
++x;
break;
default:
die();
}
v8 = *(_QWORD *)(v3 + 16);
v9 = v3 + 8;
if ( !v8 )
{
LABEL_40:
v8 = v9;
LABEL_18:
v19 = v8;
v8 = sub_470DE0(96LL, v0, v1, v2);
*(_DWORD *)(v8 + 32) = y;
*(_OWORD *)(v8 + 56) = 0LL;
*(_OWORD *)(v8 + 72) = 0LL;
*(_QWORD *)(v8 + 88) = 0LL;
*(_QWORD *)(v8 + 40) = v8 + 88;
*(_QWORD *)(v8 + 48) = 1LL;
*(_DWORD *)(v8 + 72) = 1065353216;
v11 = sub_46F910(v3, v19, v8 + 32);
if ( v12 )
{
v13 = v11 || v9 == v12 || y < *(_DWORD *)(v12 + 32);
v0 = v8;
sub_479520(v13, v8, v12, v9);
++*(_QWORD *)(v3 + 40);
}
else
{
v0 = 96LL;
v20 = v11;
sub_46FF40(v8, 96LL);
v8 = v20;
}
goto LABEL_23;
}
}
v2 = v9;
while ( 1 )
{
v1 = *(_QWORD *)(v8 + 16);
v10 = *(_QWORD *)(v8 + 24);
if ( y > *(_DWORD *)(v8 + 32) )
break;
LABEL_13:
if ( !v1 )
goto LABEL_16;
v2 = v8;
v8 = v1;
}
while ( v10 )
{
v8 = v10;
v1 = *(_QWORD *)(v10 + 16);
v10 = *(_QWORD *)(v10 + 24);
if ( y <= *(_DWORD *)(v8 + 32) )
goto LABEL_13;
}
v8 = v2;
LABEL_16:
if ( v8 == v9 || y < *(_DWORD *)(v8 + 32) )
goto LABEL_18;
LABEL_23:
v14 = *(_QWORD *)(v8 + 48);
v1 = x % v14;
v15 = *(__int64 **)(*(_QWORD *)(v8 + 40) + 8 * v1);
v16 = v1;
if ( v15 )
{
v2 = *v15;
v0 = *(unsigned int *)(*v15 + 8);
while ( x != (_DWORD)v0 )
{
v2 = *(_QWORD *)v2;
if ( v2 )
{
v0 = *(int *)(v2 + 8);
v1 = v0 % v14;
if ( v16 == v0 % v14 )
continue;
}
goto LABEL_28;
}
v2 += 16LL;
}
else
{
LABEL_28:
v17 = sub_470DE0(24LL, v0, v1, v2);
*(_QWORD *)v17 = 0LL;
v0 = x % v14;
*(_DWORD *)(v17 + 8) = x;
*(_QWORD *)(v17 + 16) = 0LL;
v2 = sub_46FD80(v8 + 40, v16, x, v17, 1LL) + 16;
}
v3 = *(_QWORD *)v2;
if ( !*(_QWORD *)v2 )
die();
++v4;
}
while ( len > v4 );
if ( x == 38 && y == 38 )
printf(qword_648440, "flag{md5(your input)}", 21LL);
else
printf(qword_648440, "wrong", 5LL);
sub_404B60(qword_648440);
if ( v21 != v23 )
sub_46FF40(v21, v23[0] + 1);
if ( v24 != __readfsqword(0x28u) )
sub_59B4A0();
return 0LL;
}

由于是逐字符校验,出错直接die,可以考虑获取die的时候已校验的长度,即已走的步数,通过遍历4个方向检查走的步数是否增加,如果步数增加,则表明新的方向可以走,如果步数不变,表示新的方向不可走,使用dfs即可走出迷宫

已走的步数存在r14寄存器中,可以修改die函数,在die函数内输出r14寄存器的值,也可以patch main函数,将die修改为返回r14寄存器的值,这里选择patch main函数

接下来使用dfs遍历迷宫,找到正确路径

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
#!/usr/bin/en python

import subprocess

maze = [[0 for _ in range(40)] for _ in range(40)]

def dfs(x=0, y=0, idx=0, path=[]):
if (idx >= len(path)):
return
global maze
for d in "wasd":
tmp_path = path.copy()
tmp_x = x
tmp_y = y
tmp_path[idx] = d.encode()
match(d):
case 'w':
tmp_y -= 1
case 's':
tmp_y += 1
case 'a':
tmp_x -= 1
case 'd':
tmp_x += 1
if (maze[tmp_x][tmp_y] == 1):
# 已经走过的 避免死循环
continue
proc = subprocess.run("./mapmap", input=b''.join(tmp_path))
next_idx = proc.returncode
if (tmp_x == 38 and tmp_y == 38):
print(b''.join(tmp_path))
if (next_idx == idx):
# 路径长度不变 不可走
continue
maze[tmp_x][tmp_y] = 1
dfs(tmp_x, tmp_y, idx+1, tmp_path)

maze[0][0] = 1
path = [b'w' for _ in range(140)]
dfs(0, 0, 0, path)
for y in range(40):
for x in range(40):
print(maze[x][y], end=' ')
print()

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
❯ python ex.py
wrong
wrong
wrong
wrong
b'ssssddwwwwddddssaassssssaassddssaassddssddssssssddssaassddssddwwddssssddwwddddssssssssssddddddddddwwddssddddddwwwdsssdwwwwwwwwwwwwwwwwwwwwww'
wrong
wrong
wrong
wrong
wrong
wrong
flag{md5(your input)}
b'ssssddwwwwddddssaassssssaassddssaassddssddssssssddssaassddssddwwddssssddwwddddssssssssssddddddddddwwddssddddddwwaawwddwwaaaawwddddddssssssss'
wrong
wrong
wrong
wrong
wrong
wrong
wrong
wrong
wrong
wrong

正确的迷宫路径ssssddwwwwddddssaassssssaassddssaassddssddssssssddssaassddssddwwddssssddwwddddssssssssssddddddddddwwddssddddddwwaawwddwwaaaawwddddddssssssss

kernel

windows sys驱动题,exe文件利用驱动提供的功能处理输入的flag

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
int __fastcall main_0(int argc, const char **argv, const char **envp)
{
char *v3; // rdi
__int64 i; // rcx
char v6; // [rsp+40h] [rbp+0h] BYREF
int v7[2]; // [rsp+48h] [rbp+8h] BYREF
char ret_str[60]; // [rsp+68h] [rbp+28h] BYREF
int v9[9]; // [rsp+A4h] [rbp+64h] BYREF
char flag[60]; // [rsp+C8h] [rbp+88h] BYREF
char v11; // [rsp+104h] [rbp+C4h]
char v12; // [rsp+124h] [rbp+E4h]

v3 = &v6;
for ( i = 66i64; i; --i )
{
*(_DWORD *)v3 = 0xCCCCCCCC;
v3 += 4;
}
j___CheckForDebuggerJustMyCode((__int64)&unk_1400E90A2, (__int64)argv, (__int64)envp);
if ( (unsigned __int8)dev_open((__int64)v7) )
{
memset(ret_str, 0, 0x1Eui64);
v9[0] = 0;
memset(flag, 0, 0x20ui64);
v11 = 0;
v12 = 0;
printf(&byte_1400C5138);
while ( v11 != 10 )
{
v11 = j__fgetchar();
flag[v12++] = v11;
}
dev_check_flag(v7[0], 0x222000, (int)flag, 32, ret_str, 0x20u, (LPDWORD)v9);
dev_close(*(_QWORD *)v7);
printf("%s\n");
((void (__fastcall *)(const char *))system)("pause");
return 0;
}
else
{
printf(&byte_1400C5120);
return 0;
}
}

flag的校验逻辑在sys驱动中

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
__int64 __fastcall kernel_check(__int64 a1, __int64 a2)
{
int j; // [rsp+20h] [rbp-C8h]
int v4; // [rsp+28h] [rbp-C0h] BYREF
int v5; // [rsp+2Ch] [rbp-BCh]
unsigned int i; // [rsp+30h] [rbp-B8h]
int k; // [rsp+34h] [rbp-B4h]
int v8; // [rsp+38h] [rbp-B0h]
int v9; // [rsp+3Ch] [rbp-ACh]
int v10; // [rsp+40h] [rbp-A8h]
unsigned int v11; // [rsp+44h] [rbp-A4h]
PCSTR Format; // [rsp+48h] [rbp-A0h]
int v13; // [rsp+50h] [rbp-98h]
int v14; // [rsp+54h] [rbp-94h]
_DWORD *v15; // [rsp+58h] [rbp-90h]
int v16; // [rsp+60h] [rbp-88h]
int v17; // [rsp+64h] [rbp-84h]
PCSTR v18; // [rsp+68h] [rbp-80h]
int v19[4]; // [rsp+70h] [rbp-78h] BYREF
int encrypted_flag[8]; // [rsp+80h] [rbp-68h] BYREF
char flag[40]; // [rsp+A0h] [rbp-48h] BYREF

v9 = 0;
v4 = 0;
v5 = 0;
v15 = (_DWORD *)sub_1400016E0(a2);
v13 = v15[6];
Format = *(PCSTR *)(a2 + 24);
v11 = v15[4];
v16 = v15[2];
v10 = v13;
if ( v13 == 0x222000 )
{
DbgPrint(&byte_140001FA0);
v8 = 0;
memset(flag, 0, sizeof(flag));
memset(encrypted_flag, 0, sizeof(encrypted_flag));
v18 = Format;
for ( i = 0; i < v11; ++i )
flag[i] = v18[i];
for ( j = 0; j < 32; j += 8 )
{
v4 = 0;
v5 = 0;
v4 = (unsigned __int8)flag[j] << 24;
v4 += (unsigned __int8)flag[j + 1] << 16;
v4 += (unsigned __int8)flag[j + 2] << 8;
v4 += (unsigned __int8)flag[j + 3];
v5 = (unsigned __int8)flag[j + 4] << 24;
v5 += (unsigned __int8)flag[j + 5] << 16;
v5 += (unsigned __int8)flag[j + 6] << 8;
v5 += (unsigned __int8)flag[j + 7];
v19[0] = 0x1234;
v19[1] = 0x3A4D;
v19[2] = 0x5E6F;
v19[3] = 0xAA33;
v17 = 2;
v14 = 33;
xxtea_encrypt(33i64, &v4, v19);
encrypted_flag[v8] = v4;
encrypted_flag[v8 + 1] = v5;
v8 += 2;
}
for ( k = 0; k < 8; ++k )
{
if ( encrypted_flag[k] == ans[k] )
++v9;
}
DbgPrint(&byte_140001FB0);
if ( v9 == 8 )
{
strcpy((char *)Format, "flag is you input");
*(_QWORD *)(a2 + 56) = 18i64;
DbgPrint(&byte_140001FE0);
}
else
{
strcpy((char *)Format, "wrong");
*(_QWORD *)(a2 + 56) = 6i64;
DbgPrint(&byte_140002000);
}
}
else if ( v10 != 2237440 )
{
goto LABEL_19;
}
DbgPrint(aIrpdevicecontr, v11);
DbgPrint("CODE_WRITE %X\n", 2237440i64);
DbgPrint(Format);
*(_QWORD *)(a2 + 56) = 0i64;
LABEL_19:
*(_QWORD *)(a2 + 56) = 65i64;
IofCompleteRequest((PIRP)a2, 0);
return 0i64;
}

xxtea加密,每次加密8个字节,大端序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 __fastcall sub_140001740(unsigned int a1, unsigned int *a2, __int64 a3)
{
__int64 result; // rax
unsigned int v4; // [rsp+0h] [rbp-28h]
unsigned int v5; // [rsp+4h] [rbp-24h]
unsigned int v6; // [rsp+8h] [rbp-20h]
unsigned int i; // [rsp+Ch] [rbp-1Ch]

v5 = *a2;
v6 = a2[1];
v4 = 0;
for ( i = 0; i < a1; ++i )
{
v5 += (*(_DWORD *)(a3 + 4i64 * (v4 & 3)) + v4) ^ (v6 + ((v6 >> 6) ^ (4 * v6)));
v4 -= 0x61CEEEEF;
v6 += (*(_DWORD *)(a3 + 4i64 * ((v4 >> 11) & 3)) + v4) ^ (v5 + ((v5 >> 6) ^ (8 * v5)));
}
*a2 = v5;
result = 4i64;
a2[1] = v6;
return result;
}

写对应的xxtea解密

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
#!/usr/bin/env python

ans = [0x8CCAF011, 0x835A03B8, 0x6DCC9BAD, 0xE671FA99, 0xE6011F35, 0xE5A56CC8, 0xD4847CFA, 0x5D8E0B8E]

key = [0x1234, 0x3A4D, 0x5E6F, 0xAA33]


flag = [0 for _ in range(8)]

for i in range(4):
x = ans[i*2]
y = ans[i*2+1]
sum = 0x100000000 - ((0x61CEEEEF * 33) & 0xFFFFFFFF)
for _ in range(33):
y = 0x100000000 + y - (((key[(sum>>11)&3])+sum)^(x+((x>>6)^(x<<3))))
y &= 0xFFFFFFFF
sum = (sum + 0x61CEEEEF) & 0xFFFFFFFF
x = 0x100000000 + x - (((key[sum&3])+sum)^(y+((y>>6)^(y<<2))))
x &= 0xFFFFFFFF
flag[i*2] = x
flag[i*2+1] = y

flag_s = b""
for f in flag:
flag_s += f.to_bytes(4, byteorder='big')

print(flag_s)

文字谜题

输入字符串后使用parse处理字符串,返回值与l1使用list_eq进行比较,校验通过后计算输入字符串hash得到flag

分析list_eq发现是两个链表的比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
std::operator<<<std::char_traits<char>>(_bss_start, "Enter your magic code: ", envp);
vars10 = 0;
flag = &vars10;
vars8 = 0LL;
v3 = std::ios::widen(*(__int64 *)((char *)&std::cin[30] + *(_QWORD *)(std::cin[0] - 24)), 10LL);
std::getline<char,std::char_traits<char>,std::allocator<char>>(std::cin, &flag, (unsigned int)v3);
std::endl<char,std::char_traits<char>>((std::ostream *)_bss_start);
*(_QWORD *)&v4 = parse(&flag);
*((_QWORD *)&v4 + 1) = &l1;
if ( list_eq(v4) && vars8 == 0x23B )
{
SHA256_Init(vars20);
SHA256_Update(vars20, flag, vars8);
v8 = &vars90;
SHA256_Final(&vars90, vars20);
v9 = (unsigned __int64)"Congratulations! Your flag is: flag{W0w_U_Ar3_V3ry_G00d_A7_C0unt1n9_";
std::operator<<<std::char_traits<char>>(
_bss_start,
"Congratulations! Your flag is: flag{W0w_U_Ar3_V3ry_G00d_A7_C0unt1n9_",
v10);

分析处理输入的parse,只接收ab-3个字符,3个字符分别对应3种处理

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
__int64 __fastcall parse0(__int64 a1, __int64 a2, __int64 a3)
{
// a2 = 1
// a3 = 0
v3 = (char *)a1;
v4 = a3;
LABEL_2:
for ( i = a2; ; i = (i - 1) / 3u )
{
v6 = *v3;
if ( !*v3 )
break;
while ( 1 )
{
++v3;
if ( v6 == 'a' )
{
i *= 2;
goto LABEL_2;
}
if ( v6 == 'b' )
break;
if ( v6 != '-' )
goto LABEL_18;
v7 = operator new(0x10uLL);
v8 = v4;
v9 = v7;
v10 = 1LL;
if ( v4 )
{
do
{
v8 = *(_QWORD *)(v8 + 8);
v11 = v10++;
}
while ( v8 );
}
else
{
v11 = 0;
}
a1 = (unsigned int)(i + v11);
v12 = num2ch(a1, a2, v8);
*(_QWORD *)(v9 + 8) = v4;
i = 1;
v4 = v9;
*(_BYTE *)v9 = v12;
v6 = *v3;
if ( !*v3 )
goto LABEL_9;
}
a3 = (unsigned int)(3 * (i / 3));
if ( i % 3 != 1 || (i & 1) != 0 )
LABEL_18:
bad_input(a1, a2, a3);
}
LABEL_9:
v13 = operator new(0x10uLL);
v14 = v4;
v15 = v13;
v16 = 1LL;
if ( v4 )
{
do
{
v14 = *(_QWORD *)(v14 + 8);
v17 = v16++;
}
while ( v14 );
}
else
{
v17 = 0;
}
*(_BYTE *)v15 = num2ch((unsigned int)(i + v17), a2, v14);
result = v15;
*(_QWORD *)(v15 + 8) = v4;
return result;
}

关注第一个for循环,初始化i = 1

1
2
LABEL_2:
for ( i = a2; ; i = (i - 1) / 3u )

依次取出一个字符进行比较,若字符为ai *= 2,goto进行下一次循环,跳过for循环结尾的i = (i - 1) / 3

1
2
3
4
5
if ( v6 == 'a' )
{
i *= 2;
goto LABEL_2;
}

若字符为b,使用break跳出while循环,需要确保i % 3 == 1i & 1 == 0,随后执行for循环结尾的i = (i - 1) / 3

1
2
3
4
5
      if ( v6 == 'b' )
break;
if ( i % 3 != 1 || (i & 1) != 0 )
LABEL_18:
bad_input(a1, a2, a3);

若字符为-,创建一个新的链表节点v9,计算链表长度v11,计算v12 = num2ch(i+v11),将计算得到的v12储存在节点v9中,并将v9插入到结果链表头部

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
v7 = operator new(0x10uLL);
v8 = v4;
v9 = v7;
v10 = 1LL;
if ( v4 )
{
do
{
v8 = *(_QWORD *)(v8 + 8);
v11 = v10++;
}
while ( v8 );
}
else
{
v11 = 0;
}
a1 = (unsigned int)(i + v11);
v12 = num2ch(a1, a2, v8);
*(_QWORD *)(v9 + 8) = v4;
i = 1;
v4 = v9;
*(_BYTE *)v9 = v12;

num2ch对输入的字符异或0x8D,并且要求输入字符小于0x100,即为一个字节

1
2
3
4
5
6
7
__int64 __fastcall num2ch(__int64 a1, __int64 a2, __int64 a3)
{
LOBYTE(a1) = a1 ^ 0x8D;
if ( (unsigned int)(a1 - 1) > 0xFE )
bad_input(a1, a2, a3);
return (unsigned int)a1;
}

若字符不为ab-parse返回

parse对应的算法如下(这里使用列表代替链表,方向相反)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def parse(s:str):
i = 1
ls = []
for c in s:
if (c == 'a'):
i *= 2
elif (c == 'b'):
assert((i % 3 == 1) and (i % 2 == 0))
i = (i - 1) // 3
elif (c == '-'):
deep = len(ls)
new_c = (i + deep) ^ 0x8D
ls.append(new_c)
i = 1

动态调试获取校验链表l1的值

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
.bss:00005D849CA5F300 ; l1
.bss:00005D849CA5F300 _ZL2l1 db 49h ; DATA XREF: main+97↑o
.bss:00005D849CA5F300 ; _GLOBAL__sub_I__Z9bad_inputv+9B↑w
.bss:00005D849CA5F301 align 8
.bss:00005D849CA5F308 qword_5D849CA5F308 dq 5D849CA5F310h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+A2↑w
.bss:00005D849CA5F310 ; l2
.bss:00005D849CA5F310 _ZL2l2 db 53h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+5B↑o
.bss:00005D849CA5F310 ; _GLOBAL__sub_I__Z9bad_inputv+94↑w
.bss:00005D849CA5F311 align 8
.bss:00005D849CA5F318 qword_5D849CA5F318 dq 5D849CA5F320h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+54↑w
.bss:00005D849CA5F320 ; l3
.bss:00005D849CA5F320 _ZL2l3 db 43h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+4D↑o
.bss:00005D849CA5F320 ; _GLOBAL__sub_I__Z9bad_inputv+8D↑w
.bss:00005D849CA5F321 align 8
.bss:00005D849CA5F328 qword_5D849CA5F328 dq 5D849CA5F330h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+46↑w
.bss:00005D849CA5F330 ; l4
.bss:00005D849CA5F330 _ZL2l4 db 43h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+3F↑o
.bss:00005D849CA5F330 ; _GLOBAL__sub_I__Z9bad_inputv+86↑w
.bss:00005D849CA5F331 align 8
.bss:00005D849CA5F338 qword_5D849CA5F338 dq 5D849CA5F340h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+38↑w
.bss:00005D849CA5F340 ; l5
.bss:00005D849CA5F340 _ZL2l5 db 32h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+31↑o
.bss:00005D849CA5F340 ; _GLOBAL__sub_I__Z9bad_inputv+7F↑w
.bss:00005D849CA5F341 align 8
.bss:00005D849CA5F348 qword_5D849CA5F348 dq 5D849CA5F350h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+2A↑w
.bss:00005D849CA5F350 ; l6
.bss:00005D849CA5F350 _ZL2l6 db 30h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+23↑o
.bss:00005D849CA5F350 ; _GLOBAL__sub_I__Z9bad_inputv+78↑w
.bss:00005D849CA5F351 align 8
.bss:00005D849CA5F358 qword_5D849CA5F358 dq 5D849CA5F360h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+1C↑w
.bss:00005D849CA5F360 ; l7
.bss:00005D849CA5F360 _ZL2l7 db 32h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+7↑w
.bss:00005D849CA5F360 ; _GLOBAL__sub_I__Z9bad_inputv+15↑o
.bss:00005D849CA5F361 align 8
.bss:00005D849CA5F368 qword_5D849CA5F368 dq 5D849CA5F370h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+E↑w
.bss:00005D849CA5F370 ; l8
.bss:00005D849CA5F370 _ZL2l8 dq 34h ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv↑o
.bss:00005D849CA5F370 ; _GLOBAL__sub_I__Z9bad_inputv+62↑w
.bss:00005D849CA5F378 qword_5D849CA5F378 dq 0 ; DATA XREF: _GLOBAL__sub_I__Z9bad_inputv+6D↑w
1
ans_ls = [0x34, 0x32, 0x30, 0x32, 0x43, 0x43, 0x53, 0x49]

需要计算每个值对应的字符串输入,由于逆推难度较大,考虑使用递归暴力计算

计算逻辑中存在(i-1)//3的操作,因此实际计算的最大值可能远大于0x100,此处选择100000,保留最短输入字符串

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
ans_ls = [0x34, 0x32, 0x30, 0x32, 0x43, 0x43, 0x53, 0x49]
ls = [(ans_ls[i] ^ 0x8D) - i for i in range(8)]

parse_ls = [None for _ in range(0x100)]

def recur(k, s):
if (k > 100000):
return
global parse_ls
try:
if (parse_ls[k] == None or len(s) < len(parse_ls[k])):
parse_ls[k] = s
except:
pass
try:
recur(k*2, s+'a')
except:
pass
if ((k % 3 == 1) and (k % 2 == 0)):
try:
recur((k-1)//3, s+'b')
except:
pass

recur(1, "")

for i in range(0x100):
print(hex(i), parse_ls[i])

for d in ls:
print(parse_ls[d], end='-')

爆破得到正确的输入字符串,得到flag

1
2
3
4
5
❯ ./puzzle
Enter your magic code: aaaabaaabaababaaababaaaababaababaaabaababaab-aaaabaaaaabababaaabaaaabaabaabaaaababababaaababababababaababaabababaaabaababababaabababaababaaaabababababa-aaaabaaabaababaaababaaaabaaabababaaababaabab-aaaabaaaaabababaaabaaaabaabaabaaaababababaaababababababaababaabababaaabaababababaabababaababaabaababababaa-aaaabaaabaababaaababaaaaba-aaaaaaaaaabababaab-aaaabaaaaabababaaabaaaabaabaabaaaababababaaababababababaababaabababaaabaababababaabababaababaabaabababababaababaaa-aaaabaaaaabababaaabaaaabaabaabaaaababababaaababababababaababaabababaaabaababababaabababaababaabaabababaaab

Congratulations! Your flag is: flag{W0w_U_Ar3_V3ry_G00d_A7_C0unt1n9_3c968eb5d72c3dca}

crazyaes

PE文件头表示被修改,手动恢复

UPX加壳,将PPP0和PPP1还原为UPX0和UPX1,upx -d脱壳

比赛中没注意到里面的UPX表示,用x64dbg手动脱的,没符号还没法调

有几处花指令手动修复下

1
2
3
4
5
6
7
8
9
.text:0043B4CF             loc_43B4CF:                             ; CODE XREF: _main_0+FB↑j
.text:0043B4CF 74 03 jz short near ptr loc_43B4D3+1
.text:0043B4CF
.text:0043B4D1 75 01 jnz short near ptr loc_43B4D3+1
.text:0043B4D1
.text:0043B4D3
.text:0043B4D3 loc_43B4D3: ; CODE XREF: _main_0:loc_43B4CF↑j
.text:0043B4D3 ; _main_0+111↑j
.text:0043B4D3 E8 68 00 E0+call near ptr 4B23B540h

main里面输入输出

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
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
char v4; // [esp+0h] [ebp-130h]
char v5; // [esp+0h] [ebp-130h]
char v6; // [esp+0h] [ebp-130h]
unsigned int j; // [esp+D0h] [ebp-60h]
int i; // [esp+DCh] [ebp-54h]
int v9; // [esp+E8h] [ebp-48h]
char v10; // [esp+F7h] [ebp-39h]
char encrypted[24]; // [esp+100h] [ebp-30h] BYREF
char ans[20]; // [esp+118h] [ebp-18h]

ans[0] = -76;
ans[1] = 56;
ans[2] = 54;
ans[3] = 48;
ans[4] = 30;
ans[5] = 104;
ans[6] = 72;
ans[7] = 87;
ans[8] = 81;
ans[9] = 1;
ans[10] = -73;
ans[11] = 3;
ans[12] = -101;
ans[13] = -104;
ans[14] = -29;
ans[15] = 126;
memset(encrypted, 17, 15);
encrypted[15] = 1;
v10 = 0;
v9 = 0;
printf((int)&unk_4A2114, v4);
while ( v10 != 10 )
{
v10 = getchar();
flag[v9++] = v10;
}
for ( i = 0; i < 16; ++i )
encrypted[i] = flag[i];
aes_encrypt(0, encrypted, 16, &aes_key);
for ( j = 0; j < 0x10; ++j )
{
if ( encrypted[j] != ans[j] )
{
printf((int)"wrong!!!", v5);
Sleep(0x1388u);
j___loaddll(0);
}
}
printf((int)"WOW!!!", v5);
system("pause");
return printf((int)"\n", v6);
}

标准的aes解密出来是错误的,应该是魔改了奇怪的东西

aes加密在add_round_key后面多了一轮异或操作

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
int __cdecl aes_encrypt(int _0, int data, int len, int key)
{
int j; // [esp+D0h] [ebp-14Ch]
int m; // [esp+D0h] [ebp-14Ch]
int n; // [esp+D0h] [ebp-14Ch]
int i; // [esp+DCh] [ebp-140h]
int k; // [esp+E8h] [ebp-134h]
char xor_box[24]; // [esp+F4h] [ebp-128h]
int state[6]; // [esp+10Ch] [ebp-110h] BYREF
char expaned_key[244]; // [esp+124h] [ebp-F8h] BYREF

j__memset(expaned_key, 0, 0xF0u);
memset(state, 0, 16);
xor_box[0] = 0;
xor_box[1] = 1;
xor_box[2] = 2;
xor_box[3] = 3;
xor_box[4] = 1;
xor_box[5] = 0;
xor_box[6] = 3;
xor_box[7] = 2;
xor_box[8] = 2;
xor_box[9] = 3;
xor_box[10] = 0;
xor_box[11] = 1;
xor_box[12] = 3;
xor_box[13] = 2;
xor_box[14] = 1;
xor_box[15] = 0;
aes_key_expansion(_0, key, expaned_key);
for ( i = 0; i < len; i += 4 * 4[_0] )
{
for ( j = 0; j < 4 * 4[_0]; ++j )
*((_BYTE *)state + j) = *(_BYTE *)(data + j + i);
for ( k = 0; k <= 10[_0]; ++k )
{
if ( k > 0 )
{
sub_bytes(_0, state);
shift_rows(_0, state);
if ( k < 10[_0] )
mix_columns(_0, state);
}
add_round_key(_0, state, expaned_key, k);
for ( m = 0; m < 4 * 4[_0]; ++m )
*((_BYTE *)state + m) ^= xor_box[m];
}
for ( n = 0; n < 4 * 4[_0]; ++n )
*(_BYTE *)(data + n + i) = *((_BYTE *)state + n);
}
return 0;
}

S盒代换后多了一个异或0xA1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl sub_43AAA0(int a1, int a2)
{
int result; // eax
int j; // [esp+D0h] [ebp-14h]
int i; // [esp+DCh] [ebp-8h]

__CheckForDebuggerJustMyCode(&unk_4B20F4);
for ( i = 0; ; ++i )
{
result = a1;
if ( i >= 4[a1] )
break;
for ( j = 0; j < 4; ++j )
{
*(_BYTE *)(a2 + j + 4 * i) = sub_434D0B(*(_BYTE *)(a2 + j + 4 * i));
*(_BYTE *)(a2 + j + 4 * i) ^= 0xA1u;
}
}
return result;
}

列混合后多了一个异或0x54

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
void __cdecl sub_43A530(int a1, int a2)
{
char v2; // bl
int j; // [esp+DCh] [ebp-44h]
int m; // [esp+DCh] [ebp-44h]
int k; // [esp+E8h] [ebp-38h]
int i; // [esp+F4h] [ebp-2Ch]
char v7[12]; // [esp+100h] [ebp-20h]
char v8[20]; // [esp+10Ch] [ebp-14h]

v8[0] = 2;
v8[1] = 3;
v8[2] = 1;
v8[3] = 1;
v8[4] = 1;
v8[5] = 2;
v8[6] = 3;
v8[7] = 1;
v8[8] = 1;
v8[9] = 1;
v8[10] = 2;
v8[11] = 3;
v8[12] = 3;
v8[13] = 1;
v8[14] = 1;
v8[15] = 2;
for ( i = 0; i < 4[a1]; ++i )
{
for ( j = 0; j < 4; ++j )
{
if ( (unsigned int)j >= 4 )
j____report_rangecheckfailure();
v7[j] = 0;
for ( k = 0; k < 4; ++k )
{
v2 = v7[j];
v7[j] = sub_433C3A(*(_BYTE *)(a2 + k + 4 * i), v8[4 * j + k]) ^ v2;
}
}
for ( m = 0; m < 4; ++m )
{
*(_BYTE *)(a2 + m + 4 * i) = v7[m];
*(_BYTE *)(a2 + m + 4 * i) ^= 0x54u;
}
}
}

修改AES对应的代码部分进行解密

比赛的时候电脑上没AES源码,嗯逆了两个小时,还原了加密,解密的时候列混合不会写

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
#!/usr/bin/env python

ans = [0xB4, 0x38, 0x36, 0x30, 0x1E, 0x68, 0x48, 0x57, 0x51, 0x01, 0xB7, 0x03, 0x9B, 0x98, 0xE3, 0x7E]

key = "gah43jJKgfjGMeAR"

# 密钥生成过程直接dump
key_box = [0x67, 0x61, 0x68, 0x34, 0x33, 0x6A, 0x4A, 0x4B, 0x67, 0x66, 0x6A, 0x47, 0x4D, 0x65, 0x41, 0x52,
0x2B, 0xE2, 0x68, 0xD7, 0x18, 0x88, 0x22, 0x9C, 0x7F, 0xEE, 0x48, 0xDB, 0x32, 0x8B, 0x09, 0x89,
0x14, 0xE3, 0xCF, 0xF4, 0x0C, 0x6B, 0xED, 0x68, 0x73, 0x85, 0xA5, 0xB3, 0x41, 0x0E, 0xAC, 0x3A,
0xBB, 0x72, 0x4F, 0x77, 0xB7, 0x19, 0xA2, 0x1F, 0xC4, 0x9C, 0x07, 0xAC, 0x85, 0x92, 0xAB, 0x96,
0xFC, 0x10, 0xDF, 0xE0, 0x4B, 0x09, 0x7D, 0xFF, 0x8F, 0x95, 0x7A, 0x53, 0x0A, 0x07, 0xD1, 0xC5,
0x29, 0x2E, 0x79, 0x87, 0x62, 0x27, 0x04, 0x78, 0xED, 0xB2, 0x7E, 0x2B, 0xE7, 0xB5, 0xAF, 0xEE,
0xDC, 0x57, 0x51, 0x13, 0xBE, 0x70, 0x55, 0x6B, 0x53, 0xC2, 0x2B, 0x40, 0xB4, 0x77, 0x84, 0xAE,
0x69, 0x08, 0xB5, 0x9E, 0xD7, 0x78, 0xE0, 0xF5, 0x84, 0xBA, 0xCB, 0xB5, 0x30, 0xCD, 0x4F, 0x1B,
0x54, 0x8C, 0x1A, 0x9A, 0x83, 0xF4, 0xFA, 0x6F, 0x07, 0x4E, 0x31, 0xDA, 0x37, 0x83, 0x7E, 0xC1,
0xA3, 0x7F, 0x62, 0x00, 0x20, 0x8B, 0x98, 0x6F, 0x27, 0xC5, 0xA9, 0xB5, 0x10, 0x46, 0xD7, 0x74,
0xCF, 0x71, 0xF0, 0xCA, 0xEF, 0xFA, 0x68, 0xA5, 0xC8, 0x3F, 0xC1, 0x10, 0xD8, 0x79, 0x16, 0x64]

s_box = [
0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01,
0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D,
0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4,
0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC,
0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7,
0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2,
0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E,
0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB,
0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB,
0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C,
0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5,
0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C,
0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D,
0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A,
0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3,
0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D,
0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A,
0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6,
0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E,
0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9,
0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9,
0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99,
0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16
]
t_box = [2, 3, 1, 1, 1, 2, 3, 1, 1, 1, 2, 3, 3, 1, 1, 2]

def sub_2A353C(a, k):
for i in range(k):
a = (27 * ((a >> 7) & 1)) ^ (2 * a)
a &= 0xFF
return a
def sub_2A3C3A(a, b):
x = 0
for i in range(8):
x ^= sub_2A353C(a, i) * ((b >> i) & 1)
return x


# 加密过程
data = [0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66]

xor_box = [0, 1, 2, 3, 1, 0, 3, 2, 2, 3, 0, 1, 3, 2, 1, 0]
for i in range(11):
if (i > 0):
for j in range(16):
data[j] = s_box[data[j]] ^ 0xA1

for j in range(4):
for k in range(j):
tmp = data[j]
data[j+0] = data[j+4]
data[j+4] = data[j+8]
data[j+8] = data[j+12]
data[j+12] = tmp

if (i < 10):
tmp_box = [0, 0, 0, 0]
for j in range(4):
for k in range(4):
tmp_box[k] = 0
for r in range(4):
a = data[r+4*j]
b = t_box[r+4*k]
tmp_box[k] ^= sub_2A3C3A(a, b)
for k in range(4):
data[k+4*j] = tmp_box[k] ^ 0x54

for j in range(16):
data[j] ^= key_box[i*16+j]
for j in range(16):
data[j] ^= xor_box[j]
for d in data:
print(hex(d), end=' ')
print()

exit()

要地守护

安卓题,AS刚好没装对应的SDK,没法调试,给的MUMU虚拟机对linux用户很不友好,静态分析了镜像没思路放弃了


长城杯2024决赛RE
https://blog.noxke.icu/2024/05/25/ctf_wp/长城杯2024决赛RE/
作者
noxke
发布于
2024年5月25日
许可协议