2026獬豸杯wp(全解)
检材密码:EVJbYf&+eStnx5B+C^bj%YPSr)gr
手机取证
1.请分析检材1:手机的序列号是什么?【答案格式:AB65CDEFG6HIJKLM,字母全部大写】
AM69VKKVU4CMGELB
火眼中没有看到有关信息

那就只能翻文件了,这里其实可以全局搜索serialno或者Serial Number

定位一下文件,这是手机Linux内核的上一次启动日志的备份文件,导出搜索一下

2.请分析检材1:该手机的具体型号是什么?【答案格式:小米17至尊版】
一加Ace 5 至尊版
在基本信息这里可以看到 一加 Ace

那就继续全局搜索一下一加 Ace,可以看到与答案格式相同的答案

同时在手机adb/modules/AIHZ_GJX目录下也可以看到两个文件,说明手机型号是伪装的

3.请分析检材1:手机中已删除的联系人手机号码是什么?【答案格式:12345678910】
13696966666
查看通讯录即可

4.请分析检材1:手机中找到密码本文件,计算其MD5哈希值,取后6位,字母大写。【答案格式:EF3898】
AD3563
在文件中直接搜索密码

找到密码本,计算哈希即可

5.请分析检材1:嫌疑人下载的图片隐写工具名称是什么?【答案格式:outguess.zip,英文小写】
download steghide-0.5.1-win32.zip
在/data/com.tugoubutu.liulanqi/databases找到浏览器的历史下载记录

还有一种方式就是比赛的时候猜工具名,搜steg,可以看到

6.请分析检材1:嫌疑人使用隐写工具进行文件隐写密码是什么?【答案格式:ABCD@202002】
JHTJ@202605
在图片中看到了一张名为important的图片
想起来有个密码本,然后用上题的工具爆破一下密码,一个个试太慢了,写脚本爆破

import subprocess
import os
steghide = r"d:\OneDrive\Desktop\2026獬豸杯\steghide-0.5.1-win32\steghide\steghide.exe"
image = r"d:\OneDrive\Desktop\2026獬豸杯\steghide-0.5.1-win32\important.jpg"
wordlist = r"d:\OneDrive\Desktop\2026獬豸杯\常用密码.txt"
output_dir = r"d:\OneDrive\Desktop\2026獬豸杯\steghide_output"
output_file = os.path.join(output_dir, "extracted.txt")
os.makedirs(output_dir, exist_ok=True)
with open(wordlist, "r", encoding="utf-8") as f:
passwords = [line.strip() for line in f if line.strip()]
total = len(passwords)
for i, passwd in enumerate(passwords):
print(f"\r{i+1}/{total}", end="", flush=True)
try:
result = subprocess.run(
[steghide, "extract", "-sf", image, "-p", passwd, "-f", "-xf", output_file],
capture_output=True,
timeout=10,
)
if result.returncode == 0:
print(f"\n{passwd}")
break
except subprocess.TimeoutExpired:
continue
else:
print("\nFailed.")
7.请分析检材1:嫌疑人向数据贩子购买公民个人信息共计多少条?【答案格式:123】
100
在翻看手机软件的过程中,看到了Telegram、 i聊、连趣等聊天软件
查看一下软件的聊天数据库,tg是空的,在连趣data/com.lianqujiaoyou.chat/app_flutter/文件夹下看到了聊天数据库文件

8.请分析检材1:嫌疑人向数据贩子交易时使用的付款方式是什么?【答案格式:支付宝】
银行卡
由上题聊天对话可知,后面用i聊谈钱

那就看看i聊的聊天数据,在data/uni.app.UNI04963C4/files/1600123285_3137373036323233393731/msg_0.db看到聊天记录

9.请分析检材1:数据贩子交易过程中提供的银行卡号是多少?【答案格式:6222351234482925678】
6222355973482923738
由上题可知
10.请分析检材1:接上题,嫌疑人所使用聊天工具的用户 ID 是多少?【答案格式:12345】
17706223971
在content目录下可以看到一串数字

在相同文件夹下的im.db文件夹下可以印证数字就是用户id

在连趣中,也可以看到嫌疑人发送了一个添加好友的二维码

扫描二维码

结果如下

11.请分析检材1:嫌疑人使用浏览器访问了该下载链接,该浏览器对应的包名是什么?【答案格式:com.heytap.market】
com.meiit.browser
直接搜索浏览器,看见三个浏览器

土狗浏览器是第五题的,只有一个steghide,鲁班浏览器中看到百度网盘链接

12.请分析检材1:嫌疑人于何时下载数据商人发送的数据压缩包文件?【答案格式:2022-02-02 12:00:00】
2026-05-12 17:00:05
查看百度网盘的数据库文件data/com.baidu.netdisk/databases/7045389826filelist.db

找到数据的压缩包,查看时间戳为1778576405873

13.请分析检材1:接上题,该压缩包的解压密码是什么?【答案格式:根据实际值填写】
17706223971
聊天记录中可以知道压缩包密码为手机号,然后用户id又是手机号格式,尝试一下

成功解压

14.请分析检材1:接上题,年龄在 40 至 60 岁(含 40、60 岁)的富豪共有多少人?【答案格式:123】
46
对年龄排序,选中即可

15.综合分析:请找出嫌疑人筛选的重点客户名单,并统计名单内共有多少人?【答案格式:1】
5
对第五题的引写图片提取出来的payload.out

010打开是一个被截断的 ZIP

虽然外层 ZIP 数据流尾部截断,但关键的<font style="color:rgb(51,51,51);">xl/worksheets/sheet1.xml</font>与<font style="color:rgb(51,51,51);">xl/sharedStrings.xml</font>可恢复

