Reverse_3

随缘刷题

[网鼎杯 2020 青龙组]singal

链接: https://buuoj.cn/challenges#[网鼎杯 2020 青龙组]singal

EXE 32

IDA中 _main 函数反编译结果如下

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4[117]; // [esp+18h] [ebp-1D4h] BYREF

__main();
qmemcpy(v4, dword_403040, 456u);
vm_operad(v4, 114);
puts("good,The answer format is:flag {}");
return 0;
}

这里dword_403040只运算最低一字节

1
2
3
4
5
6
7
8
9
dword_403040 = [
0x0A, 0x04, 0x10, 0x08, 0x03, 0x05, 0x01, 0x04, 0x20, 0x08, 0x05, 0x03, 0x01, 0x03, 0x02, 0x08,
0x0B, 0x01, 0x0C, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x03, 0x21, 0x01, 0x0B, 0x08, 0x0B,
0x01, 0x04, 0x09, 0x08, 0x03, 0x20, 0x01, 0x02, 0x51, 0x08, 0x04, 0x24, 0x01, 0x0C, 0x08, 0x0B,
0x01, 0x05, 0x02, 0x08, 0x02, 0x25, 0x01, 0x02, 0x36, 0x08, 0x04, 0x41, 0x01, 0x02, 0x20, 0x08,
0x05, 0x01, 0x01, 0x05, 0x03, 0x08, 0x02, 0x25, 0x01, 0x04, 0x09, 0x08, 0x03, 0x20, 0x01, 0x02,
0x41, 0x08, 0x0C, 0x01, 0x07, 0x22, 0x07, 0x3F, 0x07, 0x34, 0x07, 0x32, 0x07, 0x72, 0x07, 0x33,
0x07, 0x18, 0x07, 0xA7, 0x07, 0x31, 0x07, 0xF1, 0x07, 0x28, 0x07, 0x84, 0x07, 0xC1, 0x07, 0x1E,
0x07, 0x7A]

跟进到 vm_operad 函数

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
int __cdecl vm_operad(int *a1, int a2)
{
int result; // eax
char Str[200]; // [esp+13h] [ebp-E5h] BYREF
char v4; // [esp+DBh] [ebp-1Dh]
int v5; // [esp+DCh] [ebp-1Ch]
int v6; // [esp+E0h] [ebp-18h]
int v7; // [esp+E4h] [ebp-14h]
int v8; // [esp+E8h] [ebp-10h]
int v9; // [esp+ECh] [ebp-Ch]

v9 = 0;
v8 = 0;
v7 = 0;
v6 = 0;
v5 = 0;
while ( 1 )
{
result = v9;
if ( v9 >= a2 )
return result;
switch ( a1[v9] )
{
case 1:
Str[v6 + 100] = v4;
++v9;
++v6;
++v8;
break;
case 2:
v4 = a1[v9 + 1] + Str[v8];
v9 += 2;
break;
case 3:
v4 = Str[v8] - LOBYTE(a1[v9 + 1]);
v9 += 2;
break;
case 4:
v4 = a1[v9 + 1] ^ Str[v8];
v9 += 2;
break;
case 5:
v4 = a1[v9 + 1] * Str[v8];
v9 += 2;
break;
case 6:
++v9;
break;
case 7:
if ( Str[v7 + 100] != a1[v9 + 1] )
{
printf("what a shame...");
exit(0);
}
++v7;
v9 += 2;
break;
case 8:
Str[v5] = v4;
++v9;
++v5;
break;
case 10:
read(Str);
++v9;
break;
case 11:
v4 = Str[v8] - 1;
++v9;
break;
case 12:
v4 = Str[v8] + 1;
++v9;
break;
default:
continue;
}
}
}

根据这里的switch语句来分析
switch里对于各个数值的操作有些与CPU指令相似
原来这就是虚拟机逆向啊

  • 0x010x08分别决定计算结果的存储位置
  • 0x02计算加法, 0x03计算减法, 0x04计算异或, 0x05计算乘法
  • 0x0B自增, 0x1C自减
  • 0x0A输入, 0x07用于校验加密后的结果

然后照着代码把虚拟机函数反着写一遍即可获得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
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
#!/usr/bin/env python3
a1 = [
0x0A, 0x04, 0x10, 0x08, 0x03, 0x05, 0x01, 0x04, 0x20, 0x08, 0x05, 0x03, 0x01, 0x03, 0x02, 0x08,
0x0B, 0x01, 0x0C, 0x08, 0x04, 0x04, 0x01, 0x05, 0x03, 0x08, 0x03, 0x21, 0x01, 0x0B, 0x08, 0x0B,
0x01, 0x04, 0x09, 0x08, 0x03, 0x20, 0x01, 0x02, 0x51, 0x08, 0x04, 0x24, 0x01, 0x0C, 0x08, 0x0B,
0x01, 0x05, 0x02, 0x08, 0x02, 0x25, 0x01, 0x02, 0x36, 0x08, 0x04, 0x41, 0x01, 0x02, 0x20, 0x08,
0x05, 0x01, 0x01, 0x05, 0x03, 0x08, 0x02, 0x25, 0x01, 0x04, 0x09, 0x08, 0x03, 0x20, 0x01, 0x02,
0x41, 0x08, 0x0C, 0x01]
a1.reverse()
a2 = [
0x07, 0x22, 0x07, 0x3F, 0x07, 0x34, 0x07, 0x32, 0x07, 0x72, 0x07, 0x33, 0x07, 0x18, 0x07, 0xA7,
0x07, 0x31, 0x07, 0xF1, 0x07, 0x28, 0x07, 0x84, 0x07, 0xC1, 0x07, 0x1E, 0x07, 0x7A]
cipher = []

for _ in range(len(a2)):
if a2[_] == 0x07:
cipher.append(a2[_+1])
cipher.reverse()
flag = ""
v4 = 0
v6 = 0
v8 = 0
v9 = 0
while v9 < len(a1)-1:
if a1[v9] == 0x01:
v4 = cipher[v6]
v6 += 1
v8 += 1
v9 += 1
if a1[v9+1] == 0x02:
v8 = v4 - a1[v9]
v9 += 2
elif a1[v9+1] == 0x03:
v8 = v4 + a1[v9]
v9 += 2
elif a1[v9+1] == 0x04:
v8 = v4 ^ a1[v9]
v9 += 2
elif a1[v9+1] == 0x05:
v8 = v4 // a1[v9]
v9 += 2
elif a1[v9] == 0x06:
v9 += 1
elif a1[v9] == 0x0B:
v8 = v4 + 1
v9 += 1
elif a1[v9] == 0x0C:
v8 = v4 - 1
v9 += 1
elif a1[v9] == 0x08:
v4 = v8
v9 += 1
if a1[v9+1] == 0x02:
v8 = v4 - a1[v9]
v9 += 2
elif a1[v9+1] == 0x03:
v8 = v4 + a1[v9]
v9 += 2
elif a1[v9+1] == 0x04:
v8 = v4 ^ a1[v9]
v9 += 2
elif a1[v9+1] == 0x05:
v8 = v4 // a1[v9]
v9 += 2
elif a1[v9] == 0x06:
v9 += 1
elif a1[v9] == 0x0B:
v8 = v4 + 1
v9 += 1
elif a1[v9] == 0x0C:
v8 = v4 - 1
v9 += 1
flag += chr(v8)
print("flag{" + flag[::-1] + "}")

flag{757515121f3d478}


[GXYCTF2019]simple CPP

链接: https://buuoj.cn/challenges#[GXYCTF2019]simple CPP

EXE 64

