type
Post
status
Published
date
Jul 10, 2026
slug
summary
本篇目标:构建一个「加密操作全自动监控系统」。第12-13篇分别讲了 Cipher、MessageDigest、Mac、Signature 的 Hook 方法——但在实际逆向中,你面对一个陌生 App 时不知道它用了哪些加密、在什么时机调用、密钥从哪来。一个个试效率太低。本篇的方案是:加载一个脚本,操作 App,所有加密操作自动"吐"出来——算法名称、密钥、IV、输入、输出、调用栈,一目了然。
tags
frida
实用教程
加解密
逆向工程
category
公众号
icon
password
网址
上次编辑时间
Jul 10, 2026 06:38 AM
comment
AI 总结
本篇目标:构建一个「加密操作全自动监控系统」。第12-13篇分别讲了 Cipher、MessageDigest、Mac、Signature 的 Hook 方法——但在实际逆向中,你面对一个陌生 App 时不知道它用了哪些加密、在什么时机调用、密钥从哪来。一个个试效率太低。本篇的方案是:加载一个脚本,操作 App,所有加密操作自动"吐"出来——算法名称、密钥、IV、输入、输出、调用栈,一目了然。这就是安卓逆向圈子里说的「算法自吐」。
配套脚本:本篇产出的crypto_monitor.js(700+ 行,6 个模块,单文件复制即用)已整理好打包。 关注本公众号后私信回复关键词「脚本」 即可获取,本系列与后续 Unidbg / SO 逆向 / ARM 汇编 等系列脚本会统一在此发放并持续更新。
一、什么是「算法自吐」
1.1 概念
「算法自吐」不是一个 Frida API 的名字,而是一种逆向工作模式:
- 加载一个预先编写好的、覆盖所有标准加密 API 的 Hook 脚本
- 正常操作 App(登录、下单、搜索……)
- 从控制台输出中直接读出 App 使用的加密方案
你不需要提前知道 App 用了什么算法、不需要在 jadx 中搜索加密代码、不需要处理代码混淆——因为不管 App 的业务代码怎么写、怎么混淆,它最终都要调用
javax.crypto.Cipher、java.security.MessageDigest 等标准 API。Hook 标准 API 就能把整个加密链路看清楚。1.2 与第12-13篇的区别
| | 第 12-13 篇 | 本篇(算法自吐) |
| :-- | :-- | :-- |
| 定位 | 理解每个 API 的原理和 Hook 方法 | 一键使用 |
| 脚本结构 | 每个 API 单独的脚本 | 全部整合为一个脚本 |
| 输出格式 | 基础 console.log | 彩色彩条事件头、字段对齐、调用栈过滤 |
| Cipher 实例关联 | 无(getInstance/init/doFinal 分散输出) | 自动关联同一 Cipher 实例的三步操作 |
| 输出范围 | 单 API 字段 | 完整自吐参数(算法+密钥+IV+输入+输出+栈) |
| 性能控制 | 无 | 模块开关、栈帧包名过滤、限速 |
| 落地姿势 | 需要按目标 API 改 | 复制脚本、改 CONFIG 即用 |
1.3 工作流

算法自吐工作流
二、核心挑战:Cipher 实例关联
2.1 问题
Cipher 的三步操作(
getInstance → init → doFinal)是在同一个 Cipher 实例上依次调用的:如果你分别 Hook 这三个方法,它们的回调是独立触发的——你在
getInstance 回调中看到了 AES/CBC,在 init 回调中看到了密钥,在 doFinal 回调中看到了明文和密文。但问题是:这三次回调之间没有天然的关联。如果 App 在多线程中同时进行多次加密(比如同时加密密码和签名),三步操作的回调会交错出现,你无法区分哪个密钥对应哪次加密。2.2 解决方案:用 Cipher 实例的 hashCode 作为关联 ID
为什么选
hashCode?Frida JS 端没有标准 WeakRef 引 Java 对象的能力,自增 ID 又无法在 doFinal 时拿回 getInstance 时分配的值。hashCode 是 Java 对象自带、跨方法可复读的身份标识——最适合做"查 ctx"的 key。在三步操作中,
this(Java 对象引用)是同一个 Cipher 实例,所以 this.hashCode() 在三处取到的值相同,可以将信息聚合到一起:
hashCode 关联机制
这个关联机制是本篇脚本与第12/13篇分散 Hook 脚本的核心区别。
风险注脚:JavaObject.hashCode()默认是 identity hash(System.identityHashCode),32-bit 整数——理论上两个不同对象可碰撞。本脚本的应对措施是在doFinal输出报告后立刻delete cipherCtxMap[id]:既避免长会话内存累积,也将"同一时刻活跃实例数"压到最小,让碰撞概率降到可忽略。代价是同一 Cipher 重复doFinal(连续多块加密)时第二次会丢上下文——绝大多数业务"一次性 doFinal" 不受影响。
三、完整的 crypto\_monitor.js
本篇的核心产出。所有代码包在一个 IIFE 中——把下方第 3.1 ~ 3.7 节七段代码顺序拼接进同一个
.js 文件即可使用。各模块之间的关系:

