19thCISCN初赛WriteUp

第一次参加国赛,难哭了😭😭

WriteUp

babygame

godot逆向,AES算法

附件程序是个小游戏。拖入x64dbg分析,一路F9,在入口点附近可以看到Godot Engine字样

入口点附近找到godot engine字样

上网查找一下可以知道godot是一种游戏引擎,类似unity,可以解包

使用GDRETools/gdsdecomp解析exe可以得到一个babygame文件夹,文件夹下的scripts目录打开如下

scripts目录

010editor可以打开gd文件直接查看,如下是flag.gd,出现了主要的检验逻辑,加密使用了AES_ECB

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
extends CenterContainer

@onready var flagTextEdit: Node = $PanelContainer / VBoxContainer / FlagTextEdit
@onready var label2: Node = $PanelContainer / VBoxContainer / Label2

static var key = "FanAglFanAglOoO!"
var data = ""

func _on_ready() -> void :
Flag.hide()

func get_key() -> String:
return key

func submit() -> void :
data = flagTextEdit.text

var aes = AESContext.new()
aes.start(AESContext.MODE_ECB_ENCRYPT, key.to_utf8_buffer())
var encrypted = aes.update(data.to_utf8_buffer())
aes.finish()

if encrypted.hex_encode() == "d458af702a680ae4d089ce32fc39945d":
label2.show()
else:
label2.hide()

func back() -> void :
get_tree().change_scene_to_file("res://scenes/menu.tscn")

extends Node

题目提示需要收集金币(得分),在game_manager.gd里可以发现得分会修改key,A会被替换成B

1
2
3
4
5
6
7
8
9
@onready var fan = $"../Fan"

var score = 0

func add_point():
score += 1
if score == 1:
Flag.key = Flag.key.replace("A", "B")
fan.visible = true

所以把key改一下,然后使用Crypto库解密即可

Exp:

1
2
3
4
5
6
7
8
9
from Crypto.Cipher import AES
import binascii
enc="d458af702a680ae4d089ce32fc39945d"
enc_bytes=binascii.unhexlify(enc)
key=b"FanBglFanBglOoO!"
cipher = AES.new(key, AES.MODE_ECB)
decrypted = cipher.decrypt(enc_bytes)
print(decrypted)
#flag{wOW~youAregrEaT!}

复现

wasm-login

