ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

安卓逆向助手:抓包脱壳反编译全流程脚本化实战

2026/9/25 7:50:57 拓冰建站 浏览量
安卓逆向助手:抓包脱壳反编译全流程脚本化实战 简介安卓逆向助手是一款面向Android应用开发者与安全研究人员的图形化逆向工具旨在降低APK反编译与分析门槛让初学者也能快速理解应用内部结构。它集成dex2jar、JD-GUI、apktool、baksmali等常用组件支持一键将Dalvik字节码转为可读Java源码并可查看XML布局、图片、字符串等资源文件便于排查安全漏洞、分析混淆代码或调试界面逻辑。资源包共69个文件以jar核心库、bat与sh启动脚本、exe可执行程序为主另含txt说明、pem与pk8签名文件、so库及readme等压缩包约15.18MB解压后即可在Windows环境直接运行。目前已有216人学习下载适合移动安全入门者、逆向工程学习者及需要快速审计APK的开发者参考使用。1. 安卓逆向助手从抓包到脱壳一个脚本把重复活干掉做安卓逆向的同行都有个共识真正耗时间的从来不是分析算法本身而是前面那一堆重复劳动。装环境、配证书、抓包、定位加密入口、脱壳、反编译、找关键类一套流程走下来半天就没了真正盯着 smali 或 so 看逻辑的时间可能不到两小时。安卓逆向助手这类工具要解决的就是这件事——把环境准备、抓包代理、脱壳、静态反编译、关键字符串定位这些环节串成一条可复用的流水线让分析的人把精力留给算法还原和协议复现。它适合两类人一是刚入门、还在被环境配置劝退的新手二是做批量分析、需要把流程脚本化的老手。下面按我实际落地的顺序把每个环节拆开讲清楚包括参数怎么设、哪里容易翻车。2. 环境与抓包把证书、代理、模拟器这三件事一次配对2.1 为什么抓包总是失败证书信任链的三个断点安卓抓包翻车九成不是工具问题而是证书信任链断在三个地方。第一个断点是系统证书区Android 7.0 之后应用默认只信任系统 CA用户手动装的证书对目标 App 无效第二个断点是 App 自身的证书校验很多应用内置了证书固定SSL Pinning走代理直接握手失败第三个断点是模拟器或真机的网络配置代理没生效或者 DNS 走了直连。常见做法是把用户证书转成系统证书格式塞进/system/etc/security/cacerts/这需要 root 或者可写系统分区。文件名必须是证书的 hash 值加.0hash 用openssl x509 -inform PEM -subject_hash_old -in cert.pem | head -1算出来。这一步做完系统级信任就通了但证书固定还得单独处理后面章节讲。我一般会先确认三件事再动手设备是否 root、系统分区是否可写、目标 App 的 targetSdkVersion。targetSdk 低于 24 的应用对用户证书还认高于 24 就必须走系统证书。这个判断能省掉大量无效尝试。2.2 用脚本一键完成证书安装与代理配置手动敲命令容易漏步骤我习惯写一个 bash 脚本把证书转换、推送、权限设置、代理配置串起来。下面这个脚本假设你已经用抓包工具导出了 PEM 格式证书设备已 root 且adb可用。#!/bin/bash # 安卓逆向助手 - 证书安装与代理配置脚本 CERT_PEMburp_cert.pem # 抓包工具导出的 PEM 证书 DEVICE_CERT_DIR/system/etc/security/cacerts PROXY_HOST192.168.1.100 # 抓包机 IP PROXY_PORT8080 # 1. 计算证书 hash 并生成系统证书文件名 HASH$(openssl x509 -inform PEM -subject_hash_old -in $CERT_PEM | head -1) cp $CERT_PEM ${HASH}.0 # 2. 推送证书到设备临时目录 adb push ${HASH}.0 /sdcard/ # 3. 挂载系统分区为可写并复制证书 adb shell su -c mount -o rw,remount /system adb shell su -c cp /sdcard/${HASH}.0 ${DEVICE_CERT_DIR}/ adb shell su -c chmod 644 ${DEVICE_CERT_DIR}/${HASH}.0 # 4. 设置全局 HTTP 代理 adb shell settings put global http_proxy ${PROXY_HOST}:${PROXY_PORT} # 5. 验证证书是否生效 adb shell ls -l ${DEVICE_CERT_DIR}/${HASH}.0 echo 证书安装完成代理已指向 ${PROXY_HOST}:${PROXY_PORT}逻辑说明脚本先算出证书的 subject_hash_old 值这是安卓系统识别证书的唯一标识文件名不对系统直接忽略。推送后必须改权限为 644否则系统读不到。代理用settings put global设置比在 Wi-Fi 设置里手动填更稳重启后依然生效。参数说明PROXY_HOST填抓包机在局域网里的 IP不要填 127.0.0.1模拟器里 127.0.0.1 指向模拟器自身。PROXY_PORT和抓包工具监听端口一致。如果设备没有su第三步会失败需要先用 Magisk 之类的方案获取 root。提示部分定制 ROM 的/system分区是只读的mount -o rw,remount会报错。这种情况改用 Magisk 模块把证书挂载到系统证书目录或者用adb root后重试。2.3 绕过证书固定的两种实用思路证书固定是抓包路上最大的拦路虎。现象很明确代理配好了浏览器能抓到包但目标 App 一请求就报 SSL 握手失败或者直接无网络。原因是 App 在代码里硬编码了服务端证书的公钥或 hash只认自己的。第一种思路是动态 Hook用 Frida 在运行时替换证书校验逻辑。常见做法是 HookX509TrustManager的checkServerTrusted方法让它直接返回空。下面这段 Frida 脚本是通用模板// 安卓逆向助手 - 绕过 SSL Pinning 的 Frida 脚本 Java.perform(function () { var X509TrustManager Java.use(javax.net.ssl.X509TrustManager); var SSLContext Java.use(javax.net.ssl.SSLContext); // 替换 TrustManager 的校验方法 var TrustManager Java.registerClass({ name: com.example.TrustAll, implements: [X509TrustManager], methods: { checkClientTrusted: function (chain, authType) {}, checkServerTrusted: function (chain, authType) {}, getAcceptedIssuers: function () { return []; } } }); // 替换 SSLContext 初始化时的 TrustManager var TrustManagers [TrustManager.$new()]; var SSLContext_init SSLContext.init.overload( [Ljavax.net.ssl.KeyManager;, [Ljavax.net.ssl.TrustManager;, java.security.SecureRandom ); SSLContext_init.implementation function (km, tm, sr) { SSLContext_init.call(this, km, TrustManagers, sr); }; });逻辑说明脚本注册了一个什么都不校验的 TrustManager然后把SSLContext.init的调用替换掉强制使用这个 TrustManager。这样无论 App 怎么设置证书固定最终走的都是不校验的路径。参数说明Java.registerClass的 name 可以随便填只要不冲突。如果目标 App 用了 OkHttp 的CertificatePinner还需要额外 Hookokhttp3.CertificatePinner.check方法把参数里的 host 和 peerCertificates 直接放行。第二种思路是静态修改反编译 APK 后找到证书校验相关代码直接删掉或改成 return true然后重打包签名。这种方式适合 Frida 不方便用的场景比如 App 有反调试检测。但重打包可能触发签名校验需要一并处理。3. 脱壳与反编译从内存 dump 到可读 smali 的完整链路3.1 壳的三种类型与对应脱壳策略安卓壳大致分三代。第一代是整体加密APK 里的 dex 被加密运行时在内存里解密后加载典型特征是classes.dex很小或者根本不存在。第二代是抽取式把每个方法的字节码抽走运行时再填回去静态反编译看到的全是 nop。第三代是 VMP 和指令混淆核心逻辑跑在自定义解释器里脱壳难度最高。对付第一代壳最直接的办法是内存 dump。App 运行起来后解密后的 dex 一定在内存里用 root 权限读取/proc/pid/maps找到 dex 文件映射把对应内存段 dump 出来。常见工具是 frida-dexdump一条命令就能跑# 安卓逆向助手 - 内存 dump 脱壳 # 先启动目标 App保持前台运行 frida-dexdump -U -f com.target.app # -U 表示 USB 设备-f 表示启动并注入 # dump 出的 dex 文件会保存在当前目录逻辑说明frida-dexdump 会枚举进程内存中所有符合 dex 文件头dex\n035或dex\n036的内存段逐个 dump 并尝试修复文件头。对于第一代壳通常能直接拿到完整的 dex。参数说明-f后面跟包名工具会自动启动 App 并注入。如果 App 有反 Frida 检测需要先用其他手段绕过比如改 Frida 的默认端口和进程名。dump 出来的 dex 可能不完整需要用dex2jar或jadx验证如果反编译报错说明 dump 时内存还没完全解密可以多试几次或者等 App 运行到特定界面再 dump。第二代抽取壳的脱壳要复杂一些因为方法体是运行时填的。常见做法是用 Frida HookArtMethod的Invoke或者interpreter相关函数在方法执行时把填好的字节码 dump 出来。这类脚本网上有现成的核心思路是遍历所有类和方法主动触发每个方法的解释执行然后从内存里读回字节码。这个过程比较耗时一个中型 App 可能要跑十几分钟。3.2 反编译工具选型jadx、apktool、GDA 各自适合什么场景脱壳拿到 dex 之后下一步是反编译成可读代码。工具选型直接影响效率我一般按场景分工具输出形式适合场景短板jadxJava 源码快速阅读逻辑、搜索字符串混淆严重时变量名全是 a、b、capktoolsmali 资源需要修改后重打包阅读体验差逻辑跳转多GDA反编译 分析恶意样本分析、交叉引用大文件卡顿IDAso 反汇编native 层算法分析学习曲线陡我通常先用 jadx 打开 dex用全局搜索找关键字符串比如 “sign”、“encrypt”、“token”定位到相关类之后再决定要不要深入 smali。如果核心逻辑在 so 里jadx 只能看到native方法声明这时候就得把 so 拖进 IDA 或者 Ghidra。jadx 的命令行版本适合批量处理# 安卓逆向助手 - jadx 批量反编译 jadx -d output_dir --show-bad-code app.dex # -d 指定输出目录 # --show-bad-code 强制输出反编译失败的代码方便定位问题逻辑说明--show-bad-code很关键默认情况下 jadx 遇到反编译失败的代码会跳过导致你看到的类不完整。加上这个参数会把失败的代码以注释形式输出至少能知道哪里有问题。参数说明如果 dex 有多个可以用--inputs指定多个文件。输出目录不要和输入目录重叠否则会覆盖。对于大型 Appjadx 可能吃满内存用-j参数限制线程数或者加-Xmx4g调大 JVM 堆。3.3 用 grep 和交叉引用快速定位加密入口反编译出来的代码动辄几万行怎么快速找到加密入口我的习惯是三步走。第一步抓包拿到请求体和响应体观察有没有明显的 base64、hex 或者固定长度的密文。第二步在 jadx 里搜索请求 URL 的路径片段找到发起请求的类。第三步从请求类往上追看参数是怎么拼出来的通常会追到一个工具类或者 native 方法。如果字符串被混淆了搜索不到明文 URL可以换思路搜 OkHttp 或者 Retrofit 的注解。很多 App 用 Retrofit 做网络层接口定义里有POST、GET注解注解的值就是 URL 路径。搜POST能快速定位所有接口。对于 native 层用 IDA 打开 so 后先看导出函数列表找Java_开头的 JNI 函数。如果函数名被混淆成sub_xxxx就看JNI_OnLoad里注册的函数表那里有 Java 方法和 native 函数的映射关系。找到加密函数后重点看它调用了哪些系统库函数比如AES_encrypt、MD5_Update这些能帮你快速判断算法类型。注意有些 App 会把关键字符串加密存储运行时才解密。这种情况下静态搜索找不到需要在运行时用 Frida HookString构造函数或者StringBuilder.append把解密后的字符串打出来。4. 避坑与排查那些让我熬夜的翻车现场4.1 抓包抓到一堆乱码不是加密而是压缩现象抓包工具里看到的请求体和响应体全是二进制乱码以为是自定义加密花了两小时分析算法最后发现是 gzip 压缩。原因抓包工具默认可能没有自动解压或者 App 在请求头里带了Content-Encoding: gzip但工具没识别。解决先看请求头和响应头的Content-Encoding字段如果是 gzip 或 deflate在抓包工具里开启自动解压或者手动用zlib解。这个坑我踩过不止一次现在拿到乱码第一件事就是看头部。4.2 Frida 注入就闪退反调试在作祟现象Frida 脚本一注入App 立刻崩溃或者重启。原因App 检测到了 Frida 的痕迹常见检测点包括端口 27042、进程名里带 frida、/data/local/tmp下有 frida-server 文件。解决改 frida-server 的默认端口和文件名用-l参数指定自定义端口。更彻底的办法是把 frida-server 编译成随机名字或者用 Magisk 模块隐藏。如果 App 还检测/proc/self/maps里的 frida 相关 so需要用 Frida 的hide功能或者改 so 名字。4.3 dump 出来的 dex 反编译报错文件头修复没做对现象内存 dump 出的 dex 用 jadx 打开提示 “not a valid dex file”。原因dump 时内存段可能包含多个 dex 或者不完整文件头里的 checksum 和 signature 对不上。解决用dexfixer或者手动修复文件头。常见做法是保留dex\n035开头到文件末尾重新计算 checksum 和 SHA-1 signature。有些工具会自动做这一步比如 frida-dexdump 的--fix选项。如果还是不行说明 dump 的内存段不对需要根据/proc/pid/maps里的 dex 映射范围精确 dump。4.4 重打包后安装失败签名校验和 v2 签名问题现象反编译修改后重新打包安装时提示 “App not installed” 或者 “签名不一致”。原因Android 7.0 之后引入了 v2 签名方案apktool 默认只做 v1 签名导致安装失败。解决用apksigner做 v1v2 双签名命令是apksigner sign --ks keystore.jks --v1-signing-enabled true --v2-signing-enabled true app.apk。另外如果原 App 有签名校验逻辑重打包后签名变了会触发校验失败需要一并 Hook 或修改校验代码。4.5 模拟器里跑得好好的真机上抓不到包现象模拟器里代理配置正常抓包没问题换到真机就不行。原因真机和抓包机不在同一网段或者真机走了移动数据没走 Wi-Fi。解决确认真机和抓包机连的是同一个 Wi-Fi代理 IP 填抓包机的局域网 IP。如果真机必须走移动数据可以用adb reverse把代理端口反向映射到设备本地然后代理填 127.0.0.1。这个命令是adb reverse tcp:8080 tcp:8080之后设备上访问 127.0.0.1:8080 就会转发到电脑的 8080 端口。5. 进阶技巧把重复流程脚本化用 Python 串起整条流水线前面每个环节单独做都不难难的是每次分析新 App 都要重来一遍。我的做法是写一个 Python 主控脚本把设备检查、证书安装、代理设置、Frida 注入、脱壳、反编译串成一条命令。下面是一个简化版的框架# 安卓逆向助手 - 流水线主控脚本 import subprocess import os import sys class AndroidReverseHelper: def __init__(self, package_name, proxy_host, proxy_port): self.package package_name self.proxy f{proxy_host}:{proxy_port} self.work_dir f./workspace/{package_name} os.makedirs(self.work_dir, exist_okTrue) def check_device(self): 检查设备连接和 root 状态 result subprocess.run([adb, devices], capture_outputTrue, textTrue) if device not in result.stdout: print(设备未连接) sys.exit(1) root_check subprocess.run( [adb, shell, su, -c, id], capture_outputTrue, textTrue ) if uid0 not in root_check.stdout: print(设备未 root部分功能不可用) return True def setup_proxy(self): 设置全局代理 subprocess.run([ adb, shell, settings, put, global, http_proxy, self.proxy ]) print(f代理已设置为 {self.proxy}) def dump_dex(self): 内存 dump 脱壳 output os.path.join(self.work_dir, dex) os.makedirs(output, exist_okTrue) subprocess.run([ frida-dexdump, -U, -f, self.package, -o, output ]) print(fdex 已 dump 到 {output}) def decompile(self): jadx 反编译 dex_dir os.path.join(self.work_dir, dex) output os.path.join(self.work_dir, java) subprocess.run([ jadx, -d, output, --show-bad-code, dex_dir ]) print(f反编译结果在 {output}) def run(self): self.check_device() self.setup_proxy() self.dump_dex() self.decompile() print(流水线执行完成) if __name__ __main__: helper AndroidReverseHelper( package_namecom.target.app, proxy_host192.168.1.100, proxy_port8080 ) helper.run()逻辑说明这个类把每个环节封装成方法run方法按顺序调用。check_device先确认设备可用setup_proxy设置代理dump_dex调用 frida-dexdumpdecompile调用 jadx。每个方法的输出都放在独立的 workspace 目录下方便后续查找。参数说明package_name是目标 App 的包名proxy_host和proxy_port是抓包机的地址。work_dir按包名分目录避免不同 App 的结果混在一起。实际使用中我会在dump_dex之前加一个等待让 App 完全启动后再 dump否则可能 dump 到不完整的 dex。这个框架的价值在于可扩展。比如加一个bypass_ssl_pinning方法在 dump 之前先注入 Frida 脚本绕过证书固定加一个search_keyword方法在反编译结果里自动搜索 “sign”、“encrypt” 等关键词并输出文件路径和行号。我现在的习惯是每分析一个新 App就把这次用到的特殊处理逻辑沉淀到脚本里下次遇到同类壳或同类保护直接复用。最后说一个我自己的教训不要迷信一键工具。工具能解决 80% 的通用场景但剩下 20% 的硬骨头比如 VMP 壳、自定义协议、so 层混淆还是得靠手工分析。脚本化的意义是把你从重复劳动里解放出来让你有更多时间啃那 20%。我一般会在脚本跑完自动反编译后先花十分钟浏览一遍关键类判断保护强度再决定是继续自动化还是转手工。这个判断习惯帮我省了很多无效折腾。希望帮到你。本文还有配套的精品资源点击获取