目录
  1. 1. 概述
  2. 2. 一、APK 拆解与重打包
    1. 2.1. 1.1 apktool
    2. 2.2. 1.2 aapt / aapt2(资源检查)
    3. 2.3. 1.3 jarsigner / apksigner
    4. 2.4. 1.4 zipalign
  3. 3. 二、DEX / 字节码分析
    1. 3.1. 2.1 d8 / dx(DEX 编译器)
    2. 3.2. 2.2 dexdump
    3. 3.3. 2.3 baksmali / smali
    4. 3.4. 2.4 dex2jar + JD-GUI / JADX
    5. 3.5. 2.5 deopt / dexopt 与 ART 编译器
  4. 4. 三、SO / Native 层分析
    1. 4.1. 3.1 objdump(binutils)
    2. 4.2. 3.2 readelf
    3. 4.3. 3.3 nm
    4. 4.4. 3.4 strings — 快速字符串扫描
    5. 4.5. 3.5 objcopy / strip / patchelf
  5. 5. 四、动态调试工具
    1. 5.1. 4.1 adb 逆向专用命令
    2. 5.2. 4.2 strace
    3. 5.3. 4.3 frida(动态插桩)
    4. 5.4. 4.4 ltrace(库调用追踪)
  6. 6. 五、网络抓包
    1. 6.1. 5.1 tcpdump
    2. 6.2. 5.2 Charles / mitmproxy
  7. 7. 六、反调试与绕过工具
    1. 7.1. 6.1 反调试检测命令
    2. 7.2. 6.2 绕过工具
  8. 8. 七、命令速查表
  9. 9. 八、高级实战:完整逆向工作流
  10. 10. 面试常考问题
【逆向安全技术-基础篇】逆向开发常用命令

概述

Android 逆向工程的第一步是熟悉工具链。本文梳理逆向开发中最常用的命令行工具及其使用场景,覆盖 APK 拆解、DEX 分析、SO 调试、网络抓包等核心环节。每个命令都会深入其底层原理、典型输出解读以及实战中的常见陷阱。

Android 逆向工具链按工作流可以划分为以下阶段:

[APK 获取] → [资源/Manifest 审查] → [DEX 反编译] → [Native SO 分析]

[Smali 修改/插桩]

[重打包/签名] → [动态调试/验证]

一、APK 拆解与重打包

1.1 apktool

apktool 是 Android 逆向的瑞士军刀,用于反编译 APK 中的资源文件和 smali 代码。其内部基于 smali/baksmaliaapt 构建。

# 反编译 APK
apktool d target.apk -o output_dir

# 重新打包
apktool b output_dir -o repacked.apk

# 仅解码资源,不解码 dex(-s 跳过 smali)
apktool d -s target.apk -o output_dir

# 强制解码(跳过资源 ID 冲突检测)
apktool d -f target.apk -o output_dir

# 保留原始资源 ID(不进行资源混淆还原,用于特殊场景)
apktool d --no-res target.apk -o output_dir

# 指定框架资源(处理系统应用或定制 ROM 的 APK)
apktool if framework-res.apk
apktool d -p framework_dir target.apk -o output_dir
参数 含义
d / decode 解码/反编译
b / build 重新打包
-s 跳过 dex 解码(保留 classes.dex 原始状态)
-r 跳过资源解码
-f 强制删除目标目录覆盖
-p 指定 framework 资源目录
--only-main-classes 仅反编译主 dex(处理 multi-dex)

apktool 输出目录结构详解:

output_dir/
├── AndroidManifest.xml # 反编译后的 Manifest(可读文本)
├── apktool.yml # apktool 元数据(版本、sdk 信息等)
├── original/ # 原始未解码的文件
│ ├── AndroidManifest.xml # 原始二进制 Manifest
│ └── META-INF/ # 原始签名文件
├── res/ # 解码后的资源文件
│ ├── values/
│ │ ├── strings.xml # 字符串资源(含混淆前后的映射)
│ │ ├── styles.xml
│ │ └── public.xml # 资源 ID 映射表(关键!)
│ └── ...
└── smali/ # Smali 代码
└── com/
└── example/
└── MainActivity.smali