js+wasm逆向(没学过jsweb逆向╥﹏╥…

附件是个网页登录校验项目

附件文件列表

题目描述:

题目描述

在项目根目录下执行python -m http.server 4000可以把项目跑起来(当然直接看代码也行,但是当时没思路,于是就找了个办法把项目跑起来看看)

运行项目

index.html的script部分如下

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
  <script src="crypto-js.js"></script>
<script src="build/release.js"></script>
<script type="module">
import { authenticate } from "./build/release.js";
// 初始化 WASM 模块
async function initWasm() {
const wasmStatus = document.getElementById('wasm-status');
const loginForm = document.getElementById('login-form');
const loginBtn = document.getElementById('login-btn');
const loginSpinner = document.getElementById('login-spinner');
const statusMessage = document.getElementById('status-message');
const errorMessage = document.getElementById('error-message');
const passwordInput = document.getElementById('password');
const togglePasswordBtn = document.getElementById('toggle-password');

try {

// 初始化完成
wasmStatus.textContent = 'WASM 已加载';
wasmStatus.classList.add('text-success');

// 切换密码可见性
togglePasswordBtn.addEventListener('click', function() {
const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
passwordInput.setAttribute('type', type);

const icon = this.querySelector('i');
const text = this.querySelector('span');

if (type === 'text') {
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
text.textContent = '隐藏';
} else {
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
text.textContent = '显示';
}
});

// 登录表单提交处理
loginForm.addEventListener('submit', async function(e) {
e.preventDefault();

// 显示加载状态
loginBtn.disabled = true;
loginSpinner.classList.remove('hidden');
statusMessage.classList.add('hidden');

try {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;

// 调用 WASM 中的 authenticate 函数
const authResult = authenticate(username, password);
const authData = JSON.parse(authResult);

// 模拟发送到服务器
console.log('发送到服务器的数据:', authData);

// 模拟服务器响应
simulateServerRequest(authData)
.then(response => {
if (response.success) {
// 登录成功
alert('登录成功!');
} else {
// 登录失败
showError(response.message || '登录失败,请重试');
}
})
.catch(error => {
console.error('登录错误:', error);
showError('网络错误,请稍后重试');
})
.finally(() => {
// 恢复按钮状态
loginBtn.disabled = false;
loginSpinner.classList.add('hidden');
});

} catch (error) {
console.error('WASM 处理错误:', error);
showError('内部错误,请联系管理员');

// 恢复按钮状态
loginBtn.disabled = false;
loginSpinner.classList.remove('hidden');
}
});

// 显示错误消息
function showError(message) {
errorMessage.textContent = message;
statusMessage.classList.remove('hidden');

// 添加动画效果
const errorBox = statusMessage.querySelector('div');
errorBox.classList.add('animate-shake');
setTimeout(() => {
errorBox.classList.remove('animate-shake');
}, 500);
}

// 模拟服务器请求
function simulateServerRequest(data) {
return new Promise(resolve => {
// 模拟网络延迟
setTimeout(() => {
// 实际应用中这里应该是真实的 API 请求
// 这里仅作演示,使用本地判断
const check = CryptoJS.MD5(JSON.stringify(data)).toString(CryptoJS.enc.Hex);
if (check.startsWith("ccaf33e3512e31f3")){
resolve({ success: true });
}else{
resolve({ success: false });
}
}, 1000);
});
}

} catch (error) {
console.error('WASM 加载失败:', error);
wasmStatus.textContent = 'WASM 加载失败';
wasmStatus.classList.add('text-danger');

// 禁用登录按钮
loginBtn.disabled = true;
loginBtn.classList.add('bg-neutral-400');
loginBtn.classList.remove('bg-primary', 'hover:bg-primary/90');
}
}

// 页面加载完成后初始化 WASM
window.addEventListener('load', initWasm);
</script>
</body>
</html>

<!-- 测试账号 admin 测试密码 admin-->

审阅代码可知,校验逻辑在simulateServerRequest()里,主要是如下几行

1
2
3
4
5
6
const check = CryptoJS.MD5(JSON.stringify(data)).toString(CryptoJS.enc.Hex);
if (check.startsWith("ccaf33e3512e31f3")){
resolve({ success: true });
}else{
resolve({ success: false });
}

data是传入的参数,实际参数应该是authData:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 调用 WASM 中的 authenticate 函数
const authResult = authenticate(username, password);
const authData = JSON.parse(authResult);

// 模拟服务器响应
simulateServerRequest(authData).then(response => {
if (response.success) {
// 登录成功
alert('登录成功!');
} else {
// 登录失败
showError(response.message || '登录失败,请重试');
}
})

所以校验逻辑为:调用wasm的authenticate函数,返回值求md5后与给定的哈希值头”ccaf33e3512e31f3”进行比较。结合题目描述,authenticate函数除了用到index.html末尾的测试账密,还可能用到了时间戳,意味着需要求出这个时间戳进而求出完整哈希值。可以选择爆破时间戳,但是在此之前还得了解一下时间戳怎么来的

release.js用来连接wasm和js,在release.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
24
const adaptedImports = {
env: Object.setPrototypeOf({
abort(message, fileName, lineNumber, columnNumber) {
// ~lib/builtins/abort(~lib/string/String | null?, ~lib/string/String | null?, u32?, u32?) => void
message = __liftString(message >>> 0);
fileName = __liftString(fileName >>> 0);
lineNumber = lineNumber >>> 0;
columnNumber = columnNumber >>> 0;
(() => {
// @external.js
throw Error(`${message} in ${fileName}:${lineNumber}:${columnNumber}`);
})();
},
"console.log"(text) {
// ~lib/bindings/dom/console.log(~lib/string/String) => void
text = __liftString(text >>> 0);
console.log(text);
},
"Date.now"() {
// ~lib/bindings/dom/Date.now() => f64
return Date.now();
},
}, Object.assign(Object.create(globalThis), imports.env || {})),
};

此处设置了环境量(环境函数?)Date.now(),通过工具wabt下的wasm2c转化出wasm的c代码,也可以发现相关内容

c代码中的date.now

接下来,要想爆破时间戳,需要能够设置authenticate函数使用的时间戳。在wasm里难以修改相关逻辑,只好在release.js中修改Date.now()的定义,使其在被authenticate函数调用的时候返回我们想要的数。这里可以创建一个全局变量globalThis.mytime,然后修改Date.now()的返回值为这个全局变量,这样就可以设置不同的时间戳来让authenticate函数使用了。

修改release.js

最后抄一下index.html里检验逻辑的js代码就可以写出hook脚本:

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
import { authenticate } from "./build/release.js";// 模仿index.html导入authenticate函数,后续直接调用
import CryptoJS from "crypto-js";// index.html的检验逻辑用到了这个文件,需要导入,否则照抄会报错

function hook(){
var startTime=new Date("2025-12-21T22:40:00+08:00").getTime();
var endTime=new Date("2025-12-22T01:10:00+08:00").getTime();
// 这里的时间范围是通过文件修改时间推测出来的(看其他师傅的wp才get到这点,太细了)
const username = "admin";
const password = "admin";

console.log("Start brute forcing...");
for(let i=startTime;i<=endTime;i+=1){
globalThis.mytime = i;
// 下面几行直接抄原代码即可
// 调用 WASM 中的 authenticate 函数
const authResult = authenticate(username, password);
const authData = JSON.parse(authResult);
var check = CryptoJS.MD5(JSON.stringify(authData)).toString(CryptoJS.enc.Hex);
if(check.startsWith("ccaf33e3512e31f3")){
console.log("found hash:",check);// 返回操作改成输出hash
}
}
console.log("Brute forcing completed.");
}

hook();
// Start brute forcing...
// found hash: ccaf33e3512e31f36228f0b97ccbc8f1
// Brute forcing completed.

eternum

go逆向,待更新…

vvvmmm

vm逆向,待更新…

作者

SydzI

发布于

2025-12-28

更新于

2026-01-04

许可协议

评论