0%

osu!gamingCTF部分wp

参赛ID:1K0CT

笑点解析:osu有CTF CTF有osu

Crypto/base727 | base编码 | 编码转化

题目给出的加密脚本和结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import binascii

flag = open('flag.txt').read()

def encode_base_727(string):
base = 727
encoded_value = 0

for char in string:
encoded_value = encoded_value * 256 + ord(char)

encoded_string = ""
while encoded_value > 0:
encoded_string = chr(encoded_value % base) + encoded_string
encoded_value //= base

return encoded_string

encoded_string = encode_base_727(flag)
print(binascii.hexlify(encoded_string.encode()))

# 06c3abc49dc4b443ca9d65c8b0c386c4b0c99fc798c2bdc5bccb94c68c37c296ca9ac29ac790c4af7bc585c59d

chr()函数将参数转化为对应的Unicode字符 而.encode()方法将该字符转化为UTF-8编码 所以不能直接将结果unhexlify后进行解密 而需要先进行UTF-8解码 关于加密的逻辑 无非是10进制->256进制->727进制 倒推即可 以下是解密脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def decode_base_727(encoded_string):
base = 727
decoded_value = 0

for char in encoded_string:
decoded_value = decoded_value * base + ord(char)

decoded_string = ""
while decoded_value > 0:
decoded_string = chr(decoded_value % 256) + decoded_string
decoded_value //= 256

return decoded_string

encoded = binascii.unhexlify(b'06c3abc49dc4b443ca9d65c8b0c386c4b0c99fc798c2bdc5bccb94c68c37c296ca9ac29ac790c4af7bc585c59d').decode(encoding="utf-8")
print(decode_base_727(encoded))

# osu{wysiwysiwysiywsywiwywsi}

Reverse/SAT-before-osu | z3

直接将题目给出的关系塞到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
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
from z3 import *

sol = Solver()

# Define the variables
a = Int('a')
b = Int('b')
c = Int('c')
d = Int('d')
e = Int('e')
f = Int('f')
g = Int('g')
h = Int('h')
i = Int('i')
j = Int('j')
k = Int('k')
l = Int('l')
m = Int('m')
n = Int('n')
o = Int('o')
p = Int('p')
q = Int('q')
r = Int('r')
s = Int('s')
t = Int('t')
u = Int('u')
v = Int('v')
w = Int('w')
x = Int('x')
y = Int('y')
z = Int('z')

sol.add(b + c + w == 314)
sol.add(t + d + u == 290)
sol.add(p + w + e == 251)
sol.add(v + l + j == 274)
sol.add(a + t + b == 344)
sol.add(b + j + m == 255)
sol.add(h + o + u == 253)
sol.add(q + l + o == 316)
sol.add(a + g + j == 252)
sol.add(q + x + q == 315)
sol.add(t + n + m == 302)
sol.add(d + b + g == 328)
sol.add(e + o + m == 246)
sol.add(v + v + u == 271)
sol.add(f + o + q == 318)
sol.add(s + o + j == 212)
sol.add(j + j + n == 197)
sol.add(s + u + l == 213)
sol.add(q + w + j == 228)
sol.add(i + d + r == 350)
sol.add(e + k + u == 177)
sol.add(w + n + a == 288)
sol.add(r + e + u == 212)
sol.add(q + l + f == 321)

sol.check()
answer = [a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z]
while sol.check() == sat:
m = sol.model()
for letter in answer:
try:
value = m[letter].as_long()
print(chr(value), end="")
except:
pass
condition = []
for d in m:
condition.append(d() != m[d])
sol.add(Or(condition))
# osu{0rZ_p3PpY_my_s4v1oR}

Reverse/wysi-baby | js逆向 | 爆破