import struct, zlib, re
data = open(r'd:\OneDrive\Desktop\2026獬豸杯\steghide-0.5.1-win32\steghide\payload.out', 'rb').read()
idx = data.find(b'PK\x03\x04')
inner = data[idx:]
# 解压外层的 deflate
nlen = struct.unpack_from('<H', inner, 26)[0]
elen = struct.unpack_from('<H', inner, 28)[0]
d = zlib.decompressobj(-15)
xlsx_data = d.decompress(inner[30+nlen+elen:], max_length=50000)
# 遍历内层 ZIP,提取 sharedStrings 和 sheet1
strings = []
sheet_xml = ''
off = 0
while off < len(xlsx_data) - 4:
if xlsx_data[off:off+4] != b'PK\x03\x04':
off += 1
continue
nlen = struct.unpack_from('<H', xlsx_data, off+26)[0]
elen = struct.unpack_from('<H', xlsx_data, off+28)[0]
name = xlsx_data[off+30:off+30+nlen].decode('utf-8')
csize = struct.unpack_from('<I', xlsx_data, off+18)[0]
method = struct.unpack_from('<H', xlsx_data, off+8)[0]
dstart = off + 30 + nlen + elen
if 'sharedStrings' in name:
raw = xlsx_data[dstart:dstart+csize]
xml = (zlib.decompress(raw, -15) if method == 8 else raw).decode('utf-8')
strings = re.findall(r'<t[^>]*>([^<]+)</t>', xml)
elif 'sheet1' in name:
raw = xlsx_data[dstart:dstart+csize]
sheet_xml = (zlib.decompress(raw, -15) if method == 8 else raw).decode('utf-8')
off = dstart + csize
# 解析 sheet1 的行数据
rows = re.findall(r'<row[^>]*>(.*?)</row>', sheet_xml, re.DOTALL)
header = ['姓名', '身份证号码', '银行卡(借记卡)', '手机号码', '出生日期', '年龄', '出生地']
print('=' * 120)
print(f"{header[0]:<10} {header[1]:<18} {header[2]:<19} {header[3]:<12} {header[4]:<12} {header[5]:<4} {header[6]}")
print('=' * 120)
for row in rows[1:]:
vals = re.findall(r'<v>([^<]+)</v>', row)
row_data = []
for v in vals:
if v.isdigit() and int(v) < len(strings):
row_data.append(strings[int(v)])
else:
row_data.append(v)
print(f"{row_data[0]:<10} {row_data[1]:<18} {row_data[2]:<19} {row_data[3]:<12} {row_data[4]:<12} {row_data[5]:<4} {row_data[6]}")
print('=' * 120)
计算机取证
1.请分析检材2:密码连续错误输入多少次数后,系统会自动锁定用户账户?【答案格式:1】
3
直接win+R输入cmd,然后输入net accounts即可

或者是win+R输入gpedit.msc,查看账户策略

2.请分析检材2:检材中对应的微信 wxid 是多少?【答案格式:wxid_1a2b3c4d5e6f】
wxid_q1w2e3r4t5y6u7i8o9
在文档中看到了微信数据文件

3.请分析检材2:E盘 BitLocker 恢复密钥末尾六位是多少?【答案格式:616912】
126269
在微信数据目录下看到两张带二维码的图片

扫描一下二维码


用第一张图片的密钥成功解开
4.请分析检材2:VC加密容器的外层加密卷密码是什么?【答案格式:根据实际值填写】
JHTJ!@#¥A313
在D盘可以看到一个名称为1的1G文件,大概率就是VC容器了

同时该目录下有个1.png的图片,用记事本打开可以看到外层密码

5.请分析检材2:带有“豆包AI生成”水印的图片一共有多少张?【答案格式:1】
6
在图片这里看到了四张

然后bitlocker密钥那里刚刚也有两张
6.请分析检材2:VC加密容器的隐藏加密卷密码是什么?【答案格式:根据实际值填写】
ClearSky@SecretSignal#SevenMileJasmine
对上述豆包生成的四张图片进行分析,发现3.png中存在一张二维码

得到隐藏加密卷密码ClearSky@SecretSignal#SevenMileJasmine

7.请分析检材2:接上题,嫌疑人的接头暗号是什么?【答案格式:根据实际值填写】
步行九千米
用隐藏卷密码挂载VC容器发现密码错误

看了joy的wp后发现,原来还需要桌面上的密钥文件。。。

挂载后看到几个音频文件,应该是音频引写

用Audacity打开Secret Signal看一下频谱图

8.请分析检材2:接上题,嫌疑人的接头地点在哪里?【答案格式:天津117大厦201室】
Taipei 101 building 502 room
对这四张二进制图片文件进行分析

