獬豸杯 2026 决赛

2026獬豸杯WP(全解)

围绕 獬豸杯 的公开复盘与解题记录。

作者:玫幽倩 发布日期:2026-07-15 27 次阅读

签到题

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>序列号

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查询

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,这边放了很多数码相机拍的照片

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

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

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

先确定一下聊天双方是谁

风有约: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表,有一栏就是年龄,直接筛选即可

=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> 可恢复

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 做异或就好啦

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函数调用,比如

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

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

ifconfig
systemctl status sshd

ip有,ssh开着,很正常啊

直接ssh连接

成功连接,查看内核版本

uname -a

所以本题答案为3.10.0

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

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

这边可疑使用getfacl这个命令

getfacl /etc/shadow

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

所以本题答案是ahao

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

查服务先

netstat -antp

看到了nginx

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

查一下版本号即可

nginx -v

所以答案是nginx/1.20

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

ps aux

先去进程里找这个守卫

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

[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

<?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]】

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

crontab -l

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

[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这边

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

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

php里边还都说自己是后台

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

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的情况

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!")

所以开始爆破

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找一下这个对应哪一家

发现有混淆,需要解一下

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()
[+] 已输出还原代码: 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查询即可

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】

还是数据分析

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狂喜

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】

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了

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

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

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

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解包

python3 pyinstxtractor.py EduRATSample.exe

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

接着尝试反编译

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)

得到如下结果

[代码块] <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协议

基本原始逻辑就相当于

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流量

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流量

dns

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

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

6578616c7468726561642d64726f702e696e7472616e65742e6c61622e

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

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

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

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

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

USER bob
PASS Ftp_S3cret_9z

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

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

icmp

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

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

ICMP_COVERT_FLAG{ping_payload_stego}

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

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

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

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

发现服务端返回状态码:

HTTP/1.1 200 OK

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

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 包,目标端口包括:

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 流量:

telnet

或者过滤 23 端口 TCP 流:

tcp.port == 23

其实都一样

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

因此登录名为:

charlie

完整口令 FLAG 为:

Telnet_PASS_FLAG{legacy_cleartext}

所以flag就是charlie-Telnet_PASS_FLAG{legacy_cleartext}

六、内存取证

还有背景

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

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

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

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

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

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

看着有点复杂,其实就是

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

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

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

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

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

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

所以本题答案是8

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

回到 sub_140009000 函数

按照原型长这样子

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

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

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

可以看到函数这几行

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

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

所以可去内存搜索字节:

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

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

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

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

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

所以是0x2761

即10081

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

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

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}