0%

AmateursCTF2024逆向方向wp

参赛ID:1K0CT, Team : 4W3, 最终排名:88th(公开赛道)

做到flocto师傅的题真的每次都要有感而发一下 真正能让我全身心投入的赛题 给一个陌生的考点 但是不至于让你寸步难行 有种接触新东西的时候那个东西对你欲拒还迎激发探索欲的美 可能这就是flocto师傅出的题的魅力吧

revtale-1 | GM逆向

和LACTF那次不同的是这次就是纯粹的套GM壳检测flag而不是游戏逆向 没有魔改data文件 直接用UndertaleModTool看代码即可:

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
201
202
203
204
205
206
207
208
209
210
pk = [95, 119, 51]
draw_set_font(font_det)
for (var i = 0; i < array_length(char_grid); i++)
{
for (var j = 0; j < array_length(char_grid[i]); j++)
{
jitter_x = random_range(-2, 2)
jitter_y = random_range(-2, 2)
if ((cc == j) && (rc == i) && (!solv))
draw_set_color(c_yellow)
else
draw_set_color(c_white)
draw_text_transformed(((128 + (j * 64)) + jitter_x), ((100 + (i * 32)) + jitter_y), char_grid[i][j], 2, 2, 0)
}
}
ch = char_grid[rc][cc]
if ((ch == "}") && (!solv))
draw_set_color(c_yellow)
else
draw_set_color(c_white)
if ((cnt > 75))
draw_text_transformed((320 - ((2 * string_width(s)) / 2)), 20, s, (2 + scl_modif), (2 + scl_modif), 0)
old = "amateursCTF{fak3_flag_w1dth_t3st}"
draw_text_transformed((320 - ((2 * string_width("amateursCTF{fak3_flag_w1dth_t3st}")) / 2)), 60, current, 2, 2, 0)
dr = 0
dc = 0
if keyboard_check_pressed(ord("Z"))
{
show_debug_message(ch)
if ((ch != "<"))
{
if ((string_length(current) < 40))
{
current += ch
if ((ch == "}"))
{
if scr_check_flag(current)
{
solv = 1
audio_play_sound(snd_cymbal, 10, false)
window_set_caption("...")
shk += 10
}
else
{
fails += 1
s = ("incorrect flag attempt #" + string(fails))
current = ""
if (((fails % 10) == 0))
s = "Stay determined!"
if ((fails > 10))
shk += 120
else if ((fails > 3))
shk += 30
}
}
}
else
s = "That's not the flag!"
}
else if ((string_length(current) > 0))
current = string_delete(current, string_length(current), 1)
}
if keyboard_check_pressed(vk_down)
dr += 1
if keyboard_check_pressed(vk_up)
dr -= 1
if keyboard_check_pressed(vk_right)
dc += 1
if keyboard_check_pressed(vk_left)
dc -= 1
if ((dr != 0) || (dc != 0))
{
nr = (rc + dr)
nr = clamp(nr, 0, (array_length(char_grid) - 1))
if ((array_length(char_grid[nr]) == 0))
{
nr = (rc + (2 * dr))
nr = clamp(nr, 0, (array_length(char_grid) - 1))
}
nc = clamp((cc + dc), 0, (array_length(char_grid[nr]) - 1))
rc = nr
cc = nc
}
if ((scl_modif > 0))
{
v = clamp((scl_modif * 127.5), 0, 255)
layer_background_blend(bg_id, make_color_rgb(v, v, v))
}

funcs = [color_get_hue, color_get_green]
function scr_check_flag(argument0) //gml_Script_scr_check_flag
{
l = string_lower(argument0)
if ((string_pos("gaster", l) != 0))
{
window_set_caption("redacted")
game_end(1)
}
if ((string_pos("frisk", l) != 0))
{
window_set_caption("don't make this hard")
game_end(1)
}
if string_starts_with(argument0, "amateursCTF{")
{
if ((string_length(argument0) > 15))
{
a = scr_a(argument0)
if ((a[12] == "{"))
{
window_set_caption("no")
return 0;
}
for (i = 0; i < array_length(a); i++)
{
}
aa = (ord(a[12]) & 4095)
ab = (ord(a[13]) & 2047)
ac = (ord(a[14]) & 1023)
ad = (ord(a[15]) & 511)
arr_op = array_reverse
color_check = color_get_saturation
if ((a[12] == a[13]) && (a[12] != a[14]))
{
if ((ac == (aa - 2)))
{
if (((ab + 152) == self.color_check(16711935)) && ((ad + 133) == self.color_check(128)))
{
if (((obj_input_field.pk[0] ^ ord(a[16])) == 0))
{
if (((obj_input_field.pk[1] ^ ord(a[17])) == 0))
{
if (((obj_input_field.pk[2] ^ ord(a[18])) == 0))
{
if ((ord(a[19]) == (power(2, 6) | 31)))
{
r = self.arr_op(["%", "3", "v", "0", "l", "_"], 0, 6)
c = 0
if ((a[19] == r[0]))
c += 1
if ((a[20] == r[1]))
c += 2
if ((a[21] == r[2]))
c += 3
if ((a[22] == r[3]))
c += 4
if ((a[23] == r[4]))
c += 5
if ((a[20] == a[21]))
c += 5
if ((c == 15))
{
if ((string_pos(file_text_read_string(file_text_open_read("f.txt")), argument0) != 0))
return ((45887 == scr_c((((string_char_at(argument0, 29) + string_char_at(argument0, 30)) + string_char_at(argument0, 31)) + string_char_at(argument0, 32)))) && (ord(a[32]) == 125));
}
}
}
}
}
}
}
}
return 0;
}
else
return 0;
}
else
show_message(("flag wrapper missing... you entered " + argument0))
return 0;
}

function scr_a(argument0) //gml_Script_scr_a
{
o = []
for (i = 0; i <= string_length(argument0); i++)
{
if ((i > 0))
array_push(o, string_char_at(argument0, i))
}
return o;
}

function scr_b(argument0) //gml_Script_scr_b
{
alpha = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"]
for (k = 0; k < 16; k++)
{
if ((alpha[k] == argument0))
return k;
}
return -1;
}

function scr_c(argument0) //gml_Script_scr_c
{
o = 0
for (j = 1; j <= string_length(argument0); j++)
{
o *= 16
cr = string_char_at(argument0, j)
d = scr_b(cr)
show_debug_message(((((((((("+= " + string(d)) + " ") + string(j)) + " ") + string(string_length(argument0))) + " ") + argument0) + " ") + cr))
if ((d == -1))
return -1;
o += d
}
return o;
}

对flag进行了分段检测 直接解出每段的flag拼接即可amateursCTF{ggez_w3_l0v3_vm_b33f}

cplusplus | 代码理解

64位无壳 主函数(已重命名变量和函数):

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
unsigned __int64 n1; // rax
unsigned __int64 n2; // rax
unsigned __int64 num; // [rsp+0h] [rbp-70h]
__int64 i; // [rsp+8h] [rbp-68h]
FILE *stream; // [rsp+10h] [rbp-60h]
char random[2]; // [rsp+1Eh] [rbp-52h] BYREF
char input[72]; // [rsp+20h] [rbp-50h] BYREF
unsigned __int64 v11; // [rsp+68h] [rbp-8h]

v11 = __readfsqword(0x28u);
fgets(input, 64, stdin);
stream = fopen("/dev/urandom", "r");
fread(random, 1uLL, 2uLL, stream);
fclose(stream);
num = 0LL;
for ( i = 0LL; input[i]; ++i )
{
n1 = ilproc(num, input[i]);
n2 = func0(n1, random[0]);
num = func1(n2, random[1]);
if ( i )
printf(", ");
printf("%zu", num);
}
putchar(10);
return 0LL;
}

