2026獬豸杯(逆向+流量+内存取证)

先发比较确定的后三个检材吧,前三个检材确定一点之后再发,有一些答案还不确定(

经常有师傅问我有没有快一些的,不用手搓的

有的兄弟有的,ai一把梭,流量分析8秒出,没什么技巧啊,题目一扔就出答案了

好了我们还是手搓看一下

四、逆向分析

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獬豸杯(逆向+流量+内存取证)
https://mei-you-qian.github.io/2026/06/15/2026獬豸杯/
作者
Meiyouqian
发布于
2026年6月15日
许可协议