☰
2026盘古石预赛手机取证题解析:APK 与 SQLCipher 数据提取实战
2026/10/3 12:11:02 网站建设 项目流程

1. 盘古石预赛手机取证题里 APK 与 SQLCipher 到底考什么

盘古石预赛的手机取证题,表面看是十几道问答题,实际考的是两件事:你能不能从一堆 APK 里快速定位目标应用,以及你能不能把加密数据库还原成可读的表。2026 这届的 Q1 到 Q14 基本围绕黄志远、方俊朗两份 phone.E01 检材展开,其中 Q11、Q12 是分水岭——前面靠aapt dump badging和 shared_prefs 就能拿分,后面必须反编译 APK、还原 SQLCipher 的密钥构造逻辑、再爆破登录密码哈希。

先说清楚这套题适合谁看。如果你正在准备盘古石、美亚杯这类取证比赛,或者工作中要处理 Android 检材里的加密 SQLite,这篇的步骤可以直接照做。核心检索词就三个:盘古石手机取证、APK 反编译、SQLCipher 数据提取。它们分别对应三类动作——识别应用、拆包找密钥、解密读表。

我拿到的检材挂载方式是这样的:黄志远 partition1 挂/mnt/h、partition2 挂/mnt/i;方俊朗 partition1 挂/mnt/j、partition2 挂/mnt/k。Android 的应用数据在 partition2 的data/下,APK 本体在app/下。这个目录结构决定了后面所有命令的路径,你换成自己的挂载点即可。

为什么 SQLCipher 是难点?普通 SQLite 文件头是SQLite format 3,直接sqlite3就能开。SQLCipher 加密后文件头是随机的,没有 key 连表名都读不出来。而 Flutter 应用常用的sqflite_sqlcipher插件,密钥往往不是硬编码,而是「前缀 + 时间戳 + 后缀」动态拼出来的,时间戳还藏在 shared_prefs 里。这就逼着你把 APK 反编译和文件系统取证串起来做。

Q11 的完整链路是这样的:从libapp.so里 strings 出密码模板Pgs-dbw和Good,从 shared_prefs 拿到时间戳1776839203359,拼成完整密码Pgs-dbw1776839203359Good,但直接拿它当 PRAGMA key 会失败——因为代码里还有一层substring(2, len-1)截取。截完变成s-dbw1776839203359Goo,这才是真正的 key。这个坑我在实测时踩过,后面第 5 节会专门讲。

再补一句工具准备。aapt来自 Android SDK build-tools,strings来自 binutils,SQLCipher 解密建议用 Python 的sqlcipher3库而不是系统 CLI,因为 CLI 版本和插件用的 SQLCipher 版本经常对不上,会报file is not a database。这些工具在 Kali 或 Ubuntu 上都能装,不需要额外环境。

2. TaoToken 前置:给取证脚本接一个稳定的模型推理入口

取证题里有一类活是重复且耗脑的:把几十个 APK 的 label 批量提取出来做分类,或者把 bot 执行摘要里的命令逐条归类统计。这类活适合写脚本,但脚本里如果要嵌「判断这个应用是不是短视频/理财类」的语义判断,接一个模型 API 会省很多事。我用 TaoToken 做这类辅助推理,它的接口兼容 OpenAI 格式,改个 Base URL 就能用。

先把入口说清楚。官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 根地址是 https://taotoken.net/api 。注意 API 地址后面不加任何查询参数,直接拼/v1/chat/completions就是标准路径。模型对话页面在 https://taotoken.net/api-keys 之外还有独立的对话入口,如果你只是想先试试模型能不能正确分类应用,可以直接去模型对话页 https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 手动问几句。

拿 Key 的流程不复杂:登录后进控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 创建一个新 Key。创建时建议给 Key 起个能认出来的名字,比如forensics-apk-classify,方便后面在脚本里区分用途。Key 只在创建时完整显示一次,复制下来存到环境变量里,别写死在脚本中。

如果你打算长期跑取证脚本、批量处理检材,可以考虑 Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite ,它的额度模型更适合这种持续调用的场景。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各语言的调用示例,Python 用openai库最省事。

