DownUnderCTF 2021 Flag_Checker

Flag Checker

IDA Pro 打开:

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
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int i; // [rsp+0h] [rbp-40h]
int j; // [rsp+4h] [rbp-3Ch]
__int64 v5; // [rsp+8h] [rbp-38h]
char s[40]; // [rsp+10h] [rbp-30h] BYREF
unsigned __int64 v7; // [rsp+38h] [rbp-8h]

v7 = __readfsqword(0x28u);
sub_11EF(a1, a2, a3);
printf("What's the flag?: ");
fgets(s, 37, stdin);
if ( strlen(s) != 36 )
sub_1BDC();
for ( i = 0; i <= 15; ++i )
{
combine((__int64)s);
v5 = shuffle1(s);
shuffle2(s, v5);
}
for ( j = 0; j <= 35; ++j )
{
if ( s[j] != byte_4080[j] )
sub_1BDC();
}
puts("Correct! :)");
exit(0);
}

程序先读取输入,进行十六次加密操作(combine、shuffle1、shuffle2)。最终将加密结果与
数组 byte_4080 进行比对。

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
unsigned __int64 __fastcall combine(__int64 a1)
{
int v2[8]; // [rsp+10h] [rbp-C0h] BYREF
int v3[8]; // [rsp+30h] [rbp-A0h] BYREF
int v4[8]; // [rsp+50h] [rbp-80h] BYREF
int v5[8]; // [rsp+70h] [rbp-60h] BYREF
int v6[8]; // [rsp+90h] [rbp-40h] BYREF
int v7[6]; // [rsp+B0h] [rbp-20h] BYREF
unsigned __int64 v8; // [rsp+C8h] [rbp-8h]

v8 = __readfsqword(0x28u);
v2[0] = 0;
v2[1] = 1;
v2[2] = 2;
v2[3] = 6;
v2[4] = 12;
v2[5] = 18;
v3[0] = 3;
v3[1] = 4;
v3[2] = 5;
v3[3] = 11;
v3[4] = 17;
v3[5] = 23;
v4[0] = 7;
v4[1] = 8;
v4[2] = 9;
v4[3] = 13;
v4[4] = 14;
v4[5] = 15;
v5[0] = 10;
v5[1] = 16;
v5[2] = 22;
v5[3] = 28;
v5[4] = 29;
v5[5] = 35;
v6[0] = 19;
v6[1] = 20;
v6[2] = 24;
v6[3] = 25;
v6[4] = 26;
v6[5] = 30;
v7[0] = 21;
v7[1] = 27;
v7[2] = 31;
v7[3] = 32;
v7[4] = 33;
v7[5] = 34;
num_combine(a1, v2);
num_combine(a1, v3);
num_combine(a1, v4);
num_combine(a1, v5);
num_combine(a1, v6);
num_combine(a1, v7);
return v8 - __readfsqword(0x28u);
}

__int64 __fastcall num_combine(__int64 a1, int *a2)
{
char v2; // bl
char v3; // bl
char v4; // bl
char v5; // bl
char v6; // bl
char v7; // al
__int64 result; // rax
unsigned __int8 v9; // [rsp+14h] [rbp-14h]
unsigned __int8 v10; // [rsp+15h] [rbp-13h]
unsigned __int8 v11; // [rsp+16h] [rbp-12h]
unsigned __int8 v12; // [rsp+17h] [rbp-11h]
unsigned __int8 v13; // [rsp+18h] [rbp-10h]
unsigned __int8 v14; // [rsp+19h] [rbp-Fh]
char v15; // [rsp+1Ah] [rbp-Eh]
char v16; // [rsp+1Bh] [rbp-Dh]
char v17; // [rsp+1Ch] [rbp-Ch]
char v18; // [rsp+1Dh] [rbp-Bh]
char v19; // [rsp+1Eh] [rbp-Ah]
unsigned __int8 v20; // [rsp+1Fh] [rbp-9h]

v9 = *(_BYTE *)(*a2 + a1);
v10 = *(_BYTE *)(a2[1] + a1);
v11 = *(_BYTE *)(a2[2] + a1);
v12 = *(_BYTE *)(a2[3] + a1);
v13 = *(_BYTE *)(a2[4] + a1);
v14 = *(_BYTE *)(a2[5] + a1);
v2 = v11 ^ v9 ^ char_transform(v9);
v3 = char_transform(v11) ^ v2;
v15 = v3 ^ char_transform(v13);
v4 = v12 ^ v10 ^ char_transform(v10);
v5 = char_transform(v12) ^ v4;
v16 = v5 ^ char_transform(v14);
v17 = v13 ^ char_transform(v9);
v18 = v14 ^ char_transform(v10);
v6 = v9 ^ char_transform(v9);
v19 = v6 ^ char_transform(v11);
v7 = char_transform(v10);
v20 = v10 ^ v7 ^ char_transform(v12);
*(_BYTE *)(a1 + *a2) = v15;
*(_BYTE *)(a1 + a2[1]) = v16;
*(_BYTE *)(a1 + a2[2]) = v17;
*(_BYTE *)(a1 + a2[3]) = v18;
*(_BYTE *)(a1 + a2[4]) = v19;
result = v20;
*(_BYTE *)(a1 + a2[5]) = v20;
return result;
}