IDA中 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
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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
int __cdecl main(int argc, const char **argv, const char **envp)
{
bool v3; // si
__int64 v4; // rax
__int64 v5; // r8
__int64 v6; // r8
unsigned __int8 *v7; // rax
unsigned __int8 *v8; // rbx
int v9; // er10
__int64 v10; // r11
void **v11; // r9
void **v12; // r8
__int64 v13; // rdi
__int64 v14; // r15
__int64 v15; // r12
__int64 v16; // rbp
int v17; // ecx
unsigned __int8 *v18; // rdx
__int64 v19; // rdi
__int64 *v20; // r14
__int64 v21; // rbp
__int64 v22; // r13
__int64 *v23; // rdi
__int64 v24; // r8
__int64 v25; // r12
__int64 v26; // r15
__int64 v27; // rbp
__int64 v28; // rdx
__int64 v29; // rbp
__int64 v30; // rbp
__int64 v31; // r10
__int64 v32; // rdi
__int64 v33; // r8
bool v34; // dl
__int64 v35; // rax
void **v36; // rdx
__int64 v37; // rax
__int64 v38; // r8
__int64 v39; // rax
void *v40; // rcx
__int64 v42; // [rsp+20h] [rbp-68h]
void *Block[2]; // [rsp+30h] [rbp-58h] BYREF
unsigned __int64 v44; // [rsp+40h] [rbp-48h]
unsigned __int64 v45; // [rsp+48h] [rbp-40h]

v3 = 0;
v44 = 0i64;
v45 = 15i64;
LOBYTE(Block[0]) = 0;
v4 = sub_7FF69CF519C0(std::cout, "I'm a first timer of Logic algebra , how about you?", envp);
std::ostream::operator<<(v4, sub_7FF69CF51B90);
sub_7FF69CF519C0(std::cout, "Let's start our game,Please input your flag:", v5);
sub_7FF69CF51DE0(std::cin, Block);
std::ostream::operator<<(std::cout, sub_7FF69CF51B90);
if ( v44 - 5 > 0x19 )
{
v39 = sub_7FF69CF519C0(std::cout, "Wrong input ,no GXY{} in input words", v6);
std::ostream::operator<<(v39, sub_7FF69CF51B90);
goto LABEL_43;
}
v7 = (unsigned __int8 *)operator new(0x20ui64);
v8 = v7;
if ( v7 )
{
*(_QWORD *)v7 = 0i64;
*((_QWORD *)v7 + 1) = 0i64;
*((_QWORD *)v7 + 2) = 0i64;
*((_QWORD *)v7 + 3) = 0i64;
}
else
{
v8 = 0i64;
}
v9 = 0;
if ( v44 )
{
v10 = 0i64;
do
{
v11 = Block;
if ( v45 >= 0x10 )
v11 = (void **)Block[0];
v12 = &qword_7FF69CF56048;
if ( (unsigned __int64)qword_7FF69CF56060 >= 0x10 )
v12 = (void **)qword_7FF69CF56048;
v8[v10] = *((byte_0 *)v11 + v10) ^ *((byte_0 *)v12 + v9 % 27);
++v9;
++v10;
}
while ( v9 < v44 );
}
v13 = 0i64;
v14 = 0i64;
v15 = 0i64;
v16 = 0i64;
if ( (int)v44 > 30 )
goto LABEL_27;
v17 = 0;
if ( (int)v44 <= 0 )
goto LABEL_27;
v18 = v8;
do
{
v19 = *v18 + v13;
++v17;
++v18;
switch ( v17 )
{
case 8:
v16 = v19;
goto LABEL_23;
case 16:
v15 = v19;
goto LABEL_23;
case 24:
v14 = v19;
LABEL_23:
v19 = 0i64;
break;
case 32:
sub_7FF69CF519C0(std::cout, "ERRO,out of range", (unsigned int)v44);
exit(1);
}
v13 = v19 << 8;
}
while ( v17 < (int)v44 );
if ( v16 )
{
v20 = (__int64 *)operator new(0x20ui64);
*v20 = v16;
v20[1] = v15;
v20[2] = v14;
v20[3] = v13;
goto LABEL_28;
}
LABEL_27:
v20 = 0i64;
LABEL_28:
v42 = v20[2];
v21 = v20[1];
v22 = *v20;
v23 = (__int64 *)operator new(0x20ui64);
if ( IsDebuggerPresent() )
{
sub_7FF69CF519C0(std::cout, "Hi , DO not debug me !", v24);
Sleep(0x7D0u);
exit(0);
}
v25 = v21 & v22;
*v23 = v21 & v22;
v26 = v42 & ~v22;
v23[1] = v26;
v27 = ~v21;
v28 = v42 & v27;
v23[2] = v42 & v27;
v29 = v22 & v27;
v23[3] = v29;
if ( v26 != 0x11204161012i64 )
{
v23[1] = 0i64;
v26 = 0i64;
}
v30 = v26 | v25 | v28 | v29;
v31 = v20[1];
v32 = v20[2];
v33 = v28 & *v20 | v32 & (v25 | v31 & ~*v20 | ~(v31 | *v20));
v34 = 0;
if ( v33 == 0x8020717153E3013i64 )
v34 = v30 == 0x3E3A4717373E7F1Fi64;
if ( (v30 ^ v20[3]) == 0x3E3A4717050F791Fi64 )
v3 = v34;
if ( (v26 | v25 | v31 & v32) == (~*v20 & v32 | 0xC00020130082C0Ci64) && v3 )
{
v35 = sub_7FF69CF519C0(std::cout, "Congratulations!flag is GXY{", v33);
v36 = Block;
if ( v45 >= 0x10 )
v36 = (void **)Block[0];
v37 = sub_7FF69CF51FD0(v35, v36, v44);
sub_7FF69CF519C0(v37, "}", v38);
j_j_free(v8);
}
else
{
sub_7FF69CF519C0(std::cout, "Wrong answer!try again", v33);
j_j_free(v8);
}
LABEL_43:
if ( v45 >= 0x10 )
{
v40 = Block[0];
if ( v45 + 1 >= 0x1000 )
{
v40 = (void *)*((_QWORD *)Block[0] - 1);
if ( (unsigned __int64)(Block[0] - v40 - 8) > 0x1F )
invalid_parameter_noinfo_noreturn();
}
j_j_free(v40);
}
return 0;
}

主函数一堆的运算过程看起来有点无从下手
仔细分析一遍之后可以得出下面的流程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
v12 = "i_will_check_is_debug_or_not"
v8 = plain ^ v12
v16 = v8[:8]
v15 = v8[8:16]
v14 = v8[16:24]
v13 = v8[24:]
v25 = v15 & v16
v26 = v14 & ~v16
v27 = ~v15
v28 = v14 & ~v15
v29 = v16 & ~v15
v23[0] = v15 & v16
v23[1] = v14 & ~v16
v23[2] = v14 & ~v15
v23[3] = v16 & ~v15
v14 & ~v16 == 0x11204161012
v30 = v25 | v26 | v28 | v29 == 0x3E3A4717373E7F1F
v31 = v15
v32 = v14
v33 = v28 & v16 | v32 & (v25 | v31 & ~v16 | ~(v31 | v16)) == 0x8020717153E3013
v30 ^ v13 == 0x3E3A4717050F791F
(v26 | v25 | v31 & v32) == (~v16 & v32 | 0xC00020130082C0C)

其中v12需要使用动调来获得内容

1
v8[v10] = *((byte_0 *)v11 + v10) ^ *((byte_0 *)v12 + v9 % 27);

同时我们可以根据这条代码来推测flag内容的长度应该为27

Z3解方程组

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
#!/usr/bin/env python3
from z3 import *
from Crypto.Util.number import *
v13, v14, v15, v16 = BitVecs("v13 v14 v15 v16", 64)
s = Solver()
s.add(v14 & ~v16 == 0x11204161012)
s.add((v15 & v16)|(v14 & ~v16)|(v14 & ~v15)|(v16 & ~v15) == 0x3E3A4717373E7F1F)
s.add(((v14 & ~v15) & v16)| (v14 & (v15 & v16 | v15 & ~v16 | ~(v15 | v16))) == 0x8020717153E3013)
s.add(0x3E3A4717373E7F1F ^ v13 == 0x3E3A4717050F791F)
s.add(((v14 & ~v16) | (v15 & v16) | (v15 & v14)) == (~v16 & v14 | 0xC00020130082C0C))
s.check()
m = s.model()
"""
print(m)
[v13 = 842073600,
v16 = 4483973367147818765,
v14 = 577031497978884115,
v15 = 864693882343402508]
"""

cipher = b""
v12 = "i_will_check_is_debug_or_no"
flag = "flag{"
m = [4483973367147818765, 864693882343402508, 577031497978884115, 842073600]
for _ in m:
cipher += long_tobyte_0s(_)
print(cipher)
for _ in range(len(cipher)):
flag += chr(cipher[_] ^ ord(v12[_]))
flag += "}"
print(flag)

貌似是因为多解的原因
导致最后得到的flag不正确
所以比赛时给出了flag的第二部分"e!P0or_a"

flag{We1l_D0ne!P0or_algebra_am_i}


[WUSTCTF2020]level4

链接: https://buuoj.cn/challenges#[WUSTCTF2020]level4

ELF 64

