June 6, 2023

第三届陕西省赛 REVERSE

我的upx -d怎么坏了

0x00 Routine Check Shell

upx壳,好像改了点什么,但是压缩壳只要跑起来就可以dump出来

0x01 Maze

打开主程序可以发现是迷宫题,那么直接拿到迷宫就可以走迷宫了,然而多解

0x02 Get Flag

获取迷宫发现跑出四个路径

DDDRRURURRURRRRRRDDDDRDDD

DDDRRURURRDDDLDDDDRRRRRRRUR

RRRDRRURRRRRRDDDDRDDD

RRRDRRDDDLDDDDRRRRRRRUR

后面说是最短路径,所以

1
flag{ae2de0be8285f69db701d4dba8721a40}

babypython

0x00 Routine Check Shell

鉴定为python字节码阅读题

0x01 GPT Time

大部分都是无用代码只有中间可用,然后直接丢到gpt翻译

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
flag = '************************************'
value = ''
output = ''
i = 0

while i < len(flag):
temp = flag[i] ^ 8
value += temp
i += 1

for _ in range(1000):
x = 3
y = x * 3
z = y / 4
w = z - 5

if w == 0:
print('This line will never be executed')

result = ''
invalid_list = [1, 2, 3] * 20

for _ in range(50):
result += 'A'
invalid_list.append(4)

for i in range(len(output)):
temp = chr(ord(output[i]) + 3)
output += temp

for _ in range(30):
result += output.lower()

for _ in range(950):
x = 3
y = x * 3
z = y / 4
w = z - 5

if w == 0:
print('This line will also never be executed')

obfuscated_output = base64.b64encode(output.encode()).decode()[::-1]
obfuscated_output = obfuscated_output[::-1].replace('g', '1').replace('H', '3')

print(obfuscated_output)

0x02 Get Flag

gpt有时候会翻译出错误代码,对着字节码阅读下修改写出解密脚本

1
2
3
4
5
6
7
8
9
import base64

enc = '=1nb0A3b7AUQwB3b84mQ/E0MvJUb+EXbx5TQwF3bt52bAZncsd9c'
enc = enc.replace('1', 'g').replace('3', 'H').replace('a', 'w')
obfuscated_output = base64.b64decode(enc.encode()[::-1])
print(obfuscated_output)
for i in range(len(obfuscated_output)):
print(chr((obfuscated_output[i] - 3) ^ 8), end = "")
# flag{5dcbafe63fbf3b7d8647c1aee650ae9c}

BadCoffee

0x00 Routine Check Shell

js混淆题

0x01 调试与审计

调试

然而我们可以先调试看看,在与js同目录创建一个index.html,内容为

1
<script src="./BadCoffee.js" type="text/javascript" charset="utf-8"></script>

随后在该目录

1
python3 -m http.server

即可调试该js,那么代码是经过混淆的,然而有个很直观的enc,直接开调该函数即可

image-20230608193711286

不难发现就是这两个循环在对flag进行加密,稍微调试下再观察堆栈不难发现加密过程

image-20230608194004011

审计