网站源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function wysi() {
if (combos.length === 8) {
var cs = combos.join("");
var csr = cs + cs.split("").reverse().join("");
var res = CryptoJS.AES.decrypt("5LJJj+x+/cGxhxBTdj/Q2RxkhgbH7v8b/IgX9Kjptpo=", CryptoJS.enc.Hex.parse(csr + csr), { mode: CryptoJS.mode.ECB }).toString(CryptoJS.enc.Utf8);
// if prefix is "osu{" then its correct
if (res.startsWith("osu{")) {
document.getElementById("music").innerHTML = '<audio src="./wysi.mp3" autoplay></audio>';
console.log(res);
} else {
// reset
console.log("nope.");
combos = [];
}
}
}

$(document).ready(function() {
$("#frame").on("click", "button", function() {
var buttonValue = $(this).data("value");
combos.push(buttonValue);
wysi();
});
});

核心逻辑是检测点击的按钮来向combos压入按钮对应的值 然后当combos的长度为8时通过连接comboscombos[::-1] 再连接自身来构成32位密钥进行AES解密 直接爆破

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
for (var i = 0; i < 10; i++) {
for (var j = 0; j < 10; j++) {
for (var k = 0; k < 10; k++) {
for (var l = 0; l < 10; l++) {
for (var m = 0; m < 10; m++) {
for (var n = 0; n < 10; n++) {
for (var o = 0; o < 10; o++) {
for (var p = 0; p < 10; p++) {
combos = [i, j, k, l, m, n, o, p]
var cs = combos.join("");
var csr = cs + cs.split("").reverse().join("");
try{var res = CryptoJS.AES.decrypt("5LJJj+x+/cGxhxBTdj/Q2RxkhgbH7v8b/IgX9Kjptpo=", CryptoJS.enc.Hex.parse(csr + csr), { mode: CryptoJS.mode.ECB }).toString(CryptoJS.enc.Utf8);}
catch(err){break;}
if (res.startsWith("osu{")) {
console.log(res);
}
}
}
}
}
}
}
}
}

// osu{baby_js_osu_web_uwu}

(正常来说可以用python遍历1~100000000高位填充0来解密 但是不知道为什么没爆出来)

Reverse/ecs!catch | Unity游戏逆向

题目描述:

1
2
3
4
5
Receive an SS (with maximum score) on "Bakamitai", the hardest map, to receive the flag. Shouldn't be too difficult, right?