IDA中 main 函数反编译结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl main(int argc, const char **argv, const char **envp)
{
puts("Practice my Data Structure code.....");
puts("Typing....Struct.....char....*left....*right............emmmmm...OK!");
init("Typing....Struct.....char....*left....*right............emmmmm...OK!", argv);
puts("Traversal!");
printf("Traversal type 1:");
type1(&unk_601290);
printf("\nTraversal type 2:");
type2(&unk_601290);
printf("\nTraversal type 3:");
puts(" //type3(&x[22]); No way!");
puts(&byte_400A37);
return 0;
}

二叉树中序遍历

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall type1(char *a1)
{
__int64 result; // rax

if ( a1 )
{
type1(*((_QWORD *)a1 + 1));
putchar(*a1);
result = type1(*((_QWORD *)a1 + 2));
}
return result;
}

二叉树后序遍历

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall type2(char *a1)
{
int result; // eax

if ( a1 )
{
type2(*((_QWORD *)a1 + 1));
type2(*((_QWORD *)a1 + 2));
result = putchar(*a1);
}
return result;
}

结合输出结果来看
先序遍历的输出结果应该就是flag

这里不会写转换脚本
直接在IDA里面动调unk_601290的数据结构了

数据+8Byte 是左子节点的地址
数据+16Byte 是右子节点的地址

flag{This_IS_A_7reE}


[GUET-CTF2019]number_game

链接: https://buuoj.cn/challenges#[GUET-CTF2019]number_game

ELF 64

IDA中 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
unsigned __int64 __fastcall main(int a1, char **a2, char **a3)
{
_QWORD *v4; // [rsp+8h] [rbp-38h]
__int64 v5; // [rsp+10h] [rbp-30h] BYREF
__int16 v6; // [rsp+18h] [rbp-28h]
__int64 v7; // [rsp+20h] [rbp-20h] BYREF
__int16 v8; // [rsp+28h] [rbp-18h]
char v9; // [rsp+2Ah] [rbp-16h]
unsigned __int64 v10; // [rsp+38h] [rbp-8h]

v10 = __readfsqword(0x28u);
v5 = 0LL;
v6 = 0;
v7 = 0LL;
v8 = 0;
v9 = 0;
__isoc99_scanf("%s", &v5);
if ( (unsigned int)sub_4006D6((const char *)&v5) )
{
v4 = sub_400758((__int64)&v5, 0, 0xAu);
sub_400807((__int64)v4, (__int64)&v7);
v9 = 0;
sub_400881((char *)&v7);
if ( (unsigned int)sub_400917() )
{
puts("TQL!");
printf("flag{");
printf("%s", (const char *)&v5);
puts("}");
}
else
{
puts("your are cxk!!");
}
}
return __readfsqword(0x28u) ^ v10;
}
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_4006D6(const char *a1)
{
__int64 result; // rax
int i; // [rsp+1Ch] [rbp-4h]

if ( strlen(a1) == 10 )
{
for ( i = 0; i <= 9; ++i )
{
if ( a1[i] > 52 || a1[i] <= 47 )
goto LABEL_2;
}
result = 1LL;
}
else
{
LABEL_2:
puts("Wrong!");
result = 0LL;
}
return result;
}

函数sub_4006D6要求输入的字符串长度为10且字符集为"01234"
通过控制输入, 动态调试sub_400807得到重新排序之后的字符串
因为实在懒得看代码了, 曲线救国

1
2
3
4
5
0123401234
2331440021

0011223344
3140420213

根据两组输入输出则可以得出解密序列为

1
seq = [6, 3, 8, 1, 5, 7, 9, 0, 2, 4]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 __fastcall sub_400881(char *a1)
{
__int64 result; // rax

byte_601062 = *a1;
byte_601067 = a1[1];
byte_601069 = a1[2];
byte_60106B = a1[3];
byte_60106E = a1[4];
byte_60106F = a1[5];
byte_601071 = a1[6];
byte_601072 = a1[7];
byte_601076 = a1[8];
result = (unsigned __int8)a1[9];
byte_601077 = a1[9];
return result;
}
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
.data:0000000000601060 unk_601060      db  31h ; 1
.data:0000000000601061 db 34h ; 4
.data:0000000000601062 byte_601062 db 23h ; DATA XREF: sub_400881+F↑w
.data:0000000000601063 db 32h ; 2
.data:0000000000601064 db 33h ; 3
.data:0000000000601065 db 33h ; 3
.data:0000000000601066 db 30h ; 0
.data:0000000000601067 byte_601067 db 23h ; DATA XREF: sub_400881+1D↑w
.data:0000000000601068 db 31h ; 1
.data:0000000000601069 byte_601069 db 23h ; DATA XREF: sub_400881+2B↑w
.data:000000000060106A db 30h ; 0
.data:000000000060106B byte_60106B db 23h ; DATA XREF: sub_400881+39↑w
.data:000000000060106C db 32h ; 2
.data:000000000060106D db 33h ; 3
.data:000000000060106E byte_60106E db 23h ; DATA XREF: sub_400881+47↑w
.data:000000000060106F byte_60106F db 23h ; DATA XREF: sub_400881+55↑w
.data:0000000000601070 db 33h ; 3
.data:0000000000601071 byte_601071 db 23h ; DATA XREF: sub_400881+63↑w
.data:0000000000601072 byte_601072 db 23h ; DATA XREF: sub_400881+71↑w
.data:0000000000601073 db 30h ; 0
.data:0000000000601074 db 34h ; 4
.data:0000000000601075 db 32h ; 2
.data:0000000000601076 byte_601076 db 23h ; DATA XREF: sub_400881+7F↑w
.data:0000000000601077 byte_601077 db 23h ; DATA XREF: sub_400881+8D↑w
.data:0000000000601078 db 31h ; 1

可以看到通过v7赋值的地址都是恰好是空值的位置, 则这里是在填空

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 sub_400917()
{
unsigned int v1; // [rsp+0h] [rbp-10h]
int i; // [rsp+4h] [rbp-Ch]
int j; // [rsp+8h] [rbp-8h]
int k; // [rsp+Ch] [rbp-4h]

v1 = 1;
for ( i = 0; i <= 4; ++i )
{
for ( j = 0; j <= 4; ++j )
{
for ( k = j + 1; k <= 4; ++k )
{
if ( *((byte_0 *)&unk_601060 + 5 * i + j) == *((byte_0 *)&unk_601060 + 5 * i + k) )
v1 = 0;
if ( *((byte_0 *)&unk_601060 + 5 * j + i) == *((byte_0 *)&unk_601060 + 5 * k + i) )
v1 = 0;
}
}
}
return v1;
}

看到这里就差不多明白了
横纵不能出现相同的数字, 那么这个程序就是个数独游戏了
且数独的布局如下

1
2
3
4
5
6
sudoku = [
1, 4, X, 2, 3,
3, 0, X, 1, X,
0, X, 2, 3, X,
X, 3, X, X, 0,
4, 2, X, X, 1]

解出后得出序列为

1
cipher = [0, 4, 2, 1, 4, 2, 1, 4, 3, 0]
1
2
3
4
5
6
7
#!/usr/bin/env python3
cipher = [0, 4, 2, 1, 4, 2, 1, 4, 3, 0]
seq = [6, 3, 8, 1, 5, 7, 9, 0, 2, 4]
flag = [0 for _ in range(10)]
for _ in range(len(cipher)):
flag[_] = str(cipher[seq[_]])
print("flag{"+"".join(flag)+"}")

flag{1134240024}


[GWCTF 2019]re3

链接: https://buuoj.cn/challenges#[GWCTF 2019]re3

ELF 64

IDA中 main 函数反编译结果如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
int i; // [rsp+8h] [rbp-48h]
char s[40]; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+48h] [rbp-8h]

v5 = __readfsqword(0x28u);
__isoc99_scanf("%39s", s);
if ( (unsigned int)strlen(s) != 32 )
{
puts("Wrong!");
exit(0);
}
mprotect(&dword_400000, 0xF000uLL, 7);
for ( i = 0; i <= 223; ++i )
*((byte_0 *)sub_402219 + i) ^= 0x99u;
sub_40207B(&unk_603170);
sub_402219(s);
}
1
2
for ( i = 0; i <= 223; ++i )
*((byte_0 *)sub_402219 + i) ^= 0x99u;

注意看着一段代码
这里在对函数sub_402219的内容进行异或运算