import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
import easyocr, cv2, numpy as np
from itertools import permutations
XOR_KEY = 0xDA
IMAGES = ['5.png', '6.png', '7.png', '8.png']
def extract(img_path, reader):
img = cv2.imread(img_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
# 多种预处理
sources = [('orig', img)]
for t in [127, 150, 180]:
_, b = cv2.threshold(gray, t, 255, cv2.THRESH_BINARY)
sources.append((f't{t}', b))
sources.append(('inv', 255 - img))
# 提取所有8位二进制字符串
raw = []
for name, src in sources:
for bbox, text, conf in reader.readtext(src):
text = text.strip()
if all(c in '01' for c in text) and len(text) == 8:
pts = np.array(bbox, dtype=np.float64)
cy = pts[:, 1].mean()
raw.append((cy, text, conf))
# 按Y排序
raw.sort(key=lambda x: x[0])
# 聚类:Y坐标差<30的归为一组
groups = []
for cy, text, conf in raw:
placed = False
for g in groups:
if abs(cy - g[0][0]) < 30:
g.append((cy, text, conf))
placed = True
break
if not placed:
groups.append([(cy, text, conf)])
# 每组取置信度最高的文本
result = []
for g in groups:
best = max(g, key=lambda x: x[2])
result.append(best[1])
return result
def decode(bins):
return ''.join(chr(int(b,2)^XOR_KEY) if 32<=int(b,2)^XOR_KEY<127 else '?' for b in bins)
def main():
print("初始化OCR...")
reader = easyocr.Reader(['en'], gpu=False)
all_bins = []
for f in IMAGES:
bins = extract(f, reader)
print(f"{f}: {bins}")
all_bins.extend(bins)
# 暴力排列前7个,找最可读的结果
first7, rest = all_bins[:7], all_bins[7:]
best, best_msg = all_bins, decode(all_bins)
best_score = sum(c.isalpha() or c==' ' or c.isdigit() for c in best_msg)
for p in permutations(first7):
msg = decode(list(p) + rest)
score = sum(c.isalpha() or c==' ' or c.isdigit() for c in msg)
if score > best_score:
best, best_msg, best_score = list(p)+rest, msg, score
print(f"\n解码结果: {best_msg}")
if __name__ == '__main__':
main()

9.请分析检材2:木马残留样本中,核心信息窃取配置数量为多少?【答案格式:1】
5
在E盘看到一个.c文件

导出分析代码,识别出以下5类信息窃取功能
| 序号 | 窃取类型 | 配置标识 | 代码位置 |
|---|---|---|---|
| 1 | 键盘记录器 | g_KEYLOG_PATH ("keylog.dat") |
第43行 |
| 2 | 浏览器凭据 | g_BROWSER_DB ("Login Data") |
第58行 |
| 3 | Cookie窃取 | g_COOKIE_DB ("Cookies") |
第59行 |
| 4 | 屏幕截图 | g_SCREENSHOT_DIR |
第49行 |
| 5 | 进程注入窃取 | SimulateProcessInjection() |
第171-238行 |
10.请分析检材2:木马残留样本中,申请的内存保护标志是什么?【答案格式:PAGE_READWRITE (0x04)】
PAGE_EXECUTE_READWRITE (0x40)
主要的木马核心功能使用的是 PAGE_EXECUTE_READWRITE (0x40)


11.请分析检材2:嫌疑人涉案交易使用的银行卡号是什么?【答案格式:纯数字】
6221882234367490125
在文档下可以看到一个加密的表格和密码

根据密码提示爆破一下

先看看当天日期

开始爆破

得到密码JUHE@20260512

打开文件

12.请分析检材2:AI 换脸图片大概率使用的 AI 模型是哪一个?【答案格式:根据实际值填写】
steadydancer
找到ai换脸图片

使用https://hivedetect.ai/hjuEXY进行检测

这里建议不要仿真导出,答案会有所不同
13.请分析检材2:萤小石的身份证号码是什么?【答案格式:纯数字】
330.......527
在D盘看到有两个关于身份证的图片

大概率都是直接看这个压缩包的,因为压缩包加密了,导出放随波逐流修复

然后可以看到

并没有身份证号,将图片拖到010,查看文件末尾可以看到

14.请分析检材2:小众通联工具绑定的手机号码为多少?【答案格式:纯数字】
18136091921
E盘下有一个雷电手机模拟器的备份文件

还原一下

启动之后看到里面有个i聊

放文件管理器并没有找到有关文件(可能是没root?)
那就用解压工具打开备份文件,挂载data磁盘

挂载之后跟手机检材一样,直接翻文件即可

15.请分析检材2:小众通联工具添加好友的具体时间是什么?【答案格式:2025-01-01 01:01:01】
2026-05-13 16:12:37
依旧在上题的数据库文件中

转换时间戳

16.请分析检材2:挖矿程序(请勿在本地运行)的版本是什么?【答案格式:1.00.1】
6.26.0
在用户痕迹里可以看到有个可疑程序

追踪一下这个程序

查看一下配置文件,看到了XMR矿池地址

说明是挖矿软件,查看程序属性

17.请分析检材2:门罗币钱包地址后6位是什么?【答案格式:根据实际值填写】
6soTWp
由上题可知
服务器取证
首先感谢范叔的wp,提供了好多比赛没解出来的题目🙏
服务主要的坑就在于那个sql备份文件在计算机上,其他的其实都还好
仿真之后直接配好的ip
那就直接ai一把嗦了 那就可以直接用finalshell连上服务器

1.请分析检材3:服务器的内核版本号是多少?【答案格式:4.25.0】
3.10.0
查看系统信息

或者使用命令uname -r

2.请分析检材3:嫌疑人将某普通用户加入特殊用户组,赋予其读取 /etc/shadow 的权限,请问该普通用户的用户名是什么?【答案格式:admin】
ahao
首先检查 <font style="color:rgb(0, 0, 0);">/etc/shadow</font> 文件的权限和关联用户组
ls -la /etc/shadow
cat /etc/group | grep shadow

shadow组中没有普通用户,说明权限可能是通过ACL赋予的。使用 getfacl检查
getfacl /etc/shadow
返回 user:ahao:r--,确认 ahao (uid=1000) 被额外授予读取权限
3. 请分析检材3:分析嫌疑人所使用的 Web 服务器,其具体名称及主版本号是 【答案格式:apache/2.40】
apache/1.20.1
通过 nginx -v 获取Nginx版本信息,Web服务器为 nginx,主版本号为 1.20.1

4. 请分析检材3:嫌疑人借助 AI 部署 “守卫” 脚本,非管理员 IP 登录时将触发定时强制登出,该服务每隔几分钟触发一次?【答案格式:10】
5
检查一下系统服务cat /etc/systemd/system/net-monitor.service

找到守卫脚本/usr/local/bin/network-check.py,打开看看

5. 请分析检材3:计算服务器内数据库备份文件,计算其SHA256哈希值,取后6位,字母大写。【答案格式:6DEF3898】
FF8180
先在/tmp/db_backup目录下看到了备份文件

但是后面发现这几个压缩包全为空,然后在opt目录下看到一个faka.sql文件

但是在后面做题过程中发现,这个数据库也缺失信息,最后在计算机中找到🙂

这里有点问题,不能直接右键计算哈希,导出文件也会失败(说是特定镜像触发火眼解密失败,这个问题已经修好啦)

仿真一下,然后cope到本地,计算哈希

6. 请分析检材3:MySQL 数据库 root 用户的登录密码是什么?【答案格式:根据实际值填写】
Root@123456
在数据库备份脚本中发现MySQL密码cat /etc/cron.daily/autobackup.sh

或者是在网站配置文件/usr/share/nginx/html/fk/config.php找到

7. 请分析检材3:外挂网站所对外开放的端口号是多少?【答案格式:1234】
8081
检查Nginx配置文件cat /etc/nginx/nginx.conf,看到网站对外开放的端口为 8081

8. 请分析检材3:嫌疑人为上传大型恶意插件修改文件上传限制,其最大上传限制是多少?【答案格式:100M】
60M
从php.ini和nginx.conf确认上传限制
cat /etc/php.ini | grep upload_max_filesize

9. 请分析检材3:嫌疑人设置数据库定时自动备份并上传至境外,请问该境外服务器的IP地址是多少?【答案格式:8.8.8.8]】
8.208.44.202
从第六题的数据库定时备份脚本中就可以看到配置境外服务器的ip

