Android 数据存储总览

Android 取证最难的不是"读不出来"。

最后更新 2026-10-09版本 v1.0维护 DigiForensics查看历史 0

关键词:Android 分区、userdata、FBE、SELinux、ADB、多用户 难度:入门 前置知识:磁盘分区与 GPT 结构、Linux 目录结构、哈希与只读挂载规范 相关文章:应用私有数据与外部存储、APK 逆向与 Manifest 分析

一、概述

Android 取证最难的不是"读不出来"。

是读出来的那一份数据,到底代表哪个时刻的设备状态。

手机几乎从不关机。userdata 是全盘加密的,没有密钥就是一整片随机字节。/data/data/ 上还压着 SELinux,不是 root 就进不去。更麻烦的是,一台设备上可能同时跑着机主、工作资料、访客三套彼此隔离的数据。

真正把人带偏的失败模式,是读到了一部分,然后以为读全了。

设备锁着的时候,adb shell ls /data/data/ 不报错,只是给回一份很短的包名列表。这份输出和"设备上只装过这几个应用"在终端上根本分不出来。案件于是沿着"该设备使用痕迹很少"走下去,真正的证据一次都没碰到。

所以本篇要解决的就一件事:动手之前先把结论边界划出来。

它在流程里的位置是采集环节的前置判定层。不产出证据材料,只决定后面能不能产出证据。

上游是镜像到底是块设备还是逻辑提取、ro.crypto.* 属性读不读得到;下游是应用数据提取(第 4 步)、即时通讯数据定位(见 即时通讯取证)和结论表述(见 4.4 结论表述)。

跳过这一层直接提取,后面每一章的结论都失去边界。

涉及的证据形态(具体到路径与结构,不是"日志文件"这种大类):

形态 具体位置 结构特征
设备状态快照 adb devices -l、getprop、pm list users、getenforce 几 KB 纯文本,是结论边界本身
包管理台账 /data/system/packages.xml 单行长 XML,含 firstInstallTime / installerPackageName,含卸载保留条目
加密策略 ro.crypto.state / type / mountstate、/data/misc/vold/ block=FDE / file=FBE,决定 CE 与 DE 分层
CE 数据层 /data/user/<userId>/<包名>/ 凭据解锁后才挂载,历史路径 /data/data/ 是软链
DE 数据层 /data/user_de/<userId>/ 开机即可读,与锁屏凭据无关
分区表 /dev/block/by-name/、/proc/partitions、GPT 注意 super 动态分区与 _a/_b 双槽
系统日志 adb bugreport 打包的 logcat + dumpsys 压缩包,含全量服务转储

能回答什么:这台设备处于什么加密状态;能拿到哪些用户档案、哪些数据层;哪些应用装过、什么时候装的、从哪装的;本次提取的覆盖上限在哪里。

不能回答什么:具体应用里存了什么内容(那是 应用私有数据 与 SQLite 那几篇的事);设备被锁定时的 CE 内容,拿不到就记为受限,别写成"没有";设备上发生过什么行为,得先有时间线;服务器侧数据不在检材范围内。

一句话概括:本篇给的是"能做什么"的前置判断,不是"做了什么"的结论。

本文不做的事:不讨论 bootloader 解锁,也不提供 FRP 绕过。设备已经锁定时的手段也不写——不是没写,是这类手段在取证里就是攻击。

设备处理不了的时候,正确动作是三步:停止操作、记录状态、回到委托方沟通。

读者前提:磁盘分区与 GPT 结构、Linux 目录树与权限模型、哈希与只读挂载的规范(见 取证流程与原则)。文件级深度解析在 NTFS 结构与 MFT 解析 与 ext4 结构与 inode 里已经铺过。

getprop 都没跑过的话,先按 三、操作步骤 的第 1 步做一遍。别直接跳到提取。

另外有个技术前提值得先看一眼:委托方交来的手机如果是锁屏且未授权解锁,TWRP 恢复分区取证 讲的物理镜像路线才是对的,本篇的逻辑提取路线不适用。