crypto\_monitor.js 7 模块架构
3.1 骨架:配置与辅助函数
入口、CONFIG 开关、智能格式化、调用栈过滤、限速器——后续每个模块都会复用。
阅读地图(这段代码长,扫读时按顺序找这 4 处): 1.CONFIG(功能开关 + 输出控制 + 性能 + 外观)—— 改这里就能定制脚本行为 2.C调色板 +tint()—— 256 色背景标签 + ANSI 着色封装 3.bytesToHex/smartFormat—— 字节到展示字符串的两个核心格式器 4.printHeader/printField/printMultiInput/printStack—— 4 个输出原语,全部带buf参数支持原子事件
3.2 Cipher 监控
第二章介绍的 hashCode 关联机制在这里实现。
getInstance 创建上下文 → init 补充 mode/key/iv → doFinal 输出完整报告并清理上下文(避免长会话累积内存)。阅读地图: 1.Cipher.getInstance三个重载(String / String,String / String,Provider)—— 都建cipherCtxMap[id]2.handleInit(...)抽取共享逻辑 —— 再被五个Cipher.init重载复用 3.reportCipher(...)—— 用buf累积事件,结尾一次console.log(buf.join("\n"))原子吐出 4.Cipher.doFinal三个重载分别接null/byte[]/byte[],offset,len,全部调reportCipher
3.3 MessageDigest 监控
Hash 不需要复杂关联——每个 MessageDigest 实例独立计算。把
update 累积的输入用同一个 hashCode 串起来,在 digest() 时一并打印。3.4 Mac / HMAC 监控
模式与 MessageDigest 类似,但 Mac 在
init 时即可拿到 key,所以上下文从 init 起。阅读地图: 1.Mac.init两个重载(Key / Key+ParamSpec)—— 建macDataMap[id]并存 key 2.Mac.update三个重载(byte\[\] / byte\[\],int,int / ByteBuffer)—— 累积到inputs3.Mac.doFinal()无参 —— 用printMultiInput把多段update用 ①②③ 区分后打印 4. 故意不hookdoFinal(byte[])—— 它内部 =update + doFinal(),hook 便捷版会双打印
3.5 Signature 监控
Signature 多用于 RSA / ECDSA 签名,逻辑比 Mac 更直接:
update 显示数据,sign / verify 输出最终结果。阅读地图: 1.Signature.update两个重载(byte\[\] / byte\[\],int,int)—— 每次调用直接打一个独立事件,不做累积 2.Signature.sign()—— 单独打 sign 事件(含签名结果 + 栈) 3.Signature.verify(byte[])—— 单独打 verify 事件,标题里写明→ true/false4. 共 4 个独立 hook,无共享上下文(不像 Cipher 需要ctxMap)
3.6 密钥生成 / PBKDF2 监控
SecretKeySpec 构造拦截硬编码密钥;SecretKeyFactory.generateSecret 抓 PBKDF2 派生过程(密码 + 盐 + 迭代次数 + 派生密钥)。阅读地图: 1.SecretKeySpec.$init(byte[], String)—— 拦截密钥装配,看到字节 + 算法名 2.IvParameterSpec.$init(byte[])—— 拦截 IV 装配,常与上一条紧邻出现(参考第 4.3 节双事件案例) 3.SecretKeyFactory.generateSecret(KeySpec)—— 拦截 PBKDF2/PBE 派生,能拿到原始密码、盐、迭代次数 + 派生密钥 4. 这一节是业务侧逆向最重要的入口:硬编码密钥几乎都在这里现形
3.7 启动 Banner 与 IIFE 收尾

crypto\_monitor Banner截图
四、输出格式解读
4.1 Cipher 输出
真机运行时,事件头是带背景色的彩条标签:Cipher 青、Hash 深蓝、HMAC 品红、Signature 金、KeyGen / KeyDerive 绿。下面三张截图都是 QQ 音乐启动后真实抓到的事件,与脚本结构一一对照。