10. 请分析检材3:嫌疑人登录后台的目录路径是什么?【答案格式:/abc/abc】
/fk/static/
查看目录发现服务器上有两个后台入口 /fk/admin/ 和 /fk/static/
find /usr/share/nginx/html/fk/ -name "login.php"


通过 nginx 日志分析发现 /fk/static/login.php被访问33次,远高于 /fk/admin/login.php的7次
zcat -f /var/log/nginx/access.log* | awk '$7 ~ /\/fk\/(static|admin)\/login/ {print $7}' | sort | uniq -c | sort -rn

在/fk/static/login.php有独立密码盐 PWD=2025baofu!,证明这是真实后台

11.请分析检材3:访问后台登录页面次数最多的 IP 地址是什么?【答案格式:192.168.1.1】
192.168.203.135
从 nginx access.log 统计访问 /fk/(static|admin)/login.php 的IP地址
zcat -f /var/log/nginx/access.log* | awk '$7 ~ /\/fk\/(static|admin)\/login\.php/ {cnt[$1]++} END {for (ip in cnt) print cnt[ip], ip}' | sort -rn | head -5

12.请分析检材3:网站后台管理员的明文密码为多少?【答案格式:根据实际值填写】
abc123456
得到数据库备份文件之后,可以直接运行一下sql文件,在shua_config表中看到密码

由11可知加密算法为 sha256(明文密码 + 2025baofu!),解密一下密码

得到密码

13. 请分析检材3:该网站支付接口所对接的第三方接入商名称是什么?【答案格式:根据实际值填写】
无忧支付
这道题有个坑点,数据库中的payapi为13,其他的所有支付渠道均为2,走彩虹易支付接口通道
在数据这里看到支付接口为13