/*
output.txt:
816696039, 862511530, 897431439, 341060728, 173157153, 31974957, 491987052, 513290022, 463763452, 949994705, 910803499, 303483511, 378099927, 773435663, 305463445, 656532801, 655150297, 28357806, 69914739, 213536453, 962912446, 458779691, 598643891, 94970179, 732507398, 792930123, 216371336, 680163935, 397010125, 693248832, 926462193, 419350956, 594922380, 944019434, 93600641, 116339550, 373995190, 558908218, 700841647, 703877327, 665247438, 690373754, 35138387, 389900716, 625740467, 682452898, 894528752, 603308386, 442640217, 15961938, 573068354
*/

/dev/urandom是Linux系统的一个随机数发生器 程序从里面取了两次1byte数据存入random中 用这两个1byte随机数对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
unsigned __int64 __fastcall add(__int64 a1)
{
unsigned __int64 i; // [rsp+0h] [rbp-18h]
unsigned __int64 high; // [rsp+10h] [rbp-8h]

for ( high = 1LL; (high & a1) != 0; high *= 2LL )
;
for ( i = high | a1; ; i &= ~high )
{
high >>= 1;
if ( !high )
break;
}
return i;
}

unsigned __int64 __fastcall ilproc(unsigned __int64 num1, unsigned __int64 num2)
{
unsigned __int64 count; // [rsp+18h] [rbp-8h]

for ( count = 0LL; count < num2; count = add(count) )
num1 = add(num1);
return num1 % mod;
}

unsigned __int64 __fastcall process(
unsigned __int64 num,
unsigned __int64 random,
unsigned __int64 _1,
__int64 (__fastcall *ilproc)(unsigned __int64, unsigned __int64))
{
while ( random )
{
if ( (random & 1) != 0 )
_1 = ilproc(_1, num) % mod;
num = ilproc(num, num) % mod; // 0x3B9ACA07
random >>= 1;
}
return _1;
}

unsigned __int64 __fastcall func0(unsigned __int64 a1, unsigned __int64 a2)
{
return process(a1, a2, 0LL, ilproc);
}

unsigned __int64 __fastcall func1(unsigned __int64 a1, unsigned __int64 a2)
{
return process(a1, a2, 1uLL, func0);
}

比较有迷惑性的是add(arg)函数(已重命名) 实际上就是lambda arg:arg + 1 剩下的难点就是找到这两个随机数 好在它们的长度只有1byte 直接爆破即可:

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
#include<stdio.h>
#include<iostream>
#include<string.h>

using namespace std;

unsigned int mod = 0x3B9ACA07;
unsigned int enc[] = {816696039, 862511530, 897431439, 341060728, 173157153, 31974957, 491987052, 513290022, 463763452, 949994705, 910803499, 303483511, 378099927, 773435663, 305463445, 656532801, 655150297, 28357806, 69914739, 213536453, 962912446, 458779691, 598643891, 94970179, 732507398, 792930123, 216371336, 680163935, 397010125, 693248832, 926462193, 419350956, 594922380, 944019434, 93600641, 116339550, 373995190, 558908218, 700841647, 703877327, 665247438, 690373754, 35138387, 389900716, 625740467, 682452898, 894528752, 603308386, 442640217, 15961938, 573068354};

/*
copy funcs here...
*/

int main(){
char check[8];
// memcpy(check, "amateurs", 8);
// for(unsigned int i = 0; i <= 0xFF; i++){
// for(unsigned int j = 0; j <= 0xFF; j++){
// unsigned int num = 0, n1 = 0, n2 = 0, f = 0;
// for(int c = 0; c < 8; c++){
// n1 = ilproc(num, check[c]);
// n2 = func0(n1, i);
// num = func1(n2, j);
// if(num == enc[c]){
// f++;
// }
// else{
// break;
// }
// }
// if(f == 8){
// printf("Key: 0x%x, 0x%x\n", i, j);
// return 0;
// }
// }
// }
int key[2] = {0xed, 0x29};
string flag = "";
unsigned int num = 0, n1 = 0, n2 = 0;
for(int c = 0; c < 51; c++){
for(int chr = 0; chr <= '}'; chr++){
unsigned int numtmp = num, n1tmp = n1, n2tmp = n2;
n1tmp = ilproc(num, chr);
n2tmp = func0(n1tmp, key[0]);
numtmp = func1(n2tmp, key[1]);
if(numtmp == enc[c]){
flag += (char)chr;
num = numtmp;
n1 = n1tmp;
n2 = n2tmp;
break;
}
}
}
cout << flag << endl;
// amateursCTF{r/programminghorror/comments/18x7vk9/}
return 0;
}

typo | pyre

题目直接给了一个混淆变量名和函数名的.py:

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
import random as RrRrRrrrRrRRrrRRrRRrrRr
RrRrRrrrRrRRrrRRrRRrRrr = int('1665663c', 20)
RrRrRrrrRrRRrrRRrRRrrRr.seed(RrRrRrrrRrRRrrRRrRRrRrr)
arRRrrRRrRRrRRRrRrRRrRr = bytearray(open('flag.txt', 'rb').read())
arRRrrRrrRRrRRRrRrRRrRr = '\r'r'\r''r''\\r'r'\\r\r'r'r''r''\\r'r'r\r'r'r\\r''r'r'r''r''\\r'r'\\r\r'r'r''r''\\r'r'rr\r''\r''r''r\\'r'\r''\r''r\\\r'r'r\r''\rr'
arRRrrRRrRRrRrRrRrRRrRr = [
b'arRRrrRRrRRrRRrRr',
b'aRrRrrRRrRr',
b'arRRrrRRrRRrRr',
b'arRRrRrRRrRr',
b'arRRrRRrRrrRRrRR'
b'arRRrrRRrRRRrRRrRr',
b'arRRrrRRrRRRrRr',
b'arRRrrRRrRRRrRr'
b'arRrRrRrRRRrrRrrrR',
]
arRRRrRRrRRrRRRrRrRRrRr = lambda aRrRrRrrrRrRRrrRRrRrrRr: bytearray([arRrrrRRrRRrRRRrRrRrrRr + 1 for arRrrrRRrRRrRRRrRrRrrRr in aRrRrRrrrRrRRrrRRrRrrRr])
arRRrrRRrRRrRRRrRrRrrRr = lambda aRrRrRrrrRrRRrrRRrRrrRr: bytearray([arRrrrRRrRRrRRRrRrRrrRr - 1 for arRrrrRRrRRrRRRrRrRrrRr in aRrRrRrrrRrRRrrRRrRrrRr])
def arRRrrRRrRRrRrRRrRrrRrRr(hex):
for id in range(0, len(hex) - 1, 2):
hex[id], hex[id + 1] = hex[id + 1], hex[id]
for list in range(1, len(hex) - 1, 2):
hex[list], hex[list + 1] = hex[list + 1], hex[list]
return hex
arRRRRRRrRRrRRRrRrRrrRr = [arRRrrRRrRRrRrRRrRrrRrRr, arRRRrRRrRRrRRRrRrRRrRr, arRRrrRRrRRrRRRrRrRrrRr]
arRRRRRRrRRrRRRrRrRrrRr = [RrRrRrrrRrRRrrRRrRRrrRr.choice(arRRRRRRrRRrRRRrRrRrrRr) for arRrrrRRrRRrRRRrRrRrrRr in range(128)]
def RrRrRrrrRrRRrrRRrRRrrRr(arr, ar):
for r in ar:
arr = arRRRRRRrRRrRRRrRrRrrRr[r](arr)
return arr
def arRRrrRRrRRrRrRRrRrrRrRr(arr, ar):
ar = int(ar.hex(), 17)
for r in arr:
ar += int(r, 35)
return bytes.fromhex(hex(ar)[2:])
arrRRrrrrRRrRRRrRrRRRRr = RrRrRrrrRrRRrrRRrRRrrRr(arRRrrRRrRRrRRRrRrRRrRr, arRRrrRrrRRrRRRrRrRRrRr.encode())
arrRRrrrrRRrRRRrRrRRRRr = arRRrrRRrRRrRrRRrRrrRrRr(arRRrrRRrRRrRrRrRrRRrRr, arrRRrrrrRRrRRRrRrRRRRr)
print(arrRRrrrrRRrRRRrRrRRRRr.hex())
# 5915f8ba06db0a50aa2f3eee4baef82e70be1a9ac80cb59e5b9cb15a15a7f7246604a5e456ad5324167411480f893f97e3

