文章总结: 本文介绍利用Frida进行安卓APP加密快速定位。内容包括HookHashMap、ArrayList、String等常用类追踪数据,及HookMessageDigest、Mac、Cipher等加密库实现算法自吐。提供了完整JS脚本用于打印密钥、IV、加解密数据及调用栈,有效辅助分析Java层加密逻辑。 综合评分: 88 文章分类: 移动安全,逆向分析,渗透测试,安全工具
【APP测试】快速定位加密
原创
d0n9x1e d0n9x1e
蝉SEC
2026年1月15日 14:12 江苏
function showStacks() {
console.log(
Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
Java: hook系统函数、
1. HashMap的put方法
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a, b) {
if(a.equals("username")){
//打印函数栈信息
showStacks();
console.log("hashMap.put: ", a, b);
}
return this.put(a, b);
}
function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
2.ArrayList的add、addAll、set方法等
var arrayList = Java.use("java.util.ArrayList");
arrayList.add.overload('java.lang.Object').implementation = function (a) {
if(a.equals("username=15968079477")){
showStacks();
console.log("arrayList.add: ", a);
}
//console.log("arrayList.add: ", a);
return this.add(a);
}
arrayList.add.overload('int', 'java.lang.Object').implementation = function (a, b) {
console.log("arrayList.add: ", a, b);
return this.add(a, b);
}
3.TextUtils的isEmpty方法
var textUtils = Java.use("android.text.TextUtils");
textUtils.isEmpty.implementation = function (a) {
if(a == "2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0="){
showStacks();
console.log("textUtils.isEmpty: ", a);
}
//console.log("textUtils.isEmpty: ", a);
return this.isEmpty(a);
}
4.Log
var log = Java.use("android.util.Log");
log.w.overload('java.lang.String', 'java.lang.String').implementation = function (tag, message) {
showStacks();
console.log("log.w: ", tag, message);
return this.w(tag, message);
}
5.Collections的sort方法
用于对列表(List)进行排序
var collections = Java.use("java.util.Collections");
collections.sort.overload('java.util.List').implementation = function (a) {
showStacks();
try{
var result = Java.cast(a, Java.use("java.util.LinkedList"));
console.log("collections.sort List: ", result.toString());
}catch (e) {
result = Java.cast(a, Java.use("java.util.ArrayList"));
console.log("collections.sort List: ", result.toString());
}
return this.sort(a);
}
collections.sort.overload('java.util.List', 'java.util.Comparator')
.implementation = function (a, b) {
showStacks();
try{
var result = Java.cast(a, Java.use("java.util.LinkedList"));
console.log("collections.sort List Comparator: ", result.toString());
}catch (e) {
result = Java.cast(a, Java.use("java.util.ArrayList"));
console.log("collections.sort List Comparator: ", result.toString());
}
return this.sort(a, b);
}
6.JSONObject的put、getString方法等
var jSONObject = Java.use("org.json.JSONObject");
jSONObject.put.overload('java.lang.String', 'java.lang.Object').implementation = function (a, b) {
showStacks();
//var result = Java.cast(a, Java.use("java.util.ArrayList"));
console.log("jSONObject.put: ", a, b);
return this.put(a, b);
}
jSONObject.getString.implementation = function (a) {
//showStacks();
//var result = Java.cast(a, Java.use("java.util.ArrayList"));
console.log("jSONObject.getString: ", a);
var result = this.getString(a);
console.log("jSONObject.getString result: ", result);
return result;
}
7.Toast的show方法
显示信息提示
var toast = Java.use("android.widget.Toast");
toast.show.implementation = function () {
showStacks();
console.log("toast.show: ");
return this.show();
}
8.Base64
var base64 = Java.use("android.util.Base64");
base64.encodeToString.overload('[B', 'int').implementation = function (a, b) {
showStacks();
console.log("base64.encodeToString: ", JSON.stringify(a));
var result = this.encodeToString(a, b);
console.log("base64.encodeToString result: ", result)
return result;
}
java.net.URLEncoder
java.util.Base64
okio.Base64
okio.ByteString
9.String的getBytes、isEmpty方法
var str = Java.use("java.lang.String");
str.getBytes.overload().implementation = function () {
showStacks();
var result = this.getBytes();
var newStr = str.$new(result);
console.log("str.getBytes result: ", newStr);
return result;
}
str.getBytes.overload('java.lang.String').implementation = function (a) {
showStacks();
var result = this.getBytes(a);
var newStr = str.$new(result, a);
console.log("str.getBytes result: ", newStr);
return result;
}
10. String构造函数的Hook
var stringFactory = Java.use("java.lang.StringFactory");
stringFactory.newStringFromString.implementation = function (a) {
showStacks();
var retval = this.newStringFromString(a);
console.log("stringFactory.newStringFromString: ", retval);
return retval;
}
stringFactory.newStringFromChars.overload('[C').implementation = function (a) {
showStacks();
var retval = this.newStringFromChars(a);
console.log("stringFactory.newStringFromChars: ", retval);
return retval;
}
newStringFromBytes、newStringFromChars
newStringFromString、newStringFromStringBuffer、newStringFromStringBuilder
11. StringBuilder、StringBuffer的Hook
var sb = Java.use("java.lang.StringBuilder");
sb.toString.implementation = function () {
var retval = this.toString();
if (retval.indexOf("Encrypt") != -1) {
showStacks();
}
console.log("StringBuilder.toString: ", retval);
return retval;
}
var sb = Java.use("java.lang.StringBuffer");
sb.toString.implementation = function () {
var retval = this.toString();
if (retval.indexOf("username") != -1) {
showStacks();
}
console.log("StringBuffer.toString: ", retval);
return retval;
}
12. findViewById 找控件id(打印R$id的属性)
a) Java.enumerateLoadedClassesSync枚举所有已加载的类
如果不知道类路径,可以用这个方法,然后过滤一下类名
b) frida -U -f com.dodonew.online -l HookDemo.js -o log.txt --no-pause
-f 代码让frida帮我们重新启动app,一开始就注入js
--no-pause 直接运行主线程,中途不暂停
c) R$id 内部类的访问
d) R$id.btn_login.value 类的属性的访问
var btn_login_id = Java.use("com.dodonew.online.R$id").btn_login.value;
console.log("btn_login_id", btn_login_id);
var appCompatActivity = Java.use("android.support.v7.app.AppCompatActivity");
appCompatActivity.findViewById.implementation = function (a) {
if(a == btn_login_id){
showStacks();
console.log("appCompatActivity.findViewById: ", a);
}
return this.findViewById(a);
}
13. setOnClickListener
hook这个函数,比对控件id,打印函数栈
var btn_login_id = Java.use("com.dodonew.online.R$id").btn_login.value;
console.log("btn_login_id", btn_login_id);
var view = Java.use("android.view.View");
view.setOnClickListener.implementation = function (a) {
if(this.getId() == btn_login_id){
showStacks();
console.log("view.id: " + this.getId());
console.log("view.setOnClickListener is called");
}
return this.setOnClickListener(a);
}
test代码完全体
Java.perform(function () {
function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
// var hashMap = Java.use("java.util.HashMap");
// hashMap.put.implementation = function (a, b) {
// if(a.equals("username")){
// showStacks();
// console.log("hashMap.put: ", a, b);
// }
// //console.log("hashMap.put: ", a, b);
// return this.put(a, b);
// }
// var arrayList = Java.use("java.util.ArrayList");
// arrayList.add.overload('java.lang.Object').implementation = function (a) {
// if (a.equals("username=15968079477")) {
// showStacks();
// console.log("arrayList.add.overload('java.lang.Object'): ", a);
// }
// return this.add(a);
// }
// arrayList.add.overload('int', 'java.lang.Object').implementation = function (a, b) {
// console.log("arrayList.add.overload('int', 'java.lang.Object'): ", a, b);
// return this.add(a, b);
// }
// var textUtils = Java.use("android.text.TextUtils");
// textUtils.isEmpty.implementation = function (a) {
// if(a == "2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0="){
// showStacks();
// console.log("textUtils.isEmpty: ", a);
// }
// //console.log("textUtils.isEmpty: ", a);
// return this.isEmpty(a);
// }
// var log = Java.use("android.util.Log");
// log.w.overload('java.lang.String', 'java.lang.String').implementation = function (tag, message) {
// showStacks();
// console.log("log.w: ", tag, message);
// return this.w(tag, message);
// }
// var collections = Java.use("java.util.Collections");
// collections.sort.overload('java.util.List').implementation = function (a) {
// try{
// showStacks();
// var result = Java.cast(a, Java.use("java.util.ArrayList"));
// console.log("collections.sort List: ", result.toString());
// }catch (e) {
//
// }
// return this.sort(a);
// }
// collections.sort.overload('java.util.List', 'java.util.Comparator').implementation = function (a, b) {
// try{
// showStacks();
// var result = Java.cast(a, Java.use("java.util.ArrayList"));
// console.log("collections.sort List Comparator: ", result.toString());
// }catch (e) {
//
// }
// return this.sort(a, b);
// }
// var jSONObject = Java.use("org.json.JSONObject");
// jSONObject.put.overload('java.lang.String', 'java.lang.Object').implementation = function (a, b) {
// if (a.indexOf("Encrypt") != -1) {
// showStacks();
// console.log("jSONObject.put: ", a, b);
// }
// return this.put(a, b);
// }
// jSONObject.getString.implementation = function (a) {
// showStacks();
// console.log("jSONObject.getString: ", a);
// var result = this.getString(a);
// console.log("jSONObject.getString result: ", result);
// return result;
// }
});
加密库相关的hook(自吐算法)
SSL相关的hook
socket相关的hook
SocketOutputStream
SocketInputStream
读写文件相关的java.io.File
证书双向验证 Keystore.load 通常有证书和密码
安卓退出进程的方式
//快速定位协议头加密okhttp3的addHeader方法
var okhttp_Builder = Java.use('okhttp3.Request$Builder');
okhttp_Builder.addHeader.implementation = function (a, b) {
showStacks();
return this.addHeader(a, b);
}
算法自吐
应用场景
加密在Java层,并且调用了标准加密库的
加密在Java层,但是使用反射调用标准加密库的
加密在so层,但是用jni调用了Java标准加密库的
app被加固,但是调用了标准加密库的
通过打印函数栈辅助定位加密代码所在类
局限性
加密在Java层,自写的标准算法或非标准算法
加密在so层,C/C++写的算法
加密在Java层,并且调用了标准加密库,但是加密前和加密后的数据都做了其他处理
注意点
会发现我们直接hook okio.ByteString会报错,是因为这是第三方的库,实际上这个库在安卓本身也有,com.android.okhttp.okio.ByteString
Java.perform(function () {
function showStacks() {
console.log(
Java.use("android.util.Log")
.getStackTraceString(
Java.use("java.lang.Throwable").$new()
)
);
}
function logOutPut(msg) {
Java.use("android.util.Log").d("xiaojianbang", "frida inject: " + msg);
}
var ByteString = Java.use("com.android.okhttp.okio.ByteString");
function toBase64(tag, data) {
//logOutPut(tag + " Base64: " + ByteString.of(data).base64());
console.log(tag + " Base64: " + ByteString.of(data).base64());
}
function toHex(tag, data) {
//logOutPut(tag + " Hex: " + ByteString.of(data).hex());
console.log(tag + " Hex: " + ByteString.of(data).hex());
}
function toUtf8(tag, data) {
//logOutPut(tag + " Utf8: " + ByteString.of(data).utf8());
console.log(tag + " Utf8: " + ByteString.of(data).utf8());
}
// toBase64([48,49,50,51,52]);
// toHex([48,49,50,51,52]);
// toUtf8([48,49,50,51,52]);
//console.log(Java.enumerateLoadedClassesSync().join("\n"));
var messageDigest = Java.use("java.security.MessageDigest");
messageDigest.update.overload('byte').implementation = function (data) {
console.log("MessageDigest.update('byte') is called!");
showStacks();
return this.update(data);
}
messageDigest.update.overload('java.nio.ByteBuffer').implementation = function (data) {
console.log("MessageDigest.update('java.nio.ByteBuffer') is called!");
showStacks();
return this.update(data);
}
messageDigest.update.overload('[B').implementation = function (data) {
console.log("MessageDigest.update('[B') is called!");
showStacks();
var algorithm = this.getAlgorithm();
var tag = algorithm + " update data";
toUtf8(tag, data);
toHex(tag, data);
toBase64(tag, data);
console.log("=======================================================");
return this.update(data);
}
messageDigest.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
console.log("MessageDigest.update('[B', 'int', 'int') is called!");
showStacks();
var algorithm = this.getAlgorithm();
var tag = algorithm + " update data";
toUtf8(tag, data);
toHex(tag, data);
toBase64(tag, data);
console.log("=======================================================", start, length);
return this.update(data, start, length);
}
messageDigest.digest.overload().implementation = function () {
console.log("MessageDigest.digest() is called!");
showStacks();
var result = this.digest();
var algorithm = this.getAlgorithm();
var tag = algorithm + " digest result";
toHex(tag, result);
toBase64(tag, result);
console.log("=======================================================");
return result;
}
messageDigest.digest.overload('[B').implementation = function (data) {
console.log("MessageDigest.digest('[B') is called!");
showStacks();
var algorithm = this.getAlgorithm();
var tag = algorithm + " digest data";
toUtf8(tag, data);
toHex(tag, data);
toBase64(tag, data);
var result = this.digest(data);
var tags = algorithm + " digest result";
toHex(tags, result);
toBase64(tags, result);
console.log("=======================================================");
return result;
}
messageDigest.digest.overload('[B', 'int', 'int').implementation = function (data, start, length) {
console.log("MessageDigest.digest('[B', 'int', 'int') is called!");
showStacks();
var algorithm = this.getAlgorithm();
var tag = algorithm + " digest data";
toUtf8(tag, data);
toHex(tag, data);
toBase64(tag, data);
var result = this.digest(data, start, length);
var tags = algorithm + " digest result";
toHex(tags, result);
toBase64(tags, result);
console.log("=======================================================", start, length);
return result;
}
var mac = Java.use("javax.crypto.Mac");
mac.init.overload('java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (key, AlgorithmParameterSpec) {
console.log("Mac.init('java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
return this.init(key, AlgorithmParameterSpec);
}
mac.init.overload('java.security.Key').implementation = function (key) {
console.log("Mac.init('java.security.Key') is called!");
var algorithm = this.getAlgorithm();
var tag = algorithm + " init Key";
var keyBytes = key.getEncoded();
toUtf8(tag, keyBytes);
toHex(tag, keyBytes);
toBase64(tag, keyBytes);
console.log("=======================================================");
return this.init(key);
}
mac.update.overload('byte').implementation = function (data) {
console.log("Mac.update('byte') is called!");
return this.update(data);
}
mac.update.overload('java.nio.ByteBuffer').implementation = function (data) {
console.log("Mac.update('java.nio.ByteBuffer') is called!");
return this.update(data);
}
mac.update.overload('[B').implementation = function (data) {
console.log("Mac.update('[B') is called!");
var algorithm = this.getAlgorithm();
var tag = algorithm + " update data";
toUtf8(tag, data);
toHex(tag, data);
toBase64(tag, data);
console.log("=======================================================");
return this.update(data);
}
mac.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
console.log("Mac.update('[B', 'int', 'int') is called!");
var algorithm = this.getAlgorithm();
var tag = algorithm + " update data";
toUtf8(tag, data);
toHex(tag, data);
toBase64(tag, data);
console.log("=======================================================", start, length);
return this.update(data, start, length);
}
mac.doFinal.overload().implementation = function () {
console.log("Mac.doFinal() is called!");
var result = this.doFinal();
var algorithm = this.getAlgorithm();
var tag = algorithm + " doFinal result";
toHex(tag, result);
toBase64(tag, result);
console.log("=======================================================");
return result;
}
var cipher = Java.use("javax.crypto.Cipher");
cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function () {
console.log("Cipher.init('int', 'java.security.cert.Certificate') is called!");
return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation = function () {
console.log("Cipher.init('int', 'java.security.Key', 'java.security.SecureRandom') is called!");
return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation = function () {
console.log("Cipher.init('int', 'java.security.cert.Certificate', 'java.security.SecureRandom') is called!");
return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation = function () {
console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom') is called!");
return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation = function () {
console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom') is called!");
return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters').implementation = function () {
console.log("Cipher.init('int', 'java.security.Key', 'java.security.AlgorithmParameters') is called!");
return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key').implementation = function () {
console.log("Cipher.init('int', 'java.security.Key') is called!");
var algorithm = this.getAlgorithm();
var tag = algorithm + " init Key";
var className = JSON.stringify(arguments[1]);
if(className.indexOf("OpenSSLRSAPrivateKey") === -1){
var keyBytes = arguments[1].getEncoded();
toUtf8(tag, keyBytes);
toHex(tag, keyBytes);
toBase64(tag, keyBytes);
}
console.log("=======================================================");
return this.init.apply(this, arguments);
}
cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function () {
console.log("Cipher.init('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec') is called!");
var algorithm = this.getAlgorithm();
var tag = algorithm + " init Key";
var keyBytes = arguments[1].getEncoded();
toUtf8(tag, keyBytes);
toHex(tag, keyBytes);
toBase64(tag, keyBytes);
var tags = algorithm + " init iv";
var iv = Java.cast(arguments[2], Java.use("javax.crypto.spec.IvParameterSpec"));
var ivBytes = iv.getIV();
toUtf8(tags, ivBytes);
toHex(tags, ivBytes);
toBase64(tags, ivBytes);
console.log("=======================================================");
return this.init.apply(this, arguments);
}
cipher.doFinal.overload('java.nio.ByteBuffer', 'java.nio.ByteBuffer').implementation = function () {
console.log("Cipher.doFinal('java.nio.ByteBuffer', 'java.nio.ByteBuffer') is called!");
showStacks();
return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload('[B', 'int').implementation = function () {
console.log("Cipher.doFinal('[B', 'int') is called!");
showStacks();
return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload('[B', 'int', 'int', '[B').implementation = function () {
console.log("Cipher.doFinal('[B', 'int', 'int', '[B') is called!");
showStacks();
return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload('[B', 'int', 'int', '[B', 'int').implementation = function () {
console.log("Cipher.doFinal('[B', 'int', 'int', '[B', 'int') is called!");
showStacks();
return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload().implementation = function () {
console.log("Cipher.doFinal() is called!");
showStacks();
return this.doFinal.apply(this, arguments);
}
cipher.doFinal.overload('[B').implementation = function () {
console.log("Cipher.doFinal('[B') is called!");
showStacks();
var algorithm = this.getAlgorithm();
var tag = algorithm + " doFinal data";
var data = arguments[0];
toUtf8(tag, data);
toHex(tag, data);
toBase64(tag, data);
var result = this.doFinal.apply(this, arguments);
var tags = algorithm + " doFinal result";
toHex(tags, result);
toBase64(tags, result);
console.log("=======================================================");
return result;
}
cipher.doFinal.overload('[B', 'int', 'int').implementation = function () {
console.log("Cipher.doFinal('[B', 'int', 'int') is called!");
showStacks();
var algorithm = this.getAlgorithm();
var tag = algorithm + " doFinal data";
var data = arguments[0];
toUtf8(tag, data);
toHex(tag, data);
toBase64(tag, data);
var result = this.doFinal.apply(this, arguments);
var tags = algorithm + " doFinal result";
toHex(tags, result);
toBase64(tags, result);
console.log("=======================================================", arguments[1], arguments[2]);
return result;
}
var signature = Java.use("java.security.Signature");
signature.update.overload('byte').implementation = function (data) {
console.log("Signature.update('byte') is called!");
return this.update(data);
}
signature.update.overload('java.nio.ByteBuffer').implementation = function (data) {
console.log("Signature.update('java.nio.ByteBuffer') is called!");
return this.update(data);
}
signature.update.overload('[B', 'int', 'int').implementation = function (data, start, length) {
console.log("Signature.update('[B', 'int', 'int') is called!");
var algorithm = this.getAlgorithm();
var tag = algorithm + " update data";
toUtf8(tag, data);
toHex(tag, data);
toBase64(tag, data);
console.log("=======================================================", start, length);
return this.update(data, start, length);
}
signature.sign.overload('[B', 'int', 'int').implementation = function () {
console.log("Signature.sign('[B', 'int', 'int') is called!");
return this.sign.apply(this, arguments);
}
signature.sign.overload().implementation = function () {
console.log("Signature.sign() is called!");
var result = this.sign();
var algorithm = this.getAlgorithm();
var tag = algorithm + " sign result";
toHex(tag, result);
toBase64(tag, result);
console.log("=======================================================");
return result;
}
});
so关键函数
NewStringUTF
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:蝉SEC d0n9x1e d0n9x1e《【APP测试】快速定位加密》
版权声明
本站仅做备份收录,仅供研究与教学参考之用。
读者将信息用于其他用途的,全部法律及连带责任由读者自行承担,本站不承担任何责任。








评论