Cipher 事件真机截图
如何解读:
- 算法:
AES/CBC/PKCS5Padding · ENCRYPT—— AES、CBC 模式、PKCS5 填充、加密方向
- 密钥:
34633364643966623062346163335631(32 hex = 16 字节 = AES-128)
- 把这 16 字节当 ASCII 解码 =
"4c3dd9fb0b4a3561"—— 本身就是个 16 字符的 hex 字符串(类似 MD5 截断结果直接.getBytes()当 AES key 用)。表面 AES-128,实际熵只有 8 字节
- IV:与密钥完全相同的字节。CBC 模式的 IV 应该每次随机生成,IV ≡ Key 是经典反模式:相同明文永远加密成相同密文,且暴露了 key recovery 攻击面
- 输入:起始字节
1f 8b是 GZIP 魔数 —— 业务先gzip压缩 JSON,再走 AES。末尾...(997 bytes)是smartFormat截断标记,告诉你完整长度,屏上只展示前maxDataLength=256字节
- 输出:
...(496 bytes)表明密文总长 496 字节(16 字节倍数,CBC 强制对齐)
- 调用栈:
com.tencent.beacon.base.net.b.c.b→util.b.b—— 这是腾讯灯塔 Beacon SDK 的网络上报路径,QQ 音乐用它进行用户行为埋点。
4.2 Hash 多段输入(SHA-256)

Hash 多段输入真机截图
如何解读:
- 算法:SHA-256
- 输入分 ①~④ 四段 —— 每个环号是脚本帮你区分的"第几次
update()":
- ①
"false"—— 某个布尔标志
- ②
"https://y.qq.com/music/common/upload/t_cm3_photo_publish/7184371.png"—— 图片资源 URL
- ③
8000000080000000—— 8 字节占位(两个0x80000000,常见的"未指定的宽高")
- ④
"android.support.rastermill.FrameSequenceDrawable"—— 解码器类全限定名
- 输出:32 字节(256 位)摘要,用作磁盘缓存项的 key
- 调用栈:
com.bumptech.glide.load.engine.cache.SafeKeyGenerator.calculateHexStringDigest→getSafeKey→DiskLruCacheWrapper.get—— Glide 图片库生成磁盘缓存键的标准路径。
4.3 KeyGen 双事件(SecretKeySpec + IvParameterSpec)

KeyGen 双事件真机截图
这是两条独立事件紧挨着出现,因为业务代码连续构造了 SecretKeySpec 和 IvParameterSpec。
上半:KEYGEN · SecretKeySpec · AES
- 密钥:
"4c5d9fb0b4af2561"(smartFormat 双行:文本 + hex)
- 长度:16 bytes(128 bits)→ AES-128
- 栈:
com.tencent.beacon.base.net.b.c.a→util.b.b/a—— 同样是腾讯灯塔 Beacon SDK
下半:KEYGEN · IvParameterSpec
- IV:
34633564396662306234616632353631,ASCII 解码 =4c5d9fb0b4af2561
- 与上面的密钥字节完全相同
五、性能控制:限速、过滤、按需启用
5.1 限速
某些 App 在启动时会进行大量的哈希操作(类校验、签名验证等),可能在几秒内触发几百次 MessageDigest 调用。
CONFIG.rateLimitPerSecond 控制每秒最多输出多少条日志。触发后行为:令牌耗尽时
canLog() 返回 false,整个事件(含 header / 字段 / 栈)全部丢弃,不会出现半条事件的情况。设为 0 表示完全不限速。5.2 包名过滤
设置
CONFIG.filterPackage = "com.example.app" 后,调用栈只显示包含该包名的帧。这样可以过滤掉 Android Framework 和第三方库的内部调用,只看 App 业务代码触发的加密操作。触发后行为:只过调用栈这一字段——事件本身仍然完整打印(header、密钥、IV、输入、输出都在),仅栈帧被剪短。即使过滤后栈为空,事件其它字段也不受影响。
5.3 按需启用模块
如果你只关心对称加密(AES),可以关闭其他模块:
触发后行为:开关为
false 时,对应模块的 Java.perform 整个块根本不执行——hook 完全不安装到目标进程。和限速(事件丢弃)不一样,关闭模块连开销都没有,可放心用于性能敏感场景。5.4 与 deoptimizeEverything 配合
如果加载脚本后操作 App 没有任何输出,可能是 JIT 导致 Hook 失效(第 07 篇讲过)。在脚本开头加上:
触发后行为:把 ART 已 JIT 编译的方法全部"反优化"回解释执行,Hook 命中率上升、但启动 1-3 秒会出现明显卡顿。仅在确实抓不到事件时再启用。
5.5 已知失效场景
「算法自吐」不是万能的。以下四种场景下脚本会看不到加密(或看到不完整)——提前知道边界,可以避免怀疑自己抄错代码。