改一下变量和函数名程序的逻辑就很清晰了:

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
import random as rand
data1 = int('1665663c', 20)
rand.seed(data1)
data2 = bytearray(open('flag.txt', 'rb').read())
data3 = '\r'r'\r''r''\\r'r'\\r\r'r'r''r''\\r'r'r\r'r'r\\r''r'r'r''r''\\r'r'\\r\r'r'r''r''\\r'r'rr\r''\r''r''r\\'r'\r''\r''r\\\r'r'r\r''\rr'
data4 = [
b'arRRrrRRrRRrRRrRr',
b'aRrRrrRRrRr',
b'arRRrrRRrRRrRr',
b'arRRrRrRRrRr',
b'arRRrRRrRrrRRrRR'
b'arRRrrRRrRRRrRRrRr',
b'arRRrrRRrRRRrRr',
b'arRRrrRRrRRRrRr'
b'arRrRrRrRRRrrRrrrR',
]
lam1 = lambda arg: bytearray([each + 1 for each in arg])
lam2 = lambda arg: bytearray([each - 1 for each in arg])
def func1(hex):
for id in range(0, len(hex) - 1, 2):
hex[id], hex[id + 1] = hex[id + 1], hex[id]
for list in range(1, len(hex) - 1, 2):
hex[list], hex[list + 1] = hex[list + 1], hex[list]
return hex
func_table = [func1, lam1, lam2]
func_table = [rand.choice(func_table) for each in range(128)]
def rand(arr, ar):
for r in ar:
arr = func_table[r](arr)
return arr
def func1(arr, ar):
ar = int(ar.hex(), 17)
for r in arr:
ar += int(r, 35)
return bytes.fromhex(hex(ar)[2:])
result = rand(data2, data3.encode())
result = func1(data4, result)
print(result.hex())

用一个随机的函数表来对flag进行操作然后Base16 -> Base17 每一个操作都是可逆的 只需要把它们写成逆操作再逆序执行加密时的函数就能逆向整个加密过程:

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
import random as rd
seed = int('1665663c', 20)
rd.seed(seed)
flag1 = bytearray.fromhex("0x5915f8ba06db0a50aa2f3eee4baef82e70be1a9ac80cb59e5b9cb15a15a7f7246604a5e456ad5324167411480f893f97e3"[2:])
data1 = '\r'r'\r''r''\\r'r'\\r\r'r'r''r''\\r'r'r\r'r'r\\r''r'r'r''r''\\r'r'\\r\r'r'r''r''\\r'r'rr\r''\r''r''r\\'r'\r''\r''r\\\r'r'r\r''\rr'
data2 = [
b'arRRrrRRrRRrRRrRr',
b'aRrRrrRRrRr',
b'arRRrrRRrRRrRr',
b'arRRrRrRRrRr',
b'arRRrRRrRrrRRrRR'
b'arRRrrRRrRRRrRRrRr',
b'arRRrrRRrRRRrRr',
b'arRRrrRRrRRRrRr'
b'arRrRrRrRRRrrRrrrR',
]
func1 = lambda arg: bytearray([(each - 1) & 0xff for each in arg])
func2 = lambda arg: bytearray([(each + 1) & 0xff for each in arg])
def func3(hex):
for id in range(1, len(hex) - 1, 2):
hex[id], hex[id + 1] = hex[id + 1], hex[id]
for id in range(0, len(hex) - 1, 2):
hex[id], hex[id + 1] = hex[id + 1], hex[id]
return hex
result = [func3, func1, func2]
result = [rd.choice(result) for each in range(128)]
def rd(arr, ar):
for r in ar[::-1]:
arr = result[r](arr)
return arr
def func4(arr, ar):
ar = int(ar.hex(), 16)
for r in arr:
ar -= int(r, 35)
hexn = 0
# ar = int(ar.hex(), 17)
while ar > 0:
hexn = (hexn * 16) + (ar % 17)
ar //= 17
return bytearray.fromhex(hex(hexn)[2:][::-1])
result2 = func4(data2, flag1)
flag = rd(result2, data1.encode())
print(flag)
# bytearray(b'amateursCTF{4t_l3ast_th15_fl4g_isn7_misspelll3d}')

dill-with-it | py-deserialized vuln

main.py:

1
2
3
4
# Python 3.10.12
from pickle import loads
larry = b"\x80\x04ctypes\nFunctionType......\x93(VLooks like you got it!\nVNah, try again.\nlg4\n\x86R."
print(loads(larry))

给出了一串序列化的python代码 正常的序列化python object 可以通过dill库来完全还原源代码(要使用对应的python版本的dill或pickle库):

1
2
3
4
5
6
7
def Python_object():
...
dill.source.getsource(dill.loads(dill.dumps(Python_object)))
"""
def Python_object():
...
"""

但是这道题利用了反序列化的漏洞 因为反序列化实际上就是用PVM解释序列化的python代码 当序列化的类中出现类似__reduce__()的魔术方法 且其返回值形式为(callable, (arg1, arg2, ...))时 反序列化由这个类实例化的对象的序列化字符串时会直接执行callable(arg1, arg2, ...) 此时load(s)返回的就不是一个python object而是执行这个函数得到的返回值 例如:

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
import dill
import pickletools
import pickle

def Test2(a, b):
return a + b

class Test():
def __reduce__(self):
return (Test2, (1, 2, ))
t = Test()
serialized = pickle.dumps(t)
print(serialized)
print(pickletools.dis(serialized))
print(pickle.loads(serialized))
"""
b'\x80\x04\x95\x1e\x00\x00\x00\x00\x00\x00\x00\x8c\x08__main__\x94\x8c\x05Test2\x94\x93\x94K\x01K\x02\x86\x94R\x94.'
0: \x80 PROTO 4
2: \x95 FRAME 30
11: \x8c SHORT_BINUNICODE '__main__'
21: \x94 MEMOIZE (as 0)
22: \x8c SHORT_BINUNICODE 'Test2'
29: \x94 MEMOIZE (as 1)
30: \x93 STACK_GLOBAL
31: \x94 MEMOIZE (as 2)
32: K BININT1 1
34: K BININT1 2
36: \x86 TUPLE2
37: \x94 MEMOIZE (as 3)
38: R REDUCE
39: \x94 MEMOIZE (as 4)
40: . STOP
highest protocol among opcodes = 4
None
3
"""

其中pickletools.dis(serialized)可以用来打印可读性更高的PVM opcode 该版本的opcode释义如下:

MARK = b’(‘ # push special markobject on stack
STOP = b’.’ # every pickle ends with STOP
POP = b’0’ # discard topmost stack item
POP_MARK = b’1’ # discard stack top through topmost markobject
DUP = b’2’ # duplicate top stack item
FLOAT = b’F’ # push float object; decimal string argument
INT = b’I’ # push integer or bool; decimal string argument
BININT = b’J’ # push four-byte signed int
BININT1 = b’K’ # push 1-byte unsigned int
LONG = b’L’ # push long; decimal string argument
BININT2 = b’M’ # push 2-byte unsigned int
NONE = b’N’ # push None
PERSID = b’P’ # push persistent object; id is taken from string arg
BINPERSID = b’Q’ # ; stack
REDUCE = b’R’ # apply callable to argtuple, both on stack
STRING = b’S’ # push string; NL-terminated string argument
BINSTRING = b’T’ # push string; counted binary string argument
SHORT_BINSTRING= b’U’ # ; “ < 256 bytes
UNICODE = b’V’ # push Unicode string; raw-unicode-escaped’d argument
BINUNICODE = b’X’ # ; counted UTF-8 string argument
APPEND = b’a’ # append stack top to list below it
BUILD = b’b’ # call setstate or dict.update()
GLOBAL = b’c’ # push self.find_class(modname, name); 2 string args
DICT = b’d’ # build a dict from stack items
EMPTY_DICT = b’}’ # push empty dict
APPENDS = b’e’ # extend list on stack by topmost stack slice
GET = b’g’ # push item from memo on stack; index is string arg
BINGET = b’h’ # ; “ 1-byte arg
INST = b’i’ # build & push class instance
LONG_BINGET = b’j’ # push item from memo on stack; index is 4-byte arg
LIST = b’l’ # build list from topmost stack items
EMPTY_LIST = b’]’ # push empty list
OBJ = b’o’ # build & push class instance
PUT = b’p’ # store stack top in memo; index is string arg
BINPUT = b’q’ # “ ; “ 1-byte arg
LONG_BINPUT = b’r’ # “ ; “ 4-byte arg
SETITEM = b’s’ # add key+value pair to dict
TUPLE = b’t’ # build tuple from topmost stack items
EMPTY_TUPLE = b’)’ # push empty tuple
SETITEMS = b’u’ # modify dict by adding topmost key+value pairs
BINFLOAT = b’G’ # push float; arg is 8-byte float encoding
TRUE = b’I01\n’ # not an opcode; see INT docs in pickletools.py
FALSE = b’I00\n’ # not an opcode; see INT docs in pickletools.py
# Protocol 2
PROTO = b’\x80’ # identify pickle protocol
NEWOBJ = b’\x81’ # build object by applying cls.new to argtuple
EXT1 = b’\x82’ # push object from extension registry; 1-byte index
EXT2 = b’\x83’ # ditto, but 2-byte index
EXT4 = b’\x84’ # ditto, but 4-byte index
TUPLE1 = b’\x85’ # build 1-tuple from stack top
TUPLE2 = b’\x86’ # build 2-tuple from two topmost stack items
TUPLE3 = b’\x87’ # build 3-tuple from three topmost stack items
NEWTRUE = b’\x88’ # push True
NEWFALSE = b’\x89’ # push False
LONG1 = b’\x8a’ # push long from < 256 bytes
LONG4 = b’\x8b’ # push really big long
_tuplesize2code = [EMPTY_TUPLE, TUPLE1, TUPLE2, TUPLE3]
# Protocol 3 (Python 3.x)
BINBYTES = b’B’ # push bytes; counted binary string argument
SHORT_BINBYTES = b’C’ # ; “ < 256 bytes
# Protocol 4
SHORT_BINUNICODE = b’\x8c’ # push short string; UTF-8 length < 256 bytes
BINUNICODE8 = b’\x8d’ # push very long string
BINBYTES8 = b’\x8e’ # push very long bytes string
EMPTY_SET = b’\x8f’ # push empty set on the stack
ADDITEMS = b’\x90’ # modify set by adding topmost stack items
FROZENSET = b’\x91’ # build frozenset from topmost stack items
NEWOBJ_EX = b’\x92’ # like NEWOBJ but work with keyword only arguments
STACK_GLOBAL = b’\x93’ # same as GLOBAL but using names on the stacks
MEMOIZE = b’\x94’ # store top of the stack in memo
FRAME = b’\x95’ # indicate the beginning of a new frame
# Protocol 5
BYTEARRAY8 = b’\x96’ # push bytearray
NEXT_BUFFER = b’\x97’ # push next out-of-band buffer
READONLY_BUFFER = b’\x98’ # make top of stack readonly

结合此表可以看出__reduce__()漏洞实现的底层原理是这样的指令结构:

1
2
3
4
5
6
7
8
# MEMOIZE/GET/...            用以确保栈上一定有接下来REDUCE要使用的函数
STACK_GLOBAL/GLOBAL 获取这个函数
MARK
DATATYPE arg1
DATATYPE arg2
...
TUPLE 将参数正序入栈 若参数个数超过3个就会用这样的方式打包入栈 否则就会用TUPLEx直接入栈
REDUCE 已这些参数执行函数并将返回值以元组的方式打包放在栈顶

有了这些基础知识再看这题 先用dis打印出可读性较高的指令:

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
  0: \x80 PROTO      4