那么当然也可以手动审计下,分析下这两个循环,不难发现就是把这个key从头到尾然后再从尾到头异或了一遍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function enc(input) {
var _0x137834 = _0x229b
, _0x3aaed1 = {
'XmyuE': function(_0x57b977, _0x20fa18, _0x570bf6) {
return _0x57b977(_0x20fa18, _0x570bf6);
}
}
, xor_input = []
, key = [-0x593 * -0x1 + 0xa7e * -0x1 + 0x2 * 0x2ea, -0x192e + 0x3a * -0x3b + 0x270d, 0x1287 + 0x44b * -0x2 + -0x972, -0x247e + -0x2613 + 0x4b7f * 0x1, -0x1adf + 0x7e3 + 0x1 * 0x138d, 0xa * 0x1aa + 0x1147 * 0x1 + -0x215b, 0xe03 + -0x1e31 + 0x1039, -0xfc + 0x25e1 * -0x1 + -0x9c2 * -0x4, 0x8b9 * -0x4 + 0x456 + -0xb * -0x2cf, 0xeae + -0xb * -0x9d + -0x14e7 * 0x1, 0x1 * -0xe75 + -0x9d5 + 0x1 * 0x193d, 0x2150 + -0x12d9 * 0x1 + -0xdd9, -0x1b3b + 0x1 * 0x557 + -0x16a9 * -0x1, -0x25 * 0xb3 + -0xa49 + 0x2500, 0x20a8 + 0x15b8 + -0x3 * 0x11fb, -0x1b8 + -0x1383 + 0x1 * 0x15c3, 0xe22 * -0x2 + -0x84c + 0x1 * 0x2528, -0x3 * -0x1a + -0xa * -0x185 + -0x12f * 0xd, 0x1128 + -0x1 * 0x2467 + 0x2dd * 0x7, 0x818 * 0x4 + 0x1d2 * 0xc + -0x3619, -0x1c5e + -0x1b80 + -0x638 * -0x9, 0x1b * -0xa7 + 0x2 * 0x9a7 + -0xcd, -0x9fa + 0x1cb2 * -0x1 + 0x26d3, 0x13 * 0x11f + 0x23f3 + 0x326 * -0x12, -0x13e1 + 0x5 * -0x3de + 0x3 * 0xd5a, -0x1cb6 + -0x14a3 + 0x1 * 0x3235, -0x2d9 + -0x1 * -0x116f + -0xe3c, 0x97 + -0x98e + 0x943, 0x1e0c + 0x1bf * 0x1 + 0x10 * -0x1ed, 0x12 * 0x72 + 0x1 * -0x919 + -0x1 * -0x14e, 0x9d * 0x10 + 0x128d * 0x1 + -0x1ba6, 0x2535 + 0x8fd * -0x3 + -0x2e * 0x35, 0xb0a * 0x3 + -0x2163 + 0xdb, 0x392 + 0x6c6 + -0x35 * 0x2f, 0x1911 + 0x31 * -0x29 + -0x2 * 0x84e, 0x1e2a + 0x1 * 0x3d1 + -0x3b3 * 0x9, 0x1047 + 0x1c * 0xc1 + -0x2556, -0x4f4 * -0x6 + -0x11b * 0x17 + -0x2e * 0x17, 0x1808 * 0x1 + 0x220f * -0x1 + 0xa25, 0x1bb0 + -0x216d + 0x1 * 0x613, -0x1c01 + -0x1e4f * -0x1 + -0x15a * 0x1, -0x179 + -0x1152 + 0x12d3];
for (let i = -0x25 * 0x92 + 0xc4 * 0x25 + -0x73a; i < 0x17c + -0x13 * -0x20 + -0x3b2; i++) {
xor_input[i] = _0x3aaed1[_0x137834(0x142)](xxx, key['at'](i), input[_0x137834(0x14a)](i)['charCodeAt']());
}
for (let j = 0x19fd + 0x26d3 * 0x1 + -0x3d0 * 0x11; j < 0x1b59 * 0x1 + 0xd74 + -0x28a3; j++) {
xor_input[j] = xxx(xor_input['at'](j), key['at'](-0x1808 + 0x1 * -0x3d6 + 0x1c07 - j));
} //-0x1808 + 0x1 * -0x3d6 + 0x1c07 == 1
console.log(xor_input);
return xor_input;
}

0x02 Get Flag

那么接着调试拿到密文直接就可以拿到flag了

1
2
3
4
5
6
7
key = [233, 129, 127, 238, 145, 144, 11, 43, 87, 134, 243, 158, 197, 216, 111, 136, 152, 29, 204, 31, 26, 228, 39, 148, 215, 220, 90, 76, 251, 57, 183, 184, 150, 157, 156, 176, 13, 41, 30, 86, 244, 8]
enc = [135, 25, 72, 151, 195, 229, 195, 207, 178, 104, 51, 81, 132, 91, 91, 170, 60, 178, 32, 64, 134, 134, 64, 32, 178, 60, 170, 91, 91, 132, 81, 51, 104, 178, 207, 195, 229, 192, 136, 81, 13, 156]
encc = [135, 25, 72, 151, 195, 212, 228, 212, 250, 101, 39, 77, 163, 77, 70, 167, 119, 184, 7, 77, 144, 154, 93, 10, 185, 48, 179, 77, 71, 163, 67, 61, 113, 156, 196, 136, 239, 241, 128, 93, 84, 156]

