CTF 2024 Write up
本萌新刷推的时候无意间看见这个 CTF 2024 ,虽然期末在即,但还是想玩一下。
第一次参加有关 CTF 的东西,所以写博客记录以下。
Check In

点了 Submit,在 Console 中没有发现任何网络请求,所以答案一定在本地。
在 Sources 中搜索一番,发现了可疑目标。

从正则可以看出,这是一个8位字符的字符串,且仅包含a-z A-Z 0-9。
所以思路很简单,遍历这些可能字符的字符码进行爆破。
[['a', 'z'], ['A', 'Z'], ['0', '9']].forEach(range => {
for (let i = range[0].charCodeAt(0); i <= range[1].charCodeAt(0); i++) {
const realFlag = (0, _rust_sdk__WEBPACK_IMPORTED_MODULE_1__/* .getFlag1 */.S7)(i);
if (realFlag.length > 0) {
console.log(realFlag);
}
}
});Hook 入代码后,点击按钮,得到了 Flag 1。
document.querySelector("body > main > div:nth-child(3) > div > button").click();
Bytecode
Bytecode,字节码。
尝试逆向 WASM 无果后,我决定向第一题一样直接 Hook 入代码。
分析 Hook 入点
首先看 mangleBuffer 是什么,尝试 console.log(mangleBuffer),控制台运行:
document.querySelector("body > main > div:nth-child(4) > div > input").value = '1';
document.querySelector("body > main > div:nth-child(4) > div > button").click();得到结果:

Uint8Array是 JavaScript 中的一种类型化数组,用于处理二进制数据。
Uint8Array中的 “Uint” 表示 “无符号整数”,而 “8” 表示每个元素占用 8 位(即 1 字节)。因此,
Uint8Array的每个元素都是一个无符号的 8 位整数,其取值范围是 0 到 255。
接下来看到执行部分,可以看到一共有三次执行,前两次都是针对第一位和第二位的判断,第三次才是真正的执行。
vm.loadCode 表示使用 Uint8Array 用于表示某种虚拟机(注释中标记为:HumbleVM)可以理解并执行的代码。
这些代码可能是低级的机器码或字节码,它们直接控制虚拟机的操作。
爆破前两次执行
我们可以暴力破解前两次的运行,尝试得出规律:
for (let i = 0; i <= 256; i++) {
if (vm.run(Uint8Array.from([i]))) {
console.log(i);
}
}控制台直接得到结果162。
对于第二次执行,我们也使用同样的方法:
for (let i = 0; i <= 256; i++) {
if (vm.run(Uint8Array.from([162, i]))) {
console.log(i);
}
}控制台得到结果154。
所以接下来我们要找出输入什么值得到的 mangleBuffer 前两位为 [ 162, 154 ].
这里毫无技巧可言,人工暴力随机输入后得到以下条件:
- 输入的字符串为 8 位
- 第一个字符为 0

爆破第三次执行
我们针对第三次执行开始爆破。
思路:使用vm.loadCode分次载入Uint8Array,爆破结果。
for (let i = 0; i <= 256; i++) {
if (vm.run(Uint8Array.from([162, 154, i]))) {
console.log(i);
}
}| 输入 | 输出 |
|---|---|
| 1, 1 | 所有结果均为 true |
| 1, 1, 4 | malformed bytecode (expected an operand for instruction 4) |
| 1, 1, 4, 208 | 208 |
以此类推可以得到正确的 mangleBuffer 为 [162, 154, 208, 201, 155, 234, 192, 218,158]。
爆破 Flag
得到了 mangleBuffer,我们还需要得到 flag。
尝试对每一位进行爆破
for (let i = 0; i <= 255; i++) {
const flag = `0${String.fromCharCode(i)}xxxxxx`
const mangledBuffer = (0, _rust_sdk__WEBPACK_IMPORTED_MODULE_1__/* .mangle */.dW)(flag);
if (mangledBuffer[3] === 208) {
console.log(String.fromCharCode(i));
}
}得到:
| Uint8 | Flag | |
|---|---|---|
| 第二位 | 208 | z |
| 第三位 | 201 | c |
| 第四位 | 155 | 1 |
| 第五位 | 234 | @ |
| 第六位 | 192 | j |
| 第七位 | 218 | p |
| 第八位 | 158 | 4 |
所以 Flag 为 0z1@cjp4。
总结

这是我第一次参加 CTF,感觉过程很暴力,不知道是否有更好的方法。