wasm-login

wasm逆向,下载附件得到build(wasm产物),一个js和一个index.html,此外,产物内除了wasm还有其对应的.wasm.map文件,即Source Map

可以在index.html所在目录下用python -m http.server开一个服务,防止CORS不允许访问wasm

F12可以看到注释里写了 测试账号 admin 测试密码 admin
410c6e239a009f323a5ecaeb1a5a838b-2026-02-23-14-57-09
继续审计index内的源码,以及simulateServerRequest()函数源码,可以看到其调用了wasm中的authenticate函数,parse返回的JSON,然后把对象JSON.stringify后计算MD5,并用check.startsWith("ccaf33e3512e31f3")判断是否是正确的check
fd1c98b6b0ecc1527b8e75fc1b49391d-2026-02-23-14-58-08
审计wasm,只需将.map中的\n替换为扩展就能拿到源映射。分析代码,要用Date.now()作为签名secret,结合用户名和密码做HMAC-SHA256生成signature

我们知道了用户名和密码,要通过HMAC只需调用release.js已有的函数,爆破时间戳找出正确的md5值就可以

题目提示是2025年12月第三个周末到周一凌晨,可以把时间缩小到2025-12-21 21:00:00 至 2025-12-22 06:00:00 间,对应的时间戳就是[1766322000000,1766354400000]

编写solve.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
25
26
27
28
29
30
31
32
import { authenticate } from './build/release.js';
import crypto from 'crypto';

const TARGET_PREFIX = "ccaf33e3512e31f3";
const USERNAME = "admin";
const PASSWORD = "admin";

function solve() {
const startTs = 1766322000000;
const endTs = 1766354400000;
let count = 0;
let currentTimestamp = 0;

Date.now = () => currentTimestamp;

for (let ts = endTs; ts >= startTs; ts--) {
currentTimestamp = ts;
const authResult = authenticate(USERNAME, PASSWORD);
const check = crypto.createHash('md5').update(authResult).digest('hex');

if (check.startsWith(TARGET_PREFIX)) {
console.log("Timestamp:", ts);
console.log("Check:", check);
return;
}

count++;
if (count % 100000 === 0) console.log(`Checking ${count}. Current: ${ts}.`);
}
}

solve();

node运行得到flag:flag{ccaf33e3512e31f36228f0b97ccbc8f1}

The Silent Heist

题目说利用机器学习监控了交易的 20 个统计学维度,系统学习了正常用户行为模式(资金流向、设备指纹的协方差关系等),一旦提交的数据分布偏离了“正常模型”,就会警报。并给了一份包含 1000 条正常交易记录的日志public_ledger.csv

需要伪造一批新的交易记录,日志需要包含 20 个特征列 (feat_0 至 feat_19)

  • feat_0: 交易金额 (Transaction Amount)
  • feat_1 ~ feat_19: 加密的设备与行为指纹

要求是:

  • 伪造的交易总金额 (feat_0 总和) 必须超过 $2,000,000,交易全部被判定为“正常”。
  • 然后不能直接重放截取到的日志数据,禁止大量重复单一数据
  • 伪造完直接发送csv数据流即可,EOF结尾

题目中提及了协方差关系,查询资料可知,协方差是衡量两个变量之间线性关系的统计指标,正值表示正相关,负值表示负相关,而相关系数则是标准化后的协方差

对于本题20维的数据,我们需要一个模型来学习它们之间的关系,而最适合的方式是使用高斯混合模型学习(GMM)
98ea370d930fc133d0484f59c2fb9742-2026-02-23-15-08-22
GMM 假设数据是由几个高斯分布(正态分布)混合而成的,影响其中一类数据(一个feat)会影响到其它的feat

那在满足了特征检测后,我们还需要想办法训练一个模型,在本地check一条被伪造的交易记录是否正常

题目简介中同时说明了有一套基于 Isolation Forest (孤立森林) 的反欺诈系统,孤立森林是一种无监督学习算法,用来识别异常值。原理在于异常数据数量较少,且与正常数据差异较大,因此在被隔离时需要较少的步骤

借助sklearn库,我们可以用所给的 1000 条数据训练本地的 Isolation Forest模型,在生成数据后,先用本地模型测一下,如果本地模型认为是“正常”的(predict 为 1),那服务器大概率也会认为是正常的

同时金额不是正数的交易记录也要被过滤掉,并且还要进行去重,防止记录碰撞。伪造足够的记录直到超过2000000(最好大一点点,确保通过)
public_ledger.csv同目录下编写exp.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
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
from pwn import *
import pandas as pd
import numpy as np
from sklearn.mixture import GaussianMixture
from sklearn.ensemble import IsolationForest