查看set.php
发现有php混淆,用脚本进行解密
"""
白猿网络PHP加密解密脚本 - 完整版
从原始set.php一次性解密,输出可读的set_decoded.php
"""
import re
SRC = r'd:\OneDrive\Desktop\2026獬豸杯\文件文档_20260616_142753\火眼-文件导出\set.php'
DST = r'd:\OneDrive\Desktop\2026獬豸杯\文件文档_20260616_142753\火眼-文件导出\set_decoded.php'
with open(SRC, 'r', encoding='utf-8', errors='replace') as f:
content = f.read()
print(f"Original size: {len(content)}")
# ============================================================
# 1. 提取3个GLOBALS数组的原始hex数据
# ============================================================
# GLOBALS[B000B0000] - 分隔符 |(|;|+
arr1_raw = "H*|(|;|+4242424242304230".split("|(|;|+")
# GLOBALS[B0000BBB0] - 分隔符 |}|@|h
m2 = re.search(r'\$GLOBALS\[B0000BBB0\]=explode\("\|\}\|@\|h",\s*"([^"]+)"\)', content)
arr2_raw = m2.group(1).split("|}|@|h")
# GLOBALS[B0000BB00] - 分隔符 |1|Q|d (通过call_user_func_array定义)
m3 = re.search(r'call_user_func_array\("explode",array\("\|1\|Q\|d","([^"]+)"\)\)', content)
arr3_raw = m3.group(1).split("|1|Q|d")
# 解码hex为可读字符串
def decode_hex_arr(raw_arr):
result = []
for x in raw_arr:
if x == 'H*':
result.append('H*')
else:
try:
result.append(bytes.fromhex(x).decode('utf-8', errors='replace'))
except:
result.append(x)
return result
arr1 = decode_hex_arr(arr1_raw)
arr2 = decode_hex_arr(arr2_raw)
arr3 = decode_hex_arr(arr3_raw)
arrays = {
'B000B0000': arr1,
'B0000BBB0': arr2,
'B0000BB00': arr3,
}
print(f"GLOBALS arrays: B000B0000({len(arr1)}), B0000BBB0({len(arr2)}), B0000BB00({len(arr3)})")
# ============================================================
# 2. 解析PHP索引(十进制、八进制、十六进制)
# ============================================================
def parse_index(s):
s = s.strip()
if s.startswith('0x') or s.startswith('0X'):
return int(s, 16)
elif s.startswith('0') and len(s) > 1 and all(c in '01234567' for c in s):
return int(s, 8)
else:
return int(s)
# ============================================================
# 3. 第一步:替换 pack($GLOBALS[K1][0], $GLOBALS[K2][N])
# 这是最关键的替换,必须在GLOBALS替换之前做
# pack("H*", hex_string) = 解码后的字符串
# ============================================================
result = content
pack_direct = r'pack\(\$GLOBALS\[([^\]]+)\]\[([^\]]+)\],\s*&?\$GLOBALS\[([^\]]+)\]\[([^\]]+)\]\)'
def replace_pack_direct(m):
key1, idx1_s, key2, idx2_s = m.group(1), m.group(2), m.group(3), m.group(4)
try:
idx2 = parse_index(idx2_s)
except:
return m.group(0)
arr = arrays.get(key2)
if arr and idx2 < len(arr):
val = arr[idx2]
esc = val.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t")
return "'" + esc + "'"
return m.group(0)
result = re.sub(pack_direct, replace_pack_direct, result)
print(f"After pack direct: {content.count('pack(') - result.count('pack(')} replaced")
# ============================================================
# 4. 第二步:替换所有 $GLOBALS[KEY][IDX]
# ============================================================
globals_idx = r'\$GLOBALS\[([^\]]+)\]\[([^\]]+)\]'
def replace_globals_idx(m):
key, idx_s = m.group(1), m.group(2)
try:
idx = parse_index(idx_s)
except:
return m.group(0)
arr = arrays.get(key)
if arr and idx < len(arr):
val = arr[idx]
esc = val.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t")
return "'" + esc + "'"
return m.group(0)
result = re.sub(globals_idx, replace_globals_idx, result)
print(f"After GLOBALS idx: {content.count('$GLOBALS[') - result.count('$GLOBALS[')} replaced")
# ============================================================
# 5. 第三步:替换 pack('H*', 'hexstring') - 已解码的pack调用
# ============================================================
pack_hex = r"pack\('H\*',\s*'([0-9a-fA-F]+)'\)"
def replace_pack_hex(m):
hex_str = m.group(1)
try:
decoded = bytes.fromhex(hex_str).decode('utf-8', errors='replace')
esc = decoded.replace("\\", "\\\\").replace("'", "\\'").replace("\r", "\\r").replace("\n", "\\n").replace("\t", "\\t")
return "'" + esc + "'"
except:
return m.group(0)
result = re.sub(pack_hex, replace_pack_hex, result)
# ============================================================
# 6. 第四步:替换通过中间变量的pack调用
# 模式: $U4jcV1='H*'; $U4jcV2='decoded_value'; ... pack($U4jcV1,$U4jcV2)
# 因为第二个参数已经是解码后的字符串,pack('H*', decoded)是错的
# 正确做法:直接用第二个参数的值
# ============================================================
result = re.sub(r'pack\(\$U4jc\w+,\s*(\$U4jc\w+)\)', r'\1', result)
# 也处理 pack($U4jcV1, $U4jcV2) 其中V1不是H*的情况(保留)
# 但实际上所有pack第一个参数都是H*,所以上面的替换是安全的
print(f"After all pack: {result.count('pack(')} remaining")
# ============================================================
# 7. 第五步:替换 call_user_func_array
# ============================================================
def replace_cufa(m):
func = m.group(1)
args = m.group(2)
args = re.sub(r'&(\$\w)', r'\1', args)
return func + '(' + args + ')'
for _ in range(10):
prev = result
result = re.sub(r'call_user_func_array\("([^"]+)",\s*array\(([^)]+)\)\)', replace_cufa, result)
if result == prev:
break
print(f"After CUFA: {result.count('call_user_func_array')} remaining")
# ============================================================
# 8. 第六步:简化 is_array/goto 模式
# 模式: if(is_array(X))goto A;goto B;A:$var=&val;goto C;B:$var=val;C:
# 简化为: $var=val;
# ============================================================
for _ in range(10):
prev = result
# 匹配: if(is_array(...))goto L1;goto L2;L1:statement1;goto L3;L2:statement2;L3:
result = re.sub(
r'if\(is_array\([^)]+\)\)goto\s+(\w+);goto\s+(\w+);\1:([^;]*);goto\s+(\w+);\2:([^;]*);(\w+):',
r'\5;\6:',
result
)
if result == prev:
break
print(f"After is_array: {result.count('if(is_array(')} remaining")
# ============================================================
# 9. 第七步:删除初始化块(explode数据定义)
# ============================================================
init_marker = "U4jx3:"
init_pos = result.find(init_marker)
if init_pos > 0:
init_end = result.find(';', init_pos) + 1
header_end = result.find('*/') + 3
header = result[:header_end]
result = header + '\n' + result[init_end:]
print("Removed init block")
# ============================================================
# 10. 第八步:清理define和ord
# ============================================================
result = re.sub(r'if\(!defined\([^)]+\)\)define\([^;]+;', '', result)
result = re.sub(r'ord\((\d+)\)', r'\1', result)
# ============================================================
# 11. 第九步:替换 $GLOBALS[常量名] 为可读变量名
# 追踪变量别名链
# ============================================================
# 找 $CONSTNAME=&$U4jVar 和 $U4jtI9Z=&$realvar 的映射
alias_to_temp = {}
for m in re.finditer(r'\$([A-Z0-9]{8})=&?\$(\w+);', result):
alias_to_temp[m.group(1)] = m.group(2)
temp_to_real = {}
for m in re.finditer(r'\$(U4jtI\w+)=&?\$(\w+);', result):
temp_to_real[m.group(1)] = m.group(2)
# 解链
alias_to_real = {}
for alias, temp in alias_to_temp.items():
visited = set()
current = temp
while current in temp_to_real and current not in visited:
visited.add(current)
current = temp_to_real[current]
alias_to_real[alias] = current
# 替换 $GLOBALS[CONSTNAME]
def replace_globals_const(m):
key = m.group(1)
if key in alias_to_real:
return '$' + alias_to_real[key]
return m.group(0)
result = re.sub(r'\$GLOBALS\[([A-Z0-9]{8})\]', replace_globals_const, result)
# ============================================================
# 12. 第十步:替换混淆变量名为原始变量名
# ============================================================
var_map = {
'BB00B0BB': 'islogin', 'BB00BB00': 'title', 'BB00BB0B': 'string',
'BB00BBB0': 'query', 'BB00BBBB': 'mod', 'BB0B0000': 'user',
'BB0B00B0': 'oldpwd', 'BB0B00BB': 'newpwd2', 'BB0B0B00': 'ad',
'BB0B0BB0': 'config_file', 'BB0B0BBB': 'addOptions', 'BB0BB000': 'key',
'BB0BB00B': 'k', 'BB0BB0B0': 'server_software', 'BB0BB0BB': 'software',
'BB0BBB00': 'captcha_open', 'BB0BBB0B': 'captcha_id',
'BB0BBBB0': 'captcha_key', 'BB0BBBBB': 'captcha_open_free',
'BBB00000': 'captcha_open_reg', 'BBB0000B': 'captcha_open_login',
'BBB000B0': 'defendid', 'BBB000BB': 'file', 'BBB00BB0': 'is_proxy',
'BBB00BBB': 'mail_name', 'BBB0B000': 'result', 'BBB0B00B': 'faka_mail',
'BBB0B0B0': 'url', 'BBB0B0BB': 'content', 'BBB0BB00': 'url_arr',
'BBB0BB0B': 'data', 'BBB0BBB0': 'arr', 'BBB0BBBB': 'payadd',
'BBBB0000': 'epay_select', 'BBBB000B': 'payapi', 'BBBB00B0': 'account',
'BBBB00BB': 'username', 'BBBB0B00': 'stype', 'BBBB0B0B': 'filename',
'BBBB0BB0': 'contents', 'BBBB0BBB': 'rest',
'BB0B000B': 'newpwd',
}
for old, new in var_map.items():
result = re.sub(r'\$' + old + r'\b', '$' + new, result)
# ============================================================
# 13. 第十一步:清理中间变量赋值
# ============================================================
# 删除 unset($U4jtI9Z); 链
result = re.sub(r'unset\(\$U4jtI\w+\);', '', result)
result = re.sub(r'unset\(\$U4jc\w+\);', '', result)
# 删除 $U4jcV1='H*'; (pack的第一个参数,不再需要)
result = re.sub(r"\$U4jc\w+='H\*';", '', result)
# 删除 $U4jeFvar='...'; (无效的中间赋值)
result = re.sub(r"\$U4jeFvar='[^']*';", '', result)
# 简化 $U4jcV2='value';$U4jeF0=$U4jcV2; => $U4jeF0='value';
def simplify_var_copy(m):
var1 = m.group(1)
val = m.group(2)
var2 = m.group(3)
return f'${var2}={val};'
result = re.sub(r"\$U4jc(\w+)='([^']*)';\$U4jeF(\w+)=\$U4jc\1;", simplify_var_copy, result)
# 删除 $U4jtI9Z=&$realvar;$realvar=&$U4jtI9Z; (无操作)
result = re.sub(r'\$U4jtI9Z=&?\$\w+;\$\w+=&?\$U4jtI9Z;', '', result)
# ============================================================
# 14. 第十二步:计算算术常量
# ============================================================
E_USER_NOTICE = 1024
# 多轮扫描,逐步替换变量值
for round_num in range(5):
var_values = {}
new_parts = []
# 按分号分割处理
segments = result.split(';')
for seg in segments:
seg = seg.strip()
if not seg:
new_parts.append(seg)
continue
m = re.match(r'\$(U4j[\w]+)\s*=\s*(.+)', seg)
if m:
vname = m.group(1)
expr = m.group(2).strip()
# 替换已知变量
for vn, vv in var_values.items():
expr = expr.replace('$' + vn, str(vv))
# 替换E_USER_NOTICE
expr = expr.replace('E_USER_NOTICE', str(E_USER_NOTICE))
# 尝试计算纯算术表达式
if re.match(r'^[\d\s\+\-\*/\(\)%]+$', expr):
try:
val = eval(expr)
var_values[vname] = val
seg = f'${vname}={val}'
except:
pass
new_parts.append(seg)
result = ';'.join(new_parts)
# ============================================================
# 15. 第十三步:删除纯算术中间变量(只被赋值一次的$U4j变量)
# ============================================================
# 找出只出现一次的$U4j赋值并删除
for _ in range(3):
u4j_vars = re.findall(r'\$(U4j[vhcte]?P?\w+)', result)
var_counts = {}
for v in u4j_vars:
var_counts[v] = var_counts.get(v, 0) + 1
# 删除只出现一次的纯数字赋值
single_use = [v for v, c in var_counts.items() if c == 1]
for var in single_use:
result = re.sub(r'\$' + re.escape(var) + r'=-?\d+;', '', result)
# ============================================================
# 16. 格式化输出
# ============================================================
# 添加换行
result = result.replace(';', ';\n')
# 清理多余空行
result = re.sub(r'\n{3,}', '\n\n', result)
# 清理行首空白
lines = result.split('\n')
result = '\n'.join(line.rstrip() for line in lines)
# ============================================================
# 17. 保存
# ============================================================
with open(DST, 'w', encoding='utf-8') as f:
f.write(result)
print(f"\nDecoded size: {len(result)}")
print(f"Remaining $GLOBALS: {result.count('$GLOBALS[')}")
print(f"Remaining pack(): {result.count('pack(')}")
print(f"Remaining goto: {result.count('goto ')}")
搜索一下13,看到无忧支付