面试要点:apktool 反编译后生成的是 smali 代码而非 Java 源码,smali 是 Dalvik/ART 虚拟机的汇编语言,每一个寄存器操作都清晰可见。public.xml 文件记录了资源 ID 与资源名称的映射关系,在分析资源引用时非常重要。

apktool 常见问题与解决:

# 问题1: "Could not decode arsc file" → 资源表损坏或使用了新的资源格式
# 解决: 升级 apktool 到最新版本,或使用 --no-res 跳过资源解码

# 问题2: 重打包后运行时闪退 → 资源 ID 冲突或签名校验
# 解决: 检查 apktool.yml 中的版本信息,确保与原始 APK 的 targetSdk 一致

# 问题3: Java 堆内存不足 → 大型 APK 反编译时需要更多内存
java -Xmx4G -jar apktool.jar d target.apk -o output_dir

1.2 aapt / aapt2(资源检查)

aapt(Android Asset Packaging Tool)是分析 APK 结构的高效工具,无需完整反编译即可获取关键信息:

# 查看 APK 基本信息(包名、版本、SDK 等)
aapt dump badging target.apk

# 输出示例:
# package: name='com.example.app' versionCode='1' versionName='1.0'
# sdkVersion:'21'
# targetSdkVersion:'30'
# uses-permission: name='android.permission.INTERNET'
# application-label:'Example App'

# 列出 APK 内所有文件
aapt list target.apk

# 查看资源表内容
aapt dump resources target.apk | grep -E "string|permission"

# aapt2 (Android 8.0+ 推荐)
aapt2 dump strings target.apk
aapt2 dump resources target.apk

# 提取权限列表(用于安全审计)
aapt dump permissions target.apk
aapt dump badging target.apk | grep uses-permission