二、核心原理

2.1 分区结构:哪些分区有取证价值

现代 Android 使用 GPT 分区表,/dev/block/by-name/ 下的软链接是标准访问方式。

分区 常见名 内容 取证价值
引导程序 bootloader 厂商引导链(高通 sbl/abl/devcfg/tz/hyp) ★★ 设备识别
内核/启动 boot、init_boot 启动镜像。init_boot 为 Android 13 起新增,具体划分随版本与设备而异,以实际分区表为准 ★★★ 伪造启动、反取证可能留痕
恢复 / 系统 / 厂商 recovery / system / vendor 恢复镜像;AOSP 框架与系统应用(Android 10+ 只读,priv-app 可作分析对象);OEM HAL 与内核模块 ★★ ~ ★★★
用户数据 ** userdata** ** /data 全部内容,本篇主战场** ★★★★★
逻辑容器 / 启动状态 super / misc 动态分区容器,容纳 system/vendor/product/odm;引导计数与恢复标记 ★★

必须掌握的现代变化:Android 10 引入 A/B 无缝更新与 Virtual A/B,系统分区变成 super 内的逻辑分区,同一物理分区上同时存在 _a、_b 两个槽位。只读一侧会漏掉另一槽的旧系统版本与已删除系统应用数据。

adb shell ls -l /dev/block/by-name/ ; adb shell cat /proc/partitions   # 设备侧真实布局(优先)
fdisk -l DigiForensics.dd                                            # 镜像侧先确认有无 super 动态分区
partx -s -o START,SECTORS,NAME DigiForensics.dd                       # util-linux 的 partx

2.2 /data 是取证主战场

/data
├── data/            ← 历史路径,Android 7+ 已是 /data/user/0/ 的兼容软链
│   └── <包名>/
├── user/            ← ★ 实际存放处
│   ├── 0/           ← 主用户
│   └── 10/          ← 工作资料 / 次要用户(userId 常见 10–15)
├── user_de/         ← ★ Device-Encrypted(DE)数据,锁屏前即可读
│   └── 0/
├── app/             ← 预装应用 APK
├── media/           ← 内部共享存储(DCIM、Download 等,FUSE 呈现)
├── misc/
│   ├── keystore/    ← 应用密钥材料(受 TEE 保护)
│   └── vold/        ← FBE 加密策略
└── system/          ← 注意:这不是 system 分区,而是用户数据
    ├── packages.xml ← ★★ 全部已安装应用与版本、uid、安装时间戳
    └── users/0/

两条最关键的结构事实:

(1)/data/data/<包名> 与 /data/user/<userId>/<包名> 是同一份数据。 Android 7.0 之后应用私有目录已经迁到 /data/user/<userId>/,/data/data 变成兼容软链。

两处都要看。不同工具、不同系统版本吐出来的路径不一样,只查一个容易漏。

adb shell su -c "ls -ld /data/data /data/user/0"   # 典型输出:/data/data -> /data/user/0

(2)/data/user_de/0/ 是"解锁前可读"的那一半。 FBE 把凭据加密(CE)与设备加密(DE)分开,开机后、用户输入锁屏密码之前,系统只解密 DE 部分。

设备锁着的时候,user_de 下的东西照样读得到。这是一份常被忽略的免费证据源。

目录 全称 可读时机 典型内容
/data/user/0/ Credential-Encrypted 必须已解锁 聊天记录、账号 token、私密文件
/data/user_de/0/ Device-Encrypted 开机即可读 闹钟、后台数据、部分缓存

/data/system/packages.xml 是性价比最高的单个文件:一次回答"这台设备装过什么、什么被卸载过"。

2.3 加密状态判断:三个属性的组合

这是第一个要做的判断,而且有确定依据:StorageManager.isEncrypted() / isFileEncryptedNativeOnly() / isBlockEncrypted() 读的就是下面这三个属性。