14. 请分析检材3:网站的创建时间是什么时候?【答案格式:2026/5/20】
2018/12/14
依旧是在shua_config表中看到时间

15. 请分析检材3:网站Git配置信息中,找出作者姓名是什么?【答案格式:根据实际值填写】
ZhangSan_Sec
backup_config.txt 中 GhostOperator 为嫌疑人篡改后的干扰项
最早是在备份文件中找的密码

base64解密

转字符串

然后复盘的时候发现是错的,正确如下:
从 bash_history 追踪嫌疑人操作链
原始 .git/config 备份藏在 /usr/share/icons/hicolor/48x48/apps/... /git.txt

将576d6868626d6454595735665532566a转化为base64

再进行两次base64解密


16. 请分析检材3:分析该网站 2025 年 6 月 1 日至 12 月 31 日的全部订单,其中状态为 "已完成" 的订单总成交金额为多少?【答案格式:1234】
24073
根据源码注释,status=1是已完成状态
SELECT SUM(money) AS total_amount
FROM shua_orders
WHERE status = 1
AND addtime >= '2025-06-01'
AND addtime <= '2025-12-31 23:59:59';

17. 请分析检材3:分析 2025 年全年的订单数据,计算下单金额总计最高的那一天的总金额是多少?【答案格式:1234】
5442
SELECT DATE(addtime) AS order_date, SUM(money) AS daily_total
FROM shua_orders
WHERE YEAR(addtime) = 2025
GROUP BY DATE(addtime)
ORDER BY daily_total DESC
LIMIT 1;