sub_402219的内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:0000000000402219 sub_402219      proc near               ; CODE XREF: main+CA↑p
.text:0000000000402219 ; DATA XREF: main:loc_40217C↑o
.text:0000000000402219 ; __unwind {
.text:0000000000402219 int 3 ; Trap to Debugger
.text:0000000000402219 sub_402219 endp
.text:0000000000402219
.text:0000000000402219 ; ---------------------------------------------------------------------------
.text:000000000040221A dw 10D1h, 0D17Ch, 7518h
.text:0000000000402220 dq 812410D199999969h, 0BC9D12D1FD666666h, 61DC10D1999999B1h
.text:0000000000402220 dq 6666A91C14D159A8h, 10D199F9A8E92766h, 12D1666671BA715Eh
.text:0000000000402220 dq 1C14D1666666810Ch, 0D14F10D1666666A9h, 0D166666E9E715E10h
.text:0000000000402220 dq 14D1666666811C12h, 6666A91C14D189C9h, 715E10D14F10D166h
.text:0000000000402220 dq 66B11C5E66666F73h, 1C5E999999986666h, 99999999666666B5h
.text:0000000000402220 dq 666666B51C12A372h, 66811C12D149FAD1h, 892F964998D16666h
.text:0000000000402220 dq 1D1666666B51C12h, 0A199F9A939192F96h, 6666B11C5E93ED5Bh
.text:0000000000402220 dq 0B51C1A9999999966h, 66B5241A98666666h, 0B11C1224E7866666h
.text:0000000000402220 dq 0FD61D412D1666666h, 999999B1BC95AAD1h, 5066667AC0719CEDh
.text:0000000000402220 dq 801F0F5Ah
.text:0000000000402300

那么可以看到函数中内容无法被IDA解析, 所以被认为是数据
所以我们需要使用IDC脚本来对这部分数据进行还原

1
2
3
4
5
6
7
8
9
10
#include <idc.idc>
static main()
{
auto addr = 0x402219;
auto i;
for (i = 0; i <= 223; ++i)
{
PatchByte(addr+i, Byte(addr+i)^0x99);
}
}

先全部转为数据, 再强制转为代码, 最后再创建函数, 且main函数里nop掉那段循环异或的代码
同时main函数需要包含loc_402205, loc_40220F这两段代码, 操作同上

还原之后的函数main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
char s[40]; // [rsp+20h] [rbp-30h] BYREF
unsigned __int64 v4; // [rsp+48h] [rbp-8h]

v4 = __readfsqword(0x28u);
__isoc99_scanf("%39s", s);
if ( (unsigned int)strlen(s) != 32 )
{
puts("Wrong!");
exit(0);
}
mprotect(&dword_400000, 0xF000uLL, 7);
sub_40207B((__int64)&unk_603170);
if ( (unsigned int)sub_402219((__int64)s) )
puts("Correct!");
else
puts("Wrong!");
exit(0);
}

先分析函数sub_40207B

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned __int64 __fastcall sub_40207B(__int64 a1)
{
char v2[16]; // [rsp+10h] [rbp-50h] BYREF
__int64 v3; // [rsp+20h] [rbp-40h] BYREF
__int64 v4; // [rsp+30h] [rbp-30h] BYREF
__int64 v5; // [rsp+40h] [rbp-20h] BYREF
unsigned __int64 v6; // [rsp+58h] [rbp-8h]

v6 = __readfsqword(0x28u);
sub_401CF9(&BASE64_table_603120, 0x40uLL, (__int64)v2);
sub_401CF9(&CRC32_table_603100, 0x14uLL, (__int64)&v3);
sub_401CF9(&Prime_Constants_char_6030C0, 0x35uLL, (__int64)&v4);
sub_401CF9(MD5_Constants_4025C0, 0x100uLL, (__int64)&v5);
sub_401CF9(v2, 0x40uLL, a1);
return __readfsqword(0x28u) ^ v6;
}

BASE64_table_603120经过函数sub_401CF9的运行之后结果保存于v2
v2经过函数sub_401CF9的运行之后结果保存于a1, 即unk_603170

代码过于复杂, 动调得到unk_603170的内容

1
2
3
_603170 = [
0xCB, 0x8D, 0x49, 0x35, 0x21, 0xB4, 0x7A, 0x4C,
0xC1, 0xAE, 0x7E, 0x62, 0x22, 0x92, 0x66, 0xCE]

分析sub_402219

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__int64 __fastcall sub_402219(__int64 a1)
{
unsigned int v2; // [rsp+18h] [rbp-D8h]
int i; // [rsp+1Ch] [rbp-D4h]
char v4[200]; // [rsp+20h] [rbp-D0h] BYREF
unsigned __int64 v5; // [rsp+E8h] [rbp-8h]

v5 = __readfsqword(0x28u);
sub_400A71((__int64)v4, (__int64)&unk_603170);
sub_40196E((__int64)v4, a1);
sub_40196E((__int64)v4, a1 + 16);
v2 = 1;
for ( i = 0; i <= 31; ++i )
{
if ( *(byte_0 *)(i + a1) != qword_6030A0[i] )
v2 = 0;
}
return v2;
}

sub_400A71处理unk_603170
继续跟进看到一个名为RijnDael_AES_LONG_4023A0数据段, 那么基本可以判断这是一个AES加密程序了
sub_40196E也就是用于加密用户所输入的字符串, 且加密模式为ECB(32字节的数据分两次单独加密), 最后的密文存于qword_6030A0

1
2
3
4
5
_6030A0 = [
0xBC, 0x0A, 0xAD, 0xC0, 0x14, 0x7C, 0x5E, 0xCC,
0xE0, 0xB1, 0x40, 0xBC, 0x9C, 0x51, 0xD5, 0x2B,
0x46, 0xB2, 0xB9, 0x43, 0x4D, 0xE5, 0x32, 0x4B,
0xAD, 0x7F, 0xB4, 0xB3, 0x9C, 0xDB, 0x4B, 0x5B]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
from Crypto.Cipher import AES
_603170 = [
0xCB, 0x8D, 0x49, 0x35, 0x21, 0xB4, 0x7A, 0x4C,
0xC1, 0xAE, 0x7E, 0x62, 0x22, 0x92, 0x66, 0xCE]
_6030A0 = [
0xBC, 0x0A, 0xAD, 0xC0, 0x14, 0x7C, 0x5E, 0xCC,
0xE0, 0xB1, 0x40, 0xBC, 0x9C, 0x51, 0xD5, 0x2B,
0x46, 0xB2, 0xB9, 0x43, 0x4D, 0xE5, 0x32, 0x4B,
0xAD, 0x7F, 0xB4, 0xB3, 0x9C, 0xDB, 0x4B, 0x5B]
cipher = ""
for _ in _6030A0:
cipher += chr(_)
key = ""
for _ in _603170:
key += chr(_)
aes = AES.new(key.encode("ISO-8859-1"), AES.MODE_ECB)
print(aes.decrypt(cipher.encode("ISO-8859-1")).decode())

flag{924a9ab2163d390410d0a1f670}


[2019红帽杯]xx

链接: https://buuoj.cn/challenges#[2019红帽杯]xx

EXE 64

IDA中 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
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
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rbx
__int64 v4; // rax
__int128 *v5; // rax
__int64 v6; // r11
__int128 *v7; // r14
int v8; // edi
__int128 *v9; // rsi
char v10; // r10
int v11; // edx
__int64 v12; // r8
unsigned __int64 v13; // rcx
__int64 v14; // rcx
unsigned __int64 v15; // rax
unsigned __int64 i; // rax
__int64 v17; // rax
size_t v18; // rsi
_BYTE *v19; // rbx
_BYTE *v20; // r9
int v21; // er11
char *v22; // r8
__int64 v23; // rcx
char v24; // al
__int64 v25; // r9
__int64 v26; // rdx
__int64 v27; // rax
size_t Size; // [rsp+20h] [rbp-48h] BYREF
__int128 v30; // [rsp+28h] [rbp-40h] BYREF
int v31; // [rsp+38h] [rbp-30h]
int v32; // [rsp+3Ch] [rbp-2Ch]
int Code[4]; // [rsp+40h] [rbp-28h] BYREF
int v34; // [rsp+50h] [rbp-18h]