__int64 __fastcall char_transform(unsigned __int8 a1)
{
return (2 * a1) ^ (27 * (unsigned int)(a1 >> 7));
}

动态调试发现shuffle1不会修改input,我们只逆向combine和shuffle2。

盗一个国外大佬写的脚本: https://gist.github.com/00xc/4fc261156bd919c0179abf38e4eae637

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
from z3 import *

def ch_transform(c):
return ((c >> 7) * 0x1b ^ c * 2) & 0xff

def reverse_shuffle(inp):

mappings = {0: 29, 1: 34, 2: 13, 3: 18, 4: 15, 5: 20, 6: 35, 7: 28, 8: 19, 9: 12, 10: 21, 11: 14, 12: 3, 13: 8, 14: 25, 15: 30, 16: 1, 17: 6, 18: 9, 19: 2, 20: 31, 21: 24, 22: 7, 23: 0, 24: 5, 25: 10, 26: 17, 27: 22, 28: 27, 29: 32, 30: 11, 31: 4, 32: 23, 33: 16, 34: 33, 35: 26}
mappings = {v:k for k,v in mappings.items()}

out = [None] * len(inp)
for orig, new in mappings.items():
out[new] = inp[orig]

return out

def reverse_num_combine(inp, nums):

# The known output values
n_out = [inp[nums[i]] for i in range(6)]

# The unknown input values that produce the output
n = [BitVec(f"n{i}", 32) for i in range(6)]

s = Solver()
s.add(inp[nums[0]] == ch_transform(n[4]) ^ ch_transform(n[0]) ^ n[0] ^ n[2] ^ ch_transform(n[2]))
s.add(inp[nums[1]] == ch_transform(n[5]) ^ ch_transform(n[1]) ^ n[1] ^ n[3] ^ ch_transform(n[3]))
s.add(inp[nums[2]] == ch_transform(n[0]) ^ n[4])
s.add(inp[nums[3]] == ch_transform(n[1]) ^ n[5])
s.add(inp[nums[4]] == ch_transform(n[2]) ^ ch_transform(n[0]) ^ n[0])
s.add(inp[nums[5]] == ch_transform(n[3]) ^ ch_transform(n[1]) ^ n[1])

if s.check() == sat:

m = s.model()
final_nums = [int(str(m[e])) for e in n]

for i in range(6):
inp[nums[i]] = final_nums[i]
return inp

def reverse_combine(inp):

nums = [
[0,1,2,6,12,18],
[3,4,5,11,17,23],
[7,8,9,13,14,15],
[10,16,22,28,29,35],
[19,20,24,25,26,30],
[21,27,31,32,33,34]
]

for n in nums[::-1]:
inp = reverse_num_combine(inp, n)
return inp

if __name__ == '__main__':

target = bytes.fromhex("0f4f733c41c6a4afb441d665c899aab36c99613c4edd704615663c1b7f16a66f2313126e")

for _ in range(16):
target = reverse_shuffle(target)
target = reverse_combine(target)


print("".join(map(chr, target)))
# DUCTF{rev3rs1bl3___and___1nv3rtibl3}