aapt 在逆向中的典型用途:

  • 快速获取包名和主 Activity 名称(用于 adb shell am start
  • 审计权限组合(判断应用是否有可疑权限申请)
  • 查看 SDK 版本范围(推断可用的攻击面)

1.3 jarsigner / apksigner

Android 签名经过了三代演进,逆向工程师需要理解每一代的特点以应对不同的签名校验场景。

# V1 签名(jarsigner,逐步淘汰)
jarsigner -verbose -keystore debug.keystore -storepass android app.apk androiddebugkey

# V2/V3 签名(apksigner,现代应用推荐)
apksigner sign --ks debug.keystore --ks-pass pass:android app.apk

# 验证签名
apksigner verify -v app.apk

# 查看签名信息(用于克隆签名绕过校验)
keytool -list -v -keystore debug.keystore -storepass android

# 提取证书指纹
keytool -printcert -jarfile app.apk | grep SHA

签名版本演进原理:

V1 (JAR Signature):
META-INF/MANIFEST.MF → META-INF/CERT.SF → META-INF/CERT.RSA
对每个 JAR entry 分别计算哈希,性能差,修改资源即破坏签名

V2 (APK Signature Scheme):
在 APK 文件 ZIP 结构中的 Central Directory 之前插入签名块
对整个文件内容(不含签名块自身)计算哈希,验证快

V3 (APK Signature Scheme):
在 V2 基础上支持密钥轮换(key rotation)
签名块中包含签名证书的完整链条

V4 (APK Signature Scheme):
签名信息独立存储在 .idsig 文件中
用于 Android 11+ 的增量安装

Android 7.0 (API 24) 开始引入 APK Signature Scheme v2,对整个文件进行签名校验,比 V1 逐条目签名性能大幅提升。Android 9.0 (API 28) 进一步引入 V3 支持密钥轮换。

签名绕过技术的对应关系:

  • V1 签名校验:Hook java.security.Signature.verify() 或修改签名文件
  • V2/V3 签名校验:需要修改后使用 apksigner 重新签名,同时 Hook PackageManager.getPackageInfo() 返回原始签名信息
  • 自定义校验(Native 层):通常 Hook dlopen/dlsym 拦截 so 中的签名对比逻辑

1.4 zipalign

# 4 字节对齐优化(减小内存占用,提升加载速度)
zipalign -v 4 input.apk output.apk

# 检查对齐状态
zipalign -c -v 4 app.apk

# 重打包后必须执行对齐
apktool b output_dir -o unaligned.apk
zipalign -v 4 unaligned.apk final.apk
apksigner sign --ks debug.keystore final.apk

注意顺序:必须先 zipalign 再 apksigner,否则对 V2/V3 签名块的对齐修改会导致签名失效。


二、DEX / 字节码分析

2.1 d8 / dx(DEX 编译器)

# dx 将 jar/class 转为 dex(旧版,Android Studio 3.0 后废弃)
dx --dex --output=classes.dex input.jar

# d8 新版 dex 编译器(Android Studio 3.1+)
d8 --release --output out_dir input.jar

# d8 反编译 dex 为 jar(用于查看 bytecode)
d8 --output out_dir --lib android.jar classes.dex

# 多 dex 合并
java -jar dx.jar --dex --multi-dex --output=merged *.dex

2.2 dexdump

# 查看 dex 文件结构
dexdump -d classes.dex | head -50

# 查看特定类的方法字节码
dexdump classes.dex | grep -A 20 "Class descriptor"

# 提取所有字符串(寻找敏感信息)
dexdump classes.dex | grep -E "string|password|key|token|secret|url|http"

# 查看方法的完整指令序列
dexdump -d classes.dex | grep -A 30 "Method descriptor"

dexdump 输出解读:

Class #0            -
Class descriptor : 'Lcom/example/MainActivity;'
Access flags : 0x0001 (PUBLIC)
Superclass : 'Landroid/app/Activity;'
...
#0 : (in Lcom/example/MainActivity;)
name : 'onCreate'
type : '(Landroid/os/Bundle;)V'
access : 0x0004 (PROTECTED)
code -
registers : 5
ins : 2
outs : 2
insns size : 42 16-bit code units

2.3 baksmali / smali

# 将 dex 反汇编为 smali 代码
baksmali d classes.dex -o smali_output/

# 将 smali 代码汇编为 dex
smali a smali_output/ -o new_classes.dex

# 指定 API 级别(影响指令反汇编方式)
baksmali d -a 30 classes.dex -o smali_output/

# 反汇编时同时生成代码偏移注释(用于调试)
baksmali d --code-offsets classes.dex -o smali_output/

# 仅反汇编特定类
baksmali d classes.dex -o smali_output/ --classes com/example/MainActivity.smali

# 批量反汇编多个 dex
for dex in classes*.dex; do
baksmali d $dex -o smali_${dex%.dex}/
done

smali/baksmali 是理解字节码注入、插桩、反混淆的核心工具。当你需要修改应用逻辑(如跳过付费验证),就是在 smali 层面操作。

Smali 指令类型速查:

# 数据操作
const/4 v0, 0x1 # 给 v0 赋 4 位常量 1
const-string v0, "hello" # 给 v0 赋字符串
move v0, v1 # v0 = v1

# 方法调用
invoke-static {v0}, LClass;->method(I)V
invoke-virtual {v0, v1}, LClass;->method(LString;)Z
invoke-direct {p0}, LObject;-><init>()V

# 字段访问
iget v0, p0, LClass;->field:I
iput v0, p0, LClass;->field:I

# 条件跳转
if-eqz v0, :label # v0 == 0 时跳转
if-nez v0, :label # v0 != 0 时跳转
if-lt v0, v1, :label # v0 < v1 时跳转

# 返回
return-void
return v0 # 返回 v0(整型/布尔)
return-object v0 # 返回对象引用

2.4 dex2jar + JD-GUI / JADX

DEX 到 Java 的转换链:

# dex2jar: DEX → JAR(class 文件)
d2j-dex2jar.sh classes.dex -o classes.jar

# 然后用 JD-GUI 打开(GUI 工具)或使用 JADX 命令行
jadx -d output_source target.apk
jadx-gui target.apk

# JADX 高级选项
jadx --deobf --show-bad-code --escape-unicode target.apk -d output_source
# --deobf: 反混淆处理
# --show-bad-code: 显示无法完美反编译的代码(非常重要)
# --escape-unicode: 将 Unicode 转义为可读字符

2.5 deopt / dexopt 与 ART 编译器

理解 DEX 在 ART 中的优化过程:

# 查看 ART 编译后的 oat 文件
adb shell ls /data/app/com.example-*/oat/arm64/

# 从 oat 提取 dex
# 使用 oat2dex 工具
java -jar oat2dex.jar boot.oat
java -jar oat2dex.jar app.odex

# 在设备上检查 dex2oat 编译产物
adb shell dex2oat --runtime-arg -Xms64m --dex-file=/data/app/test.apk \
--oat-file=/data/app/test.odex

三、SO / Native 层分析

3.1 objdump(binutils)

# 查看 SO 动态符号表
objdump -T libtarget.so

# 反汇编指定函数
objdump -d libtarget.so | less

# 查看动态库依赖
objdump -p libtarget.so | grep NEEDED

# 仅反汇编 .text 段(代码段)
objdump -d -j .text libtarget.so

# 显示重定位信息(用于理解 PLT/GOT 劫持)
objdump -R libtarget.so

# 反汇编时交错显示源码(如果有调试信息)
objdump -S libtarget.so | less

objdump 反汇编输出解读(ARM64 示例):

00000000000010a4 <Java_com_example_Utils_encrypt>:
10a4: d10083ff sub sp, sp, #0x20
10a8: a9017bfd stp x29, x30, [sp, #16]
10ac: 910043fd add x29, sp, #0x10
10b0: f9000fe0 str x0, [sp, #24] ; JNIEnv* → sp+24
10b4: f9000be1 str x1, [sp, #16] ; jclass/jobject → sp+16
10b8: 94000005 bl 10cc <aes_encrypt_internal>

3.2 readelf

# 查看 ELF 头信息(魔数、架构、入口地址)
readelf -h libtarget.so

# 输出示例:
# ELF Header:
# Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
# Class: ELF64
# Data: 2's complement, little endian
# OS/ABI: UNIX - System V
# Machine: AArch64

# 查看节头表(.text .data .bss .rodata 等)
readelf -S libtarget.so

# 查看程序头(加载段信息,理解内存映射)
readelf -l libtarget.so

# 查看符号表(比 nm 更详细)
readelf -s libtarget.so

# 查看动态段内容
readelf -d libtarget.so

# 查看重定位入口
readelf -r libtarget.so

# 查看 NOTE 段(可能包含 Android 特有的 .android.note 信息)
readelf -n libtarget.so

# 检查是否有 build-id(用于符号匹配)
readelf --notes libtarget.so | grep "Build ID"

关键段(Section)说明:

段名 用途 逆向关注点
.text 可执行代码 核心逻辑、算法实现
.rodata 只读数据 硬编码字符串、常量、Key/IV
.data 已初始化全局变量 加密状态标志、配置
.bss 未初始化全局变量 运行时分配缓冲区
.plt 过程链接表 外部函数调用入口(Hook 目标)
.got 全局偏移表 外部函数实际地址(GOT 劫持)
.init_array 初始化函数指针数组 JNI_OnLoad 之前的初始化
.fini_array 析构函数指针数组 卸载时的清理逻辑
.dynamic 动态链接信息 SO 依赖、重定位表位置

3.3 nm

# 列出所有符号
nm -D libtarget.so

# 仅列出动态符号
nm --dynamic libtarget.so

# 显示未定义符号(外部依赖)
nm -u libtarget.so

# 按地址排序显示(配合 IDA 分析)
nm -n libtarget.so

# 显示符号大小
nm --print-size -D libtarget.so | sort -k2 -n

3.4 strings — 快速字符串扫描

# 提取所有可打印字符串(ASCII)
strings libtarget.so

# 设置最小长度过滤噪音
strings -n 8 libtarget.so

# 提取 UTF-16LE 编码字符串(Android 中常见)
strings -e l libtarget.so
strings -e b libtarget.so

# 在 APK 中搜索敏感字符串
strings target.apk | grep -iE "secret|password|key|token|http|api"

# 搜索特定格式(如 base64 编码密钥)
strings libtarget.so | grep -E "^[A-Za-z0-9+/]{20,}={0,2}$"

3.5 objcopy / strip / patchelf

# 查看 strip 状态
file libtarget.so
# 输出:ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV),
# dynamically linked, stripped
# "stripped" = 符号表被移除,无法直接用 addr2line 定位

# 仅剥离调试符号,保留动态符号表
objcopy --strip-debug libtarget.so libtarget_stripped.so

# 给 SO 添加依赖(用于注入自定义 SO)
patchelf --add-needed libinject.so libtarget.so

# 修改 SO 的 RPATH/RUNPATH
patchelf --set-rpath /data/local/tmp libtarget.so

# 修改 SO 的 SONAME
patchelf --set-soname libhooked.so libtarget.so

四、动态调试工具

4.1 adb 逆向专用命令

# ===== 进程与内存 =====
# 查看进程内存映射(关键调试信息)
adb shell cat /proc/<pid>/maps
# 输出解读:地址范围-权限-偏移-设备-节点-路径
# 例如:7891234000-7892345000 r-xp 00000000 fd:01 12345 /data/app/.../libtarget.so

# 查看进程状态(检测调试状态)
adb shell cat /proc/<pid>/status | grep -E "TracerPid|State|Name"
# TracerPid: 0 → 未被调试
# TracerPid: 1234 → 被进程 1234 调试

# ===== 包管理 =====
# 查看已安装应用的包名和 APK 路径
adb shell pm list packages -f

# 只列出第三方应用
adb shell pm list packages -3

# dump 应用信息(含签名、权限、版本等)
adb shell dumpsys package <package_name> | grep -A 5 "signatures"

# 查看应用路径
adb shell pm path <package_name>
# 输出:package:/data/app/com.example-xxxxx/base.apk

# 导出现有应用 APK(无需 root,Android 9+)
adb shell pm path <package_name>
adb pull /data/app/com.example-xxxxx/base.apk .

# 清除应用数据(重置应用状态,用于测试)
adb shell pm clear <package_name>

# ===== 启动与调试 =====
# 以调试模式启动应用(等待调试器连接)
adb shell am start -D -n com.example/.MainActivity

# 以调试模式启动并附加参数
adb shell am start -D -n com.example/.MainActivity -e debug true

# 停止应用
adb shell am force-stop <package_name>

# 列出当前运行中的 Activity
adb shell dumpsys activity activities | grep mResumedActivity

# ===== 日志 =====
# 按 tag 过滤日志
adb logcat -s AndroidRuntime:E ActivityManager:W

# 抓取完整日志(含 pid/tid)
adb logcat -v threadtime > full_log.txt

# 监控特定进程的日志
adb logcat --pid=$(adb shell pidof com.example)

# ===== 系统属性 =====
# 获取设备信息
adb shell getprop ro.build.version.sdk # SDK 版本
adb shell getprop ro.product.cpu.abi # CPU 架构
adb shell getprop ro.debuggable # 系统是否为 debug 版本

4.2 strace

# 追踪进程系统调用
adb shell strace -p <pid>

# 过滤文件操作(关注文件加密、数据存储)
adb shell strace -e open,read,write,close -p <pid>

# 过滤网络操作
adb shell strace -e connect,sendto,recvfrom -p <pid>

# 过滤进程/线程操作(关注反调试 fork)
adb shell strace -e fork,clone,ptrace -p <pid>

# 显示时间戳(分析性能和应用行为时序)
adb shell strace -t -p <pid>

# 显示调用耗时
adb shell strace -T -p <pid>

# 追踪子进程
adb shell strace -f -p <pid>

# 将输出重定向到文件(Android 设备上)
adb shell strace -o /sdcard/strace.log -p <pid>
adb pull /sdcard/strace.log

strace 输出解读示例:

openat(AT_FDCWD, "/sdcard/secret.txt", O_RDWR|O_CREAT, 0600) = 23
write(23, "encrypted_data_here...", 1024) = 1024
close(23) = 0
connect(16, {sa_family=AF_INET, sin_port=htons(443), sin_addr=inet_addr("103.45.67.89")}, 16) = -1 EINPROGRESS

从以上输出可以判断:(1) 应用在操作 /sdcard/ 下的文件进行加密写入;(2) 连接到一个远程服务器的 443 端口。

4.3 frida(动态插桩)

# 在设备上启动 frida-server
adb push frida-server /data/local/tmp/
adb shell chmod 755 /data/local/tmp/frida-server
adb shell /data/local/tmp/frida-server &

# 列出可注入进程
frida-ps -U

# 列出运行中的应用
frida-ps -Uai

# 以 spawn 模式启动应用(可拦截早期执行)
frida -U -f com.example.app -l script.js --no-pause

# 附加到正在运行的进程
frida -U -p 12345 -l script.js

# 附加到应用(按包名)
frida -U com.example.app -l script.js

# 跨平台使用(通过 USB)
frida -H 192.168.1.100:27042 -f com.example.app

Frida 基础示例脚本:

// 阻止应用退出
Java.perform(function() {
var System = Java.use("java.lang.System");
System.exit.implementation = function(code) {
console.log("[!] System.exit(" + code + ") blocked!");
};
});

// Hook JNI 函数
Interceptor.attach(Module.findExportByName("libtarget.so", "Java_com_example_Utils_encrypt"), {
onEnter: function(args) {
console.log("[+] encrypt() called");
console.log("[+] JNIEnv*: " + args[0]);
console.log("[+] jstring: " + Java.vm.getEnv().getStringUtfChars(args[2], null).readCString());
},
onLeave: function(retval) {
console.log("[+] encrypt() returned: " + Java.vm.getEnv().getStringUtfChars(retval, null).readCString());
}
});

4.4 ltrace(库调用追踪)

# 追踪动态库函数调用(需要 ltrace 编译器交叉编译)
adb push ltrace-arm64 /data/local/tmp/
adb shell chmod 755 /data/local/tmp/ltrace-arm64

# 追踪 libc 调用
adb shell /data/local/tmp/ltrace-arm64 -e libc.so -p <pid>

# 追踪特定动态库
adb shell /data/local/tmp/ltrace-arm64 -e libtarget.so -p <pid>

五、网络抓包

5.1 tcpdump

# 在设备上抓包
adb shell tcpdump -i wlan0 -w /sdcard/capture.pcap

# 过滤 HTTPS 流量
adb shell tcpdump -i wlan0 port 443 -w /sdcard/capture.pcap

# 过滤特定主机
adb shell tcpdump -i wlan0 host 192.168.1.100 -w /sdcard/capture.pcap

# 不过滤、不截断,抓取完整数据包
adb shell tcpdump -i wlan0 -s 0 -w /sdcard/capture.pcap

# 实时查看 DNS 查询(定位 C2 域名)
adb shell tcpdump -i wlan0 -A port 53

# 导出 pcap 到 PC 分析
adb pull /sdcard/capture.pcap
wireshark capture.pcap

5.2 Charles / mitmproxy

# 设置全局代理
adb shell settings put global http_proxy <proxy_ip>:8888

# 移除代理
adb shell settings put global http_proxy :0

# Android 7.0+ 需要安装系统级 CA 证书(root 设备)
# 1. 导出 Charles 证书 → charles.pem
# 2. 计算证书的 subject_hash
openssl x509 -inform PEM -subject_hash_old -in charles.pem | head -1
# 输出如: 3a3b4c5d
# 3. 将证书重命名为 <hash>.0
cp charles.pem 3a3b4c5d.0
# 4. 推送到系统证书目录
adb root
adb remount
adb push 3a3b4c5d.0 /system/etc/security/cacerts/
adb shell chmod 644 /system/etc/security/cacerts/3a3b4c5d.0
adb reboot

mitmproxy 透明代理模式:

# 启动 mitmproxy
mitmproxy --mode transparent --listen-port 8080

# 配置 iptables 转发(设备端,需要 root)
adb shell iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 8080
adb shell iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-port 8080

# 清除规则
adb shell iptables -t nat -F

Android 7.0+ HTTPS 抓包关键:系统默认不信任用户安装的 CA 证书。需要将 Charles/mitmproxy 证书安装为系统证书(root 后推送到 /system/etc/security/cacerts/),或使用 Xposed 模块(如 JustTrustMe)Hook 证书校验。

SSL Pinning 绕过技术:

技术 原理 工具
系统证书注入 将代理证书作为系统信任证书 adb push cert /system/etc/security/cacerts/
Hook TrustManager 拦截 SSL 上下文初始化 Frida: SSLContext.init()
Hook OkHttp 绕过 OkHttp CertificatePinner Frida: CertificatePinner.check()
修改 Network Config Patch Manifest 中网络安全配置 apktool 反编译后修改 XML
Virtual Xposed 在虚拟环境中运行 JustTrustMe 模块 VirtualXposed + JustTrustMe

六、反调试与绕过工具

6.1 反调试检测命令

# 检测 TracerPid(被调试时非 0)
adb shell cat /proc/<pid>/status | grep TracerPid

# 检测 /proc/self/stat 中的调试标志
adb shell cat /proc/<pid>/stat

# 检测调试端口(IDA 默认 23946, Frida 默认 27042)
adb shell netstat -anp | grep -E "23946|27042"

# 检测 ptrace 附加
adb shell cat /proc/<pid>/wchan
# 如果输出 "ptrace_stop" 表示进程被 ptrace 暂停

6.2 绕过工具

# MagiskHide 隐藏 root 和调试状态
magiskhide add com.target.app

# 使用 frida-server 的 anti-detection fork
# hluda-server(修改了默认端口名、进程名等特征)
adb push hluda-server /data/local/tmp/
adb shell chmod 755 /data/local/tmp/hluda-server
adb shell /data/local/tmp/hluda-server -l 0.0.0.0:27043 &

# 使用 LD_PRELOAD 注入 anti-anti-debug.so
adb shell LD_PRELOAD=/data/local/tmp/bypass.so am start -n com.example/.MainActivity

七、命令速查表

场景 命令
反编译 APK apktool d app.apk
查看 APK 信息 aapt dump badging app.apk
查看 DEX 结构 dexdump -d classes.dex
DEX → smali baksmali d classes.dex
DEX → Java jadx -d out app.apk
反汇编 SO objdump -d lib.so
查看 SO 符号 nm -D lib.so
提取 SO 字符串 strings -n 8 lib.so
查看 SO 段信息 readelf -S lib.so
查看 ELF 头 readelf -h lib.so
查看进程内存 adb shell cat /proc/<pid>/maps
查看调试状态 adb shell cat /proc/<pid>/status | grep TracerPid
抓取网络包 adb shell tcpdump -i wlan0 -w /sdcard/cap.pcap
签名验证 apksigner verify app.apk
动态插桩 frida -U -p <pid> -l script.js
追踪系统调用 adb shell strace -p <pid>
Patch SO 依赖 patchelf --add-needed lib.so target.so

八、高级实战:完整逆向工作流

以下是一个完整的 Android 逆向分析流程,整合了上述所有命令:

#!/bin/bash
# Android 逆向分析自动化脚本框架

TARGET_APK="target.apk"
OUTPUT_DIR="analysis_output"
mkdir -p $OUTPUT_DIR

# Stage 1: 静态侦查
echo "[*] Stage 1: Static Reconnaissance"
echo " [1.1] APK info..."
aapt dump badging $TARGET_APK > $OUTPUT_DIR/apk_info.txt
echo " [1.2] Extracting strings..."
strings $TARGET_APK | grep -iE "secret|key|password|token|http|api|encrypt" > $OUTPUT_DIR/sensitive_strings.txt
echo " [1.3] Checking signature..."
apksigner verify -v --print-certs $TARGET_APK > $OUTPUT_DIR/signature.txt

# Stage 2: 解包与反编译
echo "[*] Stage 2: Decompilation"
apktool d -f $TARGET_APK -o $OUTPUT_DIR/apktool_out
jadx --deobf --show-bad-code $TARGET_APK -d $OUTPUT_DIR/jadx_out

# Stage 3: 权限与组件审计
echo "[*] Stage 3: Security Audit"
grep -r "exported=\"true\"" $OUTPUT_DIR/apktool_out/AndroidManifest.xml > $OUTPUT_DIR/exported_components.txt
grep "uses-permission" $OUTPUT_DIR/apktool_out/AndroidManifest.xml | sort | uniq > $OUTPUT_DIR/permissions.txt

# Stage 4: Native 库分析
echo "[*] Stage 4: Native Analysis"
for so in $(find $OUTPUT_DIR/apktool_out/lib -name "*.so"); do
echo " Analyzing $(basename $so)..."
readelf -h $so > $OUTPUT_DIR/native_$(basename $so)_elf_header.txt 2>/dev/null
strings -n 8 $so > $OUTPUT_DIR/native_$(basename $so)_strings.txt 2>/dev/null
nm -D $so > $OUTPUT_DIR/native_$(basename $so)_symbols.txt 2>/dev/null
done

echo "[+] Analysis complete. Results in $OUTPUT_DIR/"

面试常考问题

Q1: apktool 反编译出的 smali 与 Java 源码的关系?

smali 是 DEX 字节码的文本表示,每个 .smali 文件对应一个 Java 类,每一行对应 Dalvik 虚拟机的寄存器操作指令。理解 smali 是做字节码修改和插桩的基本功。具体而言:(1) smali 基于寄存器架构,而 Java 字节码基于操作数栈,这是两者在实现层面的核心差异;(2) smali 中的 .locals N 声明了局部变量数量,非静态方法还会额外使用 p0 代表 this 引用;(3) JADX 等工具将 smali 反编译为 Java 后可能丢失精确的控制流信息(尤其在混淆代码中),而直接阅读 smali 可以得到准确的执行路径。

Q2: Android 7.0+ 抓 HTTPS 包为什么需要额外处理?

Android 7.0 引入 Network Security Config,默认只信任系统预装的 CA 证书。用户安装的证书(如 Charles 根证书)对 targetSdkVersion >= 24 的应用无效。根本原因在于 libcore/security/TrustedCertificateStore.java 中区分了 system 和 user 两个证书存储,当应用 Network Security Config 未显式信任用户证书时,SSL 握手阶段只从 system 信任库中查找证书链。解决方法:root 后安装为系统证书、Hook 证书校验、或修改应用 network_security_config.xml

Q3: apksigner V2 签名比 V1 好在哪?

V1 逐个对 JAR 条目签名,验证慢且 APK 解压后签名信息丢失,仅保护 META-INF/ 目录下的 MANIFEST.MF 中列出的文件;V2 对整个 APK 文件(APK Signing Block 之前的部分)签名,安装时一次性验证整个文件完整性,效率更高,且能检测整个文件是否被篡改,包括 ZIP 元数据。V3 进一步支持密钥轮换,允许应用在不丢失旧签名信任的前提下更新签名密钥。

Q4: readelf -S 和 readelf -l 的区别?为什么两者都需要查看?

readelf -S 显示 Section Header Table(节头表),反映编译链接时的布局视图(.text、.data、.rodata 等),主要用于静态分析和符号定位。readelf -l 显示 Program Header Table(程序头表),反映运行时加载视图(LOAD 段、DYNAMIC 段等),决定 SO 在内存中的实际映射。在逆向分析中,两者结合能精确判断:某段代码在内存中的实际地址(通过 LOAD 段的 p_vaddr 和 p_offset 计算)、哪些节会被加载到内存(通过段到节的映射关系)、以及哪些数据在运行时可能被修改(如 .got 所在的段标记为可读写)。

Q5: Frida 注入失败(”Failed to spawn: unable to connect to remote frida-server”)怎么办?

排查步骤:(1) 确认 frida-server 版本与本地 frida-tools 版本匹配——两者必须完全一致(通过 frida --version 核对);(2) 确认 frida-server 在设备上正常运行——adb shell ps | grep frida 检查进程是否存在;(3) 检查端口转发——adb forward tcp:27042 tcp:27042 是否正确建立;(4) 对于 Android 12+,app 的 zygote 进程限制可能导致 spawn 失败,尝试用 attach 模式代替(frida -U com.app -l script.js);(5) 确认设备架构与 frida-server 架构匹配——用 adb shell getprop ro.product.cpu.abi 查看设备 CPU 架构,下载对应的 frida-server 二进制文件(arm、arm64、x86、x86_64)。

打赏
  • 微信
  • 支付宝

评论