*(_OWORD *)Code = 0i64;
v34 = 0;
sub_1400018C0(std::cin, argv, Code);
v3 = -1i64;
v4 = -1i64;
do
++v4;
while ( *((_BYTE *)Code + v4) );
if ( v4 != 19 )
{
sub_140001620(std::cout, "error\n");
_exit((int)Code);
}
v5 = (__int128 *)operator new(5ui64);
v6 = *(_QWORD *)&::Code;
v7 = v5;
v8 = 0;
v9 = v5;
do
{
v10 = *((_BYTE *)v9 + (char *)Code - (char *)v5);
v11 = 0;
*(_BYTE *)v9 = v10;
v12 = 0i64;
v13 = -1i64;
do
++v13;
while ( *(_BYTE *)(v6 + v13) );
if ( v13 )
{
do
{
if ( v10 == *(_BYTE *)(v6 + v12) )
break;
++v11;
++v12;
}
while ( v11 < v13 );
}
v14 = -1i64;
do
++v14;
while ( *(_BYTE *)(v6 + v14) );
if ( v11 == v14 )
_exit(v6);
v9 = (__int128 *)((char *)v9 + 1);
}
while ( (char *)v9 - (char *)v5 < 4 );
*((_BYTE *)v5 + 4) = 0;
do
++v3;
while ( *((_BYTE *)Code + v3) );
v15 = 0i64;
v30 = *v7;
while ( *((_BYTE *)&v30 + v15) )
{
if ( !*((_BYTE *)&v30 + v15 + 1) )
{
++v15;
break;
}
if ( !*((_BYTE *)&v30 + v15 + 2) )
{
v15 += 2i64;
break;
}
if ( !*((_BYTE *)&v30 + v15 + 3) )
{
v15 += 3i64;
break;
}
v15 += 4i64;
if ( v15 >= 0x10 )
break;
}
for ( i = v15 + 1; i < 0x10; ++i )
*((_BYTE *)&v30 + i) = 0;
v17 = (__int64)sub_140001AB0((__int64)Code, v3, (unsigned __int8 *)&v30, &Size);
v18 = Size;
v19 = (_BYTE *)v17;
v20 = operator new(Size);
v21 = 1;
*v20 = v19[2];
v22 = v20 + 1;
v20[1] = *v19;
v20[2] = v19[3];
v20[3] = v19[1];
v20[4] = v19[6];
v20[5] = v19[4];
v20[6] = v19[7];
v20[7] = v19[5];
v20[8] = v19[10];
v20[9] = v19[8];
v20[10] = v19[11];
v20[11] = v19[9];
v20[12] = v19[14];
v20[13] = v19[12];
v20[14] = v19[15];
v20[15] = v19[13];
v20[16] = v19[18];
v20[17] = v19[16];
v20[18] = v19[19];
v20[19] = v19[17];
v20[20] = v19[22];
v20[21] = v19[20];
v20[22] = v19[23];
for ( v20[23] = v19[21]; v21 < v18; ++v22 )
{
v23 = 0i64;
if ( v21 / 3 > 0 )
{
v24 = *v22;
do
{
v24 ^= v20[v23++];
*v22 = v24;
}
while ( v23 < v21 / 3 );
}
++v21;
}
*(_QWORD *)&v30 = 0xC0953A7C6B40BCCEui64;
v25 = v20 - (_BYTE *)&v30;
*((_QWORD *)&v30 + 1) = 0x3502F79120209BEFi64;
v26 = 0i64;
v31 = 0xC8021823;
v32 = 0xFA5656E7;
do
{
if ( *((_BYTE *)&v30 + v26) != *((_BYTE *)&v30 + v26 + v25) )
_exit(v8 * v8);
++v8;
++v26;
}
while ( v26 < 24 );
v27 = sub_140001620(std::cout, "You win!");
std::ostream::operator<<(v27, sub_1400017F0);
return 0;
}

IDA中使用插件FindCrypt可以看到程序使用了TEA加密
结合题目可以猜出是XXTEA加密
这题基本是看着别人的WP做出来的

1
.text:0000000140001C4B  global  TEA_DELTA_140001C4B $c0 b'\xb9y7\x9e'

从上往下分析

  • 用户输入字符串, 长度为19
  • 前四个字符在字符集"qwertyuiopasdfghjklzxcvbnm1234567890"中
  • 取前四字节为密钥
  • 加密结果乱序
  • 迭代异或
  • 校验加密结果

迭代异或的方式如下

1
2
3
4
5
6
7
cipher[3] = cipher[3] ^ cipher[0]
cipher[4] = cipher[4] ^ cipher[0]
cipher[5] = cipher[5] ^ cipher[0]
cipher[6] = cipher[6] ^ cipher[0] ^ cipher[1]
cipher[7] = cipher[7] ^ cipher[0] ^ cipher[1]
cipher[8] = cipher[8] ^ cipher[0] ^ cipher[1]
...

密文如下(大端赋值, 之后需要转为小端的字符串)

1
2
3
4
*(_QWORD *)&v30 = 0xC0953A7C6B40BCCEui64;
*((_QWORD *)&v30 + 1) = 0x3502F79120209BEFi64;
v31 = 0xC8021823;
v32 = 0xFA5656E7;

且这三个变量的内存分布如下

1
2
3
__int128 v30; // [rsp+28h] [rbp-40h] BYREF
int v31; // [rsp+38h] [rbp-30h]
int v32; // [rsp+3Ch] [rbp-2Ch]

所以四个数据的整体才是密文部分

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
#!/usr/bin/env python3
import binascii
import xxtea
cipher = [
0xC0953A7C6B40BCCE,
0x3502F79120209BEF,
0xC8021823,
0xFA5656E7]
# 转小端字节串
_byte = b""
for _ in cipher:
_byte += binascii.unhexlify(hex(_)[2:].encode("ISO-8859-1"))[::-1]
# 迭代异或
__byte = [_ for _ in _byte]
for _ in range(len(__byte)-1, -1, -1):
for __ in range(_//3):
__byte[_] = __byte[_] ^ __byte[__]
# 异或结果转为字节串
___byte = ""
for _ in __byte:
___byte += chr(_)
___byte = ___byte.encode("ISO-8859-1")
# 还原顺序
seq_enc = [2, 0, 3, 1, 6, 4, 7, 5, 10, 8, 11, 9, 14, 12, 15, 13, 18, 16, 19, 17, 22, 20, 23, 21]
seq_dec = [-1 for _ in seq_enc]
for _ in range(len(seq_enc)):
seq_dec[_] = seq_enc.index(_)
____byte = b""
for _ in range(len(___byte)):
____byte += chr(___byte[seq_dec[_]]).encode("ISO-8859-1")
# 解密
key = "flag"
flag = xxtea.decrypt(____byte, key).decode()
print(flag)

flag{CXX_and_++tea}


findKey

链接: https://buuoj.cn/challenges#findKey

EXE 32

一开始没找到主函数

1
.rdata:00423028 aFlag           db 'flag{}',0           ; DATA XREF: .text:00401A3A↑o

找到这样的一个字符串
搜索文本之后找到位置loc_401A37
但是这里并没有被定义成函数
粗略看下有一段疑似插入了花指令的代码

1
2
3
4
5
6
7
8
9
10
11
12
.text:0040191D loc_40191D:                             ; CODE XREF: .text:0040193D↓j
.text:0040191D push offset byte_428C54
.text:00401922 call _strlen
.text:00401927 add esp, 4
.text:0040192A push eax
.text:0040192B push offset byte_428C54
.text:00401930 call sub_40101E
.text:00401935 add esp, 0Ch
.text:00401938 nop
.text:00401939 jz short loc_401948
.text:0040193B jnz short loc_401948
.text:0040193D jmp short near ptr loc_40191D+2

根据我做的那些逆向题目的经验
按理说jz的前一条指令是能够对ZF产生影响的, 例如cmp, test
更奇怪的是后面跟了一条jnz和一条jmp

把这三条nop掉就可以定义为函数了
反编译后了下伪代码, 感觉差不多就是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
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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // eax
size_t v5; // eax
DWORD v6; // eax
HWND v7; // eax
int v8; // eax
int v9; // eax
const CHAR *v10; // [esp+0h] [ebp-44Ch]
const CHAR *v11; // [esp+4h] [ebp-448h]
UINT v12; // [esp+8h] [ebp-444h]
CHAR v13[256]; // [esp+54h] [ebp-3F8h] BYREF
char v14[7]; // [esp+154h] [ebp-2F8h] BYREF
__int16 v15; // [esp+15Bh] [ebp-2F1h]
char v16; // [esp+15Dh] [ebp-2EFh]
char Str[33]; // [esp+160h] [ebp-2ECh] BYREF
char v18[220]; // [esp+181h] [ebp-2CBh] BYREF
__int16 v19; // [esp+25Dh] [ebp-1EFh]
char v20; // [esp+25Fh] [ebp-1EDh]
CHAR v21[256]; // [esp+260h] [ebp-1ECh] BYREF
CHAR String[4]; // [esp+360h] [ebp-ECh] BYREF
int v23; // [esp+364h] [ebp-E8h]
__int16 v24; // [esp+368h] [ebp-E4h]
CHAR Text[32]; // [esp+36Ch] [ebp-E0h] BYREF
struct tagRECT Rect; // [esp+38Ch] [ebp-C0h] BYREF
CHAR Buffer[100]; // [esp+39Ch] [ebp-B0h] BYREF
HDC hdc; // [esp+400h] [ebp-4Ch]
struct tagPAINTSTRUCT Paint; // [esp+404h] [ebp-48h] BYREF
int v30; // [esp+444h] [ebp-8h]
int v31; // [esp+448h] [ebp-4h]
LPARAM lParam; // [esp+460h] [ebp+14h]

LoadStringA(hInstance, 0x6Au, Buffer, 0x64);
if ( (unsigned int)argv > 0x111 )
{
if ( argv == (const char **)517 )
{
if ( strlen((const char *)String1) > 6 )
ExitProcess(0);
if ( strlen((const char *)String1) )
{
memset(v21, 0, sizeof(v21));
v5 = strlen((const char *)String1);
memcpy(v21, String1, v5);
v6 = strlen((const char *)String1);
v7 = (HWND)sub_40101E(String1, v6, (LPSTR)String1);
MessageBoxA(v7, v10, v11, v12);
strcpy(Str, "0kk`d1a`55k222k2a776jbfgd`06cjjb");
memset(v18, 0, sizeof(v18));
v19 = 0;
v20 = 0;
strcpy(v14, "SS");
*(_DWORD *)&v14[3] = 0;
v15 = 0;
v16 = 0;
v8 = strlen(Str);
sub_401005(v14, (int)Str, v8);
if ( _strcmpi((const char *)String1, Str) )
{
SetWindowTextA((HWND)argc, "flag{}");
MessageBoxA((HWND)argc, "Are you kidding me?", "^_^", 0);
ExitProcess(0);
}
memcpy(v13, &unk_423030, 0x32u);
v9 = strlen(v13);
sub_401005(v21, (int)v13, v9);
MessageBoxA((HWND)argc, v13, 0, 0x32u);
}
++dword_428D54;
}
else
{
if ( argv != (const char **)520 )
return DefWindowProcA((HWND)argc, (UINT)argv, (WPARAM)envp, lParam);
if ( dword_428D54 == 16 )
{
strcpy(String, "ctf");
v23 = 0;
v24 = 0;
SetWindowTextA((HWND)argc, String);
strcpy(Text, "Are you kidding me?");
MessageBoxA((HWND)argc, Text, Buffer, 0);
}
++dword_428D54;
}
}
else if ( argv == (const char **)273 )
{
v31 = (unsigned __int16)envp;
v30 = HIWORD(envp);
if ( (unsigned __int16)envp == 104 )
{
DialogBoxParamA(hInstance, (LPCSTR)0x67, (HWND)argc, (DLGPROC)DialogFunc, 0);
}
else
{
if ( (unsigned __int16)envp != 105 )
return DefWindowProcA((HWND)argc, (UINT)argv, (WPARAM)envp, lParam);
DestroyWindow((HWND)argc);
}
}
else if ( argv == (const char **)2 )
{
PostQuitMessage(0);
}
else
{
if ( argv != (const char **)15 )
return DefWindowProcA((HWND)argc, (UINT)argv, (WPARAM)envp, lParam);
hdc = BeginPaint((HWND)argc, &Paint);
GetClientRect((HWND)argc, &Rect);
v4 = strlen(Buffer);
DrawTextA(hdc, Buffer, v4, &Rect, 1u);
EndPaint((HWND)argc, &Paint);
}
return 0;
}

