文章目录
-
- [1. 前言](#1. 前言)
- [2. 准备工作](#2. 准备工作)
- [3. Hook 实现与分析](#3. Hook 实现与分析)
-
- [3.1 Hook思路](#3.1 Hook思路)
- [3.2 完整脚本与代码解析](#3.2 完整脚本与代码解析)
- [3.3 Hook结果说明](#3.3 Hook结果说明)
-
- [关于"MD5 | 明文:ELF"的说明:](#关于“MD5 | 明文:ELF”的说明:)
- [4. 章节总结](#4. 章节总结)
⚠️本博文所涉安全渗透测试技术、方法及案例,仅用于网络安全技术研究与合规性交流,旨在提升读者的安全防护意识与技术能力。任何个人或组织在使用相关内容前,必须获得目标网络 / 系统所有者的明确且书面授权,严禁用于未经授权的网络探测、漏洞利用、数据获取等非法行为。
1. 前言
在企业级应用的安全架构中,哈希算法是保障数据机密性的核心技术之一,其80%的应用场景集中在三大核心领域:密钥存储、用户口令加密和敏感数据脱敏。
然而,哈希算法的安全性并非一劳永逸------随着算力的飞速提升,MD5、SHA-1等早期算法已被证实存在碰撞漏洞,可被轻易破解;即便是采用强哈希算法,若直接对原始数据进行哈希计算(无加盐、低迭代次数),也极易遭受暴力破解或彩虹表攻击(现代GPU每秒可完成数十亿次哈希计算,简单哈希几乎无防御能力),同时无法抵御"撞库攻击"(攻击者利用其他平台泄露的明文-密文对匹配当前系统)。
基于企业实际安全需求,本文聚焦两类最具代表性的哈希场景:
- 纯哈希场景:以SHA-256、SHA-512为核心(这两种算法是当前大厂普遍采用的强哈希标准,抗碰撞性和安全性经过长期验证),通过Hook哈希计算的核心类,可直接捕获明文与密文的对应关系,适用于检测基础哈希逻辑的安全性;
- 口令哈希场景:以PBKDF2为核心(业界公认的安全密钥派生函数,通过"哈希+随机盐+高迭代次数"显著提升破解难度),需完整捕获明文、盐、迭代次数、算法名等关键参数,才能还原加密链路,这是企业级口令存储的主流方案(符合NIST等安全标准)。
选择这两个场景的原因在于:它们覆盖了企业从基础哈希应用到高级安全加固的全链路需求,且均为实际业务中最易出现安全漏洞的环节------纯哈希场景考验基础算法选型,口令哈希场景则考验加密参数配置的严谨性。
2. 准备工作
本章节使用的示例 APK、相关源码如下::
链接: https://pan.baidu.com/s/1Kj4PaVI2t587-k4khYpMZA?pwd=irce
提取码: irce
为顺利开展Hook实验,需完成以下准备工作:
-
启动Frida服务:在目标Android模拟器中部署并启动frida-server,步骤同前面几章节(如图所示)。

-
安装示例APK:将待测试的混淆APK(包含纯哈希和PBKDF2加密逻辑)拖进模拟器中安装。
3. Hook 实现与分析
3.1 Hook思路
企业哈希场景的Hook核心是"跟踪加密链路的关键节点":
- 纯哈希场景 :Java中
MessageDigest是所有哈希算法(如SHA-256、SHA-512)的统一入口,其digest方法负责接收明文并输出密文,因此Hook该方法即可捕获算法类型、明文和密文; - 口令哈希(PBKDF2)场景 :PBKDF2的加密过程涉及多个核心组件------
PBEKeySpec存储明文、盐、迭代次数等参数,SecretKeyFactory指定加密算法(如PBKDF2WithHmacSHA256),Base64Encoder负责最终密文的编码,因此需依次Hook这三个类的关键方法,才能完整还原加密链路。
3.2 完整脚本与代码解析
javascript
import Java from "frida-java-bridge";
const PACKAGE_NAME = "com.example.fridaapk";
// char数组转明文
function charToString(pwdCharArr) {
if (!pwdCharArr) return "空";
let pwd = "";
for (let i = 0; i < pwdCharArr.length; i++) pwd += pwdCharArr[i];
return pwd;
}
// 字节数组转明文
function bytesToString(byteArr) {
if (!byteArr || byteArr.length === 0) return "空";
try {
// 尝试将字节数组转换为字符串(适用于可打印字符)
const str = Java.use("java.lang.String").$new(byteArr, "UTF-8");
return str.toString();
} catch (e) {
// 转换失败则返回十六进制(适用于二进制数据如盐)
return getHexBytes(byteArr);
}
}
// 字节数组转16进制
function getHexBytes(byteArr) {
if (!byteArr || byteArr.length === 0) return "空";
let hex = "";
for (let i = 0; i < byteArr.length; i++) {
const b = byteArr[i] & 0xFF;
hex += (b < 16 ? "0" : "") + b.toString(16);
}
return hex;
}
Java.perform(() => {
try {
// Hook MessageDigest检测纯哈希算法
const MessageDigest = Java.use("java.security.MessageDigest");
MessageDigest.digest.overload('[B').implementation = function (inputBytes) {
const algorithm = this.getAlgorithm(); // 获取当前哈希算法(如SHA-256)
const result = this.digest(inputBytes); // 执行原始哈希计算
// 打印算法类型、输入明文、输出密文
console.log(`哈希算法: ${algorithm} | 明文: ${bytesToString(inputBytes)} | 密文: ${getHexBytes(result)}`);
console.log("----------------------------------------");
return result;
};
console.log("----------------------------------------");
// Hook PBEKeySpec获取输入参数(PBKDF2核心参数)
const PBEKeySpec = Java.use("javax.crypto.spec.PBEKeySpec");
PBEKeySpec.$init.overload("[C", "[B", "int", "int").implementation = function (pwdChar, salt, iter, keyLen) {
// 打印明文(char数组转字符串)、盐(字节数组转十六进制)
console.log(`明文:${charToString(pwdChar)} | 盐:${getHexBytes(salt)}`);
// 打印迭代次数和密钥长度(PBKDF2安全强度的关键指标)
console.log(`迭代次数:${iter} | 密钥长度:${keyLen}位`);
return this.$init(pwdChar, salt, iter, keyLen); // 执行原始初始化
};
// Hook SecretKeyFactory获取算法名(确认PBKDF2的具体实现)
const SecretKeyFactory = Java.use("javax.crypto.SecretKeyFactory");
SecretKeyFactory.getInstance.overload("java.lang.String").implementation = function (algo) {
console.log(`算法:${algo}`); // 如PBKDF2WithHmacSHA256
return this.getInstance(algo); // 执行原始方法
};
// Hook Base64编码获取最终密文(PBKDF2结果的常见存储形式)
const Base64Encoder = Java.use("java.util.Base64$Encoder");
Base64Encoder.encodeToString.overload('[B').implementation = function (byteArray) {
const result = this.encodeToString(byteArray); // 执行原始Base64编码
console.log("密文(Base64):", result); // 打印最终存储的密文
console.log("----------------------------------------");
return result;
};
} catch (error) {
console.error("Hook执行出错:", error.message);
}
});
同前面几个章节的步骤,通过命令npm run watch编译 hook 脚本。

代码关键部分解析:
-
辅助函数:
charToString:将PBEKeySpec中存储明文的char数组转换为字符串(口令通常以char数组形式传入,避免内存泄露);bytesToString:尝试将字节数组转为UTF-8字符串(适用于明文),失败则转为十六进制(适用于二进制数据如盐);getHexBytes:将字节数组转为十六进制字符串(便于密文、盐等二进制数据的可读性展示)。
-
纯哈希Hook(MessageDigest) :
通过Hook
MessageDigest.digest(byte[])方法,在计算哈希前后分别获取算法类型(getAlgorithm())、输入明文(inputBytes)和输出密文(result),直接关联明文与密文的对应关系。 -
PBKDF2链路Hook:
PBEKeySpec:Hook构造方法获取口令明文(pwdChar)、盐(salt)、迭代次数(iter)、密钥长度(keyLen),这些是PBKDF2的核心安全参数;SecretKeyFactory:HookgetInstance方法获取具体算法(如PBKDF2WithHmacSHA256),确认哈希函数的实现;Base64Encoder:HookencodeToString方法获取最终密文(企业通常将PBKDF2结果Base64编码后存储)。
3.3 Hook结果说明
运行脚本后,操作应用中与哈希相关的功能(在示例应用中点击"纯SHA256""SHA512""PBKDF2加密"3个按钮),控制台会输出如下信息(如图所示):

- 纯哈希场景:点击按钮后,会直接打印"哈希算法: SHA-256 | 明文: xxx | 密文: xxx",清晰展示输入输出关系;
- PBKDF2场景:会依次打印明文、盐、迭代次数、算法名、最终Base64密文,完整还原加密全链路。
关于"MD5 | 明文:ELF"的说明:
启动时可能出现MD5相关打印,这是Android系统加载SO库时的底层校验行为(系统会对SO文件的ELF头进行MD5哈希校验,确保文件完整性),与应用业务逻辑无关,可直接忽略。
4. 章节总结
本章通过Frida实现了企业级哈希场景的Hook,核心方法包括:
- 利用
Java.use获取目标类(如MessageDigest、PBEKeySpec); - 重写关键方法(如
digest、构造方法、getInstance)的implementation,在保留原始功能的同时插入日志逻辑; - 通过辅助函数处理字节/字符数组与字符串的转换,提升输出可读性。
选择"纯哈希(SHA-256/SHA-512)"和"口令哈希(PBKDF2)"作为案例,是因为它们构成了企业加密场景的"基础+进阶"体系:
- 纯哈希是最基础的加密形式,考验算法选型的安全性(需摒弃弱哈希);
- PBKDF2是企业级口令存储的进阶方案,通过多参数协同提升安全性,是大厂安全基线的核心要求。
掌握这两类场景的Hook方法,可有效检测加密实现是否符合安全标准,为企业数据安全加固提供依据。