这里要强调一点:TaoToken 在这套流程里只承担「语义判断」和「结果归纳」的辅助角色,真正的取证证据链——APK 包名、shared_prefs 字段、数据库表内容——必须来自本地检材本身。模型可以帮你把aapt输出的一堆 label 分类成「短视频/理财/工具」,但你不能让模型替你「猜」答案。取证的可验证性要求每个结论都能回溯到具体文件和命令输出,这一点在第 4 节的验证动作里会体现。

环境变量配置建议这样写,后面脚本直接读:

export TAOTOKEN_API_KEY="sk-你的key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"

Python 侧验证连通性:

import os from openai import OpenAI client = OpenAI( api_key=os.environ["TAOTOKEN_API_KEY"], base_url=os.environ["TAOTOKEN_BASE_URL"] + "/v1", ) resp = client.chat.completions.create( model="gpt-4o-mini", messages=[{"role": "user", "content": "回复 ok 两个字母即可"}], ) print(resp.choices[0].message.content)

跑通会打印ok。这一步只是确认 Key 和地址没问题,真正的取证脚本在第 3 节。

3. 可复制配置:aapt 解析、SQLCipher 解密与 settings 片段

这一节是全文的技术核心,给的是能直接复制运行的命令和配置。分三块:APK 信息提取、SQLCipher 密钥还原、以及一个把模型调用接进取证脚本的 settings 片段。

3.1 aapt 批量提取 APK 包信息

先确认aapt可用:

aapt version # 输出类似: Android Asset Packaging Tool, v0.2-xxxxxx

单个 APK 提取包名和 label:

aapt dump badging /mnt/i/app/com.ss.android.ugc.aweme-*/base.apk | grep -E "^package:|^application-label:"

输出:

package: name='com.ss.android.ugc.aweme' versionCode='...' versionName='...' application-label:'抖音'

批量跑整个app/目录,把结果存成 TSV,方便后面分类:

for apk in /mnt/i/app/*/base.apk; do pkg=$(aapt dump badging "$apk" 2>/dev/null | grep "^package:" | sed "s/.*name='\([^']*\)'.*/\1/") label=$(aapt dump badging "$apk" 2>/dev/null | grep "^application-label:" | sed "s/application-label:'\([^']*\)'/\1/") echo -e "${pkg}\t${label}" done > /tmp/apk_list.tsv

这个 TSV 就是 Q1、Q13 的原始证据。Q1 要数短视频应用,Q13 要数理财应用,分类标准要写清楚:哔哩哔哩算长视频不算短视频,京东购物算电商不算理财。这些判断可以人工做,也可以把 TSV 丢给模型做初筛,但最终归类要你自己核对。

3.2 SQLCipher 密钥还原与解密

以 Q11 的social_chat.db为例。第一步从 shared_prefs 拿完整密码:

cat /mnt/i/data/com.socialchat.social_chat_app/shared_prefs/FlutterSharedPreferences.xml

关键字段:

<string name="flutter.db_password">Pgs-dbw1776839203359Good</string> <string name="flutter.username">huangzhiyuan</string> <string name="flutter.user_id">usr_heiked</string>

第二步从 APK 的libapp.so确认截取逻辑:

strings /tmp/socialchat_analysis/lib/arm64-v8a/libapp.so | grep -iE "Pgs-|substring|normalizeOrSubstring|sqlcipher"

输出里能看到_normalizeOrSubstring、_substringUnchecked,结合Pgs-dbw和Good两个模板串,推断出完整密码要经过substring(2, len-1)截取。完整密码 24 字符,截完 21 字符。

第三步用 Pythonsqlcipher3解密:

import sqlcipher3 as sqlite3 full_pwd = "Pgs-dbw1776839203359Good" sub_pwd = full_pwd[2:-1] # s-dbw1776839203359Goo print("key length:", len(sub_pwd)) conn = sqlite3.connect("/mnt/i/data/com.socialchat.social_chat_app/databases/social_chat.db") cur = conn.cursor() cur.execute(f"PRAGMA key = '{sub_pwd}'") tables = cur.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall() print(tables)

成功会打印 7 个表:user, friendship, conversation, conversation_member, message, message_file, android_metadata。如果报file is not a database,说明 key 不对或 SQLCipher 版本不兼容,优先换sqlcipher3库重试。