Note: An SS is not enough! The remote has additional checks for specific scoring (the maximum score if SS'ed "legitimately").

Note: This executable is built for x86 Windows.

说明这是32位的Unity引擎制作的游戏 并且目标是达到某个特定的分值而不是超过某个分值 游戏就是osu的catch模式 直接找到和碰撞处理相关的函数OnTriggerEnter2DOnTriggerExit2D 既然游戏要达到某个分值而不是越高越好那么直接将Missed判定接入Activator判定中 删除Missed判定的逻辑:

image-20240304193305920

如果遇到箭头指的对象报错就先导入UnityEngine.dll 然后保存模块进入游戏按题目的要求打难度最高的歌拿到flag:

image-20240304193712962

Reverse/wysi-revenge | js逆向 | wasm

网站源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function wysi() {
if (combos.length === 12) {
var cs = combos.join("");
Module().then(function(mod) {
const ck = mod.cwrap('checker', 'boolean', ['string', 'number']);
if (ck(cs, cs.length)) {
document.getElementById("music").innerHTML = '<audio src="./wysi.mp3" autoplay></audio>';
console.log("osu{" + cs + "}");
} else {
console.log("nope.");
combos = [];
}
});
}
}

$(document).ready(function() {
$("#frame").on("click", "button", function() {
var buttonValue = $(this).data("value");
combos.push(buttonValue);
wysi();
});
});

基本逻辑类似wysi-baby 但是使用到了Js内联C/C++ 所以部分功能是由wasm实现的(这一点通过网页开发者工具调试得知) 一路步入到核心检查逻辑cheker()发现存在于checker.wasm 复制代码保存到本地为.wat(明文形式的wasm代码) 用wabt工具箱将其转化为可读性更高的.o文件 得到原始代码:

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
285
286
287
export memory memory(initial: 256, max: 32768);

global g_a:int = 65536;
global g_b:int = 0;
global g_c:int = 0;
global g_d:int = 0;

export table indirect_function_table:funcref(min: 1, max: 1);

export function wasm_call_ctors() {
emscripten_stack_init()
}

function f_b(a:int, b:int, c:int, d:int):int {
var e:int = g_a;
var f:int = 16;
var g:{ a:int, b:int, c:int, d:int } = e - f;
g.d = a;
g.c = b;
g.b = c;
g.a = d;
var h:int = g.d;
var i:int = 22;
var j:int = h;
var k:int = i;
var l:int = j == k;
var m:int = 0;
var n:int = 1;
var o:int = l & n;
var p:int = m;
if (eqz(o)) goto B_a;
var q:int = g.c;
var r:int = g.b;
var s:int = q + r;
var t:int = 30;
var u:int = s;
var v:int = t;
var w:int = u == v;
var x:int = 0;
var y:int = 1;
var z:int = w & y;
p = x;
if (eqz(z)) goto B_a;
var aa:int = g.b;
var ba:int = g.a;
var ca:int = aa * ba;
var da:int = 168;
var ea:int = ca;
var fa:int = da;
var ga:int = ea == fa;
var ha:int = 0;
var ia:int = 1;
var ja:int = ga & ia;
p = ha;
if (eqz(ja)) goto B_a;
var ka:int = g.d;
var la:int = g.c;
var ma:int = ka + la;
var na:int = g.b;
var oa:int = ma + na;
var pa:int = g.a;
var qa:int = oa + pa;
var ra:int = 66;
var sa:int = qa;
var ta:int = ra;
var ua:int = sa == ta;
p = ua;
label B_a:
var va:int = p;
var wa:int = 1;
var xa:int = va & wa;
return xa;
}

function f_c(a:int, b:int, c:int, d:int, e:int):int {
var f:int = g_a;
var g:int = 32;
var h:int_ptr = f - g;
h[7] = a;
h[6] = b;
h[5] = c;
h[4] = d;
h[3] = e;
var i:int = h[7];
var j:int = h[6];
var k:int = i + j;
var l:int = h[5];
var m:int = k + l;
var n:int = h[4];
var o:int = m + n;
var p:int = h[3];
var q:int = o + p;
var r:int = 71;
var s:int = q;
var t:int = r;
var u:int = s == t;
var v:int = 0;
var w:int = 1;
var x:int = u & w;
var y:int = v;
if (eqz(x)) goto B_a;
var z:int = h[7];
var aa:int = h[6];
var ba:int = z * aa;
var ca:int = h[5];
var da:int = ba * ca;
var ea:int = h[4];
var fa:int = da * ea;
var ga:int = h[3];
var ha:int = fa * ga;
var ia:int = 449280;
var ja:int = ha;
var ka:int = ia;
var la:int = ja == ka;
var ma:int = 0;
var na:int = 1;
var oa:int = la & na;
y = ma;
if (eqz(oa)) goto B_a;
var pa:int = h[7];
var qa:int = h[7];
var ra:int = pa * qa;
var sa:int = h[6];
var ta:int = h[6];
var ua:int = sa * ta;
var va:int = ra + ua;
var wa:int = 724;
var xa:int = va;
var ya:int = wa;
var za:int = xa == ya;
var ab:int = 0;
var bb:int = 1;
var cb:int = za & bb;
y = ab;
if (eqz(cb)) goto B_a;
var db:int = h[5];
var eb:int = h[5];
var fb:int = db * eb;
var gb:int = h[4];
var hb:int = h[4];
var ib:int = gb * hb;
var jb:int = fb + ib;
var kb:int = 313;
var lb:int = jb;
var mb:int = kb;
var nb:int = lb == mb;
var ob:int = 0;
var pb:int = 1;
var qb:int = nb & pb;
y = ob;
if (eqz(qb)) goto B_a;
var rb:int = h[3];
var sb:int = h[3];
var tb:int = rb * sb;
var ub:int = 64;
var vb:int = tb;
var wb:int = ub;
var xb:int = vb == wb;
var yb:int = 0;
var zb:int = 1;
var ac:int = xb & zb;
y = yb;
if (eqz(ac)) goto B_a;
var bc:int = h[7];
var cc:int = h[5];
var dc:int = bc + cc;
var ec:int = 30;
var fc:int = dc;
var gc:int = ec;
var hc:int = fc == gc;
var ic:int = 0;
var jc:int = 1;
var kc:int = hc & jc;
y = ic;
if (eqz(kc)) goto B_a;
var lc:int = h[7];
var mc:int = h[4];
var nc:int = lc - mc;
var oc:int = 5;
var pc:int = nc;
var qc:int = oc;
var rc:int = pc == qc;
y = rc;
label B_a:
var sc:int = y;
var tc:int = 1;
var uc:int = sc & tc;
return uc;
}

export function checker(a:int, b:int):int {
var c:int = g_a;
var d:int = 32;
var e:int = c - d;
var f:int_ptr = e;
g_a = e;
f[7] = a;
f[6] = b;
var g:int = f[6];
var h:int = e;
f[5] = h;
var i:int = 2;
var j:int = g << i;
var k:int = 15;
var l:int = j + k;
var m:int = -16;
var n:int = l & m;
var o:int = e;
var p:{ a:int, b:int, c:int, d:int, e:int, f:int, g:int, h:int, i:int, j:int, k:int, l:int } =
o - n;
e = p;
g_a = e;
f[4] = g;
var q:int = 0;
f[3] = q;
loop L_b {
var r:int = f[3];
var s:int = f[6];
var t:int = r;
var u:int = s;
var v:int = t < u;
var w:int = 1;
var x:int = v & w;
if (eqz(x)) goto B_a;
var y:int = f[7];
var z:int = f[3];
var aa:ubyte_ptr = y + z;
var ba:int = aa[0];
var ca:int = 24;
var da:int = ba << ca;
var ea:int = da >> ca;
var fa:int = 97;
var ga:int = ea - fa;
var ha:int = f[3];
var ia:int = 2;
var ja:int = ha << ia;
var ka:int_ptr = p + ja;
ka[0] = ga;
var la:int = f[3];
var ma:int = 1;
var na:int = la + ma;
f[3] = na;
continue L_b;
}
unreachable;
label B_a:
var oa:int = p.b;
var pa:int = 0;
var qa:int = pa;
if (oa) goto B_c;
var ra:int = p.i;
var sa:int = 0;
qa = sa;
if (ra) goto B_c;
var ta:int = p.l;
var ua:int = 0;
qa = ua;
if (ta) goto B_c;
var va:int = p.a;
var wa:int = p.c;
var xa:int = p.d;
var ya:int = p.e;
var za:int = f_b(va, wa, xa, ya);
var ab:int = 0;
var bb:int = 1;
var cb:int = za & bb;
qa = ab;
if (eqz(cb)) goto B_c;
var db:int = p.f;
var eb:int = p.g;
var fb:int = p.h;
var gb:int = p.j;
var hb:int = p.k;
var ib:int = f_c(db, eb, fb, gb, hb);
qa = ib;
label B_c:
var jb:int = qa;
var kb:int = f[5];
e = kb;
var lb:int = 1;
var mb:int = jb & lb;
var nb:int = 32;
var ob:int = f + nb;
g_a = ob;
return mb;
}
...

(这里只给出最重要的逻辑) 进一步合并精简代码得到以下逻辑:

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
global g_a:int = 65536;
global g_b:int = 0;
global g_c:int = 0;
global g_d:int = 0;

#include <stdbool.h>

struct {
int a;
int b;
int c;
int d;
} g;

int f_b(int p.a, int p.c, int p.d, int p.e) {
int temp = offset_65392; // 算完字母偏移后偏移存放的地方
int f = 16;
g_d = p.a;
g_c = p.c;
g_b = p.d;
g_a = p.e;
bool l = g_d == 22;
int o = l & 1;
bool z = g_c + g_b == 30; //p[2] + p[3] == 30
int ja = g_b * g_a == 168; //p[3] * p[4] == 168
int ua = g_d + g_c + g_b + g_a == 66; //p[0] + p[2] + p[3] + p[4] == 66
int va = ua & (ja & (z & o)); //
return va & 1;
}

>>> from z3 import *
>>> a = Int("a")
>>> c = Int("c")
>>> d = Int("d")
>>> e = Int("e")
>>> s = Solver()
>>> s.add(a == 22)
>>> s.add(c + d == 30)
>>> s.add(d * e == 168)
>>> s.add(a + c + d + e == 66)
>>> s.check()
sat
>>> m = s.model()
>>> m
[a = 22, d = 12, c = 18, e = 14]
//p[0] == 'w', p[2] == "s", p[3] == "m", p[4] == "o"

#include <stdbool.h>

struct {
int a;
int b;
int c;
int d;
int e;
} g;

int f_c(p.f, p.g, p.h, p.j, p.k) {
int* h = &g.a - 32;
f = p.f;
g = p.g;
h = p.h;
j = p.j;
k = p.k;
bool u = k + j + h + g + f == 71;
if (u) goto B_a;
bool la = f * g * h * j * k == 449280;
if (la) goto B_a;
bool za = f * f + g * g == 724;
if (za) goto B_a;
bool pb = h * h + j * j == 313;
if (pb) goto B_a;
bool xb = k * k == 64;
if (xb) goto B_a;
bool gc = f + h == 30;
if (gc) goto B_a;
bool rc = f - j == 5;
B_a:
int uc = rc & 1;
return uc;
}

>>> g = Int("g")
>>> f = Int("f")
>>> h = Int("h")
>>> j = Int("j")
>>> k = Int("k")
>>> sol = Solver()
>>> sol.add(k + j + h + g + f == 71)
>>> sol.add(f * g * h * j * k == 449280)
>>> sol.add(f * f + g * g == 724)
>>> sol.add(h * h + j * j == 313)
>>> sol.add(k == 8)
>>> sol.add(f + h == 30)
>>> sol.add(f - j == 5)
>>> sol.check()
sat
>>> m = sol.model()
>>> m
[k = 8, f = 18, j = 13, g = 20, h = 12]
//p[5] == "s", p[6] == "u", p[7] == "m", p[9] == "n", p[10] == "i"


int checker(int base, int lenth) {
int* f = offset_65440;
g_a = offset_65440;
f[7] = base_65472;
int h = offset_65440;
f[5] = h;
int n = ((lenth << 2) + 15) & 0b.... 1111 0000;
int o = offset_65440;
struct {
int a;
int b;
int c;
int d;
int e;
int f;
int g;
int h;
int i;
int j;
int k;
int l;
} p = {offset_65392};
g_a = offset_65392;
f[4] = lenth;
int q = 0;
offset_input = q;
while (offset_input < lenth) {
int now_char = *(f[7] + offset_input);
p[offset_input << 2] = now_char - 97;
offset_input += 1;
}

//↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
//取每位字母对'a'的偏移
//===========================

int qa = 0;
if (p.b == 0) { //input[1] == 'a'
qa = 0;
if (!p.i == 0) { //input[8] == 'a'
qa = 0;
if (!p.l) { //input[11] == 'a'
int cb = f_b(p.a, p.c, p.d, p.e) & 1;
qa = 0;
if (cb == 1) {
qa = f_c(p.f, p.g, p.h, p.j, p.k);
}
}
}
}
offset_65392 => offset_65440;
g_a = offset_65472;
return qa & 1;
}
// p[5] == "s", p[6] == "u", p[7] == "m", p[9] == "n", p[10] == "i"
// p[0] == 'w', p[2] == "s", p[3] == "m", p[4] == "o"
// osu{wasmosumania}

部分逻辑要是不确定可以再使用调试功能进行猜想验证 而wasm中的load指令指的是从memories中取出数据 例如:

image-20240304195435263

image-20240304195538986