for i in range(42):
print(chr(encc[i] ^ key[i] ^ key[42 - 1 - i]), end = "")
# flag{I_c0uld_neu3r_undeRstand_jvaVs3rIpt!}

Web&Assembly

0x00 Routine Check Shell

wasm题,可以调,但可以先审审看

0x01 JEB & Ghidra

直接运行可以得到以下信息,可以知道梓曰师傅还是给了很多提示的

image-20230608192121334

于是我是先拉入JEB审计,看到主函数还是比较好理解的,关键就是F8函数传入的三个值,密钥、输入和密文,当然提示里都告诉你了

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
int __f9() {
int v0 = __g0 - 176;

__g0 -= 176;
*(int*)(v0 + 172) = 0;
*(char*)(v0 + 168) = a114_514_[&gvar_8];
*(long long*)(v0 + 160) = *(long long*)&a114_514_[0];
__f11(65, "91fba5ccfef6e0905eeeb47940d25543c286b10de778fbb268ab7580414c0758", v0 + &gvar_10);
__f12(0, 0x10101);
__f12(0, "寻思苦久,小明发现了一个加密后的字符串:91fba5ccfef6e0905eeeb47940d25543c286b10de778fbb268ab7580414c0758\n");
__f12(0, 66533);
__f12(0, "你可以找到登录的密码吗,打开那扇神秘的大门!\n\n你的密码是:\n");
*(int*)v0 = v0 + 96;
__f13(v0, 0x1005a);
int v1 = __f8(v0 + &gvar_10, v0 + 96, v0 + 160);
if(v1 == 0) {
__f12(0, "看起来密码不对? 再试试看吧 ~ ");
}
else {
__f12(0, "WoW,你的Flag是你的输入!");
}

__g0 = v0 + 176;
return 0;
}

然而这个F8函数在JEB里就是依托,此时就应该上Ghidra,装一个wasm插件

https://github.com/nneonneo/ghidra-wasm-plugin

此时再点入F8函数再稍作审计,就会比较直观

image-20230608192741755

那么加密过程就是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def do_something(input, a, b, c, d):
input[d] = (input[d] ^ input[a] + input[c]) & 0xFF
input[c] = (input[c] ^ input[a] + input[b]) & 0xFF
input[b] = (input[b] ^ input[c] + input[d]) & 0xFF
input[a] = (input[a] ^ input[b] + input[d]) & 0xFF

return input


def encryption(input):
key = b'114!514!'
for i in range(len(input)):
input[i] ^= key[i]
for i in range(0x72):
input = do_something(input, 0, 1, 2, 3)
input = do_something(input, 4, 5, 6, 7)
input = do_something(input, 0, 1, 4, 5)
input = do_something(input, 2, 3, 6, 7)

return input

0x02 Get 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
def redo_something(enc, a, b, c, d):
enc[a] ^= (enc[b] + enc[d]) & 0xFF
enc[b] ^= (enc[c] + enc[d]) & 0xFF
enc[c] ^= (enc[a] + enc[b]) & 0xFF
enc[d] ^= (enc[a] + enc[c]) & 0xFF

return enc


def decryption(enc):
for i in range(0x72):
enc = redo_something(enc, 2, 3, 6, 7)
enc = redo_something(enc, 0, 1, 4, 5)
enc = redo_something(enc, 4, 5, 6, 7)
enc = redo_something(enc, 0, 1, 2, 3)
key = b'114!514!'
for i in range(len(enc)):
enc[i] = enc[i] ^ key[i]
return enc


hex_string = "91fba5ccfef6e0905eeeb47940d25543c286b10de778fbb268ab7580414c0758"
enc = bytes.fromhex(hex_string)

flag = b''
for i in range(0, len(enc), 8):
flag += decryption(bytearray(enc[i:i+8]))
print(flag)
# flag{Y0u_Kn0w_W45M_n0w!!W0oO0ow}

那么当然此题也可以调试出,不过WASM调试是有一定的冗长得根据堆栈信息配合着静态审计,因为不乏有信息是调试才能得知,可以参考下去年底RCTF的那题WASM题。

DASCTF X SU
🍬
HFCTF2022
🍪

About this Post

This post is written by P.Z, licensed under CC BY-NC 4.0.