3.3 把模型调用接进取证脚本的 settings 片段

下面这个 JSON 配置放在脚本同目录,用来控制模型辅助分类的行为。路径和字段名按你项目实际调整:

{ "taotoken": { "base_url": "https://taotoken.net/api/v1", "api_key_env": "TAOTOKEN_API_KEY", "model": "gpt-4o-mini", "timeout": 30 }, "forensics": { "apk_list_path": "/tmp/apk_list.tsv", "classify_prompt": "把下列应用按 短视频/理财/工具/其他 分类,只输出 JSON", "evidence_dir": "/mnt/i/data" } }

对应的 Python 读取逻辑:

import json, os from openai import OpenAI cfg = json.load(open("settings.json")) client = OpenAI( api_key=os.environ[cfg["taotoken"]["api_key_env"]], base_url=cfg["taotoken"]["base_url"], ) rows = open(cfg["forensics"]["apk_list_path"]).read().strip().split("\n") resp = client.chat.completions.create( model=cfg["taotoken"]["model"], messages=[ {"role": "system", "content": cfg["forensics"]["classify_prompt"]}, {"role": "user", "content": "\n".join(rows)}, ], ) print(resp.choices[0].message.content)

注意base_url这里带了/v1,因为openai库会自动拼/chat/completions。如果你用别的 HTTP 客户端直接请求,地址就是https://taotoken.net/api/v1/chat/completions。

4. 验证请求与成功结果:解密完整性怎么确认

取证和普通开发最大的区别是:跑通不等于对。SQLCipher 解密出来能读表,只说明 key 对了,还要确认数据完整、没被截断、字段没乱码。这一节给几个具体的验证动作。

4.1 表结构与行数核对

解密后先看表数量和各表行数:

cur.execute(f"PRAGMA key = '{sub_pwd}'") tables = [r[0] for r in cur.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall()] for t in tables: n = cur.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0] print(f"{t}: {n} rows")

Q11 的库正常输出 7 个表,message表行数应该和对话消息数对得上。如果某个表行数为 0 或报错,说明解密不完整。

4.2 关键字段交叉验证

Q11 要的是登录密码,验证方式是拿数据库里的password_hash和password_salt,用爆破出的密码重新算一遍哈希,看能不能对上:

import hashlib salt = "a3f8d9c2e1b4h7g6k9m2n5p8q1r4t7w" pwd = "08164085" inner = hashlib.sha256((pwd + salt).encode()).hexdigest() outer = hashlib.sha256(inner.encode()).hexdigest() print("outer:", outer) # 期望: fc29eb768c139c05c0bfcb697d9b26d194878a66451b3ab91b202e9710874a63

和数据库里user表的password_hash完全一致,才算验证通过。这个动作是 Q11 的 L2 双证据交叉验证——数据库哈希 + 独立计算匹配。

4.3 Q12 的文件消息 JOIN 验证

Q12 问「发送过几个文件给军师」,验证方式是 JOIN 查询:

conv = cur.execute("SELECT * FROM conversation WHERE name='军师'").fetchone() print("conv:", conv) files = cur.execute(""" SELECT mf.* FROM message_file mf JOIN message m ON mf.msg_id = m.id WHERE m.conversation_id = ? AND m.sender_id = 'usr_heiked' AND m.type = 'file' """, (conv[0],)).fetchall() for f in files: print(f)

正常输出 2 条:木马.zip(5242880 字节)和木马_v1.2.zip(5505024 字节)。如果只查到 1 条,检查type字段是不是file,有些应用用attachment或数字枚举。

4.4 Discord kv-storage 的 JSON 解析验证

Q5 到 Q10 依赖 Discord 的kv-storage数据库。这个库有个坑:所有列都是 bytes 类型,JSON 数据在col[7],前面还有\x08控制字符。解析方式:

import sqlite3, json conn = sqlite3.connect("/mnt/i/data/com.discord/files/kv-storage/@account.1457974771206852664/a") rows = conn.execute("SELECT rowid, * FROM messages0").fetchall() for row in rows: raw = row[7] if raw is None: continue s = raw.decode("utf-8", errors="replace").lstrip("\x08\x07\x00") try: msg = json.loads(s) except json.JSONDecodeError: continue content = msg.get("message", msg).get("content", "") if "pairing code" in content: print(row[0], content)