18. 请分析检材3:统计全部订单数据,购买数量累计最高的应用名称是什么?【答案格式:根据实际值填写】
无畏契约自动扳机+透视+准星辅助
SELECT t.name AS app_name, SUM(o.value) AS total_quantity
FROM shua_orders o
LEFT JOIN shua_tools t ON o.tid = t.tid
WHERE t.name IS NOT NULL
GROUP BY t.name
ORDER BY total_quantity DESC
LIMIT 1;

19. 请分析检材3:统计全部订单数据,用户累计购买总额最高的用户账号是哪个?【答案格式:admin】
guoqi
-- 第一步:查询订单表中消费最高的userid
SELECT userid, SUM(money) AS total_spent
FROM shua_orders
GROUP BY userid
ORDER BY total_spent DESC
LIMIT 1;
-- 第二步:关联shua_site表获取用户名
SELECT s.user
FROM shua_site s
WHERE s.zid = (
SELECT userid
FROM shua_orders
GROUP BY userid
ORDER BY SUM(money) DESC
LIMIT 1
);

20. 请分析检材3:嫌疑人创建的拥有 FILE 与 SUPER 权限的隐蔽 MySQL 账户,其用户名是什么?【答案格式:admin】
_tmp_z5m1w8
这道题得回到服务器里的数据库中找了,因为刚刚是本地mysql运行的sql备份文件


查询 mysql.user 表中所有 _tmp* 开头的账户
SELECT User, Host, Super_priv, File_priv
FROM mysql.user
WHERE User
LIKE '_tmp%';
仅_tmp_z5m1w8@localhost同时拥有Super_priv=Y和File_priv=Y

逆向分析
一眼python打包程序

使用pyinstxtractor解包成pyc

再用pylingual,把 Python 字节码(.pyc)反编译回 .py源码

编码如下
# Decompiled with PyLingual (https://pylingual.io)
# Internal filename: 'edu_ratsample.py'
# Bytecode version: 3.13.0rc3 (3571)
# Source timestamp: 1970-01-01 00:00:00 UTC (0)
# ***<module>: Failure: Different control flow
import base64
import socket
import sys
import time
C2_HOST = '192.0.2.1'
C2_PORT = 64123
BEACON_B64 = 'eyJ0IjoiYmVhY29uIiwidiI6IjEuMCJ9'
MUTEX_NAME = 'Global\\EduLab_RAT_Mutex_2026'
USER_AGENT = 'Mozilla/5.0 (EduLab; Win32) RAT-Stub/1.0'
def _xor(data: bytes, key: bytes) -> bytes:
return bytes((b ^ key[i % len(key)] for i, b in enumerate(data)))
def build_payload() -> bytes:
raw = base64.b64decode(BEACON_B64)
key = b'EDU'
return _xor(raw, key)
def try_connect() -> None:
# ***<module>.try_connect: Failure: Different control flow
s, payload = (build_payload(), socket.socket(socket.AF_INET, socket.SOCK_STREAM))
try:
pass
except OSError:
pass
finally:
s.close()
def main() -> int:
_ = MUTEX_NAME
try_connect()
return 0
sys.exit(main()) if __name__ == '__main__' else None
1. 样本中的 C2 地址与端口分别是多少?【答案格式:127.0.0.1:8080】
192.0.2.1:64123
一眼可以看到

2. 样本使用的传输层协议是什么?【答案格式:HTTP】(全大写)
TCP
代码里写的是 socket.socket(socket.AF_INET, socket.SOCK_STREAM):SOCK_STREAM在 BSD/POSIX/Windows Socket API 语义里就表示面向连接的 TCP
3. 样本外发的 User-Agent 完整字符串是什么?【按实际值填写】
Mozilla/5.0 (EduLab; Win32) RAT-Stub/1.0
由代码可知
4. Beacon 的 Base64 常量是什么?【按实际值填写】
eyJ0IjoiYmVhY29uIiwidiI6IjEuMCJ9
由代码可知
5. 样本中出现的 Mutex 名称是什么?【按实际值填写】
Global\EduLab_RAT_Mutex_2026
由代码可知
6. 是否具备注册表 Run、计划任务、服务安装或 UAC 绕过等行为?
否
从反编译得到的代码来看,只包含 C2 通信和 XOR 加密的 beacon 发送逻辑,没有任何注册表 Run 键、计划任务、服务安装或 UAC 绕过的实现
7. 该 exe 由何种方式打包?【答案格式:全大写】
PYINSTALLER
第一题就知道了
流量分析
流量分析全靠工具,全都可以一把嗦😂
这里每题都提供两种方法,lovelyspark和wireshark,其实都大差不差,就看怎么找题目需要的信息了
1. 内网用户向 intranet.corp.lab 提交登录表单。请给出 POST 正文里 password 字段的 解码后的提交值【按实际值填写】
LabPass#Q7
方法一:
用lovelyspark打开之后直接看到post
右键追踪流

对LabPass%23Q7进行url解码得到LabPass#Q7
方法二:
直接过滤http

追踪流即可

2. 客户端 10.10.10.50 向 DNS 10.10.10.53 发起一次查询。Queries 中的域名看似随机十六进制。请将该段十六进制视为 ASCII 的十六进制表示,还原出可读字符串(即解码后的域名语义)【按实际值填写】
exalthread-drop.intranet.lab
方法一:
直接可以看到dns

查询域名为6578616c7468726561642d64726f702e696e7472616e65742e6c61622e看似随机十六进制,实际是 ASCII 的 hex 编码,逐字节解码
| Hex | 65 | 78 | 61 | 6c | 74 | 68 | 72 | 65 | 61 | 64 | 2d | 64 | 72 | 6f | 70 | 2e | 69 | 6e | 74 | 72 | 61 | 6e | 65 | 74 | 2e | 6c | 61 | 62 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| ASCII | e | x | a | l | t | h | r | e | a | d | - | d | r | o | p | . | i | n | t | r | a | n | e | t | . | l | a | b |
方法二:
过滤dns,方法同上

3. 哪台客户端对哪台 FTP 服务器完成了认证?【答案格式:127.0.0.1-192.168.0.1】
10.10.10.50-10.10.10.101
方法一:
直接查看ftp
服务器返回 230 User bob logged in,确认认证成功
方法二:
过滤ftp