ro.crypto.state 取 encrypted / unencrypted;ro.crypto.type 取 block(FDE)/ file(FBE)/ aes(早期块加密遗留)/ legacy;ro.crypto.mountstate 取 mounted / unmounted,指示 DE 部分是否已挂载。

FBE 和 FDE 的取证差异:

维度 FDE(type=block) FBE(type=file)
加密粒度 整个 userdata 扇区 每个文件 + 文件名(文件名用 AES-CBC-CTS 加密)
锁屏后 /data 整体不可读 /data/user/0 不可读,/data/user_de/0 可读
物理镜像价值 无密钥基本无用 密钥未解时连文件名都看不到
典型版本 Android 5.0–9 Android 7.0 起(Android 10 起强制 FBE)
密钥保护 用户凭据经 Keymaster 保护 硬件密钥派生 512 位文件密钥,用户凭据只保护硬件密钥
# ① 加密三件套(最重要的一条判断)+ ② 一次性固定所有加密相关属性 + ③ fscrypt 挂载点
adb shell getprop ro.crypto.state ; adb shell getprop ro.crypto.type ; adb shell getprop ro.crypto.mountstate
adb shell getprop | grep -iE 'crypto|encrypt|keystore|vold|selinux' | tee Android-crypto-props.txt
sha256sum Android-crypto-props.txt ; adb shell mount | grep -iE 'fscrypt|/data'

重要的现实:FBE 的文件密钥在 TEE(可信执行环境)内由硬件密钥派生,即使有 root 也拿不到明文文件密钥。root 能读到的是"已解锁状态下被密钥解密的明文数据",以及 /data/misc/vold 下的加密策略结构。"有了 root 就等于能解 FBE"是错误认知——root 的价值在于跨过 SELinux 权限墙,不是破开 TEE。

2.4 SELinux 与 root:两道墙不是一回事

DAC(传统权限位)下 shell 不属于任何应用 uid,可 su 提权到 root;SELinux 是强制访问控制标签,root 也会被策略拒绝,需 permissive 或策略绕过。

adb shell getenforce                    # Enforcing(正常)/ Permissive(只记录不拦)
adb shell su -c id                      # uid=0(root) → 已 root
adb shell su -c "id -Z"                 # u:r:magisk:s0 ← 验证 su 后的域

取证价值常被低估的一点:SuperSU / Magisk 这类 root 工具会在设备上留下自己的数据库与日志(哪个应用何时被授予 root、授权时是否录屏/录 touch)。在网络诈骗、赌博、刷单类案件中,"root 授权记录"本身就是"该设备被用于自动化操作"的直接证据。

2.5 多用户与工作资料

adb shell pm list users
# UserInfo{0:机主:13} running
# UserInfo{10:工作资料:30}          ← 工作资料常见 userId=10

userId 0 是机主(主体数据,但不一定是嫌疑人使用的数据);10–14 通常是工作资料,业务 App 数据与个人账号互不可见,必须单独提取;其余为访客、访客分身,常用于"临时"使用,是反侦查行为的重要指标。

工作资料为什么重要:企业微信、钉钉在 work profile 下有独立沙盒,聊天记录与个人账号互不可见。只取 /data/user/0 会系统性漏掉全部工作场景证据。

iOS 侧没有这套分区与多用户机制,取证走的是 iTunes 备份与沙盒路径两条路,Manifest.db 怎么索引、备份怎么解密见第 06 篇。


三、操作步骤

第 1 步:建立连接并固定设备状态

adb devices -l | tee adb-devices.txt    # 授权状态本身是检材信息
{
  echo "=== date ===";  adb shell date;        echo "=== getprop ===";  adb shell getprop
  echo "=== id ===";    adb shell id;          echo "=== getenforce ==="; adb shell getenforce
  echo "=== users ==="; adb shell pm list users; echo "=== packages ==="; adb shell pm list packages -f -U
} > device-state.txt 2>&1
sha256sum device-state.txt

第 2 步:判断加密与访问级别