先跟进sub_40101E

1
if ( CryptCreateHash(phProv, 0x8003u, 0, 0, &phHash) )

看到这句就知道是md5加密了

再跟进sub_401005

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
unsigned int __cdecl sub_401590(LPCSTR lpString, int a2, int a3)
{
unsigned int result; // eax
unsigned int i; // [esp+4Ch] [ebp-Ch]
unsigned int v5; // [esp+54h] [ebp-4h]

v5 = lstrlenA(lpString);
for ( i = 0; ; ++i )
{
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(i + a2) ^= lpString[i % v5];
}
return result;
}

简单的异或

1
sub_401005(v14, (int)Str, v8);
  • v14 = "SS"
  • Str = "0kk`d1a`55k222k2a776jbfgd`06cjjb"
  • v8 = 32

也就是说Str在异或之后与String1的md5结果比较

1
2
3
4
5
6
#!/usr/bin/env python3
xor = "0kk`d1a`55k222k2a776jbfgd`06cjjb"
dexor = ""
for _ in xor:
dexor += chr(ord(_) ^ ord("S"))
print(dexor)

得到c8837b23ff8aaa8a2dde915473ce0991
md5解密结果为123321

再跟进函数DialogFunc

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 __stdcall sub_401B20(HWND hDlg, int a2, int a3, int a4)
{
switch ( a2 )
{
case 0x110:
return 1;
case 0x111:
if ( (unsigned __int16)a3 != 1 && (unsigned __int16)a3 != 2 )
goto LABEL_6;
EndDialog(hDlg, (unsigned __int16)a3);
return 1;
case 0x202:
LABEL_6:
String1[dword_428C50++] = 49;
return 0;
case 0x205:
String1[dword_428C50++] = 51;
return 0;
case 0x208:
if ( dword_428D58 < 3 )
++dword_428D5C;
String1[dword_428C50++] = 50;
return 0;
default:
return 0;
}
}

这里的a2一开始没太懂
后来看了别人的wp才知道貌似是Windows Message

1
2
3
#define WM_LBUTTONUP                    0x0202
#define WM_MBUTTONUP 0x0208
#define WM_RBUTTONUP 0x0205

https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-lbuttonup
https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-mbuttonup
https://docs.microsoft.com/en-us/windows/win32/inputdev/wm-rbuttonup

那么也就是按鼠标左键, 鼠标中键, 鼠标右键, 鼠标右键, 鼠标中键, 鼠标左键

值得一提的是, 调用函数DialogFunc代码

1
DialogBoxParamA(hInstance, (LPCSTR)103, (HWND)argc, (DLGPROC)DialogFunc, 0);

Resource Hacker中可以看到编号103对应着About对话框
也就是说需要在About对话框中按上述顺序点击鼠标
不是做这道题我都不会意识到我的鼠标中键已经报废了, 最后动调改String1的值

flag{n0_Zu0_n0_die}


[FlareOn5]Minesweeper Championship Registration

链接: https://buuoj.cn/challenges#[FlareOn5]Minesweeper Championship Registration

Java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import javax.swing.JOptionPane;

public class InviteValidator
{
public static void main(String[] args)
{
String response = JOptionPane.showInputDialog(null, "Enter your invitation code:", "Minesweeper Championship 2018", 3);
if (response.equals("GoldenTicket2018@flare-on.com"))
{
JOptionPane.showMessageDialog(null, "Welcome to the Minesweeper Championship 2018!\nPlease enter the following code to the ctfd.flare-on.com website to compete:\n\n" + response, "Success!", -1);
}
else
{
JOptionPane.showMessageDialog(null, "Incorrect invitation code. Please try again next year.", "Failure", 0);
}
}
}

flag写脸上了

flag{GoldenTicket2018@flare-on.com}


[网鼎杯 2020 青龙组]jocker

链接: https://buuoj.cn/challenges#[网鼎杯 2020 青龙组]jocker

EXE 32

主函数看到对函数进行异或就知道不对劲了

1
2
3
4
5
6
7
8
9
10
#include <idc.idc>
static main()
{
auto addr = 0x00401500;
auto i;
for (i = 0; i <= 186; ++i)
{
PatchByte(addr+i, Byte(addr+i)^0x41);
}
}

具体步骤参照这里
http://yoloyolo.top/2021/05/31/Reverse-3/#gwctf-2019re3
函数finally的前一部分也有被异或过, 需要重新定义为函数

函数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
int __cdecl main(int argc, const char **argv, const char **envp)
{
char Str[50]; // [esp+12h] [ebp-96h] BYREF
char Destination[80]; // [esp+44h] [ebp-64h] BYREF
DWORD flOldProtect; // [esp+94h] [ebp-14h] BYREF
size_t v7; // [esp+98h] [ebp-10h]
int v8; // [esp+9Ch] [ebp-Ch]

__main();
puts("please input you flag:");
if ( !VirtualProtect(encrypt, 0xC8u, 4u, &flOldProtect) )
exit(1);
scanf("%40s", Str);
v7 = strlen(Str);
if ( v7 != 24 )
{
puts("Wrong!");
exit(0);
}
strcpy(Destination, Str);
wrong(Str);
omg(Str);
v8 = 0;
if ( encrypt(Destination) )
finally(Destination);
return 0;
}