无输出排查决策树
| 场景 | 现象 | 处理 |
| :-- | :-- | :-- |
| AndroidKeyStore 硬件密钥 | 能看到 Cipher 调用,但
密钥 字段显示对象引用而非字节 | 硬件密钥不可导出(getEncoded() 返回 null),Frida Java 层读不出。需在 Native 层 Hook 加密引擎(见第15篇 Native 层加密还原) |
| Native 层直接加密 | App 不走 Java JCE,直接在 SO 里调 OpenSSL / Mbedtls / BoringSSL | 本篇脚本全无输出。切到 Native 层 Hook——AES_encrypt、EVP_CipherUpdate、SHA256_Update(见第15篇 Native 层加密还原) |
| Conscrypt EngineSpi 旁路 | 某些 OkHttp/TLS 实现直连 ConscryptEngineSocket,不走 javax.crypto.Cipher | 见第13篇 第五章 Provider 路径;补 hook ConscryptEngine.wrap/unwrap |
| 自定义 JCE Provider | 加固方案自实现 Provider 替换标准实现 | 用 Security.getProviders() 观察是否有非标准 Provider;hook 其 engineXxx 系列方法 |自检套路——加载脚本后正常操作 App 但完全无 Cipher / Hash 输出,先排除两件事:
- Hook 是否成功:看是否打印
[OK] Cipher 监控等启动信息
- JIT 是否吃掉了 Hook:试加
Java.deoptimizeEverything()(见 第5.4节)
两件都做了仍无输出,大概率是上表中的某种边界——此时该转 Native 层 Hook(关键词
AES_encrypt / EVP_CipherUpdate / SHA256_Update / MD5_Update),而不是反复改本篇脚本。后续 Native 加密还原专题会展开。六、与 Objection 的对比
Objection 是 Frida 的一个自动化封装工具,它也有加密监控功能:
| | crypto\_monitor.js(本篇) | Objection |
| :-- | :-- | :-- |
| Cipher 实例关联 | 有(getInstance/init/doFinal 关联输出) | 无(每个方法独立输出) |
| 智能数据显示 | smartFormat(自动判断文本/二进制) | 原始 toString |
| 密钥提取 | 自动 cast 为 SecretKeySpec 提取字节 | 只显示对象引用 |
| IV 提取 | 自动 cast 为 IvParameterSpec 提取 | 不提取 |
| PBKDF2 监控 | 有 | 无 |
| 调用栈过滤 | 可按包名过滤 | 无过滤 |
| 性能控制 | 限速 + 模块开关 | 无 |
| 定制性 | 完全可定制 | 有限 |
上表基于 Objection v1.11 之前的实测印象;新版本的watch语法及输出行为可能变化,使用前请对照 objection GitHub 文档。
选型口诀:Objection 验证调用,
crypto_monitor 还原方案。总结
「算法自吐」是安卓逆向中效率最高的加密分析手段。核心思路是 Hook 标准加密 API 的「咽喉要道」——不管 App 怎么混淆,最终都要调用
Cipher、MessageDigest、Mac 等系统类。本篇的
crypto_monitor.js 复制即用,关键特性包括:- Cipher 实例关联:通过 hashCode 将 getInstance / init / doFinal 三步操作关联到同一个上下文,输出完整的「算法 + 密钥 + IV + 输入 + 输出」
- 智能数据显示:
smartFormat自动判断文本 / 二进制,同时展示可读文本和十六进制
- 密钥自动提取:通过
Java.cast将 Key 接口转为 SecretKeySpec,提取密钥字节
- 多段输入区分:MessageDigest / Mac 的多次
update()用 ①②③ 环号标记,避免误读
- 原子事件输出:每个事件用
buf累积、一次console.log吐出,多线程并发也不会字段错位
- 彩色彩条事件头:256 色背景标签,主题再黑也能区分事件类型
- 调用栈追踪:指向 App 业务代码中调用加密的位置
- 性能控制:限速、模块开关、包名过滤
实战工作流:
加载脚本 → 操作 App → 读取自吐参数(算法 / 密钥 / IV / 输入 / 输出 / 栈)。从这些参数到 Python 离线复现只差一步——把抓到的字节抄进 pycryptodome,对照真机结果验证即可。📦 获取本篇脚本
crypto_monitor.js 完整版(700+ 行,6 模块单文件,复制即用)+ 配套 README 使用说明已打包:- 关注原公众号【泡泡以安】
- 私信回复关键词「脚本」
回复内含本系列与其它系列(Unidbg / SO 逆向 / ARM 汇编 ……)的脚本汇总,长期维护更新。
- Author:24th
- URL:https://24th.top/article/399e5b08-46db-8094-8c9b-d5883875719b
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!









