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的图片

想起来有个密码本,然后用上题的工具爆破一下密码,一个个试太慢了,写脚本爆破

python 复制代码
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>可恢复

python 复制代码
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

对这四张二进制图片文件进行分析

python 复制代码
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> 文件的权限和关联用户组

plain 复制代码
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混淆,用脚本进行解密

python 复制代码
"""
白猿网络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是已完成状态

sql 复制代码
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

sql 复制代码
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:统计全部订单数据,购买数量累计最高的应用名称是什么?【答案格式:根据实际值填写】

无畏契约自动扳机+透视+准星辅助

sql 复制代码
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

sql 复制代码
-- 第一步:查询订单表中消费最高的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* 开头的账户

sql 复制代码
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源码

编码如下

python 复制代码
# 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}

写一个解密脚本

python 复制代码
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()

查看文件内容