2: c GLOBAL 'types FunctionType'
22: ( MARK
23: c GLOBAL 'types CodeType'
39: ( MARK
40: I INT 1
43: I INT 0
46: I INT 0
49: I INT 4
52: I INT 8
55: I INT 67
59: C SHORT_BINBYTES b't\x00\xa0\x01|\x00d\x01\xa1\x02}\x01t\x02|\x01\x83\x01d\x00d\x00d\x02\x85\x03\x19\x00d\x00d\x03\x85\x02\x19\x00}\x00d\x04}\x02t\x03d\x05t\x04|\x00\x83\x01d\x06\x83\x03D\x00]\x11}\x03|\x02t\x05t\x00|\x00|\x03|\x03d\x06\x17\x00\x85\x02\x19\x00d\x07\x83\x02\x83\x017\x00}\x02q\x1d|\x02S\x00'
159: ( MARK
160: N NONE
161: V UNICODE 'big'
166: I INT -1
170: I INT -3
174: V UNICODE ''
176: I INT 0
179: I INT 8
182: I INT 2
185: t TUPLE (MARK at 159)
186: ( MARK
187: V UNICODE 'int'
192: V UNICODE 'from_bytes'
204: V UNICODE 'bin'
209: V UNICODE 'range'
216: V UNICODE 'len'
221: V UNICODE 'chr'
226: t TUPLE (MARK at 186)
227: ( MARK
228: \x8c SHORT_BINUNICODE '🔥'
234: \x8c SHORT_BINUNICODE '🤫'
240: \x8c SHORT_BINUNICODE '🧏'
246: \x8c SHORT_BINUNICODE '🎵'
252: t TUPLE (MARK at 227)
253: V UNICODE 'dill-with-it'
267: \x8c SHORT_BINUNICODE '📮'
273: I INT 0
276: C SHORT_BINBYTES b'\x00\x01\x0c\x01\x1a\x01\x04\x01\x14\x01 \x01'
290: ) EMPTY_TUPLE
291: ) EMPTY_TUPLE
292: t TUPLE (MARK at 39)
293: \x81 NEWOBJ
294: c GLOBAL 'builtins globals'
312: ) EMPTY_TUPLE
313: R REDUCE
314: \x8c SHORT_BINUNICODE '📮'
320: t TUPLE (MARK at 22)
321: \x81 NEWOBJ
322: \x94 MEMOIZE (as 0)
323: 0 POP
......

一开始是先用types.CodeTypetypes.FunctionType来定义了一个函数并在MEMO中以赋以编号0 翻译为python代码大致为:

1
2
3
4
5
6
7
8
9
10
11
code = types.CodeType(
1, 0, 0, 4, 8, 67, b't\x00\xa0\x01|\x00d\x01\xa1\x02}\x01t\x02|\x01\x83\x01d\x00d\x00d\x02\x85\x03\x19\x00d\x00d\x03\x85\x02\x19\x00}\x00d\x04}\x02t\x03d\x05t\x04|\x00\x83\x01d\x06\x83\x03D\x00]\x11}\x03|\x02t\x05t\x00|\x00|\x03|\x03d\x06\x17\x00\x85\x02\x19\x00d\x07\x83\x02\x83\x017\x00}\x02q\x1d|\x02S\x00',
(None, 'big', -1, -3, '', 0, 8, 2),
('int', 'from_bytes', 'bin', 'range', 'len', 'chr'),
('🔥', '🤫', '🧏', '🎵'),
'dill-with-it', '📮', 0,
b'\x00\x01\x0c\x01\x1a\x01\x04\x01\x14\x01 \x01',
(),
()
)
func = types.FunctionType(code, globals())

其中的PYC字节码翻译为python代码含义大致为(用这种方式定义函数中使用别的函数时LOAD_GLOBAL x中的x就是要使用的函数在预备函数列表中的下标 在这里就是('int', 'from_bytes', 'bin', 'range', 'len', 'chr')[x]):

1
2
3
4
5
6
def decodea(arg):
binstr = bin(int.from_bytes(arg, "big"))[2:][::-1]
result = ""
for i in range(0, len(binstr), 8):
result += chr(int(binstr[i:i+8], 2))
return bytearray(result, 'utf-8')

再看下面的PVM字节码:

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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
  324: g    GET        0
327: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
338: \x85 TUPLE1
339: R REDUCE
340: g GET 0
343: C SHORT_BINBYTES b'\x01.\xce\x966'
350: \x85 TUPLE1
351: R REDUCE
352: \x93 STACK_GLOBAL
353: g GET 0
356: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
367: \x85 TUPLE1
368: R REDUCE
369: g GET 0
372: C SHORT_BINBYTES b'\x01\xa6&\xf6\xc6v\xa6tN.\xce'
385: \x85 TUPLE1
386: R REDUCE
387: \x93 STACK_GLOBAL
388: g GET 0
391: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
402: \x85 TUPLE1
403: R REDUCE
404: g GET 0
407: C SHORT_BINBYTES b'\x01.v\x96N\x0e'
415: \x85 TUPLE1
416: R REDUCE
417: \x93 STACK_GLOBAL
418: V UNICODE "What's the flag? "
437: \x85 TUPLE1
438: R REDUCE
439: 0 POP
440: g GET 0
443: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
454: \x85 TUPLE1
455: R REDUCE
456: g GET 0
459: C SHORT_BINBYTES b'\x01.\xae\x0ev\x96'
467: \x85 TUPLE1
468: R REDUCE
469: \x93 STACK_GLOBAL
470: V UNICODE '> '
474: \x85 TUPLE1
475: R REDUCE
476: \x85 TUPLE1
477: R REDUCE
478: \x85 TUPLE1
479: R REDUCE
480: \x94 MEMOIZE (as 1)
481: 0 POP
482: g GET 0
485: C SHORT_BINBYTES b'\x01\xb6\xf6&v\x86N'
494: \x85 TUPLE1
495: R REDUCE
496: g GET 0
499: C SHORT_BINBYTES b'\x01&\xa6\xa6\xce'
506: \x85 TUPLE1
507: R REDUCE
508: \x93 STACK_GLOBAL
509: V UNICODE 'five nights as freddy'
532: \x85 TUPLE1
533: R REDUCE
534: 0 POP
535: g GET 0
538: C SHORT_BINBYTES b'\x01\xb6\xf6&v\x86N'
547: \x85 TUPLE1
548: R REDUCE
549: g GET 0
552: C SHORT_BINBYTES b'\x01\xa66ff\xae\x16\xce'
562: \x85 TUPLE1
563: R REDUCE
564: \x93 STACK_GLOBAL
565: g GET 1
568: \x85 TUPLE1
569: R REDUCE
570: 0 POP
571: g GET 0
574: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
585: \x85 TUPLE1
586: R REDUCE
587: g GET 0
590: C SHORT_BINBYTES b'\x01.\xce\x966'
597: \x85 TUPLE1
598: R REDUCE
599: \x93 STACK_GLOBAL
600: g GET 0
603: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
614: \x85 TUPLE1
615: R REDUCE
616: g GET 0
619: C SHORT_BINBYTES b'\x01\x0e\x86\xb6'
625: \x85 TUPLE1
626: R REDUCE
627: \x93 STACK_GLOBAL
628: g GET 0
631: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
642: \x85 TUPLE1
643: R REDUCE
644: g GET 0
647: C SHORT_BINBYTES b'\x01\xfa\xfaN\xf6\x1e\xfa\xfat.v\x96'
661: \x85 TUPLE1
662: R REDUCE
663: \x93 STACK_GLOBAL
664: g GET 0
667: C SHORT_BINBYTES b'\x01\xb6\xf6&v\x86N'
676: \x85 TUPLE1
677: R REDUCE
678: g GET 0
681: C SHORT_BINBYTES b'\x01\xce\xa6.\x9eF&v\x86N'
693: \x85 TUPLE1
694: R REDUCE
695: \x93 STACK_GLOBAL
696: g GET 0
699: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
710: \x85 TUPLE1
711: R REDUCE
712: g GET 0
715: C SHORT_BINBYTES b'\x01v\xa66'
721: \x85 TUPLE1
722: R REDUCE
723: \x93 STACK_GLOBAL
724: g GET 1
727: \x85 TUPLE1
728: R REDUCE
729: \x85 TUPLE1
730: R REDUCE
731: g GET 1
734: \x87 TUPLE3
735: R REDUCE
736: \x85 TUPLE1
737: R REDUCE
738: \x94 MEMOIZE (as 2)
739: 0 POP
740: g GET 0
743: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
754: \x85 TUPLE1
755: R REDUCE
756: g GET 0
759: C SHORT_BINBYTES b'\x01\x9ev\x86'
765: \x85 TUPLE1
766: R REDUCE
767: \x93 STACK_GLOBAL
768: g GET 0
771: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
782: \x85 TUPLE1
783: R REDUCE
784: g GET 0
787: C SHORT_BINBYTES b'\x01\x0e\x86\xb6'
793: \x85 TUPLE1
794: R REDUCE
795: \x93 STACK_GLOBAL
796: g GET 0
799: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
810: \x85 TUPLE1
811: R REDUCE
812: g GET 0
815: C SHORT_BINBYTES b'\x01\xfa\xfaN\xf6\x1e\xfa\xfat.v\x96'
829: \x85 TUPLE1
830: R REDUCE
831: \x93 STACK_GLOBAL
832: ( MARK
833: I INT 138
838: I INT 13
842: I INT 157
847: I INT 66
851: I INT 68
855: I INT 12
859: I INT 223
864: I INT 147
869: I INT 198
874: I INT 223
879: I INT 92
883: I INT 172
888: I INT 59
892: I INT 56
896: I INT 27
900: I INT 117
905: I INT 173
910: I INT 21
914: I INT 190
919: I INT 210
924: I INT 44
928: I INT 194
933: I INT 23
937: I INT 169
942: I INT 57
946: I INT 136
951: I INT 5
954: I INT 120
959: I INT 106
964: I INT 255
969: I INT 192
974: I INT 98
978: I INT 64
982: I INT 124
987: I INT 59
991: I INT 18
995: I INT 124
1000: I INT 97
1004: I INT 62
1008: I INT 168
1013: I INT 181
1018: I INT 61
1022: I INT 164
1027: I INT 22
1031: I INT 187
1036: I INT 251
1041: I INT 110
1046: I INT 214
1051: I INT 250
1056: I INT 218
1061: I INT 213
1066: I INT 71
1070: I INT 206
1075: I INT 159
1080: I INT 212
1085: I INT 169
1090: I INT 208
1095: I INT 21
1099: I INT 236
1104: l LIST (MARK at 832)
1105: g GET 2
1108: \x87 TUPLE3
1109: R REDUCE
1110: \x85 TUPLE1
1111: R REDUCE
1112: \x94 MEMOIZE (as 3)
1113: 0 POP
1114: g GET 0
1117: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
1128: \x85 TUPLE1
1129: R REDUCE
1130: g GET 0
1133: C SHORT_BINBYTES b'\x01\xfa\xfaN\xf6\xfa\xfat.v\x96'
1146: \x85 TUPLE1
1147: R REDUCE
1148: \x93 STACK_GLOBAL
1149: g GET 3
1152: g GET 0
1155: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
1166: \x85 TUPLE1
1167: R REDUCE
1168: g GET 0
1171: C SHORT_BINBYTES b'\x01\xfa\xfa\xa6v\xfa\xfat.v\x96'
1184: \x85 TUPLE1
1185: R REDUCE
1186: \x93 STACK_GLOBAL
1187: g GET 0
1190: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
1201: \x85 TUPLE1
1202: R REDUCE
1203: g GET 0
1206: C SHORT_BINBYTES b'\x01v\xa66'
1212: \x85 TUPLE1
1213: R REDUCE
1214: \x93 STACK_GLOBAL
1215: g GET 2
1218: \x85 TUPLE1
1219: R REDUCE
1220: I INT 59
1224: \x86 TUPLE2
1225: R REDUCE
1226: \x86 TUPLE2
1227: R REDUCE
1228: \x94 MEMOIZE (as 4)
1229: 0 POP
1230: g GET 0
1233: C SHORT_BINBYTES b'\x01\xcev\x96.6\x96\xaeF'
1244: \x85 TUPLE1
1245: R REDUCE
1246: g GET 0
1249: C SHORT_BINBYTES b'\x01\xfa\xfa\xb6\xa6.\x96.\xa6\xe6\xfa\xfat.\xce\x966'
1268: \x85 TUPLE1
1269: R REDUCE
1270: \x93 STACK_GLOBAL
1271: ( MARK
1272: V UNICODE 'Looks like you got it!'
1296: V UNICODE 'Wrong'
1303: l LIST (MARK at 1271)
1304: g GET 4
1307: \x86 TUPLE2
1308: R REDUCE
1309: . STOP
highest protocol among opcodes = 4
None

大量使用了一开始定义的函数 且传入的参数为编码过的字节串 通过这个函数来解码的到以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
b'\x01.\xce\x966' : bytearray(b'list\x01')
b'\x01\xa6&\xf6\xc6v\xa6tN.\xce' : bytearray(b'str.encode\x01')
b'\x01.v\x96N\x0e' : bytearray(b'print\x01')
b'\x01.\xae\x0ev\x96' : bytearray(b'input\x01')
b'\x01\xb6\xf6&v\x86N' : bytearray(b'random\x01')
b'\x01&\xa6\xa6\xce' : bytearray(b'seed\x01')
b'\x01\xa66ff\xae\x16\xce' : bytearray(b'shuffle\x01')
b'\x01\x0e\x86\xb6' : bytearray(b'map\x01')
b'\x01\xfa\xfaN\xf6\x1e\xfa\xfat.v\x96' : bytearray(b'int.__xor__\x01')
b'\x01\xce\xa6.\x9eF&v\x86N' : bytearray(b'randbytes\x01')
b'\x01v\xa66' : bytearray(b'len\x01')
b'\x01\x9ev\x86' : bytearray(b'any\x01')
b'\x01\x0e\x86\xb6' : bytearray(b'map\x01')
b'\x01\xfa\xfaN\xf6\xfa\xfat.v\x96' : bytearray(b'int.__or__\x01')
b'\x01\xfa\xfa\xa6v\xfa\xfat.v\x96' : bytearray(b'int.__ne__\x01')
b'\x01\xfa\xfa\xb6\xa6.\x96.\xa6\xe6\xfa\xfat.\xce\x966' : bytearray(b'list.__getitem__\x01')
b'\x01\xcev\x96.6\x96\xaeF' : bytearray(b'builtins\x01')

这样整个加密的流程就很简单易懂了 以下是大致加密流程及解密过程:

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
# encrypt
import random
...
flag = input(">")
random.seed('five nights as freddy')
shuffle(flag)
flag = list(map(xor, randbytes(len(flag)), flag))
table = ["Right", "Wrong"]
enc = [138, 13, 157, 66, 68, 12, 223, 147, 198, 223, 92, 172, 59, 56, 27, 117, 173, 21, 190, 210, 44, 194, 23, 169, 57, 136, 5, 120, 106, 255, 192, 98, 64, 124, 59, 18, 124, 97, 62, 168, 181, 61, 164, 22, 187, 251, 110, 214, 250, 218, 213, 71, 206, 159, 212, 169, 208, 21, 236]
return table[any(map(xor, flag, enc)) || len(flag) != 59]

# decrypt
import random

random.seed('five nights as freddy')
# randby = list(random.randbytes(59))
table = [i for i in range(59)]
random.shuffle(table)
enc = [138, 13, 157, 66, 68, 12, 223, 147, 198, 223, 92, 172, 59, 56, 27, 117, 173, 21, 190, 210, 44, 194, 23, 169, 57, 136, 5, 120, 106, 255, 192, 98, 64, 124, 59, 18, 124, 97, 62, 168, 181, 61, 164, 22, 187, 251, 110, 214, 250, 218, 213, 71, 206, 159, 212, 169, 208, 21, 236]

def xor(a, b):
return a ^ b

reslut = list(map(xor, enc, random.randbytes(59)))
flag = ""
for i in range(59):
flag += (chr(reslut[table.index(i)]))
print(flag)
# amateursCTF{p1ckL3-is_not_the_goat_l4rrY_is_m0R3_\:goat:ed}

除了这种静态分析的做法还有一种可以动调的做法 使用pickledbg

gogogaga | Go语言逆向 | Go多线程调试

IDA8.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
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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
// main.checkKey
bool __golang main_checkKey(string key)
{
__int64 v1; // r14
__int64 v2; // rcx
__int64 v4; // rdx
__int64 v5; // rcx
unsigned __int64 j; // rbx
runtime_funcval *v7; // rax
__int64 v8; // rcx
runtime_hchan *v9; // rdx
_QWORD *v10; // r11
runtime_funcval *v11; // rax
__int64 v12; // rcx
runtime_hchan *v13; // rdx
_QWORD *v14; // r11
runtime_funcval *v15; // rax
__int64 v16; // rcx
runtime_hchan *v17; // rdx
_QWORD *v18; // r11
runtime_funcval *v19; // rax
__int64 v20; // rcx
runtime_hchan *v21; // rdx
_QWORD *v22; // r11
runtime_funcval *v23; // rax
__int64 v24; // rcx
runtime_hchan *v25; // rdx
_QWORD *v26; // r11
__int64 i; // rax
uint8 v28; // si
uint8 v29; // si
bool elem; // [rsp+1h] [rbp-31h] BYREF
__int64 v31; // [rsp+2h] [rbp-30h]
uintptr v32; // [rsp+Ah] [rbp-28h]
__int64 *array; // [rsp+12h] [rbp-20h]
runtime_hchan *c; // [rsp+1Ah] [rbp-18h]
__int64 v35; // [rsp+22h] [rbp-10h]
void *retaddr; // [rsp+32h] [rbp+0h] BYREF
string v37; // 0:rcx.8,8:rdi.8
_slice_string v38; // 0:rax.8,8:rbx.8,16:rcx.8

if ( &retaddr <= *(v1 + 16) )
{
runtime_morestack_noctxt();
JUMPOUT(0x495FC5LL);
}
v37.str = &runtime_gcbits__ptr_;
v37.len = 1LL;
v38 = strings_genSplit(key, v37, 0LL, -1LL);
if ( v38.len != 5 )
return 0;
v2 = 0LL;
LABEL_6:
if ( v2 >= 5 )
{
array = v38.array;
c = runtime_makechan(&RTYPE_chan_bool_0, 5LL);
v32 = array[1];
v35 = *array;
v7 = runtime_newobject(&stru_4A7560);
v7->fn = main_checkKey_func1;
v7[2].fn = v32;
if ( *&runtime_writeBarrier.enabled )
{
runtime_gcWriteBarrier2();
v8 = v35;
*v10 = v35;
v9 = c;
v10[1] = c;
}
else
{
v8 = v35;
v9 = c;
}
v7[1].fn = v8;
v7[3].fn = v9;
runtime_newproc(v7);
v32 = array[3];
v35 = array[2];
v11 = runtime_newobject(&stru_4A7600);
v11->fn = main_checkKey_func2;
v11[2].fn = v32;
if ( *&runtime_writeBarrier.enabled )
{
runtime_gcWriteBarrier2();
v12 = v35;
*v14 = v35;
v13 = c;
v14[1] = c;
}
else
{
v12 = v35;
v13 = c;
}
v11[1].fn = v12;
v11[3].fn = v13;
runtime_newproc(v11);
v32 = array[5];
v35 = array[4];
v15 = runtime_newobject(&stru_4A76A0);
v15->fn = main_checkKey_func3;
v15[2].fn = v32;
if ( *&runtime_writeBarrier.enabled )
{
runtime_gcWriteBarrier2();
v16 = v35;
*v18 = v35;
v17 = c;
v18[1] = c;
}
else
{
v16 = v35;
v17 = c;
}
v15[1].fn = v16;
v15[3].fn = v17;
runtime_newproc(v15);
v32 = array[7];
v35 = array[6];
v19 = runtime_newobject(&stru_4A7740);
v19->fn = main_checkKey_func4;
v19[2].fn = v32;
if ( *&runtime_writeBarrier.enabled )
{
runtime_gcWriteBarrier2();
v20 = v35;
*v22 = v35;
v21 = c;
v22[1] = c;
}
else
{
v20 = v35;
v21 = c;
}
v19[1].fn = v20;
v19[3].fn = v21;
runtime_newproc(v19);
v32 = array[9];
v35 = array[8];
v23 = runtime_newobject(&stru_4A77E0);
v23->fn = main_checkKey_func5;
v23[2].fn = v32;
if ( *&runtime_writeBarrier.enabled )
{
runtime_gcWriteBarrier2();
v24 = v35;
*v26 = v35;
v25 = c;
v26[1] = c;
}
else
{
v24 = v35;
v25 = c;
}
v23[1].fn = v24;
v23[3].fn = v25;
runtime_newproc(v23);
for ( i = 0LL; i < 5; i = v31 + 1 )
{
v31 = i;
elem = 0;
runtime_chanrecv1(c, &elem);
if ( !elem )
return 0;
}
return 1;
}
else
{
v4 = v2;
v5 = v2;
if ( v38.array[v5].len == 5 )
{
for ( j = 0LL; ; ++j )
{
if ( j >= 5 )
{
v2 = v4 + 1;
goto LABEL_6;
}
if ( j >= v38.array[v5].len )
runtime_panicIndex();
v28 = v38.array[v5].str[j];
elem = v28 < 'A';
if ( v28 >= 'A' )
{
if ( j >= v38.array[v5].len )
runtime_panicIndex();
elem = v38.array[v5].str[j] > 'Z';
}
if ( elem )
{
if ( j >= v38.array[v5].len )
runtime_panicIndex();
v29 = v38.array[v5].str[j];
elem = v29 < 0x30u;
if ( v29 >= 0x30u )
{
if ( j >= v38.array[v5].len )
runtime_panicIndex();
elem = v38.array[v5].str[j] > '9';
}
if ( elem )
break;
}
}
return 0;
}
else
{
return 0;
}
}
}

整体逻辑是先检测key的格式是否是?????-?????-?????-?????-????? 然后检测?的范围是否为[A-Z0-9] 如果满足这两个条件就启动5个线程分别检测5段key

线程函数1:

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
// main.Monday
// local variable allocation has failed, the output may be wrong!
void __golang main_Monday(string key, chan_bool check)
{
__int64 v2; // r14
int len; // rsi
int cap; // rdi
string v5; // kr00_16
bool v6; // cl
char elem[33]; // [rsp+1h] [rbp-31h] BYREF
runtime_hchan *c; // [rsp+22h] [rbp-10h]
void *retaddr; // [rsp+32h] [rbp+0h] BYREF
string v10; // 0:rbx.8,8:rcx.8
_slice_uint8 v11; // 0:rax.8,8:rbx.8,16:rcx.8 OVERLAPPED

if ( &retaddr <= *(v2 + 16) )
{
runtime_morestack_noctxt();
JUMPOUT(0x49552DLL);
}
c = check;
len = key.len;
v10.str = key.str;
v10.len = len;
v11 = runtime_stringtoslicebyte(&elem[1], v10);
cap = v11.cap;
v11.cap = v11.len;
v11.len = v11.array;
v5 = encoding_base64__ptr_Encoding_EncodeToString(encoding_base64_StdEncoding, *&v11.len);
v6 = v5.len == 8 && *v5.str == '=klUSFET';
elem[0] = v6;
runtime_chansend1(c, elem);
}

通过信道check发送检测是否通过的信号elem 简单的base64编码 求出来的字节串是小端序 所以要逆转一下编码 得到第一个片段LARRY

线程函数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
28
29
30
31
32
33
34
35
36
37
// main.Tuesday
void __golang main_Tuesday(string key, chan_bool check)
{
__int64 v2; // r14
int v3; // rdx
__int64 v4; // rsi
__int64 v5; // rdi
uint8 v6; // di
char elem[9]; // [rsp+1h] [rbp-9h] BYREF
void *retaddr; // [rsp+Ah] [rbp+0h] BYREF

if ( &retaddr <= *(v2 + 16) )
{
runtime_morestack_noctxt();
JUMPOUT(0x49560DLL);
}
v3 = 0LL;
v4 = 0LL;
while ( v3 < 5 )
{
if ( key.len <= v3 )
runtime_panicIndex();
v6 = key.str[v3];
elem[0] = v6 < '0';
if ( v6 >= '0' )
elem[0] = key.str[v3] > 0x39u;
if ( elem[0] )
{
runtime_chansend1(check, &runtime_egcbss);
return;
}
v5 = (key.str[v3++] - '0');
v4 += v5;
}
elem[0] = v4 == 35;
runtime_chansend1(check, elem);
}

检测每一位是否都是数字字符 然后将对应的数字求和结果需要为35 直接77777

线程函数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
// main.Wednesday
void __golang main_Wednesday(string key, chan_bool check)
{
__int64 v2; // r14
__int128 v3; // xmm15
unsigned __int64 len; // rax
uint8 *v5; // rcx
__int64 v6; // rdx
char v7; // si
int i; // rcx
char v9; // [rsp+1h] [rbp-A9h] BYREF
__int64 v10; // [rsp+2h] [rbp-A8h]
__int128 v11[2]; // [rsp+Ah] [rbp-A0h] BYREF
runtime_tmpBuf v12; // [rsp+2Ah] [rbp-80h] BYREF
runtime_tmpBuf buf; // [rsp+4Ah] [rbp-60h] BYREF
runtime_hchan *c; // [rsp+6Ah] [rbp-40h]
runtime_hmap h; // [rsp+72h] [rbp-38h] BYREF
uint8 *keya; // [rsp+B2h] [rbp+8h]
string v17; // 0:rax.8,8:rbx.8
string v18; // 0:rbx.8,8:rcx.8
_slice_uint8 v19; // 0:rax.8,8:rbx.8,16:rcx.8

if ( &buf[24] <= *(v2 + 16) )
{
runtime_morestack_noctxt();
JUMPOUT(0x49582DLL);
}
keya = key.str;
c = check;
*&h.count = v3;
h.oldbuckets = *(&v3 + 1);
*&h.nevacuate = v3;
v11[0] = v3;
v11[1] = v3;
h.buckets = v11;
h.hash0 = runtime_fastrand();
len = key.len;
v5 = keya;
v6 = 0LL;
while ( v6 < 5 )
{
if ( len <= v6 )
runtime_panicIndex();
v7 = v5[v6];
if ( v7 < 'A' || v7 > 'Z' || (v10 = v6, v9 = v7, *runtime_mapaccess1(&RTYPE_map_uint8_bool_0, &h, &v9)) )
{
runtime_chansend1(c, &runtime_egcbss);
return;
}
v9 = keya[v10];
*runtime_mapassign(&RTYPE_map_uint8_bool_0, &h, &v9) = 1;
v6 = v10 + 1;
len = key.len;
v5 = keya;
}
v18.str = v5;
v18.len = len;
v19 = runtime_stringtoslicebyte(buf, v18);
for ( i = 0LL; i < 5; ++i )
{
if ( v19.len <= i )
runtime_panicIndex();
v19.array[i] ^= 0x60;
}
v17 = runtime_slicebytetostring(v12, v19.array, v19.len);
main_Tuesday(v17, c);
}

检测key是否全部都是大写字母 在runtime_hmap h为每个字母建立一个映射 如果检测到当前字母在map中已经是存在的key就退出 通过这层检测后将每个字母异或0x60然后调用线程函数2 因为不能用重复的字母 所以这段key可以为UVWXY

线程函数4:

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
// main.Thursday
void __golang main_Thursday(string key, chan_bool check)
{
__int64 v2; // r14
int len; // rsi
int v4; // rcx
__int64 v5; // rdx
__int64 v6; // rsi
int i; // edi
__int64 v8; // r8
__int64 v9; // r9
int v10; // r10d
char elem[33]; // [rsp+1h] [rbp-31h] BYREF
runtime_hchan *c; // [rsp+22h] [rbp-10h]
void *retaddr; // [rsp+32h] [rbp+0h] BYREF
string v14; // 0:rbx.8,8:rcx.8
_slice_uint8 v15; // 0:rax.8,8:rbx.8,16:rcx.8

if ( &retaddr <= *(v2 + 16) )
{
runtime_morestack_noctxt();
JUMPOUT(0x495929LL);
}
c = check;
len = key.len;
v14.str = key.str;
v14.len = len;
v15 = runtime_stringtoslicebyte(&elem[1], v14);
v4 = 0LL;
v5 = 0LL;
v6 = 0LL;
while ( v4 < 5 )
{
if ( v15.len <= v4 )
runtime_panicIndex();
for ( i = v15.array[v4]; i; i = v10 )
{
v8 = v5-- + 1;
v9 = v6-- + 1;
v10 = i;
if ( (i & 1) != 0 )
v5 = v8;
if ( (i & 2) != 0 )
v6 = v9;
LOBYTE(v10) = i >> 2;
}
++v4;
}
elem[0] = v5 == 5;
if ( v5 == 5 )
elem[0] = v6 == 3;
runtime_chansend1(c, elem);
}

根据每个字符的有效位对两个计数器进行操作 要求最后这两个计数器到达一定的数值 简单分析一下可以知道如果字符在A-Z范围内不可逆达到这种结果 直接用数字字符爆破:

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
#include <stdio.h>
#include <stdlib.h>

void main(){
for(int a = '1'; a <= '9'; a++){
for(int b = '1'; b <= '9'; b++){
for(int c = '1'; c <= '9'; c++){
for(int d = '1'; d <= '9'; d++){
for(int e = '1'; e <= '9'; e++){
char table[5] = {a, b, c, d, e};
int v10 = 0, v11 = 0, v7 = 0, v8 = 0;
for(int i = 0; i < 5; i++){
while(table[i]){
v10 = v7-- + 1;
v11 = v8-- + 1;
if(table[i] & 1){
v7 = v10;
}
if(table[i] & 2){
v8 = v11;
}
table[i] >>= 2;
}
}
if(v7 == 5 && v8 == 3){
printf("%c%c%c%c%c %d %d\n", a, b, c, d, e, v7, v8);
exit(0);
}
}
}
}
}
}
}
// 12277 5 3

线程函数5:

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
void __golang main_Friday(string key, chan_bool check)
{
int v2; // rax
int v3; // rcx
unsigned int v4; // rbx
int v5; // r14
int v6; // rax
int count; // rdx
int v8; // rdx
int v9; // rsi
int v10; // rax
int i; // rdx
int j; // rdx
int r; // rdx
unsigned int sr; // rsi
unsigned int srs1; // r9
int v16; // r10
unsigned int v17; // r8
unsigned int f_i_j; // r11
int rr; // rdi
unsigned int v20; // r10
internal_abi_Type_0 *v21; // [rsp+18h] [rbp-C8h] BYREF
void *v22; // [rsp+20h] [rbp-C0h]
__int64 v23; // [rsp+28h] [rbp-B8h]
void *v24; // [rsp+30h] [rbp-B0h] BYREF
int f0t5[2]; // [rsp+38h] [rbp-A8h]
int v26; // [rsp+48h] [rbp-98h]
int f0t5_[9]; // [rsp+50h] [rbp-90h]
char v28; // [rsp+98h] [rbp-48h] BYREF
int v29; // [rsp+C0h] [rbp-20h]
unsigned int v30; // [rsp+C8h] [rbp-18h]
int keya; // [rsp+E8h] [rbp+8h]
unsigned int key_8; // [rsp+F0h] [rbp+10h]

if ( &v28 <= *(v5 + 16LL) )
runtime_morestack_noctxt();
key_8 = v4;
keya = v2;
f0t5[1LL] = v3;
qmemcpy(&v24 + 2LL, "UNL0CK", 6LL);
v6 = sub_45E299(&v21);
count = 0LL;
while ( count < 6LL )
{
f0t5[0LL] = count;
v24 = runtime_makeslice(v21, v22, v23);
v8 = f0t5[0LL];
v9 = 3LL * f0t5[0LL];
f0t5_[v9] = 7LL;
f0t5_[v9 + 1LL] = 7LL;
f0t5_[v9 - 1LL] = v10;
count = v8 + 1LL;
v6 = keya;
v4 = key_8;
}
for ( i = 0LL; i < 6LL; ++i )
{
if ( !f0t5_[3LL * i] )
runtime_panicIndex();
*f0t5_[3LL * i - 1LL] = i;
}
for ( j = 0LL; j < 7LL; ++j )
{
if ( j >= f0t5_[0LL] )
runtime_panicIndex();
*(v26 + 8LL * j) = j;
}
for ( r = 1LL; r < 6LL; ++r )
{
for ( sr = 1LL; sr < 7LL; ++sr )
{
if ( v4 <= r - 1LL )
runtime_panicIndex();
srs1 = sr - 1LL;
if ( *(&v24 + sr + 1LL) == *(r + v6 - 1LL) )
{
if ( f0t5[3LL * r] <= srs1 )
runtime_panicIndex();
if ( sr >= f0t5_[3LL * r] )
runtime_panicIndex();
*(f0t5_[3LL * r - 1LL] + 8LL * sr) = *((f0t5[3LL * r - 1LL] + 8LL * sr) - 8LL);// 如果 enc[sr - 1] == input[r - 1]
// f0t5[r][sr] = f0t5[r][sr-1]
}
else
{
v16 = f0t5[3LL * r - 1LL];
if ( sr >= f0t5[3LL * r] )
runtime_panicIndex();
v17 = f0t5_[3LL * r];
f_i_j = *(v16 + 8LL * sr);
rr = f0t5_[3LL * r - 1LL];
if ( v17 <= srs1 )
runtime_panicIndex();
v20 = *(v16 + 8LL * sr - 8LL);
if ( *(rr + 8LL * sr - 8LL) < f_i_j ) // f0t5[r][sr-1] < f0t5[r][sr]
f_i_j = *(rr + 8LL * sr - 8LL); // min = f0t5[s][sr-1]
if ( v20 < f_i_j )
f_i_j = v20;
if ( sr >= v17 )
runtime_panicIndex();
*(rr + 8LL * sr) = f_i_j + 1LL; // f0t5[r][sr] = min + 1
}
}
}
if ( v30 <= 6LL )
runtime_panicIndex();
BYTE1(v24) = *(v29 + 48LL) == 3LL; // f0t5[5][6] == 3
runtime_chansend1(v21, v22);
}

初始化一个6 * 7的矩阵 第0列的值初始化为0-7 第1行初始化为0-5 然后要求处理完后matrix[5][6]的值是3 可以直接拼出key来KNLCC

要动调类似的不对同一个数据操作所以多线程并行的程序 需要在每个线程函数的入口下断点 然后在动调时切换到除主函数以外的线程函数的界面 发现在等待运行的线程锁内 进行步出会到达某个函数的入口断点 这时候对除了主线程以外的线程暂停 否则可能会因为其他线程先处理完数据而程序因为发送到信道的信号直接退出

patchflag | Windows内核逆向

Msdelta.dll研究

剩下的题目待复现…