HOST = "182.92.11.160"
PORT = 30754

context.log_level = "info" # "debug"

def main():
io = remote(HOST, PORT)

df = pd.read_csv("public_ledger.csv")
gmm = GaussianMixture(n_components=5, covariance_type="full", random_state=114514)
gmm.fit(df.values)

iso_forest = IsolationForest(contamination=0.05, random_state=114514)
iso_forest.fit(df.values)

target_sum = 2001000 # 略大一点点,确保通过检测
current_sum = 0

io.recvuntil(b"Waiting for CSV Data Stream (End with 'EOF')...")

cols = [f"feat_{i}" for i in range(20)]
header = ",".join(cols) + "\n"
io.send(header.encode())
buffer = ""
count = 0
seen_hashes = set()

for row in df.values:
line = ",".join([f"{x:.16f}" for x in row])
seen_hashes.add(hash(line))

BATCH_SIZE = 10000
while current_sum < target_sum:
samples, _ = gmm.sample(BATCH_SIZE)
preds = iso_forest.predict(samples)
valid_indices = (preds == 1) & (samples[:, 0] > 0)
valid_samples = samples[valid_indices]
for sample in valid_samples:
line = ",".join([f"{x:.16f}" for x in sample])
line_hash = hash(line)
if line_hash in seen_hashes:
continue
seen_hashes.add(line_hash)
line += "\n"
buffer += line
current_sum += sample[0]
count += 1
if len(buffer) > 8192:
io.send(buffer.encode())
buffer = ""
if count % 1000 == 0:
log.info(f"sent {count} records, sum: {current_sum:.2f}")
if current_sum > target_sum:
break
if current_sum >= target_sum:
break
if buffer:
io.send(buffer.encode())
io.sendline(b"EOF")
io.interactive()

if __name__ == "__main__":
main()

得到flag:flag{f255ace3-1b20-42fb-b4f0-9e46c6871614}

SnakeBackdoor-1

筛选所有登录admin的http请求

拉到最下面可以发现一个为302 FOUND跳转的响应,显然是登录成功的数据包

查看得到登录密码zxcvbnm123

flag值:flag{zxcvbnm12}

SnakeBackdoor-2

在攻击者登陆成功的http数据包后接着追踪,可以看到其尝试SSTI攻击{{7*7}}并成功回显的数据包

据此继续查找对/admin/preview的请求,可以发现执行{{config}}的数据包

Flask应用的 SECRET_KEY就在响应里,即:c6242af0-6891-4510-8432-e1cdf051f160

flag值:flag{c6242af0-6891-4510-8432-e1cdf051f160}

SnakeBackdoor-3

在上一题之后紧接着的数据包,可以发现一坨base64数据

解码后得到另一坨先zlib压缩再base64的逆序字符串

先逆序再拿去解压zlib,得到了类似结构的一坨先zlib压缩再base64的逆序字符串

循环往复。编写python脚本依次解开,得到攻击者使用RC4加密通讯的代码,其中得到RC4密钥:v1p3r_5tr1k3_k3y

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
import zlib
import base64
import re

def solve():
file_path = '1.txt'
with open(file_path, 'r') as f:
content = f.read().strip()
current_content = content
iteration = 0
while True:
iteration += 1
reversed_content = current_content[::-1]
padding = len(reversed_content) % 4
if padding:
reversed_content += '=' * (4 - padding)
compressed_data = base64.b64decode(reversed_content)
decompressed_data = zlib.decompress(compressed_data)
text_result = decompressed_data.decode('utf-8')
match = re.search(r"exec\(\(_\)\(b'([^']*)'\)\)", text_result)
if match:
current_content = match.group(1)
else:
print(text_result)
with open('2.txt', 'w', encoding='utf-8') as f:
f.write(text_result)
break

if __name__ == '__main__':
solve()

flag值:flag{v1p3r_5tr1k3_k3y}

SnakeBackdoor-4

使用第三题的密钥,继续追踪并解密数据包,可以得知攻击者上传、查看并解压了一个shell.zip,同时得到解压密码nf2jd092jd01

之后将恶意文件"shell"移动到/tmp,改名叫python3.13并赋权执行

由此可见木马进程执行的本体文件的名称为python3.13

flag值:flag{python3.13}

SnakeBackdoor-5

打开shell发现是Socket通信192.168.1.201:58782,边Patch边调试

接受4个字节用于生成SM4的Key,结合流量内34 95 20 46Patch一下得到Key

flag值:flag{ac46fb610b313b4f32fc642d8834b456}

SnakeBackdoor-6

由于时间问题没有做出来,赛后逆向分析可以看到使用了自定义S-Box的SM4,解密后命令是cat /flag,但互换了1和l、0和O,换回来就行