APK 逆向与 Manifest 分析

APK 是移动取证里第一份也是最容易被跳过的证据。

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

关键词:APK、AndroidManifest、aapt、apktool、jadx、apksigner、加固 难度:进阶 前置知识:Android 组件模型、XML 结构、命令行工具基础、SHA-1 与证书体系 相关文章:Android 数据存储总览、Frida 动态 Hook

一、概述

APK 是移动取证里第一份也是最容易被跳过的证据。

很多分析者拿到手机就直接翻 /data/data/,略过了两个更基础的问题:这个应用到底是什么?(包名、版本、签名证书、下载来源)它在设备上能做什么?(申请了哪些权限、哪些组件被导出、是否允许明文通信)

两个问题的答案都在 AndroidManifest.xml 里。而且不需要解密、不需要 root、不需要任何反编译工具 —— aapt dump badging 一条命令就能读出最关键的一批字段。

跳过这一步会发生什么:后面所有分析都建立在「这是一个赌博类 App」「这是一次正常的社交应用使用」这类未经核实的判断上。

包名对不上、版本比设备上装的旧、签名与官方不一致 —— 这三个事实任意一个成立,都可能改变定性方向,而它们只在 APK 里写着。

同样常见的失败是成本误判:判断不出加固与否,就没法决定接下来是静态读代码还是必须上动态。两条路的工作量差着数量级。

它在取证流程里的位置:同时服务采集与分析两环,且在分析环的最前面。

采集环的用法是:APK 从设备侧取出后先做基本属性提取。这条路对未 root 设备同样成立(pm path 无需权限),因此它常常是唯一能在无 root 情况下取得的能力画像来源。

分析环的用法是:根据 Manifest 声明反推该看私有目录里的哪些文件(见 应用私有数据),以及根据加固判定决定是否进入 Frida 动态 Hook。

上游依赖 APK 的获取与固定(见 第 1 步)。下游支撑三件事:身份认定(签名是密钥层面的唯一权威来源)、能力画像(权限组合与导出组件)、证据解释(为什么这个库打不开)。

涉及的证据形态(具体到文件名与结构):

形态 具体位置 结构特征
包声明 AndroidManifest.xml 二进制 AXML(字符串池 + 资源 ID),不能直接文本打开
业务代码 classes.dex / classes2.dex… Dalvik 字节码,多 dex 是现代应用的常态
签名 META-INF/MANIFEST.MF、*.SF、*.RSA v1 方案;v2/v3/v4 签名不在 META-INF 里,须用 apksigner 验证
native 库 lib/{arm64-v8a,armeabi-v7a,x86}/ 加固识别的主要依据,库名与体积是信号
原始资源 assets/、resources.arsc 硬编码 API 地址、密钥、渠道号的常驻位置
安装台账 /data/system/packages.xml 的包条目 与 APK 的 package 字段交叉验证对应关系
证书身份 签名证书的 SHA-256 指纹 同签名多包名 = 同一开发者或同一批打包工具

能回答什么:这是哪个应用、什么版本、由谁签名、从哪装的、申请了哪些权限、哪些组件可被外部调用、是否加固。回答这些不需要解密任何数据。

不能回答什么:应用运行时拿到了什么数据(权限声明 ≠ 实际调用);数据库里存了什么(加固与密钥在 lib/ 里);签名一致就代表同一开发者(证书可能是共享的);allowBackup=true 在 Android 12 之后意味着能用 adb backup(该路径已废弃,覆盖面不可控,见 Android 数据存储总览)。

本文不做的事:不在检材上执行 APK、不做脱壳、不构造任何针对商业应用保护机制的绕过手段。

加固识别的结论到此为止:判定「有壳」,然后说明下一步需要什么条件。

反取证视角下同样重要 —— 本篇的字段集也是识别「APK 被重打包替换」的判据清单,见 反取证手法与时间戳篡改检测。

读者前提:ZIP 结构、XML 基础,以及对 Android 组件模型(Activity / Service / BroadcastReceiver / ContentProvider)有基本认识。命令行工具只需要 aapt(或 aapt2)与 apktool。

开始之前有一件事必须先确认:APK 是从检材上原样取下来的吗?

按 第 1 步 固定哈希之后再看后面的内容。本篇所有结论都建立在「这份 APK 就是设备上那个」这个前提上,前提不成立时,权限画像与加固判定都会指向错误的对象。

二、核心原理

2.1 APK 的内部结构

APK 本质是一个 ZIP 包,但内部有一份编译后的二进制 XML(AXML):

