frida初探3:frida hook java
文章最后更新时间为:2023年07月20日 15:46:16
本篇文章主要记录frida hook java的常见用法,内容多参考自《frida 协议分析》
1. 枚举所有的类
hook第一步是需要找到需要hook的类,可以通过以下方式枚举所有加载到内存中的类:
function main() {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (name, handle){
console.log("name:" + name + " handle:" + handle)
},
onComplete: function () {
console.log("end")
}
})
})
}
setImmediate(main)
一般情况下需要通过关键字来过滤一下类:
function main() {
Java.perform(function () {
Java.enumerateLoadedClasses({
onMatch: function (name, handle){
if (name.split(".")[1] == "bluetooth"){
console.log("name:" + name + " handle:" + handle)
}
if (name.indexOf("android.bluetooth.BluetoothMap") != -1) {
console.log("name:" + name + " handle:" + handle)
}
// console.log("name:" + name + " handle:" + handle)
},
onComplete: function () {
console.log("end")
}
})
})
}
setImmediate(main)
2. 枚举类中的方法
function main() {
var a = enumMethods("android.bluetooth.BluetoothDevice")
a.forEach(function(s) {
console.log(s);
});
}
function enumMethods(targetClass)
{
var hook = Java.use(targetClass);
var ownMethods = hook.class.getDeclaredMethods();
hook.$dispose;
return ownMethods;
}
setImmediate(main)
3. 获取和修改类的字段
静态字段只要拿到类就可以访问,如果是实例字段则需要使用Java.choice先获取到类。如果字段和方法同名,则使用下划线访问字段
// 静态
var money=Java.use("com.xxx.hook.Money");
console.log(money.flag.value);
money.flag.value="修改后的结果";
console.log(money.flag.value);
// 实例
Java.choose("com.xxx.hook.Money",{
onMatch:function(obj){
console.log("Java onMatch:",obj.currency.value);
},
onComplete:function(){
}
})
4. hook内部类和匿名类
java中如果一个类定义在另一个类或一个方法里面,这样的类称为内部类,访问时需要在类和内部类名之间加上$字符。
var Wallet$InnerSturcture=Java.use("com.xxx.hook.Wallet$InnerStructure");
console.log(Wallet$InnerSturcture);
Java.choose("com.xxx.hook.Wallet$InnerStructure",{
onMatch:function(obj){
console.log("Java Wallet$InnerSturcture:",obj.bankCardsList.value);
},
onComplete:function(){
}
})
匿名类时一个没有名字的类,是内部类的简化方法,而匿名类可以借助smali的语法进行类的定位,通常如下面的这种形式:
var money=Java.use("com.xxx.app.MainActivity$1");
money.getInfo.implementation=function(){
var result=this.getInfo();
console.log(result);
return result;
}
5. 对象参数的构造
在有些方法中,需要传入对象作为参数,比如deposit方法传入Money类作为参数,如果需要构造一个Money类,则需要java.use先定位到Money类,在调用$new方法构造一个类对象,其中初始参数也需要依次传输进去,比如
var walletils=Java.use("com.XXX.hook.Wallet");
var Money=Java.use("com.XXX.hook.Money");
walletils.deposit.implementaton=function(a){
console.log("money.$init param:",a.getInfo());
return this.deposit(Money.$new("美元",1000));
6. hook方法
6.1 hook静态方法和实例方法
function test(){
Java.perform(function(){
var money=Java.use("com.xxx.hook.Money");
money.setFlag.implementation=function(a){
console.log("money.setFlag param:",a);
return this.setFlag(a);
};
money.getInfo.implementation=function(){
var result=this.getInfo();
console.log("money.getInfo:",result);
return result;
};
})
};
test();
6.2 hook构造方法
通过clazz.$init.implementation来构造函数。
function main() {
Java.perform(function () {
Java.use("com.tlamb96.kgbmessenger.b.a").$init.implementation = function (i, str1, str2, z) {
this.$init(i, str1, str2, z)
console.log(i, str1, str2, z)
}
})
}
setImmediate(main)
6.3 hook重载方法
对方法有重载时,直接hook该方法会报错。
void fun(int x , int y ){
Log.d("ROYSUE.Sum" , String.valueOf(x+y));
}
String fun(String x){
total +=x;
return x.toLowerCase();
}
所以我们需要指定具体hook的是哪个重载的方法,用法如下:
my_class.fun.overload("int" , "int").implementation = function(x,y){
...
my_class.fun.overload("java.lang.String").implementation = function(x){
当我们要hook某个方法的所有重载时,可以这么做:
//目标类
var hook = Java.use(targetClass);
//重载次数
var overloadCount = hook[targetMethod].overloads.length;
//打印日志:追踪的方法有多少个重载
console.log("Tracing " + targetClassMethod + " [" + overloadCount + " overload(s)]");
//每个重载都进入一次
for (var i = 0; i < overloadCount; i++) {
//hook每一个重载
hook[targetMethod].overloads[i].implementation = function() {
console.warn("n*** entered " + targetClassMethod);
// 打印参数
if (arguments.length) console.log();
for (var j = 0; j < arguments.length; j++) {
console.log("arg[" + j + "]: " + arguments[j]);
}
//打印返回值
var retval = this[targetMethod].apply(this, arguments); // rare crash (Frida bug?)
console.log("nretval: " + retval);
console.warn("n*** exiting " + targetClassMethod);
return retval;
}
}
6.4 hook类的所有方法
可以使用objection完成,也可以使用下面的代码,先枚举类的方法,再枚举方法的重载,再进行hook
function test(){
Java.perform(function(){
var Utils=Java.use("com.xxx.hook.Utils");
var methods=Utils.class.getDeclaredMethods();
for(let k=0;k<methods.length;k++){
var methodName=methods[k].getName();
var overloadArr=Utils[methods[k].getName()].overloads;
console.log("fun:",methodName);
for(var i=0;i<overloadArr.length;i++){
overloadArr[i].implementation=function(){
var params="";
for(var j=0;j<arguments.length;j++){
params+=arguments[j]+" ";
};
console.log("utils."+methodName+" is called! params is:",params);
return this[methodName].apply(this,arguments);
}
}
};
});
};
test();
7. 主动调用
- 静态函数直接use class然后调用方法,非静态函数需要先choose实例然后调用。
- 设置成员变量的值,写法是xx.value = yy,其他方面和函数一样。如果有一个成员变量和成员函数的名字相同,则在其前面加一个_,如_xx.value = yy
function main() {
Java.perform(function () {
//hook静态函数直接调用
var demoClass = Java.use("com.example.demo.DemoClass")
demoClass.setAge()
//hook动态函数,找到instance实例,从实例调用函数方法
Java.choose("com.example.demo.DemoClass", {
onMatch: function (instance) {
instance.setName()
},
onComplete: function () {
console.log("end")
}
})
})
}
setImmediate(main)
8. 打印栈回溯
function main() {
Java.perform(function () {
var demoClass = Java.use("com.example.demo.DemoClass")
demoClass.isHook.implementation = function () {
showStacks()
printStack("com.example.demo.DemoClass.IsHook")
var result = this.isHook()
return false
}})
}
function printStack(name) {
Java.perform(function () {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (straces != undefined && straces != null) {
var strace = straces.toString();
var replaceStr = strace.replace(/,/g, "\n");
console.log("=============================" + name + " Stack strat=======================");
console.log(replaceStr);
console.log("=============================" + name + " Stack end=======================\r\n");
Exception.$dispose();
}
});
}
setImmediate(main)
9. 利用hook快速定位
在java代码中直接搜索关键词可能难以搜索到,所以可以使用下列方式对常见的类进行hook,从而定位到相关代码。
9.1 hook hashMap 定位散列表
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a, b) {
console.log("HashMap.put: ", a, b);
return this.put(a, b);
}
9.2 hook ArrayList 定位动态数组
var arrayList = Java.use("java.util.ArrayList");
arrayList.add.overload('java.lang.Object').implementation = function (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);
}
9.3 hook Toast 定位提示
var toast = Java.use("android.widget.Toast");
toast.show.implementation = function () {
showStacks();
console.log("Toast.show");
return this.show();
}
9.4 hook findViewById 定位组件
在实际开发中,经常需要用到findViewById来获取组件,再进行事件的绑定或数据的获取,所以我们可以使用类似uiautomatorviewer的软件来获取组件id,然后使用frida来获取id对应的数值,下面是hook appCompatActivity的findViewById方法,当传入的值和btn_login_id相等时,获取函数栈,即可指定为登录按钮组件的代码位置。
Java.perform(function () {
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);
}
});
9.5 hook setOnClickListener 定位按钮点击事件
同9.4差不多
Java.perform(function () {
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);
}
});
9.6 hook JSONObject 定位JSON数据
JSONObject 类可能不是很常用,所以可能hook不到。
var jSONObject = Java.use("org.json.JSONObject");
jSONObject.put.overload('java.lang.String', 'java.lang.Object').implementation = function (a, b) {
showStacks();
console.log("JSONObject.put: ", a, b);
return this.put(a, b);
}
jSONObject.getString.implementation = function (a) {
showStacks();
var result = this.getString(a);
console.log("JSONObject.getString: ", a, result);
return result;
}
9.7 hook Collections 定位排序算法
一般app再进行数据签名时,会先对数据进行排序,因为摘要结果不可逆,所以需要保证服务端和客户端的数据顺序一致。比较常用的排序方法有Collections的sort方法,Array的sort方法,或者自己实现。下面是hook Collections的sort方法:
var collections = Java.use("java.util.Collections");
collections.sort.overload('java.util.List').implementation = function (a) {
showStacks();
console.log("Collections.sort List: ", a.toString());
return this.sort(a);
}
collections.sort.overload('java.util.List', 'java.util.Comparator').implementation = function (a, b) {
showStacks();
// 直接打印a.strings()会显示object,可以使用Java.cast转为java.util.ArrayList再打印
var result = Java.cast(a, Java.use("java.util.ArrayList"));
console.log("Collections.sort List Comparator: ", result.toString());
return this.sort(a, b);
return this.sort(a, b);
}
9.8 hook String 定位字符转换
通常在加密前,需要将string转为byte,需要用到String类的getBytes方法
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: ", 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: ", newStr);
return result;
}
9.9 hook StringBuilder 定位字符串操作
为了能高效拼接字符串,Java标准库提供了StringBuilder
,它是一个可变对象,可以预分配缓冲区,这样,往StringBuilder
中新增字符时,不会创建新的临时对象:
StringBuilder sb = new StringBuilder(1024);
for (int i = 0; i < 1000; i++) {
sb.append(',');
sb.append(i);
}
String s = sb.toString();
Java.perform(function(){
var stringBuilder = Java.use("java.lang.StringBuilder");
stringBuilder.toString.implementation = function () {
var result = this.toString.apply(this, arguments);
if(result == "username=13866668888"){
showStacks();
console.log("stringBuilder.toString is called!", result);
}
return result;
}
});
10. hook常见加解密算法
包括base64、md5、aes、des等,比较常用
var N_ENCRYPT_MODE = 1
var N_DECRYPT_MODE = 2
function showStacks() {
var Exception = Java.use("java.lang.Exception");
var ins = Exception.$new("Exception");
var straces = ins.getStackTrace();
if (undefined == straces || null == straces) {
return;
}
console.log("============================= Stack strat=======================");
console.log("");
for (var i = 0; i < straces.length; i++) {
var str = " " + straces[i].toString();
console.log(str);
}
console.log("");
console.log("============================= Stack end=======================\r\n");
Exception.$dispose();
}
//工具相关函数
var base64EncodeChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
base64DecodeChars = new Array((-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1), 62, (-1), (-1), (-1), 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, (-1), (-1), (-1), (-1), (-1), (-1), (-1), 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, (-1), (-1), (-1), (-1), (-1), (-1), 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, (-1), (-1), (-1), (-1), (-1));
function stringToBase64(e) {
var r, a, c, h, o, t;
for (c = e.length, a = 0, r = ''; a < c;) {
if (h = 255 & e.charCodeAt(a++), a == c) {
r += base64EncodeChars.charAt(h >> 2),
r += base64EncodeChars.charAt((3 & h) << 4),
r += '==';
break
}
if (o = e.charCodeAt(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.charCodeAt(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 base64ToString(e) {
var r, a, c, h, o, t, d;
for (t = e.length, o = 0, d = ''; o < t;) {
do
r = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && r == -1);
if (r == -1)
break;
do
a = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && a == -1);
if (a == -1)
break;
d += String.fromCharCode(r << 2 | (48 & a) >> 4);
do {
if (c = 255 & e.charCodeAt(o++), 61 == c)
return d;
c = base64DecodeChars[c]
} while (o < t && c == -1);
if (c == -1)
break;
d += String.fromCharCode((15 & a) << 4 | (60 & c) >> 2);
do {
if (h = 255 & e.charCodeAt(o++), 61 == h)
return d;
h = base64DecodeChars[h]
} while (o < t && h == -1);
if (h == -1)
break;
d += String.fromCharCode((3 & c) << 6 | h)
}
return d
}
function hexToBase64(str) {
return base64Encode(String.fromCharCode.apply(null, str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ")));
}
function base64ToHex(str) {
for (var i = 0, bin = base64Decode(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) {
var tmp = bin.charCodeAt(i).toString(16);
if (tmp.length === 1)
tmp = "0" + tmp;
hex[hex.length] = tmp;
}
return hex.join("");
}
function hexToBytes(str) {
var pos = 0;
var len = str.length;
if (len % 2 != 0) {
return null;
}
len /= 2;
var hexA = new Array();
for (var i = 0; i < len; i++) {
var s = str.substr(pos, 2);
var v = parseInt(s, 16);
hexA.push(v);
pos += 2;
}
return hexA;
}
function bytesToHex(arr) {
var str = '';
var k, j;
for (var i = 0; i < arr.length; i++) {
k = arr[i];
j = k;
if (k < 0) {
j = k + 256;
}
if (j < 16) {
str += "0";
}
str += j.toString(16);
}
return str;
}
function stringToHex(str) {
var val = "";
for (var i = 0; i < str.length; i++) {
if (val == "")
val = str.charCodeAt(i).toString(16);
else
val += str.charCodeAt(i).toString(16);
}
return val
}
function stringToBytes(str) {
var ch, st, re = [];
for (var i = 0; i < str.length; i++) {
ch = str.charCodeAt(i);
st = [];
do {
st.push(ch & 0xFF);
ch = ch >> 8;
}
while (ch);
re = re.concat(st.reverse());
}
return re;
}
//将byte[]转成String的方法
function bytesToString(arr) {
var str = '';
arr = new Uint8Array(arr);
for (var i in arr) {
str += String.fromCharCode(arr[i]);
}
return str;
}
function bytesToBase64(e) {
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 base64ToBytes(e) {
var r, a, c, h, o, t, d;
for (t = e.length, o = 0, d = []; o < t;) {
do
r = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && r == -1);
if (r == -1)
break;
do
a = base64DecodeChars[255 & e.charCodeAt(o++)];
while (o < t && a == -1);
if (a == -1)
break;
d.push(r << 2 | (48 & a) >> 4);
do {
if (c = 255 & e.charCodeAt(o++), 61 == c)
return d;
c = base64DecodeChars[c]
} while (o < t && c == -1);
if (c == -1)
break;
d.push((15 & a) << 4 | (60 & c) >> 2);
do {
if (h = 255 & e.charCodeAt(o++), 61 == h)
return d;
h = base64DecodeChars[h]
} while (o < t && h == -1);
if (h == -1)
break;
d.push((3 & c) << 6 | h)
}
return d
}
//stringToBase64 stringToHex stringToBytes
//base64ToString base64ToHex base64ToBytes
// hexToBase64 hexToBytes
// bytesToBase64 bytesToHex bytesToString
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);
console.log("======================================");
console.log("算法名:" + b + "|str**:" + bytesToString(a));
console.log("算法名:" + b + "|Hex**:" + bytesToHex(a));
return result;
}
var DESKeySpec = Java.use('javax.crypto.spec.DESKeySpec');
DESKeySpec.$init.overload('[B').implementation = function (a) {
showStacks();
var result = this.$init(a);
console.log("======================================");
var bytes_key_des = this.getKey();
console.log("des** |str " + bytesToString(bytes_key_des));
console.log("des** |hex " + bytesToHex(bytes_key_des));
return result;
}
DESKeySpec.$init.overload('[B', 'int').implementation = function (a, b) {
showStacks();
var result = this.$init(a, b);
console.log("======================================");
var bytes_key_des = this.getKey();
console.log("des** |str " + bytesToString(bytes_key_des));
console.log("des** |hex " + bytesToHex(bytes_key_des));
return result;
}
var mac = Java.use('javax.crypto.Mac');
mac.getInstance.overload('java.lang.String').implementation = function (a) {
showStacks();
var result = this.getInstance(a);
console.log("======================================");
console.log("算法名:" + a);
return result;
}
mac.update.overload('[B').implementation = function (a) {
//showStacks();
this.update(a);
console.log("======================================");
console.log("update:" + bytesToString(a))
}
mac.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
//showStacks();
this.update(a, b, c)
console.log("======================================");
console.log("update:" + bytesToString(a) + "|" + b + "|" + c);
}
mac.doFinal.overload().implementation = function () {
//showStacks();
var result = this.doFinal();
console.log("======================================");
console.log("doFinal结果: |str :" + bytesToString(result));
console.log("doFinal结果: |hex :" + bytesToHex(result));
console.log("doFinal结果: |base64 :" + bytesToBase64(result));
return result;
}
mac.doFinal.overload('[B').implementation = function (a) {
//showStacks();
var result = this.doFinal(a);
console.log("======================================");
console.log("doFinal参数: |str :" + bytesToString(a));
console.log("doFinal结果: |str :" + bytesToString(result));
console.log("doFinal结果: |hex :" + bytesToHex(result));
console.log("doFinal结果: |base64 :" + 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();
console.log("======================================");
console.log("算法名:" + a);
return this.getInstance(a, b);
}
md.getInstance.overload('java.lang.String').implementation = function (a) {
//showStacks();
console.log("======================================");
console.log("算法名:" + a);
return this.getInstance(a);
}
md.update.overload('[B').implementation = function (a) {
//showStacks();
console.log("======================================");
console.log("update:" + bytesToString(a))
return this.update(a);
}
md.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
//showStacks();
console.log("======================================");
console.log("update:" + bytesToString(a) + "|" + b + "|" + c);
return this.update(a, b, c);
}
md.digest.overload().implementation = function () {
//showStacks();
console.log("======================================");
var result = this.digest();
console.log("digest结果:" + bytesToHex(result));
console.log("digest结果:" + bytesToBase64(result));
return result;
}
md.digest.overload('[B').implementation = function (a) {
//showStacks();
console.log("======================================");
console.log("digest参数:" + bytesToString(a));
var result = this.digest(a);
console.log("digest结果:" + bytesToHex(result));
console.log("digest结果:" + 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);
console.log("======================================");
console.log("iv向量: |str:" + bytesToString(a));
console.log("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);
console.log("======================================");
console.log("模式填充:" + a);
return result;
}
cipher.init.overload('int', 'java.security.Key').implementation = function (a, b) {
//showStacks();
var result = this.init(a, b);
console.log("======================================");
if (N_ENCRYPT_MODE == a)
{
console.log("init | 加密模式");
}
else if(N_DECRYPT_MODE == a)
{
console.log("init | 解密模式");
}
var bytes_key = b.getEncoded();
console.log("init key:" + "|str**:" + bytesToString(bytes_key));
console.log("init key:" + "|Hex**:" + bytesToHex(bytes_key));
return result;
}
cipher.init.overload('int', 'java.security.cert.Certificate').implementation = function (a, b) {
//showStacks();
var result = this.init(a, b);
console.log("======================================");
if (N_ENCRYPT_MODE == a)
{
console.log("init | 加密模式");
}
else if(N_DECRYPT_MODE == a)
{
console.log("init | 解密模式");
}
return result;
}
cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec').implementation = function (a, b, c) {
//showStacks();
var result = this.init(a, b, c);
console.log("======================================");
if (N_ENCRYPT_MODE == a)
{
console.log("init | 加密模式");
}
else if(N_DECRYPT_MODE == a)
{
console.log("init | 解密模式");
}
var bytes_key = b.getEncoded();
console.log("init key:" + "|str**:" + bytesToString(bytes_key));
console.log("init key:" + "|Hex**:" + bytesToHex(bytes_key));
return result;
}
cipher.init.overload('int', 'java.security.cert.Certificate', 'java.security.SecureRandom').implementation = function (a, b, c) {
//showStacks();
var result = this.init(a, b, c);
if (N_ENCRYPT_MODE == a)
{
console.log("init | 加密模式");
}
else if(N_DECRYPT_MODE == a)
{
console.log("init | 解密模式");
}
return result;
}
cipher.init.overload('int', 'java.security.Key', 'java.security.SecureRandom').implementation = function (a, b, c) {
//showStacks();
var result = this.init(a, b, c);
if (N_ENCRYPT_MODE == a)
{
console.log("init | 加密模式");
}
else if(N_DECRYPT_MODE == a)
{
console.log("init | 解密模式");
}
var bytes_key = b.getEncoded();
console.log("init key:" + "|str**:" + bytesToString(bytes_key));
console.log("init key:" + "|Hex**:" + bytesToHex(bytes_key));
return result;
}
cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters').implementation = function (a, b, c) {
//showStacks();
var result = this.init(a, b, c);
if (N_ENCRYPT_MODE == a)
{
console.log("init | 加密模式");
}
else if(N_DECRYPT_MODE == a)
{
console.log("init | 解密模式");
}
var bytes_key = b.getEncoded();
console.log("init key:" + "|str**:" + bytesToString(bytes_key));
console.log("init key:" + "|Hex**:" + bytesToHex(bytes_key));
return result;
}
cipher.init.overload('int', 'java.security.Key', 'java.security.AlgorithmParameters', 'java.security.SecureRandom').implementation = function (a, b, c, d) {
//showStacks();
var result = this.init(a, b, c, d);
if (N_ENCRYPT_MODE == a)
{
console.log("init | 加密模式");
}
else if(N_DECRYPT_MODE == a)
{
console.log("init | 解密模式");
}
var bytes_key = b.getEncoded();
console.log("init key:" + "|str**:" + bytesToString(bytes_key));
console.log("init key:" + "|Hex**:" + bytesToHex(bytes_key));
return result;
}
cipher.init.overload('int', 'java.security.Key', 'java.security.spec.AlgorithmParameterSpec', 'java.security.SecureRandom').implementation = function (a, b, c, d) {
//showStacks();
var result = this.update(a, b, c, d);
if (N_ENCRYPT_MODE == a)
{
console.log("init | 加密模式");
}
else if(N_DECRYPT_MODE == a)
{
console.log("init | 解密模式");
}
var bytes_key = b.getEncoded();
console.log("init key:" + "|str**:" + bytesToString(bytes_key));
console.log("init key:" + "|Hex**:" + bytesToHex(bytes_key));
return result;
}
cipher.update.overload('[B').implementation = function (a) {
//showStacks();
var result = this.update(a);
console.log("======================================");
console.log("update:" + bytesToString(a));
return result;
}
cipher.update.overload('[B', 'int', 'int').implementation = function (a, b, c) {
//showStacks();
var result = this.update(a, b, c);
console.log("======================================");
console.log("update:" + bytesToString(a) + "|" + b + "|" + c);
return result;
}
cipher.doFinal.overload().implementation = function () {
//showStacks();
var result = this.doFinal();
console.log("======================================");
console.log("doFinal结果: |str :" + bytesToString(result));
console.log("doFinal结果: |hex :" + bytesToHex(result));
console.log("doFinal结果: |base64 :" + bytesToBase64(result));
return result;
}
cipher.doFinal.overload('[B').implementation = function (a) {
//showStacks();
var result = this.doFinal(a);
console.log("======================================");
console.log("doFinal参数: |str :" + bytesToString(a));
console.log("doFinal结果: |str :" + bytesToString(result));
console.log("doFinal结果: |hex :" + bytesToHex(result));
console.log("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);
console.log("======================================");
console.log("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);
console.log("======================================");
//console.log("RSA**:" + bytesToBase64(a));
console.log("RSA**N:" + a.toString(16));
console.log("RSA**E:" + b.toString(16));
return result;
}
var KeyPairGenerator = Java.use('java.security.KeyPairGenerator');
KeyPairGenerator.generateKeyPair.implementation = function ()
{
//showStacks();
var result = this.generateKeyPair();
console.log("======================================");
var str_private = result.getPrivate().getEncoded();
var str_public = result.getPublic().getEncoded();
console.log("公钥 |hex" + bytesToHex(str_public));
console.log("私钥 |hex" + bytesToHex(str_private));
return result;
}
KeyPairGenerator.genKeyPair.implementation = function ()
{
//showStacks();
var result = this.genKeyPair();
console.log("======================================");
var str_private = result.getPrivate().getEncoded();
var str_public = result.getPublic().getEncoded();
console.log("公钥 |hex" + bytesToHex(str_public));
console.log("私钥 |hex" + bytesToHex(str_private));
return result;
}
});
11. 定位接口的实现类
Java.perform(function () {
var classes = Java.enumerateLoadedClassesSync();
for (const index in classes) {
var className = classes[index];
if(className.indexOf("com.xxx") === -1) continue;
var clazz = Java.use(className);
var resultArr = clazz.class.getInterfaces();
if(resultArr.length === 0) continue;
for (let i = 0; i < resultArr.length; i++) {
if(resultArr[i].toString().indexOf("com.xxx.app.TestRegisterClass") !== -1){
console.log(className, resultArr);
}
}
}
});
12. 定位抽象类的实现类
Java.perform(function () {
var classes = Java.enumerateLoadedClassesSync();
for (const index in classes) {
var className = classes[index];
if(className.indexOf("com.xxx") === -1) continue;
var clazz = Java.use(className);
var resultClass = clazz.class.getSuperclass();
if(resultClass == null) continue;
if(resultClass.toString().indexOf("com.xxx.app.TestAbstract") !== -1){
console.log(className, resultClass);
}
}
});
13. byte转字符串
// byte[]转string
// 完美解决中文乱码的问题
// 网上的常规思路是将数组变为16进制字符串,然后再每两位转化成字符,这样会带来中文乱码的问题
function utf8ByteToUnicodeStr(utf8Bytes) {
var unicodeStr = "";
for (var pos = 0; pos < utf8Bytes.length;) {
var flag = utf8Bytes[pos];
var unicode = 0;
if ((flag >>> 7) === 0) {
unicodeStr += String.fromCharCode(utf8Bytes[pos]);
pos += 1;
} else if ((flag & 0xFC) === 0xFC) {
unicode = (utf8Bytes[pos] & 0x3) << 30;
unicode |= (utf8Bytes[pos + 1] & 0x3F) << 24;
unicode |= (utf8Bytes[pos + 2] & 0x3F) << 18;
unicode |= (utf8Bytes[pos + 3] & 0x3F) << 12;
unicode |= (utf8Bytes[pos + 4] & 0x3F) << 6;
unicode |= (utf8Bytes[pos + 5] & 0x3F);
unicodeStr += String.fromCharCode(unicode);
pos += 6;
} else if ((flag & 0xF8) === 0xF8) {
unicode = (utf8Bytes[pos] & 0x7) << 24;
unicode |= (utf8Bytes[pos + 1] & 0x3F) << 18;
unicode |= (utf8Bytes[pos + 2] & 0x3F) << 12;
unicode |= (utf8Bytes[pos + 3] & 0x3F) << 6;
unicode |= (utf8Bytes[pos + 4] & 0x3F);
unicodeStr += String.fromCharCode(unicode);
pos += 5;
} else if ((flag & 0xF0) === 0xF0) {
unicode = (utf8Bytes[pos] & 0xF) << 18;
unicode |= (utf8Bytes[pos + 1] & 0x3F) << 12;
unicode |= (utf8Bytes[pos + 2] & 0x3F) << 6;
unicode |= (utf8Bytes[pos + 3] & 0x3F);
unicodeStr += String.fromCharCode(unicode);
pos += 4;
} else if ((flag & 0xE0) === 0xE0) {
unicode = (utf8Bytes[pos] & 0x1F) << 12;;
unicode |= (utf8Bytes[pos + 1] & 0x3F) << 6;
unicode |= (utf8Bytes[pos + 2] & 0x3F);
unicodeStr += String.fromCharCode(unicode);
pos += 3;
} else if ((flag & 0xC0) === 0xC0) { //110
unicode = (utf8Bytes[pos] & 0x3F) << 6;
unicode |= (utf8Bytes[pos + 1] & 0x3F);
unicodeStr += String.fromCharCode(unicode);
pos += 2;
} else {
unicodeStr += String.fromCharCode(utf8Bytes[pos]);
pos += 1;
}
}
return unicodeStr;
}
14 trace方法调用
一键hook所有类和方法,见https://github.com/r0ysue/r0tracer