wasm-login
wasm逆向,下载附件得到build(wasm产物),一个js和一个index.html,此外,产物内除了wasm还有其对应的.wasm.map文件,即Source Map
可以在index.html所在目录下用python -m http.server开一个服务,防止CORS不允许访问wasm
F12可以看到注释里写了 测试账号 admin 测试密码 admin

继续审计index内的源码,以及simulateServerRequest()函数源码,可以看到其调用了wasm中的authenticate函数,parse返回的JSON,然后把对象JSON.stringify后计算MD5,并用check.startsWith("ccaf33e3512e31f3")判断是否是正确的check值

审计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)

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"
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,换回来就行