app.apk (ZIP)
├── AndroidManifest.xml    ← ★ 编译后的二进制 XML,需 aapt/aapt2 解析
├── classes.dex            ← ★ Dalvik 字节码(主代码);classes2.dex… 为多 dex
├── lib/{arm64-v8a,armeabi-v7a,x86_64}/  ← native 库,按 ABI 分目录
├── assets/                ← 原始资源(不编译),可能含配置、密钥、字体
├── res/                   ← 编译后的资源(layout、drawable、values)
├── resources.arsc         ← 资源表(字符串、样式、ID 映射)
└── META-INF/              ← ★ 签名:MANIFEST.MF(全文件 SHA-1 清单)、*.SF、*.RSA/*.DSA/*.EC
组成 取证价值 关键点
AndroidManifest.xml ★★★★★ 所有权限与组件的源头
META-INF/ ★★★★★ 签名证书是密钥层面身份的唯一权威来源
classes*.dex ★★★★ 业务逻辑,jadx 反编译
lib/*.so ★★★ 加固识别的主要依据
assets/ ★★★ 常含硬编码配置、密钥、渠道号
resources.arsc / res/ ★★ 硬编码字符串常在前者

一个实用的取证结论:resources.arsc 与 assets/ 中的硬编码字符串(API 地址、密钥、渠道号)经常是免费的高价值线索——strings 扫一遍常常几秒就能找到明文密钥或后台地址。

2.2 Manifest 的关键字段

AndroidManifest.xml 是 XML,但存的是二进制 AXML 格式(字符串池 + 资源 ID),不能直接用文本编辑器打开。

字段 取证意义 高价值判定
package 应用唯一标识 与 packages.xml 交叉验证包的对应关系
versionCode / versionName 版本 版本差异可能对应不同功能或漏洞修复
android:debuggable 是否调试包 true = 测试包,正式发布应用应为 false
android:allowBackup 是否允许备份 true = 数据可被 adb backup 提取(Android 12 前)
android:usesCleartextTraffic 允许明文 HTTP true = 存在流量被窃听风险
android:networkSecurityConfig 自定义网络安全配置 指向 XML 资源文件,指定是否信任用户 CA
android:usesPermission / exported 申请的权限 / 组件可被外部调用 见下方风险分级;exported=true 的组件可能被恶意 App 调用
android:sharedUserId 共享 UID 多个应用共用 UID = 可互相读数据
requestLegacyExternalStorage / appComponentFactory 绕过分区存储 / 组件工厂 前者 Android 10 常用,后者自定义即暗示加固或热修复框架

这可以支持「该应用由非官方渠道/非正规流程打包」的判断,是「这批 APK 是同一个人生成的一批工具」的强线索。但仍是线索,不是单独定案依据。

allowBackup=true 的取证意义:Android 12 之前 adb backup 可据此提取应用数据。虽已废弃,但它是判断「该应用数据可被其他手段绕过提取」的指标。

很多金融类应用会显式设为 false。反过来说,true 意味着开发者未做这层保护。

2.3 权限风险分级

等级 权限 取证价值
极高 READ_SMS、RECEIVE_SMS、SEND_SMS、READ_CALL_LOG、WRITE_CALL_LOG 短信与通话是核心证据来源,能读就能伪造
高 ACCESS_FINE_LOCATION、ACCESS_COARSE_LOCATION、CAMERA、RECORD_AUDIO、READ_CONTACTS、READ_PHONE_STATE 位置、影像、录音、通讯录
中 READ_EXTERNAL_STORAGE、WRITE_EXTERNAL_STORAGE、READ_MEDIA_IMAGES、READ_PHONE_NUMBERS、GET_ACCOUNTS、READ_CALENDAR 媒体文档与身份关联
低 INTERNET、ACCESS_NETWORK_STATE、ACCESS_WIFI_STATE、VIBRATE、WAKE_LOCK 网络与硬件,几乎所有应用都有

实战的读法:不是「权限越多越可疑」,而是看权限组合。

例如一个短视频应用同时持有 CAMERA + RECORD_AUDIO + READ_CONTACTS + ACCESS_FINE_LOCATION,且全部在 targetSdkVersion 允许的范围内被授予 —— 这本身就是一个值得追问的事实。

⚠️ targetSdkVersion 决定了权限的实际约束强度。 Android 运行时权限模型(用户可手动关闭)对 targetSdkVersion ≥ 23 的应用生效;老应用则安装即全给。取证时需结合 dumpsys package 的实际授予状态,而不是只看 Manifest 声明。

2.4 加固识别

加固的核心思路是把真正的 Application 类替换掉,在启动最早阶段解密 DEX 内存,因此 jadx 看到的只是壳代码。

识别通常看六类信号:

  • so 库名:libjiagu*.so、libshell*.so、libDexHelper.so、libprotect*.so
  • Application 类名:指向 StubApplication、ShellApplication 等第三方壳包,且与包名无关
  • DEX 数量:只有一个很小的 classes.dex,真实代码在 assets 的加密文件里
  • assets 加密文件:assets/jiagu_*.dat、assets/*.bin,体积与业务量成正比
  • 自定义 appComponentFactory 或非常规 multidex 实现
  • 冷启动明显变慢(加密解密开销)

jadx 结果中主类几乎无业务方法、只有大量 native 声明,是综合佐证。

前四项属强信号,单独出现即应列为「疑似加固」,不能仅凭启动耗时下结论。

unzip -l app.apk | grep '\.so$'      # 41 1234567 lib/arm64-v8a/libjiagu_v1.so  ← 壳
#                                    # 88  234567 lib/arm64-v8a/libappcore.so   ← 业务
unzip -p app.apk assets/ 2>/dev/null | head

取证意义:加固不是「无法取证」,而是改变了成本结构。

静态只能确认「这是哪个应用、声明了什么权限」。要拿到运行时数据(尤其是数据库密钥),就必须走 04 Frida 的动态路径。

报告里应写明这一点,让委托方知道结论的边界。


三、操作步骤

第 1 步:获取并固定 APK

PKG=com.digiforensics.sample
adb shell pm path $PKG     # package:/data/app/~~Hk2==/com.digiforensics.sample-1a2b3c==/base.apk
adb pull /data/app/~~Hk2==/com.digiforensics.sample-1a2b3c==/base.apk ./base.apk
sha256sum base.apk | tee base.apk.sha256 ; unzip -t base.apk | tail -2    # 期望 No errors detected

# 整个 /data/app/<目录> 取走(split apk 才可能有多个 dex)
APPDIR=$(basename $(dirname $(adb shell pm path $PKG | sed 's/package://' | tr -d '\r')))
adb shell su -c "tar -cf - -C /data/app $APPDIR" > appdir.tar && tar -xvf appdir.tar

第 2 步:读基本属性(aapt)

# 环境:Android SDK build-tools(Ubuntu: sudo apt install aapt,或从 Android Studio 的 build-tools 取 aapt2)
aapt dump badging base.apk
# package: name='com.digiforensics.sample' versionCode='148' versionName='12.3.1'
# sdkVersion:'26'                       ← minSdkVersion
# targetSdkVersion:'33'                 ← targetSdkVersion
# application-label:'样本应用'
# uses-permission: name='android.permission.READ_SMS' / ACCESS_FINE_LOCATION / CAMERA / RECORD_AUDIO
# application-debuggable                 ← 出现即 debuggable=true
# launchable-activity: name='com.digiforensics.sample.MainActivity'
# 单独看权限(更干净)/ 完整 XML 树(所有组件与属性)
aapt dump permissions base.apk ; aapt dump xmltree base.apk AndroidManifest.xml
# E: manifest (line=2)
#   A: android:versionCode(0x0101021b)=(type 0x10)0x94      ← 148
#   A: android:debuggable(0x0101000f)=(type 0x12)0xffffffff ← true 的特殊编码
#   E: application ... E: activity (line=42)  A: android:name(0x01010003)="...MainActivity"
#       A: android:exported(0x01010010)=(type 0x12)0xffffffff

读 AXML 输出的技巧:android:debuggable 与 exported 为 true 时,aapt 显示为 0xffffffff 而不是 0x1,因为它把类型编码在高 8 位。看到 0xffffffff 就要想到「布尔真」,这是初学者常困惑的地方。

第 3 步:apktool 重建资源

apktool d -f -o app-decoded base.apk ; apktool b app-decoded -o app-rebuilt.apk
# 网络安全配置是最高频的取证命中点
cat app-decoded/res/xml/network_security_config.xml
# <base-config cleartextTrafficPermitted="true">   <trust-anchors>
#     <certificates src="system"/>  <certificates src="user"/>   ← ★ 信任用户 CA,可被中间人代理
#   </trust-anchors> </base-config>

apktool 的取证用途:把二进制 AXML 转成可读文本(能精确 grep);读上述 network_security_config.xml 判断是否信任用户 CA;读 res/values/strings.xml 与 res/values-zh-rCN/ 核对多语言资源;用 Smali 精确到字节码行确认调用点;资源哈希可用于判断是否为重打包。

第 4 步:jadx 反编译 Java

# 环境:sudo apt install jadx(或用 jadx-gui)
jadx -d app-jadx base.apk --no-res ; ls app-jadx/sources/com/digiforensics/sample/
grep -rn "SQLiteDatabase\|openOrCreateDatabase" app-jadx/sources/ | head   # 库名与是否加密
grep -rn "SecretKeySpec\|Cipher.getInstance"  app-jadx/sources/ | head   # 密钥从哪来
grep -rn "getDeviceId\|ANDROID_ID\|Build.SERIAL" app-jadx/sources/ | head   # 设备信息是否派生 key

三类必查的调用(它们决定数据能不能拿到):

openOrCreateDatabase / SQLiteOpenHelper 决定库文件名与是否加密。

Cipher.getInstance / SecretKeySpec 决定密钥在哪算出来。

getDeviceId / ANDROID_ID / Build.SERIAL 说明设备标识参与运算,这类 key 常由设备信息派生。

关键认识:如果 jadx 里搜不到 Cipher、只看到一个自定义 Application 类和一个壳 so,那基本可以确认加固。此时不要在静态上继续投入时间,直接转 04。

第 5 步:签名验证与身份认定

# 环境:Android SDK build-tools 的 apksigner
apksigner verify --verbose --print-certs base.apk
# Verified using v1 scheme (JAR signing): true      ← 支持旧签名
# Verified using v2 scheme (APK Signature Scheme v2): true
# Verified using v3 scheme (APK Signature Scheme v3): true    Number of signers: 1
# Signer #1 certificate DN: CN=Sample Dev, O=DigiForensics, C=CN
# Signer #1 certificate SHA-256 digest: 3f9a...(密钥层面身份的权威标识)
# 提取证书(keytool 或 openssl)
unzip -o base.apk META-INF/*.RSA ; keytool -printcert -file META-INF/CERT.RSA
openssl pkcs7 -inform DER -in META-INF/CERT.RSA -print_certs -noout

签名的取证价值极高:签名证书的 SHA-256 是唯一能证明「两个 APK 使用同一签名证书与同一私钥」的指标。

但它证明的是密钥层面的同一性,不能直接认定现实中的开发者身份或所用打包工具。证书可能由他人代管、可能复制自公开模板、也可能被冒用。

涉赌涉诈案件中,常见做法是一个签名下出现多个包名变体(正规壳 + 赌博包)。把同签名的所有 APK 归为一组,是常见切入点。

结论应表述为「同签名主体、同一次批量打包行为」,并结合 DN、有效期、包名与安装来源交叉印证。

v1/v2/v3 签名的取舍意义:只勾选 v1 的应用兼容性最好(可被 zip 篡改)但可被重签名;开启 v2/v3 后可防篡改。

若一份「官方」应用只用了 v1 签名,是一个值得注意的配置线索 —— 但它只是线索,单独不能证明侧载。 很多内部打包、老旧工具链或第三方应用市场分发的包也只签 v1。

第 6 步:加固识别汇总

# ① so 库 / ② assets 加密文件(体积大的 bin、dat)
unzip -l base.apk | grep -E '\.so$' | awk '{print $NF}'
unzip -l base.apk | awk '$1 > 100000 {print $1, $NF}' | sort -rn | head
# ③ Application 类 / ④ dex 数量与体积
aapt dump xmltree base.apk AndroidManifest.xml | grep -A3 'E: application' | head -8
unzip -l base.apk | grep 'classes.*\.dex'
# ⑤ strings 扫敏感内容(assets + arsc)
mkdir -p unz && cd unz && unzip -q ../base.apk
strings -n 6 resources.arsc assets/* 2>/dev/null | \
  grep -iE 'http://|https://|api|key|secret|aes|password' | sort -u | head -40

加固判定的证据链写法(报告中应这样表述):

该 APK 经 apktool 解包后,assets 目录含 jiagu_config.dat(84,112 字节);lib/arm64-v8a/ 下含 libjiagu.so(312 KB);AndroidManifest.xml 中 android:name 指向 com.digiforensics.shell.StubApplication,而 classes.dex 仅 92 KB 且无业务类名。上述四项特征共同表明该应用已加固;静态分析无法获取其数据库密钥初始化逻辑,故转入动态分析。

第 7 步:验证