4. USER 与成功登录前的 PASS 明文分别是什么?【答案格式:USER-PASS 例:admin-123456】
bob-Ftp_S3cret_9z
由上题可知,FTP直接明文传输的账号密码
5. 存在来自外网段地址对 10.10.10.50 的 ICMP Echo 请求。请提取 ICMP 数据部分中隐藏的完整FLAG。【答案格式:ICMP_COVERT_FLAG{123_abc_def}】
ICMP_COVERT_FLAG{ping_payload_stego}
方法一:
lovelyspark一把嗦了

方法二:
过滤icmp

只有一个包,直接可以看到flag

6. 对 api.corp.lab 的 GET /api/me 请求返回 200。除 JSON 体外,响应中哪条 非标准 HTTP 头 泄露了高权限备份密钥?【答案格式:A-Admin-Admin】
X-Admin-Token
方法一:
找到/api/me

右键追踪流

方法二:
过滤http,找到/api/me,方法同上

7. 同一源 IP 对主机 10.10.10.200 在短时间内向多个不同目的端口发送了仅含 SYN 的 TCP 报文,请写出源 IP。【答案格式:192.168.0.1】
198.51.100.66
方法一:
直接搜索10.10.10.200,所有包均来自198.51.100.66

方法二:
过滤ip即可看到

8. 客户端通过 Telnet(端口 23) 连接 10.10.10.1。流中出现登录名与口令行。请给出登录名与完整口令FLAG(含题目中的FLAG格式)。【答案格式:用户名-FLAG】
Telnet_PASS_FLAG{legacy_cleartext}
方法一:
依旧一把嗦

方法二:
过滤Telnet协议
也是可以直接看到

内存取证
1. 该勒索进程的进程标识号(PID)是多少?【按实际值填写】
8804
使用 lovelymen对内存镜像进行进程列表分析

输出中发现可疑进程 ransom.exe,PID 为8804
后面经过分析看到这个文件就在桌面,对程序进行分析之后印证答案


2. 受害计算机的主机名(COMPUTERNAME)是什么? 【答案格式:ABC】
HACKER
直接查看系统信息就可以看到

3. 勒索软件将用户桌面下哪个子目录中的文件进行了批量加密?【仅写目录名,答案格式:abc1234_abc1234】
2026xzb_enc
将程序导出,那ida进行分析

可以看到一个文件目录,继续追踪函数分析

确实是对该目录进行的加密操作
4. 附件 secret_project4.docx.enc 采用勒索家族自定义封装格式。请对该文件进行十六进制分析,该加密文件开头的 5 字节魔数(Magic)是什么?【答案格式:ABCDe 大小写需完全一致】
RNSMa
对 secret_project4.docx.enc 进行十六进制分析:

前5字节52 4E 53 4D 61对应 ASCII 为 RNSMa,这是该勒索家族自定义封装格式的魔数标识
5. 真正的密文数据从文件起始偏移多少字节处开始?(从 0 计)【答案格式:数字】文件头部结构解析:
8
从代码可以看到加密文件的写入顺序

所以密文数据从文件起始偏移 4 + 4 = 8 字节处开始。
6. 请从内存镜像中提取本次加密所使用的对称密钥,该密钥的长度是多少字节?【答案格式:数字】
16
继续看代码

0x10u 是十六进制,转换为十进制是 16 字节
这是通过 Windows CryptoAPI 生成的随机密钥,用于 RC4 加密
7. 请写出密钥的完整十六进制表示【答案格式:无空格、无 0x】
cb8978dde5b08227aac1c40ba91849c5
由代码分析可得
前四字节为DE C0 AD DE,后四字节为BE BA FE CA,搜索一下即可
8. 请结合内存样本、导入表或静态逆向分析,对 .enc 文件中密文部分所采用的加密算法名称是什么?【答案格式:大写】
RC4
前面分析过了
9. 程序在启动加密前,通过哪个 Windows CryptoAPI 函数生成了随机密钥?(写出函数名)【答案格式:全小写】
cryptgenrandom

10.勒索程序在加密完成后会向用户展示勒索提示。请从内存镜像或提取的样本字符串中分析,攻击者要求的赎金金额是多少?【答案格式:99.9 BTC】
0.5 BTC
由字符串可知

11.攻击者留下的联系邮箱是什么?【按实际值填写】
n0t_r3al@pr0ton.me
由上题可知
12.内存镜像中还残留了受害者小张写在桌面上的一份文本文件内容。请根据镜像中的相关信息回答,该日记文件的完整文件名是什么?【按实际值填写】
摸鱼日记.txt
就在桌面上

13.日记中,小张在下午 2:52 联系上的那位安全同事,在电话里对他说了什么?【答案格式:引号内原文】
别关机,等我。
查看日记内容

14. 解密后文件的大小是多少字节?【答案格式:99999】
10081
从第四题可知,文件大小为61 27 00 00这四个字节,就是0x2761

15. 解密后的内部文档记载了一份供备份系统使用的认证令牌。请完整写出该 flag。【按实际值填写】
flag{m3m0ry_f0r3ns1cs_rc4_k3y_hunt2026}
写一个解密脚本
from pathlib import Path
from Crypto.Cipher import ARC4
BASE_DIR = Path(__file__).resolve().parent
ENC_PATH = BASE_DIR / "secret_project4.docx.enc"
OUT_PATH = BASE_DIR / "secret_project4.docx"
KEY = bytes.fromhex("cb8978dde5b08227aac1c40ba91849c5")
def main() -> None:
data = ENC_PATH.read_bytes()
plaintext = ARC4.new(KEY).decrypt(data[8:])
OUT_PATH.write_bytes(plaintext)
print(f"enc={ENC_PATH}")
print(f"out={OUT_PATH}")
print(f"magic={plaintext[:8]!r}")
print(f"size={len(plaintext)}")
if __name__ == "__main__":
main()
查看文件内容


浙公网安备 33010602011771号