目录
-
-
-
- 一、知识补充
- 二、Hook前需要了解的
- 三、Hook中出现的问题处理
- 四、代码整合
- 五、Hook Java基础类中的所有加密类
-
-
一、知识补充
Hook Java层方法分类:
- 自定义方法(开发时程序员自己写的,例如:对登录做校验的方法);
- Java基础类(安装Java自带的基础库)中相关方法 (例如:java.security.MessageDigest,这个是Java中自带的md5加密类,Hook这个类中加密方法update);
- Java第三方类库(Hook安装的第三方库中的方法,类似于python中的requests库中的get方法);
这里我们以Java基础类为例:
jscode = """
Java.perform(function(){
var TestSig = Java.use("java.security.MessageDigest"); // 类的加载路径
TestSig.update.implementation = function(str){ // str为update的参数,原update需要几个参数就写几个,需要查看该的类中update相关调用方法
send("success");
this.update(str);
};
});
"""
二、Hook前需要了解的
知道上面知识点我们可以通过Hook Java基础类或第三方类库中的所有加密类来盲猜app用了哪种加密类。app用的加密类只要在我们Hook的类中,就可以判断出app用了哪种加密类,通过调用堆栈定位到加密代码。
如何才能知道Java基础类和第三方类库中所有的加密类和通过调用什么方法进行加密的呢???
- 查询Java api文档 https://tool.oschina.net/apidocs/apidoc?api=jdk-zh
- 百度(找大佬造过的轮子)
分析
java.security.MessageDigest
类中能够获得以下信息:
- 数据用的什么加密算法;
- 调用什么方法能获取到加密前的数据;
- 调用什么方法能获取到加密后的数据;
import java.security.MessageDigest;
public class MD5Util {
public final static String MD5(String pwd) {
// 用于加密的字符
char md5String[] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F' };
try {
// 使用平台的默认字符集将此 String 编码为 byte序列,并将结果存储到一个新的 byte数组中
byte[] btInput = pwd.getBytes();
// 获得指定摘要算法的 MessageDigest对象,此处为MD5
// MessageDigest类为应用程序提供信息摘要算法的功能,如 MD5 或 SHA 算法。
// 信息摘要是安全的单向哈希函数,它接收任意大小的数据,并输出固定长度的哈希值。
MessageDigest mdInst = MessageDigest.getInstance("MD5");
// System.out.println(mdInst);
// MD5 Message Digest from SUN, <initialized>
// MessageDigest对象通过使用 update方法处理数据, 使用指定的byte数组更新摘要
mdInst.update(btInput);
// System.out.println(mdInst);
// MD5 Message Digest from SUN, <in progress>
// 摘要更新之后,通过调用digest()执行哈希计算,获得密文
byte[] md = mdInst.digest();
// System.out.println(md);
// 把密文转换成十六进制的字符串形式
int j = md.length;
// System.out.println(j);
char str[] = new char[j * 2];
int k = 0;
for (int i = 0; i < j; i++) {
// i = 0
byte byte0 = md[i];
// 95
str[k++] = md5String[byte0 >>> 4 & 0xf];
// 5
str[k++] = md5String[byte0 & 0xf];
// F
}
// 返回经过加密后的字符串
return new String(str);
}
catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
分析上面代码可知:
- Hook
方法可以获取到用的什么加密算法;getInstance
- Hook
方法可以获取到加密前后的数据,参数是加密前的数据,update
- Hook
方法可以获取到加密后的数据(返回值);digest
三、Hook中出现的问题处理
Hook脚本:
import frida #导入frida模块
import sys #导入sys模块
jscode = """
Java.perform(function(){
var TestSig = Java.use("java.security.MessageDigest"); // 类的加载路径
TestSig.getInstance.implementation = function(a){ // str为securityCheck的参数,原securityCheck需要几个参数就写几个
send("算法名:"+ a);
return this.getInstance(a);
};
});
"""
def on_message(message,data): #js中执行send函数后要回调的函数
if message["type"] == "send":
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_usb_device()
pid = process.spawn(['com.iCitySuzhou.suzhou001']) # app包名
session = process.attach(pid) # 加载进程号
script = session.create_script(jscode) #创建js脚本
script.on('message',on_message) #加载回调函数,也就是js中执行send函数规定要执行的python函数
script.load() #加载脚本
process.resume(pid) ########### 重启app
sys.stdin.read()
执行结果:
报错原因:
没有重载,即在
java.security.MessageDigest
类中有同名不同参数的
getInstance
方法。
查询Java api文档 https://tool.oschina.net/apidocs/apidoc?api=jdk-zh
我们不知道源app调用了上面的哪种方法,所以在Hook时就需要将上面的三种方法全部Hook住,也就需要给方法传递对应的参数。
getInstance
方法需要传递哪几种参数根据报错信息就可以看出来:
.overload('java.lang.String')
.overload('java.lang.String', 'java.lang.String')
.overload('java.lang.String', 'java.security.Provider')
修改脚本如下:
import frida #导入frida模块
import sys #导入sys模块
jscode = """
Java.perform(function(){
var TestSig = Java.use("java.security.MessageDigest"); // 类的加载路径
TestSig.getInstance.overload('java.lang.String').implementation = function(a){ // a为getInstance的参数,原getInstance需要几个参数就写几个
send("======================================");
send("算法名:" + a);
return this.getInstance(a, b);
};
TestSig.getInstance.overload('java.lang.String', 'java.lang.String').implementation = function(a,b){ // 原getInstance需要几个参数就写几个
send("======================================");
send("算法名:" + a);
return this.getInstance(a,b);
};
TestSig.getInstance.overload('java.lang.String', 'java.security.Provider').implementation = function(a,b){ // 原getInstance需要几个参数就写几个
send("======================================");
send("算法名:" + a);
return this.getInstance(a,b);
};
});
"""
def on_message(message,data): #js中执行send函数后要回调的函数
if message["type"] == "send":
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_usb_device()
pid = process.spawn(['com.iCitySuzhou.suzhou001']) # app包名
session = process.attach(pid) # 加载进程号
script = session.create_script(jscode) #创建js脚本
script.on('message',on_message) #加载回调函数,也就是js中执行send函数规定要执行的python函数
script.load() #加载脚本
process.resume(pid) ########### 重启app
sys.stdin.read()
四、代码整合
同理Hook其他方法时:
在
java.security.MessageDigest
类中有同名不同参数的方法时都要重载。
import frida #导入frida模块
import sys #导入sys模块
jscode = """
function showStacks() { // 打印调用栈
Java.perform(function() {
send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
});
};
function bytesToHex(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
function bytesToBase64(e) {
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var r, a, c, h, o, t;
for (c = e.length, a = 0, r = ''; a < c;) {
if (h = 255 & e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4),
r += '==';
break
}
if (o = e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2),
r += '=';
break
}
t = e[a++],
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
r += base64EncodeChars.charAt(63 & t)
}
return r
}
function bytesToString(arr) {
if (typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if (v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}
Java.perform(function () {
var md = Java.use('java.security.MessageDigest');
md.getInstance.overload('java.lang.String','java.lang.String').implementation = function (a,b) {
showStacks();
send("======================================");
send("算法名:" + a);
return this.getInstance(a, b);
};
md.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
send("======================================");
send("算法名:" + a);
return this.getInstance(a);
};
md.update.overload('[B').implementation = function (a) {
showStacks();
send("======================================");
send("update:" + bytesToString(a))
return this.update(a);
};
md.update.overload('[B','int','int').implementation = function (a,b,c) {
showStacks();
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
return this.update(a,b,c);
};
md.digest.overload().implementation = function () {
showStacks();
send("======================================");
var result = this.digest();
send("digest结果(hex):" + bytesToHex(result));
send("digest结果(base64):" + bytesToBase64(result));
return result;
};
md.digest.overload('[B').implementation = function (a) {
showStacks();
send("======================================");
send("digest参数:" + bytesToString(a));
var result = this.digest(a);
send("digest结果(hex):" + bytesToHex(result));
send("digest结果(base64):" + bytesToBase64(result));
return result;
};
});
"""
def on_message(message,data): #js中执行send函数后要回调的函数
if message["type"] == "send":
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_usb_device().attach('com.iCitySuzhou.suzhou001') # app包名
script = process.create_script(jscode) #创建js脚本
script.on('message',on_message) #加载回调函数,也就是js中执行send函数规定要执行的python函数
script.load() #加载脚本
sys.stdin.read()
在Java源代码中加密方法的路径:
com.hualong.framework.b.a.a
由此路径可以定位到加密函数。
五、Hook Java基础类中的所有加密类
import frida #导入frida模块
import sys #导入sys模块
jscode = """
function showStacks() {
Java.perform(function() {
send(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()));
});
}
function bytesToHex(arr) {
var str = "";
for (var i = 0; i < arr.length; i++) {
var tmp = arr[i];
if (tmp < 0) {
tmp = (255 + tmp + 1).toString(16);
} else {
tmp = tmp.toString(16);
}
if (tmp.length == 1) {
tmp = "0" + tmp;
}
str += tmp;
}
return str;
}
function bytesToBase64(e) {
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
var r, a, c, h, o, t;
for (c = e.length, a = 0, r = ''; a < c;) {
if (h = 255 & e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4),
r += '==';
break
}
if (o = e[a++], a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2),
r += '=';
break
}
t = e[a++],
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4 | (240 & o) >> 4),
r += base64EncodeChars.charAt((15 & o) << 2 | (192 & t) >> 6),
r += base64EncodeChars.charAt(63 & t)
}
return r
}
function bytesToString(arr) {
if (typeof arr === 'string') {
return arr;
}
var str = '',
_arr = arr;
for (var i = 0; i < _arr.length; i++) {
var one = _arr[i].toString(2),
v = one.match(/^1+?(?=0)/);
if (v && one.length == 8) {
var bytesLength = v[0].length;
var store = _arr[i].toString(2).slice(7 - bytesLength);
for (var st = 1; st < bytesLength; st++) {
store += _arr[st + i].toString(2).slice(2);
}
str += String.fromCharCode(parseInt(store, 2));
i += bytesLength - 1;
} else {
str += String.fromCharCode(_arr[i]);
}
}
return str;
}
Java.perform(function () {
var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
secretKeySpec.$init.overload('[B','java.lang.String').implementation = function (a,b) {
showStacks();
var result = this.$init(a, b);
send("======================================");
send("算法名:" + b + "|Dec密钥:" + bytesToString(a));
send("算法名:" + b + "|Hex密钥:" + bytesToHex(a));
return result;
}
var mac = Java.use('javax.crypto.Mac');
mac.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
var result = this.getInstance(a);
send("======================================");
send("算法名:" + a);
return result;
}
mac.update.overload('[B').implementation = function (a) {
showStacks();
this.update(a);
send("======================================");
send("update:" + bytesToString(a))
}
mac.update.overload('[B','int','int').implementation = function (a,b,c) {
showStacks();
this.update(a,b,c)
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
}
mac.doFinal.overload().implementation = function () {
showStacks();
var result = this.doFinal();
send("======================================");
send("doFinal结果(hex):" + bytesToHex(result));
send("doFinal结果(base64):" + bytesToBase64(result));
return result;
}
mac.doFinal.overload('[B').implementation = function (a) {
showStacks();
var result = this.doFinal(a);
send("======================================");
send("doFinal参数:" + bytesToString(a));
send("doFinal结果(hex):" + bytesToHex(result));
send("doFinal结果(base):" + bytesToBase64(result));
return result;
}
var md = Java.use('java.security.MessageDigest');
md.getInstance.overload('java.lang.String','java.lang.String').implementation = function (a,b) {
showStacks();
send("======================================");
send("算法名:" + a);
return this.getInstance(a, b);
}
md.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
send("======================================");
send("算法名:" + a);
return this.getInstance(a);
}
md.update.overload('[B').implementation = function (a) {
showStacks();
send("======================================");
send("update:" + bytesToString(a))
return this.update(a);
}
md.update.overload('[B','int','int').implementation = function (a,b,c) {
showStacks();
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
return this.update(a,b,c);
}
md.digest.overload().implementation = function () {
showStacks();
send("======================================");
var result = this.digest();
send("digest结果(hex):" + bytesToHex(result));
send("digest结果(base64):" + bytesToBase64(result));
return result;
}
md.digest.overload('[B').implementation = function (a) {
showStacks();
send("======================================");
send("digest参数:" + bytesToString(a));
var result = this.digest(a);
send("digest结果(hex):" + bytesToHex(result));
send("digest结果(base64):" + bytesToBase64(result));
return result;
}
var ivParameterSpec = Java.use('javax.crypto.spec.IvParameterSpec');
ivParameterSpec.$init.overload('[B').implementation = function (a) {
showStacks();
var result = this.$init(a);
send("======================================");
send("iv向量:" + bytesToString(a));
send("iv向量(hex):" + bytesToHex(a));
return result;
}
var cipher = Java.use('javax.crypto.Cipher');
cipher.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
var result = this.getInstance(a);
send("======================================");
send("模式填充:" + a);
return result;
}
cipher.update.overload('[B').implementation = function (a) {
showStacks();
var result = this.update(a);
send("======================================");
send("update:" + bytesToString(a));
return result;
}
cipher.update.overload('[B','int','int').implementation = function (a,b,c) {
showStacks();
var result = this.update(a,b,c);
send("======================================");
send("update:" + bytesToString(a) + "|" + b + "|" + c);
return result;
}
cipher.doFinal.overload().implementation = function () {
showStacks();
var result = this.doFinal();
send("======================================");
send("doFinal结果(hex):" + bytesToHex(result));
send("doFinal结果(base64):" + bytesToBase64(result));
return result;
}
cipher.doFinal.overload('[B').implementation = function (a) {
showStacks();
var result = this.doFinal(a);
send("======================================");
send("doFinal参数:" + bytesToString(a));
send("doFinal结果(hex):" + bytesToHex(result));
send("doFinal结果(base64):" + bytesToBase64(result));
return result;
}
var x509EncodedKeySpec = Java.use('java.security.spec.X509EncodedKeySpec');
x509EncodedKeySpec.$init.overload('[B').implementation = function (a) {
showStacks();
var result = this.$init(a);
send("======================================");
send("RSA密钥:" + bytesToBase64(a));
return result;
}
var rSAPublicKeySpec = Java.use('java.security.spec.RSAPublicKeySpec');
rSAPublicKeySpec.$init.overload('java.math.BigInteger','java.math.BigInteger').implementation = function (a,b) {
showStacks();
var result = this.$init(a,b);
send("======================================");
//send("RSA密钥:" + bytesToBase64(a));
send("RSA密钥N:" + a.toString(16));
send("RSA密钥E:" + b.toString(16));
return result;
}
});
"""
def on_message(message,data): #js中执行send函数后要回调的函数
if message["type"] == "send":
print("[*] {0}".format(message["payload"]))
else:
print(message)
process = frida.get_usb_device().attach('com.iCitySuzhou.suzhou001') # app包名
script = process.create_script(jscode) #创建js脚本
script.on('message',on_message) #加载回调函数,也就是js中执行send函数规定要执行的python函数
script.load() #加载脚本
sys.stdin.read()
输出结果:
[*] ======================================
[*] 算法名:MD5
[*] java.lang.Exception
at java.security.MessageDigest.update(Native Method)
at com.hualong.framework.b.a.a(SourceFile:33)
at com.iCitySuzhou.suzhou001.d.d.a(SourceFile:68)
at com.iCitySuzhou.suzhou001.ui.WebBrowserActivity.setCookie(SourceFile:535)
at com.iCitySuzhou.suzhou001.ui.livenews.LiveNewsArticleActivity.b(SourceFile:419)
at com.iCitySuzhou.suzhou001.ui.livenews.LiveNewsArticleActivity.a(SourceFile:397)
at com.iCitySuzhou.suzhou001.ui.livenews.LiveNewsArticleActivity.b(SourceFile:76)
at com.iCitySuzhou.suzhou001.ui.livenews.LiveNewsArticleActivity$10.a(SourceFile:377)
at c.g$a$1$1.run(SourceFile:70)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5917)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:888)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:749)
[*] ======================================
[*] update:IMEI862728036457136-IMSINNNNNNNN&&1605179123&&f1190aca-d08e-4041-8666-29931cd89dde
[*] java.lang.Exception
at java.security.MessageDigest.digest(Native Method)
at com.hualong.framework.b.a.a(SourceFile:34)
at com.iCitySuzhou.suzhou001.d.d.a(SourceFile:68)
at com.iCitySuzhou.suzhou001.ui.WebBrowserActivity.setCookie(SourceFile:535)
at com.iCitySuzhou.suzhou001.ui.livenews.LiveNewsArticleActivity.b(SourceFile:419)
at com.iCitySuzhou.suzhou001.ui.livenews.LiveNewsArticleActivity.a(SourceFile:397)
at com.iCitySuzhou.suzhou001.ui.livenews.LiveNewsArticleActivity.b(SourceFile:76)
at com.iCitySuzhou.suzhou001.ui.livenews.LiveNewsArticleActivity$10.a(SourceFile:377)
at c.g$a$1$1.run(SourceFile:70)
at android.os.Handler.handleCallback(Handler.java:815)
at android.os.Handler.dispatchMessage(Handler.java:104)
at android.os.Looper.loop(Looper.java:207)
at android.app.ActivityThread.main(ActivityThread.java:5917)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:888)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:749)
[*] ======================================
[*] digest结果(hex):e16588e92700f66c1f450ad03cbe0f14
[*] digest结果(base64):4WWI6ScA9mwfRQrQPL4PFA==
[*] java.lang.Exception
at java.security.MessageDigest.getInstance(Native Method)
at com.hualong.framework.b.a.a(SourceFile:32)
at com.iCitySuzhou.suzhou001.d.d.a(SourceFile:68)
at com.iCitySuzhou.suzhou001.ui.WebBrowserActivity.setCookie(SourceFile:535)
at com.iCitySuzhou.suzhou001.ui.livenews.LiveNewsArticleActivity$8.shouldOverrideUrlLoading(SourceFile:796)
at com.tencent.smtt.sdk.v.shouldOverrideUrlLoading(Unknown Source)
at com.tencent.smtt.sdk.WebViewClient.shouldOverrideUrlLoading(Unknown Source)
at com.tencent.smtt.sdk.v.shouldOverrideUrlLoading(Unknown Source)
at com.tencent.tbs.core.webkit.tencent.TencentWebViewClient.shouldOverrideUrlLoading(TbsJavaCore:567)
at android.webview.chromium.tencent.TencentWebViewContentsClientAdapter.b(TbsJavaCore:342)
at android.webview.chromium.tencent.TencentWebViewContentsClientAdapter.a(TbsJavaCore:238)
at com.tencent.smtt.webkit.WebViewChromiumExtension.a(TbsJavaCore:5011)
at org.chromium.tencent.TencentAwContentsClientBridge.shouldOverrideUrlLoading(TbsJavaCore:237)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:328)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:5917)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:888)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:749)