2026獬豸杯wp(全)

签到题

flag{Welcome_to_the_3rd_JuHeXieZhi_Cup}

一、手机取证

1. 请分析检材1:手机的序列号是什么?【答案格式:AB65CDEFG6HIJKLM,字母全部大写】

火眼的基本信息基本上啥都没有搜出来,只能去系统日志里边找了

在启动日志中检索 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">serialno</font>序列号

1
2
adb\lspd\log.old\kmsg.log
adb\ksu\log\dmesg.log

最后在kmsg.log找到了,即在内核消息日志找到了序列号(确实是没在这边翻过,更多是ai想到的

AM69VKKVU4CMGELB

2. 该手机的具体型号是什么?【答案格式:小米17至尊版】

设备信息里只为我们展示了蓝牙名称,所以我们必须要去查看系统配置属性才行

直接全局检索 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">ro.product.marketname</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">ro.product.model</font>这些可能记录手机型号的字段

全局搜索后可以在很多的属性文件及伪装机型模块中发现以上字段

所以手机型号是一加Ace 5 至尊版

3. 手机中已删除的联系人手机号码是什么?【答案格式:12345678910】

我们可以直接在火眼看被删除的联系人是谁

发现全都列出来了,就是老六,然后手机号是13696966666

或者也可以去联系人数据库里查

user\0\com.android.providers.contacts\databases\contacts2.db

直接SQL查询

1
2
3
4
5
select rc._id, rc.deleted, rc.display_name, m.mimetype, d.data1
from raw_contacts rc
left join data d on d.raw_contact_id=rc._id
left join mimetypes m on m._id=d.mimetype_id
where rc.deleted=1;

结果显示删除联系人老六,手机号为:

13696966666

印证了答案正确

4. 手机中找到密码本文件,计算其MD5哈希值,取后6位,字母大写。【答案格式:EF3898】

说是密码本,我们直接搜索密码看看有没有线索

发现直接就有个文件叫做常用密码,在media\0\常用密码.txt

一看发现还真是密码表,可以拿来字典爆破的那种

直接计算MD5即可

所以后六位是AD3563

5. 嫌疑人下载的图片隐写工具名称是什么?【答案格式:outguess.zip,英文小写】

说是下载的图片隐写工具了,我们去下载记录看看有没有什么线索

/media/0/Download

发现有个文件

叫steghide,明显的图片隐写工具啊

所以本题的答案就是这个文件的文件名:steghide-0.5.1-win32.zip

6. 嫌疑人使用隐写工具进行文件隐写密码是什么?【答案格式:ABCD@202002】

隐写工具解压看看

发现一个README.txt,打开发现介绍和原版的steghide一模一样

既然是steghide,还要密码,我们不难想到拿着之前那个常用密码.txt进行爆破

只差一个图片了,我们可以直接去火眼过滤的图片格式文件里边找,也可以去media/0/DCIM/Camera找

DCIM就是Digital Camera Images,这边放了很多数码相机拍的照片

我们在这边可以看见这样子一张图片

看上去就很重要啊,我们尝试爆破看看

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
import subprocess

img = "/mnt/data/important.jpg"
wordlist = "/mnt/data/常用密码.txt"
out_file = "result_output"

with open(wordlist, "r", encoding="utf-8", errors="ignore") as f:
for i, pwd in enumerate(f):
pwd = pwd.strip()
if not pwd:
continue

print(f"[{i}] testing: {pwd}")

r = subprocess.run(
["steghide", "extract",
"-sf", img,
"-p", pwd,
"-xf", out_file,
"-f"],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL
)

if r.returncode == 0:
print("\n[+] FOUND PASSWORD:", pwd)
print("[+] FILE EXTRACTED:", out_file)
break

爆破成功,得到了密码是JHTJ@202605,即本题答案

7. 嫌疑人向数据贩子购买公民个人信息共计多少条?【答案格式:123】

一下子问买了多少条,一般这种交易不会上交易平台

反而该去聊天软件找才是

有很多聊天软件,我们可以一个个尝试着找一下

最后在连趣这个软件里我们找到了这个数据库user\0\com.lianqujiaoyou.chat\app_flutter\OpenIM_v3_1355997814.db

先确定一下聊天双方是谁

1
2
风有约:1355997814
观云听海:1082423674

接着看看聊天内容

说的很直接啊,软件也很直接啊,都不加密,数据库聊天内容完全明文

甚至账密都是直接明文存的,可以直接仿真登录,不过这边没必要,就不演示了

盘古石要是也这样子直接就好了

所以本题答案是100

8. 嫌疑人向数据贩子交易时使用的付款方式是什么?【答案格式:支付宝】

继续看聊天记录啊,俩人说去i聊里边继续聊,那我们也去i聊看看数据库

找到数据库

data\uni.app.UNI04963C4\files\1600123285_3137373036323233393731\msg_0.db


可以看到很明显啊,说的是银行卡,所以本题答案是银行卡

9. 数据贩子交易过程中提供的银行卡号是多少?【答案格式:6222351234482925678】

工商银行卡号:6222355973482923738

10. 接上题,嫌疑人所使用聊天工具的用户 ID 是多少?【答案格式:12345】

接上题的话我们用的还是i聊这个软件

记得一开始从连趣到i聊的时候,数据贩子让嫌疑人发了一张二维码

我们来定位一下这个二维码openim_media_cache_4b8966d3-242a-4c1e-a2e3-8bd7168c9236.jpg

扫扫看

{“type”:”personal”,”userID”:”17706223971”,”nick”:”搁浅”,”timestamp”:1778574712018}

因此聊天工具实际用户 ID 为17706223971

11. 嫌疑人使用浏览器访问了该下载链接,该浏览器对应的包名是什么?【答案格式:com.heytap.market】

肯定是接着上几题在说

说什么浏览器访问了下载链接

根据题目的意思,应该就是这些数据,已经被加密传到了百度网盘,而且密码是手机号,而嫌疑人是用浏览器先访问的,访问完了用的网盘下载

在连趣的配置文件处,把手机号也直接明文存储了,所以我们可以知道密码应该就是17706223971

来找一下看看,我们最后可以用17706223971当作密码验证一下

然而浏览器还不少,我们一个个看看

最后发现在鲁班浏览器有很多百度网盘下载痕迹

剩下两个显然没有,所以我们确定了本文的答案是com.meiit.browser

12. 嫌疑人于何时下载数据商人发送的数据压缩包文件?【答案格式:2022-02-02 12:00:00】

很明确是百度网盘里下的了,所以我们先定位一下百度网盘的数据库位置

user\0\com.baidu.netdisk\databases\7045389826filelist.db

发现表 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">download_tasks</font> 中记录了这个数据.zip的下载详情

得到下载的时间戳,dcode转换为北京时间即可

所以是2026-05-12 17:00:05

13. 接上题,该压缩包的解压密码是什么?【答案格式:根据实际值填写】

接上题,我们已经确定了压缩包是数据.zip

而且我们也知道了密码就是手机号17706223971

所以这题答案就是17706223971,我们可以试着解密验证一下

直接索引找到解密试试看

发现成功解密压缩包

所以这题的答案是17706223971

14. 接上题,年龄在 40 至 60 岁(含 40、60 岁)的富豪共有多少人?【答案格式:123】

我们上一题已经找到确定了Excel表,有一栏就是年龄,直接筛选即可

1
=SUMPRODUCT((--F2:F100>=40)*(--F2:F100<=60))

得到一共46个人

15. 请找出嫌疑人筛选的重点客户名单,并统计名单内共有多少人?【答案格式:1】

跳的有点远,我们在第六题不是steghide爆破出了密码吗,解密出了一个文件

打开发现存在 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">重点客户.xlsx</font>

但是显示了文件损坏

发现主要是ZIP数据流尾部截断

虽然只有外层 ,但关键的 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">xl/worksheets/sheet1.xml</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">xl/sharedStrings.xml</font> 可恢复

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import struct
import zlib
import re
import xml.etree.ElementTree as ET

path = "1.zip"

data = open(path, "rb").read()

# 1. 找外层 ZIP 本地文件头
idx = data.find(b"PK\x03\x04")
if idx == -1:
raise Exception("找不到 PK\\x03\\x04")

outer = data[idx:]

nlen = struct.unpack_from("<H", outer, 26)[0]
elen = struct.unpack_from("<H", outer, 28)[0]
dstart = 30 + nlen + elen

# 2. 解外层 deflate,尾部截断没关系,能解多少算多少
d = zlib.decompressobj(-15)
xlsx_data = d.decompress(outer[dstart:], max_length=200000)

print("恢复出的内层 xlsx 数据长度:", len(xlsx_data))
print("外层是否完整:", d.eof)

files = {}

# 3. 扫内层 xlsx 的本地文件头
off = 0
while off < len(xlsx_data) - 30:
pos = xlsx_data.find(b"PK\x03\x04", off)
if pos == -1:
break

method = struct.unpack_from("<H", xlsx_data, pos + 8)[0]
csize = struct.unpack_from("<I", xlsx_data, pos + 18)[0]
nlen = struct.unpack_from("<H", xlsx_data, pos + 26)[0]
elen = struct.unpack_from("<H", xlsx_data, pos + 28)[0]

name_start = pos + 30
name = xlsx_data[name_start:name_start + nlen].decode("utf-8", errors="ignore")

dstart = name_start + nlen + elen
dend = dstart + csize

# 不完整的文件直接跳过
if dend > len(xlsx_data):
off = pos + 4
continue

raw = xlsx_data[dstart:dend]

if method == 8:
try:
raw = zlib.decompress(raw, -15)
except Exception:
off = dend
continue

files[name] = raw
off = dend

print("已恢复文件:")
for k in files:
print(" ", k)

shared_xml = files.get("xl/sharedStrings.xml")
sheet_xml = files.get("xl/worksheets/sheet1.xml")

if not shared_xml or not sheet_xml:
raise Exception("没有恢复出 sharedStrings.xml 或 sheet1.xml")

# 4. 解析 sharedStrings
strings = re.findall(r"<t[^>]*>(.*?)</t>", shared_xml.decode("utf-8", errors="ignore"), re.DOTALL)

# 5. 解析 sheet1
root = ET.fromstring(sheet_xml)
ns = {"a": "http://schemas.openxmlformats.org/spreadsheetml/2006/main"}

def col_index(cell_ref):
m = re.match(r"([A-Z]+)", cell_ref)
col = m.group(1)
num = 0
for ch in col:
num = num * 26 + ord(ch) - ord("A") + 1
return num - 1

rows = []

for row in root.findall(".//a:row", ns):
arr = []
for c in row.findall("a:c", ns):
ref = c.attrib.get("r", "")
idx = col_index(ref) if ref else len(arr)

while len(arr) <= idx:
arr.append("")

t = c.attrib.get("t")
v = c.find("a:v", ns)

if v is None:
arr[idx] = ""
continue

val = v.text or ""

if t == "s":
arr[idx] = strings[int(val)]
else:
arr[idx] = val

rows.append(arr)

# 6. 打印表格
for row in rows:
print("\t".join(row))

最后恢复出名单里5个人

所以答案是5

https://mp.weixin.qq.com/s/3lok6N3tVoqHR59AqXgw1g又学到一些吼吼吼

这边其实是分卷解压,我们搜索重点客户

可以在计算机检材的回收站搜到这样子一个zip,这是主卷,只需将刚刚的1.zip改成同名,放在同一个目录下双击这个zip文件就会自动拼起来解压

即可成功打开,确认是5人

二、计算机取证

1.请分析检材2:密码连续错误输入多少次数后,系统会自动锁定用户账户?【答案格式:1】

算是基础题吧,多少次自动锁定

我们可以在cmd输入net accounts查看

可以看到锁定阈值是3

其次我们也可以去账户策略看看,在Win+R输入gpedit.msc

所以答案是3

2.请分析检材2:检材中对应的微信 wxid 是多少?【答案格式:wxid_1a2b3c4d5e6f】

这边分析检材并没有分析出微信,甚至都分析结果里安装软件都没有微信,怀疑是下完删掉了

我们只能在这边直接搜索wxid

找到了这样子一个文件夹

所以本题的wxid应该是wxid_q1w2e3r4t5y6u7i8o9

3.请分析检材2:E盘 BitLocker 恢复密钥末尾六位是多少?【答案格式:616912】

上一题我们说到微信文件夹

双击进入后发现了MsgAttach下有两张AI图片

扫码发现这俩二维码全能解析出恢复密钥

但是咖啡那个不全,少了6位,所以正确答案应该是清单(8)里边这个

所以最后几位是126269

尝试用这个去解开E盘的BitLocker

填入恢复密钥即可

发现成功恢复,所以验证答案正确为126269

4.请分析检材2:VC加密容器的外层加密卷密码是什么?【答案格式:根据实际值填写】

翻一下电脑很容易发现D盘有一个1.png

一个png文件只有66字节,明显不是图片,是文本,我们直接notepad打开

成功找到外层加密卷的密码

JHTJ!@#¥A313

同目录还有个1,无后缀且大小1.00GB,很像加密容器

将密码填入后显示类型常规,确认是VC外层密码,本题答案是JHTJ!@#¥A313

这边打开空空倒是少见,一般都会有点啥吧,还尝试恢复,结果发现就是没东西

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

在图片一栏下看见了4张有这个水印的

但不止

刚刚第三题的那两张也是有豆包水印的

所以本题答案应该是6

6.请分析检材2:VC加密容器的隐藏加密卷密码是什么?【答案格式:根据实际值填写】

上一题让我们找了豆包水印的,想了想应该是不会白找

发现3.png在blue plane 0下是一个二维码

得到这样子一串字符,怀疑是隐藏层密码,但是打不开,比赛的时候猜的,结果真是这个,至于为什么打不开隐藏卷呢,是因为这边需要组合使用

应该想到是这个钥匙不会0作用,我们需要将其作为密钥文件,再填入密码ClearSky@SecretSignal#SevenMileJasmine,才能解开隐藏卷

发现成功解开了隐藏卷,确认密码的确是ClearSky@SecretSignal#SevenMileJasmine

7.请分析检材2:接上题,嫌疑人的接头暗号是什么?【答案格式:根据实际值填写】

解开发现有很多文件,尤其是密钥和一个叫做隐藏信号的音频

密钥写了 ⊕ 是拿来异或的,暂时应该是用不上

继续看这个隐藏信号的音频,怀疑是音频隐写

用Audacity打开

拖入文件后右键选择频谱图看看

发现了接头暗号:步行9千米

8.请分析检材2:接上题,嫌疑人的接头地点在哪里?【答案格式:天津117大厦201室】

接下来来继续深究一下这个异或 ⊕ 的密钥图片是干什么用的

跟这一串二进制有联系的线索有什么呢?

明显是图片中的这四个画着二进制的图片了

我们只需要把这四张图里的二进制按5 -> 6 -> 7 -> 8 顺序拼起来,然后每个字节和密钥 11011010 做异或就好啦

1
2
3
4
5
6
7
8
9
10
11
key = int("11011010", 2)

binary_data = [
"10001110", "10111011", "10110011", "10101010", "10111111", "10110011", "11111010",
"11101011", "11101010", "11101011", "11111010", "10111000", "10101111", "10110011",
"10110110", "10111110", "10110011", "10110100", "10111101", "11111010", "11101111",
"11101010", "11101000", "11111010", "10101000", "10110101", "10110101", "10110111",
]

result = bytes(int(x, 2) ^ key for x in binary_data)
print(result.decode("utf-8"))

得到Taipei 101 building 502 room

所以街头地点是台北101大厦502室

9.请分析检材2:木马残留样本中,核心信息窃取配置数量为多少?【答案格式:1】

这个得回到我们之前第三题解开的E盘里

在这边我们看见了有一个c文件

打开来发现是一个木马残留文件

写了很多的危险函数,虽然这些函数都没有被main函数调用,比如

1
2
3
4
5
6
7
8
CheckForDebugger()
WriteRegistryPersistence()
CreateLogFile()
WriteLogEntry()
CleanupFiles()
InitNetwork()
ConnectToC2()
SimulateProcessInjection()

确认是这个文件之后看看核心信息窃取配置数量为多少

发现主要是这样子五个

1,g_KEYLOG_PATH
键盘记录器,路径是 keylog.dat

2,g_BROWSER_DB
浏览器凭据,值是 "Login Data"

3,g_COOKIE_DB
Cookie 窃取,值是 "Cookies"

4,g_SCREENSHOT_DIR
屏幕截图目录

5,SimulateProcessInjection()
进程注入窃取

所以本题答案是5

10.请分析检材2:木马残留样本中,申请的内存保护标志是什么?【答案格式:PAGE_READWRITE (0x04)】

继续审计代码

在SimulateProcessInjection里找到了这一段

可以看到这边VirtualAllocEx的参数就写了内存保护属性

PAGE_EXECUTE_READWRITE (0x40) ,即可读可写可执行

这个宏在Windows的内存保护常量里就对应0x40,所以答案是PAGE_EXECUTE_READWRITE (0x40)

11.请分析检材2:嫌疑人涉案交易使用的银行卡号是什么?【答案格式:纯数字】

在文档下我们可以看到有一个交易银行卡.xlsx的文件,还有个密码

不过密码很狡猾啊,没有直说,给了一些爆破规则,卡了很多人的应该是这边的4个字母前缀其实是大写前缀(

为了减少爆破次数,可以尝试这个时间是20260512

然后这样子设置掩码就会爆破的快一些(也快不到哪去,慢的很,借一下yc哥的图

得到密码是JUHE@20260512

当然用hashcat也可以啦

还有我们ai也可以()

打开来就一个内容,银行卡就是6221882234367490125

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

AI换脸图片很容易找到,主要是找模型

没什么好的思路,我是觉得应该是在检材内部然后有一个AI换脸软件再找模型

但是这边明显没有什么模型或AI换脸软件

而且题目说什么“大概率”,也有点怪

或许真的是这样子做的吧,跟着钟师傅做一下

https://hivedetect.ai

这边拿出来就是reve最高,在里边就是steadydancer最高,我觉得有点牵强(

有点奇怪的题目

13.请分析检材2:萤小石的身份证号码是什么?【答案格式:纯数字】

在D盘很容易找到萤小石的身份证,但是显然这图片有点问题

还有一个身份证的压缩包

但是要密码

发现这是伪加密啊,我们定位504B0102,把这个9改掉就好了

向大家推荐Mitunlny的深入理解zip伪加密

https://czxh.top/posts/%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3zip%E4%BC%AA%E5%8A%A0%E5%AF%86/

解开发现就有完整的身份证了,所以走前边那个坏的那条路有点麻烦

但是这身份证没露照片,发现竟然藏在文件尾了

所以是330122199801209527

14.请分析检材2:小众通联工具绑定的手机号码为多少?【答案格式:纯数字】

回到一开始解密完E盘

这边有一个ldbk文件,这是雷电模拟器镜像文件

我们在雷电多开器选择还原,主要这边要64位的

可以发现还原出的有一个i聊,这应该就是题目所说的小众通联工具了,但是不知道账密,还要叫我更新,似乎仿真不太方便

这个时候我们可以直接把ldbk文件的后缀改成7z,本质7z

直接提出来扔到火眼当镜像就可以了

就可以分析出我们的数据库了

在i聊数据库中找到了萤小石对应的手机号

根据这个可以确认这个小众通联工具绑定的手机号就是18136091921

15.请分析检材2:小众通联工具添加好友的具体时间是什么?【答案格式:2025-01-01 01:01:01】

回到上一题的这个im.db

这边我们可以看到userinfo

对于搁浅有一个add_time:1778659957

解密得到是2026-05-13 16:12:37

16.请分析检材2:挖矿程序(请勿在本地运行)的版本是什么?【答案格式:1.00.1】

在用户痕迹里边可以发现有一个可疑程序timo

追踪到这个程序C:/Windows/windows/timo

发现同目录的config.json写了XMR矿池地址,符合下一题说的门罗币

再分析一下timo.exe坐实一下看看

查看字符串非常符合XMR挖矿特性

所以看这个的版本号就是了

所以答案是6.26.0

17.请分析检材2:门罗币钱包地址后6位是什么?【答案格式:根据实际值填写】

刚刚已经找到了钱包地址了,所以后六位是6soTWp

三、服务器取证

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

火眼能直接看见内核版本号是3.10.0-514.el7.x86_64

或者也可以先火眼仿真,毕竟迟早要仿真的

1
2
ifconfig
systemctl status sshd

ip有,ssh开着,很正常啊

直接ssh连接

成功连接,查看内核版本

1
uname -a

所以本题答案为3.10.0

2. 请分析检材3:嫌疑人将某普通用户加入特殊用户组,赋予其读取 /etc/shadow 的权限,请问该普通用户的用户名是什么?【答案格式:admin】

简而言之,我们只需要看看/etc/shadow的访问控制列表就好了

这边可疑使用getfacl这个命令

1
getfacl /etc/shadow

发现把普通用户ahao给了读的权限

所以本题答案是ahao

3. 请分析检材3:分析嫌疑人所使用的 Web 服务器,其具体名称及主版本号是什么?【答案格式:apache/2.40】

查服务先

1
netstat -antp

看到了nginx

所以Web服务器应该是nginx搭建的

查一下版本号即可

1
nginx -v

所以答案是nginx/1.20

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

1
ps aux

先去进程里找这个守卫

找到一个很像的,我们cat一下

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
70
71
[root@localhost ~]# cat /usr/local/bin/network-check.py
# -*- coding: utf-8 -*-
import os
import subprocess
import time

# --- 题目核心配置 ---
ALLOWED_IP = "192.168.203.100" # 嫌疑人预设的安全IP
CHECK_INTERVAL = 30 # 检查间隔(秒)
KICK_DELAY = "5 minutes" # 触发后延迟时间:5分钟

def get_current_logins():
"""获取当前所有登录用户的 IP 和 TTY 终端"""
logins = []
try:
# 执行 who 命令获取当前登录信息
process = subprocess.Popen(['who'], stdout=subprocess.PIPE)
output, err = process.communicate()
lines = output.decode('utf-8').splitlines()

for line in lines:
parts = line.split()
if len(parts) >= 2:
user = parts[0]
tty = parts[1]
ip = "unknown"
# 提取括号中的 IP 地址
for p in parts:
if '(' in p and ')' in p:
ip = p.strip('()')
logins.append({'user': user, 'tty': tty, 'ip': ip})
except:
pass
return logins

def is_job_exists(tty):
"""逻辑去重:检查 at 任务池中是否已存在针对该终端的任务"""
try:
# 扫描 at 任务池目录 /var/spool/at/
at_dir = "/var/spool/at/"
if os.path.exists(at_dir):
for f in os.listdir(at_dir):
file_path = os.path.join(at_dir, f)
if os.path.isfile(file_path):
with open(file_path, 'r') as content:
# 如果任务脚本中已包含该 tty 编号,说明已排队
if tty in content.read():
return True
except:
pass
return False

def trigger_kickout(tty):
"""触发踢人逻辑"""
if is_job_exists(tty):
return # 已存在任务则跳过,避免重复添加

# 使用 pkill 强制杀死指定终端的所有进程
kick_cmd = "pkill -9 -t {}".format(tty)

# 调用 at 命令,并将所有输出重定向到黑洞,防止产生系统邮件
os.system('echo "{}" | at now + {} > /dev/null 2>&1'.format(kick_cmd, KICK_DELAY))

if __name__ == "__main__":
while True:
current_users = get_current_logins()
for u in current_users:
# 逻辑判定:非安全IP 且 是远程终端(pts)
if u['ip'] != ALLOWED_IP and 'pts' in u['tty']:
trigger_kickout(u['tty'])
time.sleep(CHECK_INTERVAL)

发现确实是这个

每隔5分钟一次,答案是5

5. 请分析检材3:计算服务器内数据库备份文件,计算其SHA256哈希值,取后6位,字母大写。【答案格式:6DEF38】

在/tmp/db_backup这个备份路径下发现了备份文件

但都几乎是空的,没什么内容

还有/opt/faka.sql,发现也不全

只有解开了计算机的Bitlocker才会发现真正的sql备份文件在E盘

接着计算SHA256即可,得到最后六位大写为FF8180

6. 请分析检材3:MySQL 数据库 root 用户的登录密码是什么?【答案格式:根据实际值填写】

先找到核心网页业务

在/usr/share/nginx/html/fk

发现配置文件config.php

1
2
3
4
5
6
7
8
9
10
<?php
/*数据库配置*/
$dbconfig=array(
"host" => "192.168.203.155", //数据库服务器
"port" => 3306, //数据库端口
"user" => "root", //数据库用户名
"pwd" => "Root@123456", //数据库密码
"dbname" => "fk", //数据库名
"dbqz" => "shua" //数据表前缀
);

得到密码明文存储:Root@123456

7. 请分析检材3:外挂网站所对外开放的端口号是多少?【答案格式:1234】

查服务的时候就看见对外端口号是8081了

8. 请分析检材3:嫌疑人为上传大型恶意插件修改文件上传限制,其最大上传限制是多少?【答案格式:100M】

上传限制,还是php网站,所以我们想到去看看php.ini或者看看nginx的配置文件nginx.conf

在/etc/php.ini里面找到了答案,最大上传限制是60M

9. 请分析检材3:嫌疑人设置数据库定时自动备份并上传至境外,请问该境外服务器的IP地址是多少?【答案格式:8.8.8.8]】

定时任务,我们自然是先看定时任务了,直接

1
crontab -l

找到了定时自动备份脚本,看看有没有写怎么上传到国外的

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
[root@localhost conf.d]# cat /etc/cron.daily/autobackup.sh
ER="root"
DB_PASSWORD="Root@123456"
DB_NAME="fk"


REMOTE_USER="root"
REMOTE_SERVER="8.208.44.202"
REMOTE_DIR="/root/"


BACKUP_DIR="/tmp/db_backup"
# ====================================================================


TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
BACKUP_FILE="${BACKUP_DIR}/${DB_NAME}_${TIMESTAMP}.sql.gz"


mkdir -p ${BACKUP_DIR}


if mysqldump -h 127.0.0.1 -u${DB_USER} -p${DB_PASSWORD} \
--single-transaction --set-gtid-purged=OFF \
--routines --triggers --events \
${DB_NAME} | gzip > ${BACKUP_FILE}; then
echo "$(date) - 数据库备份成功:${BACKUP_FILE}"
else
echo "$(date) - 数据库备份失败!" >&2
exit 1
fi

# 需要配置 ssh-copy-id 以实现免密登录
if scp -o StrictHostKeyChecking=no ${BACKUP_FILE} ${REMOTE_USER}@${REMOTE_SERVER}:${REMOTE_DIR}; then
echo "$(date) - 文件上传至 ${REMOTE_SERVER} 成功"
rm -f ${BACKUP_FILE}
ssh ${REMOTE_USER}@${REMOTE_SERVER} "find ${REMOTE_DIR} -name '${DB_NAME}_*.sql.gz' -type f -mtime +30 -delete"
else
echo "$(date) - 文件上传至 ${REMOTE_SERVER} 失败!" >&2
exit 1
fi

exit 0

写的很明确了

REMOTE_SERVER=”8.208.44.202”

所以答案就是8.208.44.202

10. 请分析检材3:嫌疑人登录后台的目录路径是什么?【答案格式:/abc/abc】

跟网站结构有关,我们看看网站结构

还是到/usr/share/nginx/html/fk这边

1
find /usr/share/nginx/html/fk/ -name "login.php"

发现admin/login.php 和 static/login.php都存在

php里边还都说自己是后台

不确定是哪一个,但是/fk/static这个还写了独立的密码盐,更有可能一些,最后确定可以看访问日志

1
zcat -f /var/log/nginx/access.log* | awk '$7 ~ /\/fk\/(static|admin)\/login/ {print $7}' | sort | uniq -c | sort -rn

发现了/fk/static访问的多,更可能是真正的后台登录页面

11. 请分析检材3:访问后台登录页面次数最多的 IP 地址是什么?【答案格式:192.168.1.1】

这题明显就是要看访问后台登录的日志了

我们去nginx access.log看看

上题确定了后台是/fk/static/login.php,所以看看访问/fk/static/login.php的情况

1
zcat -f /var/log/nginx/access.log* | awk '$7 ~ /\/fk\/static\/login\.php/ {cnt[$1]++} END {for (ip in cnt) print cnt[ip], ip}' | sort -rn | head -5

所以答案是192.168.203.135

(虽然如果我们同时计算后台是static和admin的情况答案是一样的,但是上题都确定了是这一个,然后这题又把俩都纳入统计范围,感觉很怪,所以我这边只统计了static的)

12. 请分析检材3:网站后台管理员的明文密码为多少?【答案格式:根据实际值填写】

后台管理员的明文密码,我们之前在计算机E盘已经拿到了数据库的备份文件了

尝试打开这个备份文件看看

这边很奇怪,我连接着服务器的数据库导入不进去,但是可以导入到我本地的mysql里,可能是mysql的版本问题,所以我们就直接导入本地的mysql看看吧,主要也只是看看内容

先新建一个faka的数据库

然后直接创建就可以了

然后我们运行一下这个sql文件

成功导入,打开看看

在config里边看见了密码,但是是哈希

我们在后台登录php看见了是加盐的

即sha256(密码+”2025baofu!”)

所以开始爆破

1
hashcat -m 1410 -a 0 '94bd34e3010a6468f312eafe359e9d926fc16bba62c0b6cf646d041a5c281bf2:2025baofu!' rockyou.txt --potfile-disable --outfile-format 2,3 --status --status-timer 5

得到密码是abc123456

13. 请分析检材3:该网站支付接口所对接的第三方接入商名称是什么?【答案格式:根据实际值填写】

在备份数据库可以看到payapi对应的是13

我们去set.php找一下这个对应哪一家

发现有混淆,需要解一下

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
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import re
import sys
from pathlib import Path

def php_int(s):
s = s.strip()
if s.lower().startswith("0x"):
return int(s, 16)
# PHP 里 01042 这种是八进制
if len(s) > 1 and s.startswith("0") and re.fullmatch(r"[0-7]+", s):
return int(s, 8)
return int(s, 10)

def decode_hex(hx):
try:
return bytes.fromhex(hx).decode("utf-8", errors="ignore")
except Exception:
return ""

def find_big_explode_array(src):
# 匹配 call_user_func_array("explode",array("分隔符","内容"))
pattern = re.compile(
r'explode",array\("(?P<sep>.*?)","(?P<data>.*?)"\)\)',
re.S
)

candidates = []
for m in pattern.finditer(src):
sep = m.group("sep")
data = m.group("data")
parts = data.split(sep)
if len(parts) > 20:
candidates.append((len(parts), m, sep, data))

if not candidates:
raise RuntimeError("没找到主要的 explode 十六进制数组")

# 选最大的一组,一般就是主混淆数组
_, m, sep, data = max(candidates, key=lambda x: x[0])
return m, sep, data

def main():
if len(sys.argv) < 2:
print("用法: python3 deob_setphp.py set.php")
sys.exit(1)

in_file = Path(sys.argv[1])
src = in_file.read_text(encoding="utf-8", errors="ignore")

m, sep, data = find_big_explode_array(src)

# 1. 解出所有字符串块
arr = []
for hx in data.split(sep):
arr.append(decode_hex(hx))

strings_file = in_file.with_suffix(".strings.txt")
with strings_file.open("w", encoding="utf-8", errors="ignore") as f:
for i, text in enumerate(arr):
if text.strip():
f.write(f"\n===== INDEX {i} =====\n")
f.write(text)
f.write("\n")

# 2. 尝试还原 echo pack(...) 拼出来的代码
pack_pat = re.compile(
r'call_user_func_array\("pack",array\([^)]*?\)\);echo\s+\$U4jeF0;',
re.S
)
idx_pat = re.compile(
r'U4jcV2\s*(?:=&|=)\s*\$GLOBALS\[B0000BB00\]\[([^\]]+)\]'
)

out = []
last = m.end()

for pm in pack_pat.finditer(src, m.end()):
block = src[last:pm.start()]
idxs = idx_pat.findall(block)

if idxs:
idx = php_int(idxs[-1])
if 0 <= idx < len(arr):
out.append(arr[idx])

last = pm.end()

deob_file = in_file.with_suffix(".deobf.php")
deob_text = "".join(out)
deob_file.write_text(deob_text, encoding="utf-8", errors="ignore")

print(f"[+] 已输出还原代码: {deob_file}")
print(f"[+] 已输出全部字符串: {strings_file}")

# 3. 自动找 value=13 对应项
corpus = deob_text + "\n".join(arr)
found = False

for mm in re.finditer(r'<option\s+value=["\']13["\']>(.*?)</option>', corpus):
print(f"[+] 找到 value=13 对应: {mm.group(1)}")
found = True

if not found:
print("[!] 没自动找到 value=13,可以手动 grep:")
print(f" grep -n 'value=\"13\"' {strings_file}")

if __name__ == "__main__":
main()
1
2
3
[+] 已输出还原代码: set.deobf.php
[+] 已输出全部字符串: set.strings.txt
[+] 找到 value=13 对应: 无忧支付

解混淆一下就好了,找到value=13对应的是无忧支付

注意这边要看的是实际接入商配置,所以是这个,不是彩虹易支付

14. 请分析检材3:网站的创建时间是什么时候?【答案格式:2026/5/20】

在数据库可以直接看见是2018/12/14

15. 请分析检材3:网站Git配置信息中,找出作者姓名是什么?【答案格式:根据实际值填写】

一开始是想直接在网站的备份配置文件找

解base64得到了GhostOperator,但这是错误干扰答案

在历史命令bash_history里边我们可以看见git相关命令

发现给藏到这边去了”/usr/share/icons/hicolor/48x48/apps/… “

我们去看看

发现了git.txt文件,这才是真的

name=576d6868626d6454595735665532566a

解码得到用户名其实是ZhangSan_Sec

16. 请分析检材3:分析该网站 2025 年 6 月 1 日至 12 月 31 日的全部订单,其中状态为 “已完成” 的订单总成交金额为多少?【答案格式:1234】

数据分析题,没什么好说的,备份数据库都找到了,直接SQL查询即可

1
2
3
4
5
6
7
8
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';

所以答案为24073

17. 请分析检材3:分析 2025 年全年的订单数据,计算下单金额总计最高的那一天的总金额是多少?【答案格式:1234】

还是数据分析

1
2
3
4
5
6
7
8
9
10
11
12
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;

所以答案是5442

18. 请分析检材3:统计全部订单数据,购买数量累计最高的应用名称是什么?【答案格式:根据实际值填写】

依旧数据分析,Jerry狂喜

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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】

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
SELECT 
s.user AS username,
t.total_spent
FROM
shua_site s
INNER JOIN (
SELECT
userid,
SUM(money) AS total_spent
FROM
shua_orders
GROUP BY
userid
ORDER BY
SUM(money) DESC
LIMIT 1
) t ON s.zid = t.userid;

最高的guoqi这个账号

20. 请分析检材3:嫌疑人创建的拥有 FILE 与 SUPER 权限的隐蔽 MySQL 账户,其用户名是什么?【答案格式:admin】

这下要回到獬豸杯自己的数据库了

我们在Navicat连接一下

首先是打通ssh隧道

这边用的是服务器密码123456

之后再去连接数据库,就像我们之前在配置文件看到的那样

密码是Root@123456

这边的密码就是Root@123456了

连接之后就好做了,又是数据分析其实()

1
2
3
4
5
6
7
8
9
10
11
SELECT 
User,
Host,
Super_priv,
File_priv
FROM
mysql.user
WHERE
User LIKE '_tmp%'
AND Super_priv = 'Y'
AND File_priv = 'Y';

同时筛选这两个都有的,都是Y的就好了

所以是_tmp_z5m1w8

当然也可以直接在服务器做啦

1
2
mysql -uroot -p'Root@123456' -e "SELECT User,Host,File_priv,Super_priv FROM
mysql.user WHERE File_priv='Y' AND Super_priv='Y';"

也能得到是_tmp_z5m1w8

四、逆向分析

1. 样本中的 C2 地址与端口分别是多少?【答案格式:127.0.0.1:8080】

拿到样本,是一个exe文件

放入IDA看看,可以很快发现样本中存在着大量的PyInstaller特征文件和字符串

或者直接跳到main函数,继续步入

也写的很明确,因此不难判断本样本是由PyInstaller打包

因此我们可以先使用Pyinstxtractor解包

1
python3 pyinstxtractor.py EduRATSample.exe

解包的时候还推测出了可能是程序入口点,可以看到这个edu_ratsample.pyc明显有很大嫌疑

接着尝试反编译

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
import marshal
import types
import dis
import struct

def show(code, depth=0):
pad = " " * depth
print(f"{pad}[代码块] {code.co_name}")
print(f"{pad}[起始行] {code.co_firstlineno}")
print(f"{pad}[调用名称] {code.co_names}")
print(f"{pad}[常量] {[c for c in code.co_consts if not isinstance(c, types.CodeType)]}\n")

for const in code.co_consts:
if isinstance(const, types.CodeType):
show(const, depth + 1)

with open("edu_ratsample.pyc", "rb") as f:
data = f.read()

# Python 3.7+: pyc header = 16 bytes
# Python 3.3–3.6: 12 bytes
# 保险起见:自动跳过 header
offset = 0
magic = data[:4]
offset += 4

if magic >= b'\x33\x0d\x0d\x0a': # Python 3.7+
offset += 12
else:
offset += 8

root = marshal.loads(data[offset:])
show(root)

得到如下结果

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
[代码块] <module>
[起始行] 1
[调用名称] ('base64', 'socket', 'sys', 'time', 'C2_HOST', 'C2_PORT', 'BEACON_B64', 'MUTEX_NAME', 'USER_AGENT', 'bytes', '_xor', 'build_payload', 'try_connect', 'int', 'main', '__name__', 'exit')
[常量] [0, None, '192.0.2.1', 64123, 'eyJ0IjoiYmVhY29uIiwidiI6IjEuMCJ9', 'Global\\EduLab_RAT_Mutex_2026', 'Mozilla/5.0 (EduLab; Win32) RAT-Stub/1.0', 'data', 'key', 'return', '__main__', ('return', None)]

[代码块] _xor
[起始行] 15
[调用名称] ('bytes', 'enumerate')
[常量] [None]

[代码块] <genexpr>
[起始行] 16
[调用名称] ('len',)
[常量] [None]

[代码块] build_payload
[起始行] 19
[调用名称] ('base64', 'b64decode', 'BEACON_B64', '_xor')
[常量] [None, b'EDU']

[代码块] try_connect
[起始行] 25
[调用名称] ('build_payload', 'socket', 'AF_INET', 'SOCK_STREAM', 'settimeout', 'connect', 'C2_HOST', 'C2_PORT', 'sendall', 'USER_AGENT', 'encode', 'time', 'sleep', 'OSError', 'close')
[常量] [None, 3.0, b'UA:', 'utf-8', b'\r\n', b'PAYLOAD:', 0.2]

[代码块] main
[起始行] 40
[调用名称] ('MUTEX_NAME', 'try_connect')
[常量] [None, 0]

或者用反编译工具也可以

尝试反编译

不难发现直接反编译出了C2的地址和端口,所以本题答案是 192.0.2.1:64123

2. 样本使用的传输层协议是什么?【答案格式:HTTP】(全大写)

继续来看反编译的结果

主要看try_connect的连接过程

我们可以看到AF_INET,即Address Family Internet Protocol v4,表示使用了IPv4地址

而SOCK_STREAM则表示创建了面向连接的流式套接字,对应着TCP协议

基本原始逻辑就相当于

1
socket.socket(socket.AF_INET, socket.SOCK_STREAM)  

所以样本使用的传输层协议为TCP

3. 样本外发的 User-Agent 完整字符串是什么?【按实际值填写】

在线反编译pyc直接就出了,脚本里也能看见,只是没这样直观

4. Beacon 的 Base64 常量是什么?【按实际值填写】

在线反编译pyc直接就出了,脚本里也能看见,只是没这样直观

5. 样本中出现的 Mutex 名称是什么?【按实际值填写】

在线反编译pyc直接就出了,脚本里也能看见,只是没这样直观

6. 是否具备注册表 Run、计划任务、服务安装或 UAC 绕过等行为?

根据反编译内容,我们可以确认大概逻辑,main只用了MUTEX_NAME和try_connect,说明程序入口最终只会进入网络连接函数,没有调用其他持久化或提权函数

同时也没有发现 evalexec__import__ 等动态加载方式。因此样本无法在隐藏字符串的情况下,动态导入其他模块执行相关操作

因此样本的全部有效逻辑只有:

  1. 对Beacon进行Base64解码;
  2. 使用密钥 EDU 进行XOR处理;
  3. 创建TCP套接字;
  4. 连接固定C2;
  5. 发送User-Agent和Payload;
  6. 关闭套接字并退出。

所以本题答案为否

7. 该 exe 由何种方式打包?【答案格式:全大写】

第一题就已经确定了是PYINSTALLER,就不重复了

五、流量分析

1. 内网用户向 intranet.corp.lab 提交登录表单。请给出 POST 正文里 password 字段的解码后的提交值【按实际值填写】

就给了个流量包,而且很小,就没几条

题目说的很明确啊,是POST正文,那我们肯定先过滤POST流量

1
http.request.method==POST

发现就两条啊,题目说了是登录表单,肯定是第一条的/login

追踪HTTP流即可以看到表单内容:

其中 password 字段为LabPass%23Q7

这里 %23 是 URL 编码,表示字符 #,因此解码后的密码为LabPass#Q7

2. 客户端 10.10.10.50 向 DNS 10.10.10.53 发起一次查询。Queries 中的域名看似随机十六进制。请将该段十六进制视为 ASCII 的十六进制表示,还原出可读字符串(即解码后的域名语义)【答案格式:abc-abc.abc.abc】

题目说的很明确了是DNS流量,我们过滤DNS流量

1
dns

发现就两条啊,题目说是10.10.10.50向10.10.10.53发起了一次查询,那就直接明确了是第一条,No.6的那条

发现了Queries的域名是一串十六进制字符串

1
6578616c7468726561642d64726f702e696e7472616e65742e6c61622e

将该字符串按 ASCII 十六进制解码得到exalthread-drop.intranet.lab.

所以答案是exalthread-drop.intranet.lab

3. 哪台客户端对哪台 FTP 服务器完成了认证?【答案格式:127.0.0.1-192.168.0.1】

这边流量题的特点就是说的蛮明确的,使用过滤条件查看 FTP 控制连接:

1
ftp

可以看到五条

第11条客户端(10.10.10.50)向服务器(10.10.10.101)发送了Request说我是bob

第12条服务器回复ready

第13条客户端发送了密码Ftp_S3cret_9z

第14条服务器回复收到,在验证

第15条服务器最终回复验证成功

所以对应连接为10.10.10.50-10.10.10.101

4. USER 与成功登录前的 PASS 明文分别是什么?【答案格式:USER-PASS 例:admin-123456】

上一题完整分析过了,就是 FTP 登录过程中直接写了PASS是bob-Ftp_S3cret_9z

1
2
USER bob
PASS Ftp_S3cret_9z

5. 存在来自外网段地址对 10.10.10.50 的 ICMP Echo 请求。请提取 ICMP 数据部分中隐藏的完整FLAG。【答案格式:ICMP_COVERT_FLAG{123_abc_def}】

根据题意,使用过滤条件查看 ICMP 流量:

1
icmp

发现存在来自外网段地址 198.51.100.66 发往内网主机 10.10.10.50 的 ICMP Echo 请求。

展开 ICMP 数据部分,可以看到 Payload 中直接写了完整的flag

1
ICMP_COVERT_FLAG{ping_payload_stego}

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

使用过滤条件查看访问 api.corp.lab 的 HTTP 流量:

1
http.host == "api.corp.lab"

直接就能定位到这一条,追踪TCP流

发现服务端返回状态码:

1
HTTP/1.1 200 OK

除 JSON 响应体外,响应头中存在一个非标准 HTTP 头:

1
X-Admin-Token: TOKEN_LEAK{http_header_backup_key}

题目要求填写泄露高权限备份密钥的非标准 HTTP 头名称,因此答案为X-Admin-Token

7. 同一源 IP 对主机 10.10.10.200 在短时间内向多个不同目的端口发送了仅含 SYN 的 TCP 报文,请写出源 IP。【答案格式:192.168.0.1】

根据题目含义使用过滤条件查找仅包含 SYN 的 TCP 报文:

可以看到同一源 IP 在短时间内向主机 10.10.10.200 的多个端口发送 SYN 包,目标端口包括:

1
2
3
4
5
22
23
80
445
3389

这些端口分别对应 SSH、Telnet、HTTP、SMB 和 RDP,符合端口扫描特征,都是198.51.100.66发过来的

所以源 IP 为198.51.100.66

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

使用过滤条件查看 Telnet 流量:

1
telnet

或者过滤 23 端口 TCP 流:

1
tcp.port == 23

其实都一样

发现客户端连接目标 10.10.10.1:23,追踪TCP流即可看到登录名与密码:

因此登录名为:

1
charlie

完整口令 FLAG 为:

1
Telnet_PASS_FLAG{legacy_cleartext}

所以flag就是charlie-Telnet_PASS_FLAG{legacy_cleartext}

六、内存取证

还有背景

1
公司某员工的电脑感染了勒索病毒,所有办公文档都被加密。安全团队在病毒仍在运行时制作了内存镜像。要求从内存镜像中提取加密密钥,解密被加密的最重要文档,获取其中的 flag。

1. 该勒索进程的进程标识号(PID)是多少?【按实际值填写】

我们用Lovelymem看一下,直接就能看见有HACK用户

桌面发现了四个文件,其中有两个exe,研究看看,先提取

1
2
3
4
5
6
New-Item -ItemType Directory -Force -Path .\extracted

$env:PYTHONIOENCODING='utf-8'
vol.exe -q --cache-path .\volcache -s .\symbols -o .\extracted `
-f .\HACKER-20260606-174920.dmp windows.dumpfiles.DumpFiles `
--filter ransom --ignore-case

在进程里找这个程序

分析一下

打开IDA,shift+f12定位字符串后交叉引用查看主要函数地址

说的很明确了,你的文件被加密了,要恢复的话就给钱,妥妥勒索啊

所以确认题目所说的勒索程序就是这个ransom.exe

定位得到PID是8804

2. 受害计算机的主机名(COMPUTERNAME)是什么? 【答案格式:ABC】

HACKER,这在lovelymem直接就能看见

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

回到第一题我们发现的那个函数,发现前面有这样子一个函数,参数中包含了路径,把桌面下的2026xzb_enc这个文件夹里的文件进行了某种处理,具体函数在sub_1400014C0

继续步进,发现这个函数进行了三步

看着有点复杂,其实就是

1
2
for (int i = 0; i < 256; i++)
v51[i] = i;

即v51在生成一个256字节的S盒

然后这边进行了KSA密钥调度

最后PRGA生成密钥流并异或

所以这边是一个明显的标准RC4加密

确定了是对2026xzb_enc进行了RC4加密,所以本题答案为2026xzb_enc

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

题目说了是开头5字节魔数,其实可以直接看十六进制,前5字节是什么就是什么

所以答案是RNSMa

看加密完的,其实文件布局是这样子的

1
2
3
偏移 0x00:4 字节 Magic,"RNSM"
偏移 0x04:4 字节原始文件大小,小端序
偏移 0x08:RC4 密文

所以其实魔数是四位RNSM才对(,但是题目说5位,那就5位

5. 真正的密文数据从文件起始偏移多少字节处开始?(从 0 计)【答案格式:数字】

上一题分析过了,真正的密文数据是从偏移0x08的位置开始的

所以本题答案是8

6. 请从内存镜像中提取本次加密所使用的对称密钥,该密钥的长度是多少字节?【答案格式:数字】

回到 sub_140009000 函数

按照原型长这样子

1
2
3
4
5
BOOL CryptGenRandom(
HCRYPTPROV hProv, // phProv
DWORD dwLen, // 0x10u
BYTE *pbBuffer // qword_14000F030 + 4
);

所以可以看到密钥的长度是0x10,即16

7. 请写出密钥的完整十六进制表示【答案格式:无空格、无 0x】

可以看到函数这几行

先分配了24字节,然后说的很明确了把密钥写在偏移+4的地方,大概就是这样子

1
2
3
4
偏移 0x00:0xDEADC0DE       4字节前哨
偏移 0x04:RC4 key 16字节
偏移 0x14:0xCAFEBABE 4字节后哨
总长度:0x18 24字节

所以可去内存搜索字节:

1
DE C0 AD DE ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? BE BA FE CA

注意 x86 小端序下,<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">0xDEADC0DE</font> 在内存中是 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">DE C0 AD DE</font><font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">0xCAFEBABE</font> 在内存中是 <font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">BE BA FE CA</font>。两者中间的 16 字节即第 7 题答案

所以答案是cb<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">8978dde5b08227aac1c40ba91849c5</font>

8. 请结合内存样本、导入表或静态逆向分析,对 .enc 文件中密文部分所采用的加密算法名称是什么?【答案格式:大写】

第三题分析过了,在sub_1400014C0函数,是RC4加密

9. 程序在启动加密前,通过哪个 Windows CryptoAPI 函数生成了随机密钥?(写出函数名)【答案格式:全小写】

<font style="color:rgb(51, 51, 51);background-color:rgb(243, 244, 244);">cryptgenrandom</font>

分析过了真正生成随机密钥的函数就是 CryptGenRandom

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

第一题就分析过了,这边写的很清楚要0.5 BTC

11. 攻击者留下的联系邮箱是什么?【按实际值填写】

同一个地方,也是第一题,联系n0t_r3al@pr0ton.me

12. 内存镜像中还残留了受害者小张写在桌面上的一份文本文件内容。请根据镜像中的相关信息回答,该日记文件的完整文件名是什么?【按实际值填写】

打开看看桌面是什么就好了,所以是摸鱼日记.txt

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
小张的摸鱼日记.txt


================================================================
小张的摸鱼日记.txt
================================================================

下午 2:15
老板让我看财务发的"年度财报模板"。
邮件标题【急!速看】,行吧,速看就速看。
点开之后……好像没反应?算了,先去接杯水。

下午 2:31
回来桌面弹了个红框框,说我文件被加密了,
让我转 0.5 个比特币。
0.5 个比特币多少钱来着?我月薪都不够。

下午 2:33
冷静。先发个朋友圈。
配文:在吗?教我一下怎么把今天从日历上删掉。
零赞。连我妈都没赞。

下午 2:40
打给隔壁老李,占线。
打给我对象,她说"活该谁让你上班摸鱼"。
我没摸鱼啊,这是工作需要点的附件!
……好吧我确实也开着淘宝。

下午 2:52
老李终于回了,就一句:"别关机,等我。"
然后又没声了。
你们搞安全的能不能把话说完整啊喂!

下午 3:10
等老李的时候我重读了一遍勒索信。
错别字三个,标点不对俩,结尾还写"祝您生活愉快"。
被你勒索还愉快个屁。

下午 3:30
老李来了,捣鼓了十分钟,文件回来了。
我问他怎么弄的,他说"说了你也不懂"。
行吧大佬,奶茶我请,三杯,加料的那种。

下午 3:47
今日总结:
1. 标题带"急"和"!"的邮件,九成九有鬼。
2. 中招了先别慌,更别发朋友圈,没人赞。
3. 公司一定要养一个老李。

——— 完 ———

P.S. 早知道今天就请假了。
================================================================

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

所以是”别关机,等我。“,就在日记里

14. 解密后文件的大小是多少字节?【答案格式:99999】

我们在第四题分析过具体的文件头结构

1
2
3
偏移 0x00:4 字节 Magic,"RNSM"
偏移 0x04:4 字节原始文件大小,小端序
偏移 0x08:RC4 密文

所以原始文件大小其实是这四个字节

所以是0x2761

即10081

15. 解密后的内部文档记载了一份供备份系统使用的认证令牌。请完整写出该 flag。【按实际值填写】

对enc进行完整解密即可,算法我们知道了,密钥我们知道了,其实都知道了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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()


所以flag是flag{m3m0ry_f0r3ns1cs_rc4_k3y_hunt_2026}


2026獬豸杯wp(全)
https://mei-you-qian.github.io/2026/06/18/2026獬豸杯/
作者
Meiyouqian
发布于
2026年6月18日
许可协议