不知道主办方究竟是想考察选手的取证能力,还是想展示自己对团队赛的独特理解
( ̄▽ ̄")
简要案情
近期,某市反诈部门接到多名群众报案,均遭遇“投资理财”类电信网络诈骗,指向的犯罪团伙涉案资金巨大、作案链条隐蔽。办案单位迅速成立专案组开展电子数据取证工作。经初查,该犯罪团伙搭建虚假投资平台,通过社交软件诱骗受害人充值转账,团伙成员分散多地办公,使用多种 AI 工具进行违法犯罪活动,同时发现涉及其他犯罪事实。请根据获取的电子数据还原案件经过。
注:所有检材及相关数据均为虚拟构造,不负法律责任,如有侵权请及时告知,未经允许不得用于商业用途。
检材容器信息
文件名称: 2026 精武杯检材容器
文件大小: 140 GB (150,323,855,360 字节)
MD5: A2EA823942EDE333E8F8D182E49FBC49
SHA256: A3F0BDA2FA8CA97A6E656D52B1105EB52C91214FE349D2E3A338CDE5E5B45E74
容器密码:2026精武杯/中国人民警察大学@CPPU-ISA&拓界科技@飞鹰小队
题目
手机检材-1
手机检材-1 解压出来是个文件夹,里面有大量 app 的包

可以看出手机安装了 vpn 软件 Clash for Android 和 v2rayNG,应该会作为考点,还能确定是小米手机(有 小米运动健康 等 app)
1. 分析嫌疑人在该手机登录的小米账号ID为?(答案格式:以实际为准,如1234567890)
3134129162
查看小米健康可以看到 id

2. 嫌疑人设定了一个每周六循环的闹钟,该闹钟的备注为?(答案格式:以实际为准,如打精武杯)
给老妈汇款

3. 分析嫌疑人使用的VPN软件,配置的节点服务器地址是什么?(答案格式:不含端口,如192.168.1.1)
107.175.0.221

根据左侧的列表可以看出手机安装了至少两个 vpn 软件:Clash 和 v2rayNG
(需要将取证工具更新到最新版,否则只有 Clash 没有 v2rayNG)
首先要确定哪个是机主常用的 vpn,先看 Clash 的配置文件

配置文件的路径是 /Clash for Android(com.github.kr328.clash).bak_2/apps/com.github.kr328.clash/f/imported/6b3f5a87-bfa8-46d8-902f-78297e606186/config.yaml

配置文件中提示 客户端版本过旧,可以猜测机主很久没有使用了,来看 v2rayNG

可以直接在节点信息中看到服务端 IP
4. 接上题,上述VPN软件在该手机的桌面位置是什么?请写出第几页第几行第几列。(答案格式:页-行-列,如1-2-3)
3-1-3
根据上题,vpn 软件是 v2rayNG,要确定该 app 在桌面的位置,开始想到的是找一找相册里有没有桌面截图,翻了一下没找到
最后找了系统桌面 app 的包 com.miui.home,其中有个数据库文件 launcher4x6.db,记录了各个 app 在桌面的位置

screen:页数,在第三页 cellX:列数,从 0 开始计数,故在第 3 列 cellY:行数,也是从 0 开始计数,故在第 1 行
故最终答案为 3-1-3
5. 嫌疑人最近一次登录Google账号的日期是什么时候?(答案格式:2026-09-09)
2026-07-07
查看手机自带浏览器的历史记录

可以直接看到最后一次登录 google 账号的时间
6. 在嫌疑人手机相册中发现一张图片文件名异常,经分析该图片进行了数据隐写,请回答该异常图片的文件名称是什么?(答案格式:XXX.png)
密码.png
找了半天的图片没有看到可疑的

最后发现手机给图片做了备份,即上图中的 backup_image.zip,直接解压
(开始用 windows 自带解压功能解压失败了,换 bandzip 才解压成功)
解压出来发现有两个目录:DCIM 和 无印
DCIM 就是 Digital Camera Images,是手机默认存放照片的地方,我们看这里

显然 密码.png 非常可疑,一片空白还叫密码,猜测是 lsb 隐写,用 StegSolve 看看

隐藏了一张二维码,实锤了
7. 接上题,该异常图片中隐写的内容为?(答案格式:feiying666)
qazokm123
直接扫描上题中得到的二维码

8. 在该手机屏幕第3页第1列第4行的应用是一个伪装的密拍程序,请问机主使用该软件拍了几张图片?(答案格式:9)
1
参考第 4 题,我们通过系统桌面 app 的数据库中去查找对应位置的 app 是什么
已知该 app 在手机屏幕第 3 页第 1 列第 4 行,那么对应 screen = 3,cellX = 0,cellY = 3

定位到该 app 名为计算器,显然名字是伪装的,对应的包名为 com.androidx.calculator
根据包名可以找到两个备份

.bak 的备份中是该应用的 apk base.apk,.bak_38 的备份中是该应用的数据库 stwyuo.db
本来想直接看数据库的,结果打不开,说明可能被加密了
没办法只能逆向 base.apk 了
先看 AndroidManifest.xml

定位到一个关键类名 androidx.appcompat.app.CCActivity(查看源码发现其中有 摄像头打开失败 这样的字符串,猜测这个就是拍照的主要逻辑)
用 jadx 反编译

发现有 sqlcipher 包,说明是用 SQLCipher 对数据库进行的加密,那么我们找到密码就可以解密了
继续定位刚刚在 manifest 中看到的关键类 CCActivity
定位到如下关键代码片段
private void initCamera() {
this.mCameraView.addCameraListener(new CameraListener() { // from class: androidx.appcompat.app.CCActivity.2
@Override// com.otaliastudios.cameraview.CameraListener
public void onCameraOpened(CameraOptions cameraOptions) {
super.onCameraOpened(cameraOptions);
CCActivity.this.isCameraOpened = true;
}
@Override// com.otaliastudios.cameraview.CameraListener
public void onPictureTaken(PictureResult pictureResult) {
super.onPictureTaken(pictureResult);
CCActivity.this.mDataHandler.handlerData(CCActivity.this.mPhoneNumber, CCActivity.this.getLastLocation(), pictureResult.getData());
CCActivity.this.showText("已抓拍");
}
});
this.mBtn.setOnClickListener(new View.OnClickListener() { // from class: androidx.appcompat.app.CCActivity$$ExternalSyntheticLambda0
@Override// android.view.View.OnClickListener
public final void onClick(View view) {
CCActivity.this.m0lambda$initCamera$1$androidxappcompatappCCActivity(view);
}
});
}
在 app 完成抓拍时会调用 DataHandler 类,继续跟踪
由于我们要想办法解密数据库,由此可定位到如下片段
public /* synthetic */void m1lambda$handlerData$0$androidxappcompatappDataHandler(String str, String str2, byte[] bArr) {
File filesDir = this.mContext.getFilesDir();
String[] list = filesDir.list();
if (list != null && list.length >= 5) {
FileUtils.deleteAllInDir(filesDir);
}
File file = new File(filesDir, String.format("%s/%s", Long.valueOf(getNetworkTimeMillis()), Keys.keyDb()));
if (FileUtils.createOrExistsDir(file.getParentFile())) {
SQLiteDatabase openOrCreateDatabase = SQLiteDatabase.openOrCreateDatabase(file, Keys.keyBase(), (SQLiteDatabase.CursorFactory) null);
createTable(openOrCreateDatabase);
insertData(openOrCreateDatabase, str, str2, bArr);
openOrCreateDatabase.close();
this.mHandler.sendEmptyMessage(!uploadData(file));
}
}
关键就这两行数据库操作
File file = new File(filesDir, String.format("%s/%s", Long.valueOf(getNetworkTimeMillis()), Keys.keyDb()));
SQLiteDatabase openOrCreateDatabase = SQLiteDatabase.openOrCreateDatabase(file, Keys.keyBase(), (SQLiteDatabase.CursorFactory) null);
第一行是在创建或打开文件,通过 getNetworkTimeMillis() 获得当前网络时间戳作为目录名,调用 Keys 类的 keyDb() 方法生成文件名
第二行是通过调用 Keys 类的 keyBase() 方法生成密钥打开或创建一个被加密的数据库,数据库路径就是 file,也就是刚刚构建的文件路径
显然下一步我们需要跟踪 Keys 类
class Keys {
privatestaticfinalchar[] HEX_DIGITS = "0123456789abcdef".toCharArray();
privatestaticbyte one = 116;
privatestaticbyte[] ones = {-1, 0, 3, 5, 1, -5};
staticbyte[] onesss = {49, 48, 51, 46, 50, 55, 46, 49, 48, 56, 46, 55, 56, 58, 49, 50, 53, 54, 51};
Keys() {
}
/* JADX INFO: Access modifiers changed from: package-private */
public static String keyDb() {
int length = ones.length;
byte[] bArr = newbyte[length + 3];
for (int i = 0; i < length; i++) {
bArr[i] = (byte) (one + ones[i]);
}
bArr[length] = 46;
bArr[length + 1] = 100;
bArr[length + 2] = 98;
returnnew String(bArr);
}
/* JADX INFO: Access modifiers changed from: package-private */
public static String keyBase() {
try {
return bytes2Hex(MessageDigest.getInstance("md5").digest(ones)).substring(0, 6);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return"defkey";
}
}
public static String bytes2Hex(byte[] bArr) {
int length;
if (bArr == null || (length = bArr.length) <= 0) {
returnnull;
}
char[] cArr = newchar[length << 1];
int i = 0;
for (int i2 = 0; i2 < length; i2++) {
int i3 = i + 1;
char[] cArr2 = HEX_DIGITS;
cArr[i] = cArr2[(bArr[i2] >>> 4) & 15];
i = i3 + 1;
cArr[i3] = cArr2[bArr[i2] & 15];
}
returnnew String(cArr);
}
}
代码逻辑很简单,keyDb() 方法通过 one 进行简单的运算生成文件名,keyBase() 方法通过对 ones 数组的 16 进制计算 md5 哈希并取前 6 个字符生成密钥
用 python 重写上述逻辑即可获得数据库文件名和密钥
import hashlib
def bytes2Hex(b: bytes) -> str:
"""对应Java里的bytes2Hex,使用HEX_DIGITS = 0123456789abcdef"""
HEX_DIGITS = "0123456789abcdef"
sb = []
for bt in b:
# Java byte是有符号,python bytes是无符号,直接取即可
sb.append(HEX_DIGITS[(bt >> 4) & 0x0F])
sb.append(HEX_DIGITS[bt & 0x0F])
return"".join(sb)
def keyDb():
one = 116
ones = [-1, 0, 3, 5, 1, -5]
length = len(ones)
bArr = bytearray(length + 3)
for i in range(length):
# Java byte强转,会截断到8bit
val = (one + ones[i]) & 0xff
bArr[i] = val
bArr[length] = 46 # '.'
bArr[length+1] = 100# 'd'
bArr[length+2] = 98# 'b'
return bArr.decode('ascii')
def keyBase():
ones = bytes([x & 0xfffor x in [-1, 0, 3, 5, 1, -5]])
md5_hash = hashlib.md5(ones).digest()
hex_str = bytes2Hex(md5_hash)
return hex_str[:6]
if __name__ == "__main__":
db_name = keyDb()
base_key = keyBase()
print(f"keyDb 数据库文件名: {db_name}")
print(f"keyBase 密钥前缀: {base_key}")
'''
输出内容:
keyDb 数据库文件名: stwyuo.db
keyBase 密钥前缀: c258ba
'''
文件名与刚刚找到的已知,把数据库文件用 DB Browser for SQLCipher 打开,输入密钥即可解密

发现数据库中只有 1 个 picture 表,其中只有 1 条数据,因此只拍摄了 1 张照片
9. 分析使用该密拍软件拍摄的图片,其中显示器的型号为?(答案格式:以实际为准,如TUOJIE)
C34H890WGC
SQLCipher 查看 data 字段可以直接看到照片

导出查看

型号为 C34H890WGC
10. 机主多次通过购票软件查询出行行程,在北京时间2026年6月24日20:21:33查询的目的城市为?(答案格式:南京)
百色
查找了一下手机安装的 app,就下面这个比较符合

app 包名为 com.yipiao,找到对应包的数据库 /智行火车票(com.yipiao).bak_29/apps/com.yipiao/db/zt_userInfo.db,查看 tbl traffic search his 表
把 timestamp 字段进行 Unix 时间戳转换可以看到精确时间

可以看到在该时间机主查询的目的城市为 百色
11. 请找到机主在2026-09-20 13:41:20打开的表格文件,该文件的SM3哈希值后四位是什么?(答案格式:英文大写,如12AB)
37EA
由于是问表格文件,所有去查看了一下 WPS 的历史文件,只看到了一条记录

这条记录是 xml 格式的,具体内容为
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="recent_file_key">[{"cloudCacheFileState":3,"editCount":3,"fileScope":2,"fileState":1,"id":"1a58a8f8-b03f-4fc1-9c0e-25434dd41156","name":"快捷回复-2026-04-14 10-23-48-enterprise.xlsx","filePath":"/storage/emulated/0/Android/data/cn.wps.moffice_eng/.cache/KingsoftOffice/file/download/32342bd4-6be8-46b5-8a4f-5fad2e96aff8/com.android.fileexplorer.myprovider/快捷回复-2026-04-14 10-23-48-enterprise.xlsx","userId":"","modifyDate":1789900864236,"type":0}]</string>
</map>
其中 filePath 字段记录了打开文件的完整路径,文件名为 快捷回复-2026-04-14 10-23-48-enterprise.xlsx,modifyDate 字段记录了文件上次修改的时间戳,应该就是上次打开时间,转换过来刚好是 2026-09-20 18:41:04
直接按照记录中的文件路径找不到文件,在手机做的文档备份 backup_doc.zip 中

想用 CyberChef 直接计算 SM3 哈希的,结果转半天都没算出来

最后写了个 python 脚本计算
import hashlib
def get_file_sm3(file_path: str) -> str:
"""读取文件,计算SM3,返回大写哈希值"""
sm3_obj = hashlib.new("sm3")
buf_size = 65536
with open(file_path, "rb") as f:
while data := f.read(buf_size):
sm3_obj.update(data)
return sm3_obj.hexdigest().upper()
if __name__ == "__main__":
# 目标文件名
target_file = "快捷回复-2026-04-14 10-23-48-enterprise.xlsx"
try:
result = get_file_sm3(target_file)
print(f"文件名:{target_file}")
print(f"SM3哈希:{result}")
except FileNotFoundError:
print(f"❌ 文件不存在!请把脚本和xlsx文件放在同一个文件夹,或者填写完整绝对路径。")
except Exception as err:
print(f"❌ 计算失败:{err}")
'''
输出内容:
文件名:快捷回复-2026-04-14 10-23-48-enterprise.xlsx
SM3哈希:442A27B13AF23EA3295A388DE8438E2D91D8D50242F4FAF850DE22E414BA37EA
'''
12. 请分析嫌疑人在2026-09-20 17:39:19时播放的抖音链接为?(答案格式:以实际为准,如https://www.baidu.com/abc/123asad)
https://www.douyin.com/video/7641161472526150282
直接看抖音的观看历史

13. 请分析嫌疑人在抖音中共手动提现多少元至银行卡?(答案格式:100)
1003
字节的 app 的状态数大多不存储在数据库中,而是存储在自研的 keva 键值库里面,路径为 /抖音(com.ss.android.ugc.aweme).bak_26/apps/com.ss.android.ugc.aweme/f/keva/repo/notice_detail_cache_1625556299298107/key_notice_group_86.sgv
sgv 文件通常是明文的 json,直接用记事本打开了
直接搜索 提现

找到一条明显提现成功的记录(红色箭头),金额为 1000
但是答案不对,看了网上别的佬的题解,如上图蓝色箭头,这条提现记录中有一条 \"notice_type\":92,进一步搜索可以找到另一条记录

还有个 3 元抖音支付多次立减券,这也算提现,而且只能通过 \"notice_type\":92 定位得到,给了给了
所以答案是 1003
14. 请分析机主抖音头像从哪个应用下载的可能性最大,该应用的包名是?(答案格式:以实际为准)
com.quark.browser
通过抖音账户信息定位到 keva 键值库 /抖音(com.ss.android.ugc.aweme).bak_26/apps/com.ss.android.ugc.aweme/f/keva/repo/aweme_user/1625556299298107_aweme_user_info.sgv
但是没有找到头像相关信息
但是机主在夸克浏览器中搜索过 西装男头像

下载缓存中也有一条 jpg 图片的下载记录,只能猜测是夸克浏览器下载的了,对应包名 com.quark.browser
15. 机主相册中有大量的豪车、别墅信息用来“杀猪盘”,请分析这些照片是使用什么应用进行下载的,写出该应用的包名。(答案格式:以实际为准)
uni.UNI3DFAFCD
在照片备份的 无印 目录下有大量豪车别墅的图片

猜测 无印 是个 app 的名字,果然找到了

16. 接上题,该应用使用的下载链接来自于哪个应用,请写出链接来源应用的包名。(答案格式:以实际为准)
com.ss.android.ugc.aweme
要看应用使用的下载链接,那自然要去翻这个应用的数据库 /无印(uni.UNI3DFAFCD).bak_28/apps/uni.UNI3DFAFCD/db/wuyin_database,查看 download tasks 表

其中记录了大量下载记录,下载文件大多为图片,从 fileUrl、platform 和 courceUrl 字段都能看出下载链接来自抖音

包名为 com.ss.android.ugc.aweme
手机检材-2
解压

看到 Manifest.plist,可以确定是苹果手机
弘连的软件分析不了,下面换美亚
17. 该手机的备份密码是多少?(四位数字,答案格式:1234)
1111
直接把 Manifest.plist 丢去暴力破解,按照题目提示设置 4 位数字密码

18. 该手机的备份时间是什么时候?(答案格式:2026-09-09 09:09:09)
2026-09-23 15:02:50
Manifest.plist 中会记录备份时间

不过是 UTC 时间,我们需要加 8 小时转换为 UTC+8 时间
取证工具中也能直接看

19. 该手机的序列号是什么?(答案格式:以实际为准,英文大写)
FD2W71HVJCM0
序列号在 Manifest.plist 的 SerialNumber 字段

取证工具中也能看到

20. 该手机的蓝牙MAC地址是多少?(答案格式:英文大写,如A0:A1:4E:83:16:B0)
C0:A5:3E:82:06:E0
美亚也没取到,看网上题解都是用美亚的 Qiko 取证智能体取到的,懒得搞了,直接去找原始文件吧
由于是蓝牙 MAC 地址,我们搜索 bluetooth

/WiFi/bluetooth_status.txt 记录了蓝牙 MAC 地址
21. 分析该手机日志文件,该手机电池的激活时间(UTC+8)是什么时候?(答案格式:2022-01-23 15:02:53)
2021-12-26 20:35:27
依旧是全局搜索,问电池就搜 battery
定位到日志文件 /summaries/BatteryBDC.log,往下翻到最底部找到最早的时间,应该就是电池激活时间,注意要加 8 小时把 UTC 时间转换为 UTC+8 时间
22. 分析该手机的充电记录,该手机最近一次被充至 100% 电量的时间是什么?请回答时间戳。(答案格式:以数据库中记录的原始时间戳为准)
1783544086.8534038
由于是充电,我们先全局搜索 power
题目提示要找数据库,我们定位到 /logs/powerlogs/powerlog_2026-09-23_14-24_07CAD94F.PLSQL,疑似是充电记录的数据库
最后在 PLBatteryAgent EventBackward Battery 表中发现了 CurrentCapacity 字段,疑似是当前电量,而且先减少后增多,最后稳定在 100,符合电池电量变化的规律
如上图,最近一次被充至 100% 电量的时间戳为 1783544086.8534038
23. 嫌疑人安装蝙蝠.app的时间(UTC+8)是什么?(答案格式:以首次安装请求时间为准,如2026-09-09 09:09:09)
2026-08-12 18:07:21
开始直接看了弘连取证工具的 APP 安装卸载详情
其中记录的蝙蝠 app 的安装记录时间,但是不太对,可能这不是首次请求安装的时间,还是要看源文件(上图红色箭头)
在源日志文件中搜索蝙蝠 app 的包名 com.zsim.bzychat
按照题目要求找到第一次请求包名的记录,时间为 Aug 12 10:07:21 2026,注意要加 8 小时转换为 UTC+8 时间,即 2026-08-12 18:07:21
24. 嫌疑人手机在 2026-07-09 第一次解锁屏幕是由哪个应用/服务触发的?请写出应用包名。(答案格式:www.tuojieinfo.com)
com.apple.camera
上网找了别的师傅的题解,答案还在之前那个充电记录的数据库 /logs/powerlogs/powerlog_2026-09-23_14-24_07CAD94F.PLSQL 中
首先要定位哪个表可能记录了屏幕状态
select name from sqlite_master
where type = 'table' and name like '%screen%';
定位到 PLScreenStateAgent_EventForward_ScreenState 表很可疑
回到取证工具找到对应表,转换一下时间戳
2026-07-09 当日比较早的屏幕记录主要来自下面三个应用/服务
com.apple.lock-screen:锁屏 com.apple.camera:相机 com.apple.springboard.home-screen:桌面
综合考虑下来相机比较合理
有没有佬知道为什么我第 11 题的 CyberChief 算 sm3 哈希转了这么久
还有第 13 题,有没有什么好的工具可以解析 sgv 文件
( ̄▽ ̄;)ゞ