跟进函数wrong

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
char *__cdecl wrong(char *a1)
{
char *result; // eax
int i; // [esp+Ch] [ebp-4h]

for ( i = 0; i <= 23; ++i )
{
result = &a1[i];
if ( (i & 1) != 0 )
a1[i] -= i;
else
a1[i] ^= i;
}
return result;
}

简单的加密
继续看函数omg

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl omg(char *a1)
{
int result; // eax
int v2[24]; // [esp+18h] [ebp-80h] BYREF
int i; // [esp+78h] [ebp-20h]
int v4; // [esp+7Ch] [ebp-1Ch]

v4 = 1;
qmemcpy(v2, dword_4030C0, sizeof(v2));
for ( i = 0; i <= 23; ++i )
{
if ( a1[i] != v2[i] )
v4 = 0;
}
if ( v4 == 1 )
result = puts("hahahaha_do_you_find_me?");
else
result = puts("wrong ~~ But seems a little program");
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python3
_4030C0 = [
0x66, 0x6B, 0x63, 0x64, 0x7F, 0x61, 0x67, 0x64,
0x3B, 0x56, 0x6B, 0x61, 0x7B, 0x26, 0x3B, 0x50,
0x63, 0x5F, 0x4D, 0x5A, 0x71, 0x0C, 0x37, 0x66]
flag = ""

for _ in range(len(_4030C0)):
if _ % 2 == 0:
flag += chr(_4030C0[_] ^ _)
else:
flag += chr(_4030C0[_] + _)
print(flag)

得到flag{fak3_alw35_sp_me!!}
然而是个假flag

继续看函数encrypt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl encrypt(char *a1)
{
int v2[19]; // [esp+1Ch] [ebp-6Ch] BYREF
int v3; // [esp+68h] [ebp-20h]
int i; // [esp+6Ch] [ebp-1Ch]

v3 = 1;
qmemcpy(v2, dword_403040, sizeof(v2));
for ( i = 0; i <= 18; ++i )
{
if ( (char)(a1[i] ^ Buffer[i]) != v2[i] )
{
puts("wrong ~");
v3 = 0;
exit(0);
}
}
puts("come here");
return v3;
}
1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
flag = ""
_buffer = "hahahaha_do_you_find_me?"
_403040 = [
0x0E, 0x0D, 0x09, 0x06, 0x13, 0x05, 0x58, 0x56,
0x3E, 0x06, 0x0C, 0x3C, 0x1F, 0x57, 0x14, 0x6B,
0x57, 0x59, 0x0D]
for _ in range(len(_403040)):
flag += chr(_403040[_] ^ ord(_buffer[_]))
print(flag)

得到flag{d07abccf8a410c
缺了五个字符

看函数finally

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int __cdecl finally(char *a1)
{
unsigned int v1; // eax
int result; // eax
char v3[9]; // [esp+13h] [ebp-15h] BYREF
int v4; // [esp+1Ch] [ebp-Ch]

strcpy(v3, "%tp&:");
v1 = time(0);
srand(v1);
v4 = rand() % 100;
if ( (v3[*(_DWORD *)&v3[5]] != a1[*(_DWORD *)&v3[5]]) == v4 )
result = puts("Really??? Did you find it?OMG!!!");
else
result = puts("I hide the last part, you will not succeed!!!");
return result;
}

看着有点发蒙, 感觉也没有在做什么正经的运算
v3的赋值算是给了一点点提示吧
奇奇怪怪的脑洞

1
2
hex(ord(":")^ord('}'))
'0x47'
1
2
3
4
5
6
7
#!/usr/bin/env python3
key = 0x47
flag = "flag{d07abccf8a410c"
v3 = "%tp&:"
for _ in v3:
flag += chr(ord(_) ^ key)
print(flag)

flag{d07abccf8a410cb37a}


equation

链接: https://buuoj.cn/challenges#equation

Javascript

看着这道题莫名地会想到"高明的黑客"
没啥好说的, 直接放代码

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
#!/usr/bin/env python3
import re
import execjs
raw = open("equation.html", "r").read()
data = re.findall("if\((.*)\)", raw)[0]
data_list = data.split("&&")
exp_sum = []
res_sum = []
for cnt in range(len(data_list)):
test = data_list[cnt].split("==")[0]
equ = data_list[cnt].split("==")[1]
res = [int(execjs.eval(equ))]
res_sum.append(res)
symbol = []
if test.startswith("-"):
symbol.append("-")
else:
symbol.append("+")
test = re.sub("\]\+l\[", " + ", test)
test = re.sub("\]\-l\[", " - ", test)
test = test.replace("l[", "")[:-1]
symbol_temp = re.findall(" ([\+\-]) ", test)
for _ in symbol_temp:
symbol.append(_)
test = re.sub(" [\+\-] ", " ",test)
jscode = test.split(" ")
exp = [0 for _ in range(42)]
for _ in range(len(jscode)):
seq = int(execjs.eval(jscode[_]))
if symbol[_] == "+":
exp[seq] = 1
elif symbol[_] == "-":
exp[seq] = -1
exp_sum.append(exp)
b = Matrix(res_sum)
x = Matrix(exp_sum)
out = x.solve_right(b)
flag = ""
for _ in out:
flag += chr(_[0])
print(flag)

execjs.eval()运行的速度好慢, 也懒得找能够代替这个函数的函数了
100次17s, 1638个字符串, 5分钟左右


[FlareOn5]Ultimate Minesweeper

链接: https://buuoj.cn/challenges#[FlareOn5]Ultimate Minesweeper

.NET

先放IDA里看看
看到字符串"Congratulations!"

查看引用这个字符串的函数, 然后用dnspy看这些函数

MainForm::SquareRevealedCallback

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// UltimateMinesweeper.MainForm
// Token: 0x0600000C RID: 12 RVA: 0x00002348 File Offset: 0x00000548
private void SquareRevealedCallback(uint column, uint row)
{
if (this.MineField.BombRevealed)
{
this.stopwatch.Stop();
Application.DoEvents();
Thread.Sleep(1000);
new FailurePopup().ShowDialog();
Application.Exit();
}
this.RevealedCells.Add(row * MainForm.VALLOC_NODE_LIMIT + column);
if (this.MineField.TotalUnrevealedEmptySquares == 0)
{
this.stopwatch.Stop();
Application.DoEvents();
Thread.Sleep(1000);
new SuccessPopup(this.GetKey(this.RevealedCells)).ShowDialog();
Application.Exit();
}
}

把判断失败的代码删掉再保存程序
找出三个点就能够进入过关的逻辑
但是flag的内容应该是与点击的格子相关
而且点的位置不是随机生成的
记下位置再玩一遍就能得到flag

flag{Ch3aters_Alw4ys_W1n@flare-on.com}


[2019红帽杯]childRE

链接: https://buuoj.cn/challenges#[2019红帽杯]childRE

EXE 64

IDA中 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
55
56
57
58
59
60
61
62
63
64
65
66
67
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 v3; // rax
__int64 v4; // rax
const CHAR *v5; // r11
__int64 v6; // r10
int v7; // er9
const CHAR *v8; // r10
__int64 v9; // rcx
__int64 v10; // rax
int result; // eax
unsigned int v12; // ecx
__int64 v13; // r9
__int128 v14[2]; // [rsp+20h] [rbp-38h] BYREF

v14[0] = 0i64;
v14[1] = 0i64;
sub_140001080("%s");
v3 = -1i64;
do
++v3;
while ( *((_BYTE *)v14 + v3) );
if ( v3 != 31 )
{
while ( 1 )
Sleep(0x3E8u);
}
v4 = sub_140001280(v14);
v5 = name;
if ( v4 )
{
sub_1400015C0(*(_QWORD *)(v4 + 8));
sub_1400015C0(*(_QWORD *)(v6 + 16));
v7 = dword_1400057E0;
v5[dword_1400057E0] = *v8;
dword_1400057E0 = v7 + 1;
}
UnDecorateSymbolName(v5, outputString, 0x100u, 0);
v9 = -1i64;
do
++v9;
while ( outputString[v9] );
if ( v9 == 62 )
{
v12 = 0;
v13 = 0i64;
do
{
if ( a1234567890Qwer[outputString[v13] % 23] != *(_BYTE *)(v13 + 0x140003478i64) )
_exit(v12);
if ( a1234567890Qwer[outputString[v13] / 23] != *(_BYTE *)(v13 + 0x140003438i64) )
_exit(v12 * v12);
++v12;
++v13;
}
while ( v12 < 0x3E );
sub_140001020("flag{MD5(your input)}\n");
result = 0;
}
else
{
v10 = sub_1400018A0(std::cout);
std::ostream::operator<<(v10, sub_140001A60);
result = -1;
}
return result;
}

0x140003478i64

1
.rdata:0000000140003478 a46200860044218 db '(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&',0

先还原出outputString

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
import hashlib
_003478 = "(_@4620!08!6_0*0442!@186%%0@3=66!!974*3234=&0^3&1@=&0908!6_0*&"
_003438 = "55565653255552225565565555243466334653663544426565555525555222"
_0033A0 = "1234567890-=!@#$%^&*()_+qwertyuiop[]QWERTYUIOP{}asdfghjkl;'ASDFGHJKL:\"ZXCVBNM<>?zxcvbnm,./"
output = [0 for _ in _003478]
for _ in range(len(_003478)):
b = _0033A0.index(_003478[_])
a = _0033A0.index(_003438[_])
output[_] = chr(23 * a + b)
cipher = "".join(output)
print(cipher)

得到结果如下

1
private: char * __thiscall R0Pxx::My_Aut0_PWN(unsigned char *)Traceback (most recent call last)

然后是C++名字修饰
在一块实在没怎么了解过

https://www.freesion.com/article/6515734088/

中间的函数貌似有替换字符串顺序
断点下到UnDecorateSymbolName(v5, outputString, 0x100u, 0);就能在寄存器看到替换顺序之后字符串

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env python3
decorate = "?My_Aut0_PWN@R0Pxx@@AAEPADPAE@Z"
seq1 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
seq2 = "PQHRSIDTUJVWKEBXYLZ[MF\\]N^_OGCA"
cipher = [0 for _ in decorate]
for _ in range(len(cipher)):
cipher[_] = decorate[seq2.index(seq1[_])]
print("".join(cipher))
flag = "flag{" + hashlib.md5("".join(cipher).encode()).hexdigest() + "}"
print(flag)

flag{63b148e750fed3a33419168ac58083f5}


[ACTF新生赛2020]SoulLike

链接: https://buuoj.cn/challenges#[ACTF新生赛2020]SoulLike

ELF 64

IDA中 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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 result; // rax
char v5; // [rsp+7h] [rbp-B9h]
int i; // [rsp+8h] [rbp-B8h]
int j; // [rsp+Ch] [rbp-B4h]
int v8[14]; // [rsp+10h] [rbp-B0h] BYREF
char v9[110]; // [rsp+4Ah] [rbp-76h] BYREF
unsigned __int64 v10; // [rsp+B8h] [rbp-8h]

v10 = __readfsqword(0x28u);
printf("input flag:");
scanf("%s", &v9[6]); strcpy(v9, "actf{");
v5 = 1;
for ( i = 0; i <= 4; ++i )
{
if ( v9[i] != v9[i + 6] )
{
v5 = 0;
goto LABEL_6;
}
}
if ( !v5 )
goto LABEL_16;
LABEL_6:
for ( j = 0; j <= 11; ++j )
v8[j] = v9[j + 11];
if ( (unsigned __int8)sub_83A(v8) && v9[23] == 125 )
{
printf("That's true! flag is %s", &v9[6]);
result = 0LL;
}
else
{
LABEL_16:
printf("Try another time...");
result = 0LL;
}
return result;
}