Q8 的配对码3EXEQ5R8就在rowid=15的 bot 消息里。验证它是否生效,看rowid=15之后用户有没有重新发消息、bot 有没有正常响应——rowid=19bot 回复了正常内容,说明配对成功。

5. 本篇常见错排查:401、local proxy failed、reading choices、OAuth

这一节把取证和 API 调用两类报错放一起讲,都是实测踩过的。

5.1 API 返回 401 Unauthorized

现象:脚本调用模型接口报401,body 里写invalid api key。

排查顺序:先确认环境变量有没有生效,echo $TAOTOKEN_API_KEY看是不是空;再确认 Key 有没有多余空格或换行,复制时容易带上;最后确认base_url拼对了没有——openai库要https://taotoken.net/api/v1,直接 HTTP 请求要https://taotoken.net/api/v1/chat/completions。如果 Key 是在 API Keys 页面新建的,确认没被删除或禁用。

5.2 local proxy failed

现象:请求报local proxy failed或连接被拒。

这个通常是本地网络配置问题,不是 Key 的问题。检查你的 HTTP 客户端有没有读到系统里残留的代理环境变量,env | grep -i proxy看一下。如果有HTTP_PROXY、HTTPS_PROXY指向一个已经关掉的本地端口,请求就会失败。清掉这些变量再试:

unset HTTP_PROXY HTTPS_PROXY ALL_PROXY

5.3 reading choices 报错

现象:KeyError: 'choices'或reading 'choices'时崩。

原因一般是响应体不是标准 OpenAI 格式,可能是错误响应被当成正常响应解析了。加一层判断:

data = resp.model_dump() if hasattr(resp, "model_dump") else resp if "choices" not in data: print("unexpected response:", data) else: print(data["choices"][0]["message"]["content"])

如果打印出来是{"error": {...}},按错误信息定位,常见的是模型名写错或额度不足。

5.4 OAuth 相关报错

现象:报OAuth token expired或invalid_grant。

如果你用的是需要 OAuth 的客户端(比如某些 CLI 工具),token 过期就重新走一遍授权流程。纯 API Key 调用不会遇到这个。注意区分:API Key 是长期凭证,OAuth token 是短期凭证,两者不要混用。

5.5 SQLCipher 解密报 file is not a database

这是取证侧最高频的错。三个原因:key 不对、截取规则没应用、SQLCipher 版本不兼容。按顺序排查——先用strings确认 APK 里的密码模板和截取函数,再确认 shared_prefs 里的时间戳拼对了,最后换sqlcipher3库重试。系统sqlcipherCLI 如果报这个错,八成是版本问题,别在 CLI 上耗时间。

5.6 aapt 报 command not found

aapt不在 PATH 里。找到 Android SDK 的 build-tools 目录,把对应版本加进去:

export PATH=$PATH:$HOME/Android/Sdk/build-tools/34.0.0 aapt version

如果检材里的 APK 是 split APK(有base.apk和多个split_config.*.apk),aapt要指向base.apk,包名和 label 都在它里面。

6. 继续把这条链路用起来

这套题做完,最有价值的不是那 14 个答案,而是「APK 反编译 → shared_prefs 取参 → SQLCipher 解密 → 哈希验证」这条可复用的链路。下次遇到 Flutter 应用的加密库,你大概率还是这套动作:先strings找密码模板,再翻 shared_prefs 找动态参数,最后用 Python 解密验证。

如果你想把模型辅助分类这块固化下来,接入文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 里有完整的参数说明,API Keys 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 管理。想先手动试试模型对应用分类的判断准不准,模型对话页 https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite 可以直接问。长期跑批量取证脚本的话,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 的额度更适合持续调用。

最后留一个实操建议:每次解密成功后,把PRAGMA key、表结构、关键行数、哈希验证结果一起写进取证记录。比赛里这是拿分依据,工作里这是证据链。别只存一个「解密成功」的截图,那证明不了任何东西。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询