adb shell getprop ro.crypto.state ; adb shell getprop ro.crypto.type   # encrypted/unencrypted;block(FDE)/file(FBE)
adb shell getprop ro.crypto.mountstate ; adb shell mount | grep '/data/user/0'   # mounted → CE 已挂载
adb shell id ; adb shell su -c id ; adb shell su -c "ls /data/data/"  # 真能列出目录才算穿透成功

第 3 步:解锁与提权(仅在已授权检材上执行)

PKG=com.digiforensics.sample
adb shell mount | grep '/data/user/0'      # ① FBE 设备须先在设备端输入锁屏凭据完成解锁
adb shell su -c "ls -la /data/data/"       # ② 已 root 设备
adb shell pm path $PKG                      # ③ 未 root 的替代路径:定位 APK,无需 root
adb shell dumpsys package $PKG | head -60   #    权限、组件、签名
adb shell "run-as $PKG" ls -la             #    仅 debuggable=true 的应用

⚠️ adb shell su -c 会在检材上执行命令,写入系统日志与 shell 历史。 正式案件应:① 在检查笔录中记录操作依据;② 每次操作后重新哈希关键文件确认未被改写;③ 只读取不写入——不要 rm、不要改权限、不要 settings put。

第 4 步:提取应用私有数据

PKG=com.digiforensics.sample
adb shell su -c "du -sh /data/data/$PKG"
adb shell su -c "find /data/data/$PKG -maxdepth 2 -type d" | tee app-dirs.txt

# ① 整目录 tar 流式导出(首选;tar 包可哈希为单一证据容器)
adb shell su -c "tar -cf - -C /data/data $PKG" > extracted-$PKG.tar
tar -tf extracted-$PKG.tar | head -50          # 先列不拆,确认包完整
mkdir -p extracted/$PKG && tar -xf extracted-$PKG.tar -C extracted/$PKG

# ② 按目录 pull(tar 不可用时)
adb pull /data/data/$PKG/databases  extracted/$PKG/

# ③ 立即哈希固定
find extracted/$PKG -type f -exec sha256sum {} \; > hashes-app.txt

adb pull 对权限与符号链接敏感。 目录中有 socket、特殊设备文件或 SELinux 拒绝路径时 pull 会失败,此时用 tar(方式 ①)更稳。

第 5 步:提取系统级工件与 adb backup 的现状

adb pull /data/system/packages.xml   extracted/       # 最高性价比
adb pull /sdcard/Download/            extracted/
adb pull /sdcard/Android/media/      extracted/
adb bugreport device-bugreport.zip                    # 含 logcat + 全量 dumpsys
sha256sum device-bugreport.zip

adb backup 的现状:Android ≤ 11 尚可运行但需设备端人工确认,且大量应用 allowBackup="false" 直接拒绝;Android 12/13 各版本与厂商 ROM 行为不一致。能否使用不能靠记忆版本号,必须实测(看是否弹出确认、是否返回 Unable to backup)。覆盖面不可控,不应作为取证方案——现代主流路径是 root + 目录级提取;allowBackup 检查方法见 03。

第 6 步:验证

# ① 抽样比对设备侧与主机侧哈希(不一致 → 检查是否因 WAL 未同步或权限截断)
adb shell su -c "md5sum /data/data/com.digiforensics.sample/databases/app.db"
md5sum extracted/com.digiforensics.sample/databases/app.db
# ② 记录时间基准(设备时间用户可改 + 分析机时间 + 时差)
adb shell date; date
# ③ 确认关键文件可读
sqlite3 extracted/com.digiforensics.sample/databases/app.db ".tables"
# 报 "file is not a database" → 可能是加密库,见 [09](/wiki/02-sqlite-wal-09dfe6b3)

四、常见陷阱

Android 取证最贵的错误不是「没读到」,而是读到了一部分却以为读全了。

这一章把四类最容易出错的环节拆开:访问级别、路径覆盖、状态固定、结论表述。每一条都给机制、代价和可执行的替代动作。

4.1 加密、权限与解锁状态

陷阱 1