函数sub_83A代码过长
IDA无法反编译
需要修改文件hexrays.cfg

1
MAX_FUNCSIZE            = 64        // Functions over 64K are not decompiled

这个数值改大点就行

反编译了sub_83A就知道该干嘛了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/env python3
import re
import os
data = open("data", "r").read().replace(" ", "").replace("u;", "").replace(";", "").replace("*a1", "a1[0]")
data = re.sub("\+\+(.*)", "\\1 -= 1", data).split("\n")
write = open("data_re.py", "w")
write.write("a1 = [126, 50, 37, 88, 89, 107, 53, 110, 0, 19, 30, 56]\n")
for _ in data[::-1]:
write.write(_+"\n")
write.write("""flag = "flag{"
for _ in a1:
flag += chr(_)
flag += "}"
print(flag)""")
write.close()
os.system("python3 data_re.py")

flag{b0Nf|Re_LiT!}


[MRCTF2020]PixelShooter

链接: https://buuoj.cn/challenges#[MRCTF2020]PixelShooter

Android

貌似是用了U3D, 改成zip解压, 找到Assembly-CSharp.dll
在IDA里看字符串就行

flag{Unity_1S_Fun_233}


[安洵杯 2019]crackMe

链接: https://buuoj.cn/challenges#[安洵杯 2019]crackMe

EXE 32

IDA中 _main_0 函数反编译结果如下

1
2
3
4
5
6
7
8
9
10
11
int __cdecl __noreturn main_0(int argc, const char **argv, const char **envp)
{
int (__cdecl *v3)(_DWORD *); // [esp-4h] [ebp-D0h]

printf("please Input the flag:\n");
scanf_s("%s", &unk_41A1E4);
MessageBoxW(0, L"Exception", L"Warning", 0);
v3 = sub_41100F;
MEMORY[0] = 1;
sub_411136(HIWORD(v3));
}

运行的时候回弹一个"hooked"的窗口
动调发现函数MessageBoxW被hook到了sub_412AB0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __stdcall sub_412AB0(int a1, int a2, int a3, int a4)
{
size_t i; // [esp+D8h] [ebp-8h]

for ( i = 0; i < j_strlen(Str); ++i )
{
if ( Str[i] <= 122 && Str[i] >= 97 )
{
Str[i] -= 32;
}
else if ( Str[i] <= 90 && Str[i] >= 65 )
{
Str[i] += 32;
}
}
MessageBoxA(0, "hooked", "successed", 0);
AddVectoredExceptionHandler(0, Handler);
return 0;
}

做了一个VEH的异常处理

1
2
3
4
5
6
7
8
9
10
11
12
int __stdcall Handler_0(_DWORD **a1)
{
char v2[20]; // [esp+D0h] [ebp-18h] BYREF

if ( **a1 == -1073741819 )
{
qmemcpy(v2, "where_are_u_now?", 16);
sub_411172(&unk_41A218, v2);
SetUnhandledExceptionFilter(TopLevelExceptionFilter);
}
return 0;
}

VEH异常处理又套了一个SEH异常处理
运行完函数sub_41100F之后

1
mov dword ptr [eax], 1

这条语句会触发异常
从而进入VEH的异常处理函数

理一遍顺序

  • Base64表内容变换
  • 函数sub_411172使用SM4加密输入内容(搜一下0xA3B1BAC6)
  • Str2顺序变换
  • 函数sub_4110FF变换Base64表顺序
  • Str2与Base64编码结果比较
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
#!/usr/bin/env python3
import base64
import binascii
import pysm4

cipher = "1UTAOIkpyOSWGv/mOYFY4R!!"
cipher_list = [0 for _ in cipher ]
for _ in range(0, len(cipher_list), 2): # 换位
cipher_list[_] = cipher[_+1]
cipher_list[_+1] = cipher[_]
cipher = "".join(cipher_list).replace("!", "")

base64_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
base64_table_re = [0 for _ in base64_table]
for _ in range(len(base64_table_re)): # 换Base64表
if 97 <= ord(base64_table[(_+24)%64]) <= 122:
base64_table_re[_] = chr(ord(base64_table[(_+24)%64])-32)
elif 65 <= ord(base64_table[(_+24)%64]) <= 90:
base64_table_re[_] = chr(ord(base64_table[(_+24)%64])+32)
else:
base64_table_re[_] = base64_table[(_+24)%64]
base64_table_re = "".join(base64_table_re)

cipher_list = [0 for _ in cipher]
for _ in range(len(cipher)): # Base64换表解码
cipher_list[_] = base64_table[base64_table_re.index(cipher[_])]
cipher = "".join(cipher_list)
cipher = base64.b64decode(cipher+"==")

key = b"where_are_u_now?" # SM4解密
flag = pysm4.decrypt(int(binascii.hexlify(cipher).decode(), 16), (int(binascii.hexlify(key).decode(), 16)))
print(binascii.unhexlify(hex(flag)[2:].encode()))

flag{SM4foRExcepioN?!}


[FlareOn1]Bob Doge

链接: https://buuoj.cn/challenges#[FlareOn1]Bob Doge

EXE 64

下载下来是一个安装程序
得到Challenge1.exe
丢进dnspy64
在函数btnDecode_Click里打断点看字符串
可以看到flag

flag{3rmahg3rd.b0b.d0ge@flare-on.com}


EOF