Frida使用大全

Frida 类型关系映射表

Java类型 Frida类型
int int
float float
boolean boolean
string java.lang.string
char [C
byte [B
list java.util.List
HashMap java.util.HashMap
ArrayList java.util.ArrayList
JavaObject java.lang.Object
String[] [Ljava.lang.String

Frida常用简单脚本

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
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
// 打印调用栈1
function showStacks(){
console.log(Java.use("android.util.Log").getStackTraceString(
Java.use("java.lang.Throwable").$new()
));
}

// 打印调用栈2
function printStackTrace() {
Java.perform(function() {
var Exception = Java.use("java.lang.Exception");
var exception = Exception.$new();
var stackTrace = exception.getStackTrace().toString();
console.log("==========================\r\n" + stackTrace.replaceAll(",", "\r\n")
+ "\r\n==========================");
exception.$dispose();
});
}

// 拦截hookokhttp的请求url
Java.perform(function (){
var Builder = Java.use('okhttp3.Request$Builder');
Builder.url.overload('okhttp3.HttpUrl').implementation = function (a) {
console.log('a: ' + a)
var res = this.url(a);
// showStacks()
console.log("res: " + res)
return res;
}

// java打印堆栈
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
// SO打印堆栈
console.log(' called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');

// 拿到context上下文
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();

// 借助Gson转Json打印
var Gson = Java.use('com.google.gson.Gson').$new();
console.log("map -> " + Gson.toJsonTree(map).getAsJsonObject());

// 输出展示:map -> {"onlyCanExchange":0,"pageSize":"5","pageNum":"1"}

// frida启动hook脚本
// attach模式
frida -U -l myhook.js com.xxx.xxxx
// spawn模式
frida -U -l myhook.js -f com.xxx.xxxx --no-pause

Frida hook java层

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//基础模板如下
/*
function main(){
Java.perform(function (){
// 定位类指针
// var Utils = Java.use("需要填写类名")
var Utils = Java.use("com.luoge.com.Utils")
Utils.处理的方法名.implementation = function (){
编写业务逻辑
this.处理的方法名()
}

})
}
setImmediate(main)
*/

hook 静态方法和实例方法

下面示例对应的apk包 app-release.apk

1
2
3
4
5
6
7
8
9
10
11
// 示例1 hook静态方法 
Java.perform(function (){
var utils = Java.use("com.luoge.com.Utils")
utils.getCalc.implementation = function (arg1,arg2){
console.log("参数1--》" + arg1)
console.log("参数2--》" + arg2)
var res = this.getCalc(arg1,arg2)
console.log(res)
return 11111;
}
}

hook 重载方法

1
2
3
4
5
6
7
8
9
10
11
// 示例2 hook重载方法
Java.perform(function (){
console.log("hook start")
var utils = Java.use("com.luoge.com.Utils")
utils.getOver.overload('com.luoge.com.Money').implementation = function (arg){
console.log(arg)
var res = this.getOver(arg)
console.log(res)
return res
}
}

hook 构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 示例3 hook构造方法
Java.perform(function (){
var money = Java.use("com.luoge.com.Money");
money.$init.overload('java.lang.String', 'int').implementation = function(str, num){
console.log(str, num);
str = "欧元";
num = 2000;
this.$init(str, num);
}

money.getInfo.implementation = function (){
var ii = this.getInfo()
console.log(ii)
return ii
}
}

hook 主动调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 示例4 hook 主动调用+实例方法
Java.perform(function (){
var res = Java.use("com.luoge.com.Money").$new("xxx",11).getInfo();
console.log(res)

Java.choose("com.luoge.com.Money", {
// 每找一次调用一次
onMatch: function(obj){
console.log(obj.getInfo())
},
onComplete: function(){
console.log('内存中Money操作完毕')
}
});
}

hook 方法与字段同名

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 实例5 hook 处理字段名与方法名相同
Java.perform(function (){
var money = Java.use("com.luoge.com.Money");
money.flag.value = "nana"
Java.choose("com.luoge.com.Money", {
onMatch: function(obj){
//字段名与方方法名相同 前面需要加个下划线
obj._name.value = "ouyuan";
console.log(obj._name.value )
obj.num.value = 150000;
console.log(obj.num.value)
},
onComplete: function(){
console.log("complete!!")
}
});
}

hook 内部类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 实例6 hook 内部类
Java.perform(function (){
var InnerClass = Java.use('com.luoge.com.Money$innerClass');
InnerClass.$init.overload('java.lang.String', 'int').implementation = function(name, num) {
console.log('Parameter name:', name);
console.log('Parameter num:', num);
// 先实例化调用,因为下面的方法用到了this.name和this.num
this.$init(name,num)

console.log(this.outPrint());

return this.$init(name,num)
}
}

hook 内置的方法

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
26
27
28
29
30
31
32
33
// frida的内置函数的使用
function demo9(){
var utils = Java.use("com.luoge.com.Utils")
var methods = utils.class.getDeclaredMethods();
for (var i=0;i<methods.length; i++){
console.log(methods[i].getName())
}

// hook指定类的所有重载方法
var money = Java.use("com.luoge.com.Money")
var contra = money.class.getDeclaredConstructors();
console.log(contra)
for (var i=0;i<contra.length; i++){
var ccc = contra[i]
// console.log(ccc.toString())
// console.log(ccc.getName())
}
// hook所有字段
var field = money.class.getDeclaredFields();
console.log(field)
for (var i=0;i<field.length; i++){
var ccc = field[i]
console.log(ccc.toString())
}

// hook所有内部类
var myclass = money.class.getDeclaredClasses();
for (var i=0;i<myclass.length; i++){
var ccc = myclass[i]
console.log(ccc.toString())
console.log(ccc.getName())
}
}

hook 动态dex

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// hook_dyn_dex  参考 frida_example_2.apk的FridaActivity5
/*Android应用程序使用Dalvik虚拟机或ART(Android Runtime)来执行应用程序代码。这些代码被编译为DEX格式,也就是Dalvik Executable的缩写。DEX文件包含了应用程序的字节码指令,可以被虚拟机高效执行*/
Java.perform(function() {
Java.enumerateClassLoaders({
onMatch : function(loader) {
try {
if (loader.findClass("com.example.androiddemo.Dynamic.DynamicCheck")) {
Java.classFactory.loader = loader;
console.log(loader);
}
} catch (error) {

}
}, onComplete : function() {
}
});
var DynamicCheck = Java.use("com.example.androiddemo.Dynamic.DynamicCheck");
DynamicCheck.check.implementation = function() {
var result = this.check();
console.log("DynamicCheck.check:", result);
return true;
}
})

hook 系统类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// hook 启动失败的脚本,需要先通过GDA工具定位一下打开页面是哪个activity
function hook_MainActivity() {
Java.perform(function() {
var MainActivity = Java.use("com.example.androiddemo.MainActivity");
//frida -U --no-pause -f com.example.androiddemo -l hook.js
//--no-pause -f apk启动之前就把frida的脚本注入进apk进程里
var System = Java.use("java.lang.System");
System.getenv.overload('java.lang.String').implementation = function(name) {
var env = this.getenv(name);
if (name == "USER") {
env = "Imyang";
}
console.log("getenv:", name, env);
return env;
}
var FridaActivity7 = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity7");
//hook 构造函数
FridaActivity7.$init.implementation = function() {
this.$init();
this.next.value = true;
}
});
}

hook 切换启动页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// hook 直接启动切换至指定页面
function call_startActivity() {
Java.perform(function() {
//调用系统的类和函数
var ActivityThread = Java.use("android.app.ActivityThread");
var application = ActivityThread.currentApplication();
var context = application.getApplicationContext();
console.log(context);
var FridaActivity7 = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity7");

var Intent = Java.use("android.content.Intent");
Java.scheduleOnMainThread(function() {
var intent = Intent.$new(context, FridaActivity7.$new().getClass());
intent.setFlags(0x10000000);
console.log(intent);
context.startActivity(intent);
})

//
});

}

hook 加载本地dex

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
26
27
//动态加载dex 来源 -> frida_example_2.apk
function load_dex() {
var DecodeUtilsDex = Java.openClassFile("/data/local/tmp/DecodeUtils.dex");
console.log("DecodeUtilsDex:", DecodeUtilsDex);
Java.perform(function() {
// 加载到vm中
DecodeUtilsDex.load();
var DecodeUtils = Java.use("com.example.androiddemo.DecodeUtils");
console.log(DecodeUtils);
var FridaActivity8 = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity8");
Java.scheduleOnMainThread(function() {
console.log(DecodeUtils.$new().decode(FridaActivity8.$new().password.value));
})
});
}

function hook_FridaActivity8() {
Java.perform(function() {
var FridaActivity8 = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity8");
FridaActivity8.a.implementation = function(str) {
str = "go to next check!";
var result = this.a(str);
console.log(str, result);
return result;
}
});
}

hook java 注册类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 注册Class, 注册接口的实现   来源 -> frida_example_2.apk
function hook_FridaActivity9() {
Java.perform(function() {
var Frida9Interface = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity9$Frida9Interface");
console.log(Frida9Interface);
var Frida9InterfaceImpl = Java.registerClass({
name : "com.github.lastingyang.androiddemo.Activity.FridaActivity9.Frida9InterfaceImpl",
implements : [Frida9Interface],
methods: {
check () {
console.log("Frida9InterfaceImpl.check");
return true;
}
}
})

var FridaActivity9 = Java.use("com.github.lastingyang.androiddemo.Activity.FridaActivity9");
FridaActivity9.getInstance.implementation = function() {
console.log("FridaActivity9.getInstance");
return Frida9InterfaceImpl.$new();
}
});
}

Frida hook 常规算法

常规算法包括MD5,SHA,HMAC,AES,DES,RSA等等

1
2
下述示例代码源自app-release.apk
网盘地址链接: https://pan.baidu.com/s/1RXahCCUHAErN6JpyMOoPag?pwd=1234

MD5

MD5算法实现

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
26
27
28
29
30
31
32
33
34
35
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5 {
public static String md5_1(String str) throws Exception {
MessageDigest instance = MessageDigest.getInstance("MD5");
instance.update((str + "xialuo").getBytes());
byte[] digest = instance.digest();
StringBuilder sb = new StringBuilder();
int length = digest.length;
for (int i = 0; i < length; i++) {
sb.append(String.format("%02x", Integer.valueOf(digest[i] & 255)));
}
return sb.toString();
}

public static String md5_2(String str) {
try {
byte[] digest = MessageDigest.getInstance("MD5").digest(str.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
for (byte b : digest) {
String hexString = Integer.toHexString(b & 255);
if (hexString.length() == 1) {
sb.append('0');
}
sb.append(hexString);
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
}

hook MD5

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
26
27
28
29
30
31
32
33
34
35
36
37
38
Java.perform(function () {
// 使用okhttp中的内置函数,前提是安装了okhttp3才行;或使用上述bytesToString函数替代
var ByteString = Java.use("com.android.okhttp.okio.ByteString")
// 字节转字符串
function toUtf8(data) {
return "[xl]utf8-->", ByteString.of(data).utf8();
}

console.log("start hook V1.0")
var MessageDigest = Java.use('java.security.MessageDigest')
MessageDigest.getInstance.overload('java.lang.String').implementation = function (str) {
console.log("[*]算法是--》" + str + "《----")
// send("[*]算法是--》" + str + "《----")
return this.getInstance(str)
}
MessageDigest.update.overload('[B').implementation = function (arg) {
console.log("======[B=====")
console.log("update算法传参--》" + toUtf8(arg) + "<----") // 字节转字符串
return this.update(arg)
}
// hook digest
MessageDigest.digest.overload().implementation = function () {
_log()
var result = this.digest()
console.log('digest结果(hex)-->' + bytesToHex(result))
console.log('digest结果(b64)-->' + bytesToBase64(result))
return result
}

MessageDigest.digest.overload('[B').implementation = function (arg) {
_log()
console.log('digest 入参(str)-->' + bytesToString(arg))
var result = this.digest(arg)
console.log('digest结果(hex)-->' + bytesToHex(result))
console.log('digest结果(b64)-->' + bytesToBase64(result))
return result
}
}

SHA系列

SHA算法实现

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
26
27
28
package com.luoge.com;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class SHA {
public static String sha_256(String str) throws Exception {
MessageDigest instance = MessageDigest.getInstance("SHA-256");
instance.update(str.getBytes());
return Utils.byteToHexString(instance.digest());
}

public static String sha_1(String str) throws Exception {
try {
byte[] digest = MessageDigest.getInstance("SHA-1").digest(str.getBytes(StandardCharsets.UTF_8));
StringBuilder sb = new StringBuilder();
int length = digest.length;
for (int i = 0; i < length; i++) {
sb.append(String.format("%02x", Byte.valueOf(digest[i])));
}
return sb.toString();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
return null;
}
}
}

AES

AES算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import android.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AES {
public static String aes(String str) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec("1234567890abcdef1234567890abcdef".getBytes(), "AES");
IvParameterSpec ivParameterSpec = new IvParameterSpec("1234567890abcdef".getBytes());
Cipher instance = Cipher.getInstance("AES/CBC/PKCS5Padding");
instance.init(1, secretKeySpec, ivParameterSpec);
return Base64.encodeToString(instance.doFinal(str.getBytes("UTF-8")), 0);
}
}

DES

DES算法实现

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
import android.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESKeySpec;
import javax.crypto.spec.IvParameterSpec;

public class DES {
public static String des_1(String str) throws Exception {
SecretKey generateSecret = SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec("12345678".getBytes()));
IvParameterSpec ivParameterSpec = new IvParameterSpec("87654321".getBytes());
Cipher instance = Cipher.getInstance("DES/CBC/PKCS5Padding");
instance.init(1, generateSecret, ivParameterSpec);
instance.update(str.getBytes());
return Base64.encodeToString(instance.doFinal(), 0);
}

public static String des_2(String str) throws Exception {
SecretKey generateSecret = SecretKeyFactory.getInstance("DES").generateSecret(new DESKeySpec("12345678".getBytes()));
IvParameterSpec ivParameterSpec = new IvParameterSpec("87654321".getBytes());
Cipher instance = Cipher.getInstance("DES/CBC/PKCS5Padding");
instance.init(1, generateSecret, ivParameterSpec);
return Base64.encodeToString(instance.doFinal(str.getBytes()), 0);
}
}

hook DES

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
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
52
Java.perform(function () {    
// hookDES
console.log('=======hook DES==============')
var SecretKeyFactory = Java.use('javax.crypto.SecretKeyFactory')
SecretKeyFactory.getInstance.overload('java.lang.String').implementation = function (str) {
console.log("[*]算法是--》" + str + "《----")
// send("[*]算法是--》" + str + "《----")
return this.getInstance(str)
}
var DESKeySpec = Java.use("javax.crypto.spec.DESKeySpec")
DESKeySpec.$init.overload('[B').implementation = function (a) {
console.log('DESKeySpec key(str)-->' + bytesToString(a))
console.log('DESKeySpec key(hex)-->' + bytesToHex(a))
return this.$init(a)
}

var IvParameterSpec = Java.use("javax.crypto.spec.IvParameterSpec")
IvParameterSpec.$init.overload('[B').implementation = function (a) {
console.log('IvParameterSpec IV(str)-->' + bytesToString(a))
console.log('IvParameterSpec IV(hex)-->' + bytesToHex(a))
return this.$init(a)
}

var Cipher = Java.use('javax.crypto.Cipher');
Cipher.getInstance.overload('java.lang.String').implementation = function (arg) {
_log()
console.log("填充模式--》" + arg)
return this.getInstance(arg)
}

Cipher.update.overload('[B').implementation = function (arg) {
console.log("======[B=====")
console.log("update算法传参--》" + toUtf8(arg) + "<----") // 字节转字符串
return this.update(arg)
}
Cipher.doFinal.overload().implementation = function () {
_log()
var result = this.doFinal()
console.log('digest结果(hex)-->' + bytesToHex(result))
console.log('digest结果(b64)-->' + bytesToBase64(result))
return result
}

Cipher.doFinal.overload('[B').implementation = function (arg) {
_log()
console.log('digest 入参(str)-->' + bytesToString(arg))
var result = this.doFinal(arg)
console.log('digest结果(hex)-->' + bytesToHex(result))
console.log('digest结果(b64)-->' + bytesToBase64(result))
return result
}
}

HMAC

HMAC算法实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.luoge.com;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class HMAC {
public static String mac_1(String str) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec("FridaHook".getBytes(), "HmacSHA1");
Mac instance = Mac.getInstance("HmacSHA1");
instance.init(secretKeySpec);
instance.update(str.getBytes());
return Utils.byteToHexString(instance.doFinal());
}

public static String mac_2(String str) throws Exception {
SecretKeySpec secretKeySpec = new SecretKeySpec("FridaHook".getBytes(), "HmacSHA1");
Mac instance = Mac.getInstance("HmacSHA1");
instance.init(secretKeySpec);
instance.update(str.getBytes(), 2, 5);
return Utils.byteToHexString(instance.doFinal("xialuo".getBytes()));
}
}

hook HMAC

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Java.perform(function () {    
// hookHMAC
var SecretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec')

SecretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function (a, b) {
_log()
console.log('算法key(str)-->' + bytesToString(a))
console.log('算法是(str)-->' + b)
return this.$init(a, b)
}

var macs = Java.use("javax.crypto.Mac");
macs.update.overload('[B').implementation = function (arg) {
_log()
console.log('update 入参(str)-->' + bytesToString(arg))
var result = this.update(arg)
return result
}
macs.update.overload('[B', 'int', 'int').implementation = function (arg, a1, a2) {
console.log("======'[B', 'int', 'int'=====")
console.log(arg + "|" + a1 + "|" + a2)
return this.update(arg, a1, a2)
}

macs.doFinal.overload().implementation = function () {
_log()
var result = this.doFinal()
console.log('hmac结果(hex)-->' + bytesToHex(result))
console.log('hmac结果(b64)-->' + bytesToBase64(result))
return result
}

macs.doFinal.overload('[B').implementation = function (arg) {
_log()
console.log('doFinal 入参(str)-->' + bytesToString(arg))
var result = this.doFinal(arg)
console.log('doFinal结果(hex)-->' + bytesToHex(result))
console.log('doFinal结果(b64)-->' + bytesToBase64(result))
return result
}
}

RSA

RSA算法实现

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
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
52
53
54
55
import android.util.Base64;
import java.security.KeyFactory;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.Cipher;

public class RSA {
public static String priKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJWFpHc6vuy5SXAdSXYvLfq5WZuhnf4eGi+iAOMuBET0JtpSiRLZ6oZpUV9vEBTEVOE0O5er98EP5J1SCmmZxmsjDgcww/gC0TaoklAf8rE9aZtcfsu/70KKw209g6W9Yn8YdGp/3HdMEqON4nYKO5XGU8ENfrf4RyKXYlH2SVVrAgMBAAECgYAJ0TeGOI42nsfKm7GqF9juAGN4y3jDKZjQjdN/FxNir6Epboffe/1hC+My3+jvZCCqlLJg+AKRY4jAJ5XVbypO3tHRd9uLFgCjzREJ09J6SWyNj3KFKCkJ4vpaO0jbUAAtFGlLElc6ZtHNKabeJ0ECOgcIvVsfHpP47j1GTRU8oQJBAMXsksEmrIvCJ0l5mdDX73nRJzbxDK6m7jndE4fBe0h3Wl06iBCfuaS2x+PTjmiRWvfFu2B1/9E9Tt0jc4FQS3ECQQDBZUKZjnv6rKtwqBj1EqjIXVF2SAsttW/6vTpg6mhHYITlrqQqrt1NJ5+6PRVQr1FLDxPArNVSdoz6MFIIAiibAkA+3K+Tt0PQM78koAGRijPePea1lYPQqOY67JN6Z6JPVtEVkTSMCx78SK1eF+BAKAJ7dYrYzUGN5Gn65HqYFLeRAkBcBOFWjSxCjwwX03PkkBdNFtHe9NKU0iLQ7F6tpHsvkyZI3vrv8DoOLw9aHxxYQsLscuUUJWhvD0du97TgaJ6HAkEAoRXjsQO2UmgQcddE2e6Uxp5riOuWIEEzoW6YssCW9BznCnwXy/xamrTKhoW2cIHwn6cFx+MFmyaK5T0xAtF5pw==";
public static String pubKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC9xhBZOWWF5Icw384mJksmaJ53RBLPUbEq5hXWW4Xgf82r6Zj24e3MWOnBTcblDodXYtSsaRJilosdTQVWGetJewebKmyqh1l1lUagS1/dbII9GsGat5zMboMHLWUO9NoBS9VDxqYL2VLppNEj/Xe39gBRHIiSnmtggiHuYsEv8wIDAQAB";

public static PublicKey getPublicKey(String str) throws Exception {
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(Base64.decode(str, 0)));
}

public static PrivateKey getPrivateKey(String str) throws Exception {
return KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(Base64.decode(str, 0)));
}

public static String sign(String str) throws Exception {
PrivateKey privateKey = getPrivateKey(priKey);
Signature instance = Signature.getInstance("SHA256withRSA");
instance.initSign(privateKey);
instance.update(str.getBytes());
return Base64.encodeToString(instance.sign(), 0);
}

public static boolean verify(byte[] bArr, byte[] bArr2) throws Exception {
PublicKey publicKey = getPublicKey(pubKey);
Signature instance = Signature.getInstance("SHA256withRSA");
instance.initVerify(publicKey);
instance.update(bArr);
return instance.verify(bArr2);
}

public static byte[] encrypt(byte[] bArr) throws Exception {
PublicKey publicKey = getPublicKey(pubKey);
Cipher instance = Cipher.getInstance("RSA/None/NoPadding", "BC");
instance.init(1, publicKey);
return instance.doFinal(bArr);
}

public static byte[] decrypt(byte[] bArr) throws Exception {
PrivateKey privateKey = getPrivateKey(priKey);
Cipher instance = Cipher.getInstance("RSA/None/NoPadding", "BC");
instance.init(2, privateKey);
return instance.doFinal(bArr);
}

public static String rsa(String str) throws Exception {
return Base64.encodeToString(encrypt("xialuo".getBytes()), 0);
}
}

hook RSA

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Java.perform(function () {    
// RSA算法
var b64 = Java.use("android.util.Base64");
b64.decode.overload('java.lang.String', 'int').implementation = function (a, b) {
_log()
console.log("RSA-KEY==b64入参--" + a)
return this.decode(a, b)
}

var X509EncodedKeySpec = Java.use('java.security.spec.X509EncodedKeySpec')
X509EncodedKeySpec.$init.overload('[B').implementation = function (a) {
console.log("RSA-KEY密钥-->" + bytesToBase64(a))
return this.$init(a)
}
}

Frida Hook So层

Java导入so文件方式

1
2
3
4
5
6
7
8
9
10
11
12
// 来源:一点资讯6.2.3.apk   导入libutil.so文件
synchronized (SignUtil.class) {
if (!a.get()) {
ud0.a(context, "util");
a.set(true);
}
}

// 动态加载so文件脚本通过loadLibrary导入,导入libutil.so文件
static{
System.loadLibrary("util");
}

基于脚本获取动态的so文件

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 以dewu5.6.0.apk为例  
var addrRegisterNatives = null;
var symbols = Module.enumerateSymbolsSync("libart.so");
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {

addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
break
}
}

if (addrRegisterNatives) {
// RegisterNatives(env, 类型, Java和C的对应关系,个数)
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
var env = args[0]; // jni对象
var java_class = args[1]; // 类
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
var taget_class = "com.duapp.aesjni.AESEncrypt"; // 某个类中动态注册的so
if (class_name === taget_class) {
//只找我们自己想要类中的动态注册关系
console.log("\n[RegisterNatives] method_count:", args[3]);
var methods_ptr = ptr(args[2]);
var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
// Java中函数名字的
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
// 参数和返回值类型
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
// C中的函数内存地址
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
var name = Memory.readCString(name_ptr);
var sig = Memory.readCString(sig_ptr);
var find_module = Process.findModuleByAddress(fnPtr_ptr);
// 地址、偏移量、基地址
var offset = ptr(fnPtr_ptr).sub(find_module.base);
console.log("name:", name, "sig:", sig,'module_name:',find_module.name ,"offset:", offset);
}
}
}
});
}
// 动态注册函数地址
// frida -U -f com.shizhuang.duapp -l hook_so_register.js

Hook so的一些操作说明

参考:https://www.cnblogs.com/xiaoweigege/p/14999469.html

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
26
27
28
29
30
31
32
33
// Native方法第一个参数是JNIEnv *env 如何在Frida中获取JNIEnv对象呢?
Java.vm.getEnv();

// 如何将string类型转换jstring类型呢?
jstring = Java.vm.getEnv().newStringUtf(str);

// 如何将jstring类型转string类型呢?
aes_value = Java.vm.getEnv().getStringUtfChars(result, null).readCString()

// Hook So导出函数
let method1_addr = Module.findExportByName('libxiaowei.so','Java_com_example_xiaoweiso_MainActivity_method01');

// Hook So非导出函数
let so_addr = Module.findBaseAddress('libxiaowei.so');
// 需要去So中找到非导出函数的地址,通过IDA分析
let encrypt_addr = so_addr.add(0x42B0);

// 如何在so中定义一个字符串
let cstring = Memory.allocUtf8String("xiaoweigege");

// 如何将c中的字符串转成js string?
ptr(result).readCString()

// 将函数地址定义成一个函数能在js中进行调用
let so_addr = Module.findBaseAddress('libxiaowei.so');
let encrypt_addr = so_addr.add(0x42B0);
// new NativeFunction(address, returnType, argTypes[, abi])
let encrypt_fun = new NativeFunction(encrypt_addr, 'pointer', ['pointer']);
let cstring = Memory.allocUtf8String(str);
let result = encrypt_fun(cstring);

// 在任意apk中加载so文件
var load_model = Module.load('/data/local/tmp/libxiaowei.so')

JNI介绍

​ JNI的全称是Java Native Interface,即本地Java接口。因为 Java 具备跨平台的特点,所以Java 与 本地代码交互的能力非常弱。采用JNI特性可以增强 Java 与本地代码交互的能力,使Java和其他类型的语言如C++/C能够互相调用。

NDK介绍

​ NDK的全称是Native Development Kit, 是Android的一个工具开发包。NDK集成了交叉编译器(交叉编译器需要UNIX或LINUX系统环境),并提供了相应的mk文件隔离CPU、平台、ABI等差异,开发人员只需要简单修改mk文件。NDK能帮助开发者快速开发C、 C++的动态库,将动态库编译成.so文件供Java调用,并支持将.so和应用一起打包成 apk。

1
2
3
1. NDK帮助将C/C++库打包成动态库.so,JNI是C/C++动态库能被调用的桥梁接口
2. JNI是java接口,用于Java与C/C++之间的交互,作为两者的桥梁
3. NDK是Android工具开发包,帮助快速开发C/C++动态库,相当于JDK开发java程序一样,同时能帮打包生成.so库

JNI的定位

​ JNI提供了在Java平台上使用本地C/C++代码的能力,作为Java虚拟机实现的一部分,JNI是一个双向的接口,既允许Java调用C/C++,也允许C/C++有调用Java的能力, 如下图:

JNI支持一下两种方式和本地代码交互:

  • 可以使用JNI来“实现”基于本地库的方法(native method)以供Java应用使用, Java应用可以像调用Java API一样调用JNI实现的方法(Java中带有native声明的函数),但实际上这些方法是通过C/C++在本地实现的。
  • JNI支持invocation interface, 借此你可以将一个JVM嵌入到本地应用之中。本地应用可以“实现”一个JVM,然后通过invocation interface执行Java实现的组件。比如一个C实现的浏览器可以通过嵌入一个JVM来执行网上下载的applets。

JNI字符串函数总结

参考:https://soo-q6.github.io/blog/2019-09-29-JNI-guides-and-specifications-3/

​ GetStringUTFChars:是JINEnv*指向的函数表中的一个函数,可以将Java虚拟机中的字符串对象引用(Unicode序列)转换为C/C++中的UTF-8格式的字符串。

表头 表头
GetStringUTFChars, ReleaseStringUTFChars 获取或者释放一个UTF-8格式的字符串, 可能会返回一个拷贝
GetStringLength 返回Unicode字符的个数
GetStringUTFLength 返回表示一个UTF-8字符串所需要的字节数, 不包括’\0’
NewString 创建一个java.lang.String字符串对象(Unicode)
NewStringUTF 创建一个java.lang.String字符串对象(UTF-8)|

Frida-RPC

RPC调用示例

HOOK示例脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
// Frida基础脚本示例  来源apk:app-release.apk
function fridamethod01(inputStr){
var result = null;
Java.perform(function(){
var targetClass = Java.use("com.luoge.com.MD5");
result = targetClass.md5_1(inputStr);
});
return result;
}

rpc.exports = {
invokemethod01: fridamethod01,
}

python调用脚本

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
26
27
28
29
30
31
# -*- coding: utf-8 -*-
import frida
from flask import Flask, request

def message(message, data):
if message['type'] == 'send':
print(f"[*] {message['payload']}")
else:
print(message)

session = frida.get_usb_device().attach('com.luoge.com')
with open("./frida-rpc.js") as f:
jsCode = f.read()

# print("加载代码", jsCode)
script = session.create_script(jsCode)
script.on("message", message)
script.load()

# print("加密","1213")
# encodeResult = script.exports.invokemethod01("123")
app = Flask(__name__)

@app.route('/api', methods=['GET'])
def decrypt_class():
data = request.args.get('num')
res = script.exports.invokemethod01(data)
return res

if __name__ == "__main__":
app.run()

RPC调用XY

描述:该app未加壳【xianyu7.8.8.apk】

hook Spdy协议

1
2
3
4
5
6
7
// 处理spdy自定义协议
Java.perform(function () {
var SwitchConfig = Java.use('mtopsdk.mtop.global.SwitchConfig');
SwitchConfig.A.overload().implementation = function () {
return false;
}
});

hook示例代码

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
26
27
28
29
30
31
// 通过Hook Hashmap分析得到调用栈加密位置
function hashMap(){
var hashMap = Java.use("java.util.HashMap");
hashMap.put.implementation = function (a,b){
if (a == "Encrypt"){
// 查看调用栈
showStacks()
}
console.log('输出--》',a,b)
return this.put(a,b)
}
}

// hook指定加密参数
Java.perform(function() {
console.log("hook参数成功")
var InnerSignImpl = Java.use("mtopsdk.security.InnerSignImpl");
InnerSignImpl.getUnifiedSign.overload('java.util.HashMap', 'java.util.HashMap', 'java.lang.String', 'java.lang.String', 'boolean', 'java.lang.String').implementation = function(params, ext, appKey, authCode, useWua, requestId) {
console.log("某鱼x-sign参数1:", params);
console.log("某鱼x-sign参数2:", ext);
console.log("某鱼x-sign参数3:", appKey);
console.log("某鱼x-sign参数4:", authCode);
console.log("某鱼x-sign参数5:", useWua);
console.log("某鱼x-sign参数6:", requestId);
var result = this.getUnifiedSign(params, ext, appKey, authCode, useWua, requestId);
console.log("某鱼x-sign结果:", result);
return result;
}
});

// frida -U -l hook.js --no-pause -f com.taobao.idlefish

python调用示例

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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# -*- coding: utf-8 -*-
import re
import urllib.parse
import requests as sess
import time
import frida
session = sess.session()

st = str(int(time.time()))

def on_message(message, data):
if message['type'] == 'send':
print("[*] {}".format(message['payload']))
else:
print(message)

def get_sign(datas):
jscode = '''
rpc.exports = {
sign: function(data,times) {
var ret = null;
Java.perform(function() {
Java.choose("mtopsdk.security.InnerSignImpl",{
onMatch: function(instance){
//这些都是传入的参数,具体传参内容根据实际修改
var HashMap1 = Java.use("java.util.HashMap").$new();
HashMap1.put("data",data);

HashMap1.put("deviceId", "Ap2xlstz9Q-Xqp90jq16YWjUopNFAYEEhFHSXXqIucQC");
HashMap1.put("sid","");
HashMap1.put("uid","");
HashMap1.put("x-features","27");
HashMap1.put("appKey", "21407387");

HashMap1.put("api", "mtop.taobao.idlemtopsearch.search");

HashMap1.put("lat","0");
HashMap1.put("lng","0");
HashMap1.put("utdid", "ZUDUSVa6rmsDAOvsGCex7UWC");
HashMap1.put("extdata", "openappkey=DEFAULT_AUTH");
HashMap1.put("ttid", "270200@fleamarket_android_7.8.80");
HashMap1.put("t", times);
HashMap1.put("v", "1.0");

var jExt = Java.use("java.util.HashMap").$new();
jExt.put("pageId","");
jExt.put("pageName","");

ret = instance.getUnifiedSign(HashMap1, jExt, "21407387", "", false, "r_38").toString();
//console.log('getUnifiedSign ret value is ' + res);
// ret["result"] = res;
},
onComplete: function(){}
})
})
return ret;
}
};
'''
process = frida.get_usb_device().attach('com.taobao.idlefish')
script = process.create_script(jscode)
script.on('message', on_message)
script.load()
result = script.exports.sign(datas["data"], st)
print("某鱼x-sign结果:", result)
return result

# 通过RPC调用Frida脚本中的函数,传递参数
def get_search_data(key):
headers = {
# "x-sgext": "JAxGMpbA%2BP0VJMW%2F8%2F7PLDJ3AnQGcxF0B3YFZQR%2FEWUDcQJ2AnILcAdzEXYBJVN2AnYDcAd2VHZQdFdlB3ARdwRlAHIRdgJ2BWUGZQBlA2UDZQNlAGUBZQNlVGUDZQJlAGUCZQJlAmUCZQJlESMRZVQjVyMDJxF2AnYCdhF2EXRWJxF2EWUAZQJlAmV1BVof",
"umid": "gqEBai9LPA7C2AKLxxHs+Sd2eyQTnNgs",
# "x-sign": "azU7Bc002xAAIBpY%2FsZhzGpp0Y4uABpQHv1eVk3y4ZgCegpGuKSpkPZ9mnimIQSfiCd94ltp19LZWt4kSmFeVLsUuMDbUBpQG1AaUB",
"x-nettype": "WIFI",
"x-pv": "6.3",
"x-nq": "WIFI",
"EagleEye-UserData": "spm-cnt=a2170.8011571.0.0&spm-url=a2170.unknown.0.0",
"first_open": "0",
"x-features": "27",
"x-app-conf-v": "0",
# "x-mini-wua": "aiwT%2F0n0h5BZWWp78zBTouNqbP2798aCFzl97lyD0LKAqWsgznmsGbLulF%2BsUEcj3rhLkzNRnPzV5ZoFpRopDxsk0aZ98ihf6MNDmFvI2OhbVbEKXgxfpLfRodPMi52jZn4j2KOfoWREEHlPh4l6OuT8z5JlmlUYppp%2Bi52KZH%2FS5nQ%3D%3D",
"content-type": "application/x-www-form-urlencoded;charset=UTF-8",
# "Content-Length": "741",
# "x-t": "1700051257",
# "Content-Type": "application/x-www-form-urlencoded;charset=UTF-8",
"x-bx-version": "6.5.88",
"f-refer": "mtop",
"x-extdata": "openappkey%3DDEFAULT_AUTH",
"x-ttid": "270200%40fleamarket_android_7.8.80",
"x-app-ver": "7.8.80",
# "x-c-traceid": "ZUDUSVa6rmsDAOvsGCex7UWC1700051257967005712507",
"x-location": "0%2C0",
"x-umt": "wWYBs2RLPAKcVgKL0gppGhDT24iunbKi",
"a-orange-q": "appKey=21407387&appVersion=7.8.80&clientAppIndexVersion=1120231115161804575&clientVersionIndexVersion=0",
"x-utdid": "ZUDUSVa6rmsDAOvsGCex7UWC",
"x-appkey": "21407387",
"x-devid": "Ap2xlstz9Q-Xqp90jq16YWjUopNFAYEEhFHSXXqIucQC",
"user-agent": "MTOPSDK%2F3.1.1.7+%28Android%3B10%3BGoogle%3BPixel+4%29",
"host": "g-acs.m.goofish.com",
"Accept-Encoding": "gzip",
}
jsonString = "{\"activeSearch\":false,\"bizFrom\":\"home\",\"disableHierarchicalSort\":0,\"forceUseInputKeyword\":false,\"forceUseTppRepair\":false,\"fromFilter\":false,\"fromKits\":false,\"fromLeaf\":false,\"fromShade\":false,\"fromSuggest\":false,\"keyword\":\""+ key +"\",\"pageNumber\":1,\"relateResultListLastIndex\":0,\"relateResultPageNumber\":1,\"resultListLastIndex\":0,\"rowsPerPage\":10,\"searchReqFromActivatePagePart\":\"historyItem\",\"searchReqFromPage\":\"xyHome\",\"searchTabType\":\"SEARCH_TAB_MAIN\",\"shadeBucketNum\":-1,\"suggestBucketNum\":28,\"supportFlexFilter\":true}"
datas = {
'data':jsonString
}
result = get_sign(datas)
print(result)

headers['x-t'] = st
headers['x-sign'] = urllib.parse.quote_plus(re.findall("x-sign=(.*?)}", result,re.S)[0])
headers['x-mini-wua'] = urllib.parse.quote_plus(re.findall("x-mini-wua=(.*?),", result)[0])
headers['x-sgext'] = urllib.parse.quote_plus(re.findall("x-sgext=(.*?),", result)[0])
headers['x-c-traceid'] = f"ZUDUSVa6rmsDAOvsGCex7UWC{st}967005712507"
url = "https://g-acs.m.goofish.com/gw/mtop.taobao.idlemtopsearch.search/1.0"
response = sess.post(url, headers=headers,data=datas)
print(response.text)

if __name__ == '__main__':
key = input("输入需要搜索的商品")
print(f"查询{key}相关商品")
get_search_data("key")

RPC调用PDD

描述:该apk加密在so层,未加壳 【pdd_v6.26.apk】

hook 关键加密函数

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
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
52
53
54
55
56
57
58
59
60
61
62
function  hook1(){
let C1674a = Java.use("com.aimi.android.common.http.a");
C1674a["c"].implementation = function (str, z) {
console.log(`C1674a.m107201c is called: str=${str}, z=${z}`);
let result = this["c"](str, z);
console.log(`C1674a.m107201c result=${result}`);

//Java map的遍历
var key = result.keySet(); // 这一行获取了Map对象的键集合,这个键集合是一个迭代器
var it = key.iterator();
var results = "";
while(it.hasNext()){
var keystr = it.next();
var valuestr = result.get(keystr);
console.log("key->" + keystr + "val->" + valuestr);
results += valuestr;
}

return result;
};
}

function hook2(){
let C17763e = Java.use("com.xunmeng.pinduoduo.c.e");
C17763e["a"].implementation = function (str, i) {
console.log(`C17763e.m65496a is called: str=${str}, i=${i}`);
let result = this["a"](str, i);
console.log(`C17763e.m65496a result=${result}`);
return result;
};

}

function hook3(){
let C1674a = Java.use("com.aimi.android.common.http.a");
C1674a["h"].implementation = function (str) {
console.log(`C1674a.m107200h is called: str=${str}`);
let result = this["h"](str);
console.log(`C1674a.m107200h result=${result}`);
return result;
};

}

function hook4(){
let C35598s = Java.use("com.xunmeng.pinduoduo.secure.s");
C35598s["f"].implementation = function (context, l) {
console.log(`C35598s.mo24406f is called: context=${context}, l=${l}`);
let result = this["f"](context, l);
console.log(`C35598s.mo24406f result=${result}`);
return result;
};

}

Java.perform(function (){
// hook1()
// hook2()
// hook3()
hook4()

})

hook RPC 调用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// Rpc调用so层
function hookPdd(){
var result = null;
Java.perform(function (){

let SecureNative = Java.use("com.xunmeng.pinduoduo.secure.SecureNative");

let BaseApplication = Java.use("com.xunmeng.pinduoduo.basekit.BaseApplication")
var context = BaseApplication.getContext()

let time = Java.use("com.xunmeng.pinduoduo.basekit.util.TimeStamp")
let _ts = time.getRealLocalTime().longValue();
let Long = Java.use("java.lang.Long");
// 上下文、时间戳
result = SecureNative.deviceInfo2(context, Long.valueOf(_ts));

})
return result

}

rpc.exports = {
xiaoshayu:hookPdd
}

python调用示例

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
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
52
53
54
55
56
57
58
59
# -*- coding: utf-8 -*-
import requests
import frida
import sys

def on_message(message, data):
print("message", message)
print("data", data)


# 通过Spawn模式启动一个新的应用程序进程,并在该进程中加载Frida脚本
device = frida.get_usb_device().attach("com.xunmeng.pinduoduo")
with open('pdd-rpc.js',encoding='utf-8') as f1:
js_code = f1.read()

script = device.create_script(js_code)
script.on("message", on_message)
script.load()
# RPC调用
anti = script.exports.xiaoshayu()
print(anti)

headers = {
"Host": "api.pinduoduo.com",
"etag": "bDmY1yH4",
"accesstoken": "OEPLLFI3OENRH5CWCUD7MC5COSNGODHPFLVE3MSS37ODC2CC2JJQ12031b2",
"referer": "Android",
"lat": "KCAYS5HSM2KHIYKKPFMMWVQN5L5ZEDJQTK7D4LSDNJDW6JKAJXNA12031b2",
"al-sa": "{\"ads\":\"!!!;-[fg#&!\\yX=d6!!#&!\\\\\"-[Gs`3!!#&!\\\\\"yX=d6#k#&!\\.7C5M,#k#&!\\+kpw`z%9#&!!t<i\\pB%9#&!\\-H4-Ab)p\"}",
"p-appname": "pinduoduo",
"p-proc-time": "2099738",
"x-pdd-info": "bold_free%3Dfalse%26bold_product%3D%26front%3D1",
"x-pdd-queries": "width=1080&height=2236&net=1&brand=google&model=Pixel+4&osv=10&appv=6.26.0&pl=2",
"x-yak-llt": "1714029970723",
"p-proc": "main",
"p-mediainfo": "player=1.0.3&rtc=1.0.0",
"x-b3-ptracer": "hctrue236505f8aacb452f9618b5425f",
"user-agent": "android Mozilla/5.0 (Linux; Android 10; Pixel 4 Build/QQ2A.200405.005; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/74.0.3729.186 Mobile Safari/537.36 phh_android_version/6.26.0 phh_android_build/43953171301671e3b3baf01f859eed5581323a9e phh_android_channel/hw pversion/0",
"pdd-config": "V4:001.062600",
"multi-set": "1,1",
"content-type": "application/json;charset=UTF-8",
"anti-token": anti,
"vip": "81.69.104.49"
}

cookies = {
"acid": "91957d147138da6c4ddd5bd3649fa2fb",
"api_uid": "CiRVq2Yp/zE2uABaj49fAg=="
}
url = "https://api.pinduoduo.com/search"
params = {
"source": "index",
"pdduid": "4761406513"
}

data = '{"install_token":"3519158c-12dc-420d-8db6-3eafea319832","item_ver":"lzqq","list_id":"CS0AQ959","track_data":"refer_page_id,10002_1715083769100_0491581551;refer_search_met_pos,0","search_met":"history","max_offset":"0","source":"index","sort":"default","exposure_offset":"0","q":"茅台","page_sn":"10015","page_id":"search_result.html","size":"20","q_search":"{\\pes_req_id\\:\\515f5109-0ebc-4440-a866-282d071fdc8a\\}","requery":"0","page":"1","engine_version":"2.0","is_new_query":"1","back_search":"false"}'.encode('unicode_escape')
response = requests.post(url, headers=headers, cookies=cookies, params=params, data=data)
print(response.text)
print(response)

Frida检测

检测方式

1、D-Bus

​ Frida 使用 D-Bus 协议通信,可以遍历 /proc/net/tcp 文件,或者直接从 0-65535向每个开放的端口发送 D-Bus 认证消息,哪个端口回复了REJECT,就是 frida-server。

2、端口检测

​ 通过检测默认端口 27042 是否开放来检测frida是否开启,只需要启动时指定端口即可绕过

3、进程名检测

​ 遍历运行的进程列表,检测 frida-server 是否运行

4、默认路径

​ frida 默认会在 /data/local/tmp/re.frida.server/frida-agent-64.so 中存放 frida-agent ,可以查找此路径下是否存在对应文件

5、扫描maps 文件

​ maps 文件用于显示当前app中加载的依赖库。Frida在运行时会先确定路径下是否有 re.frida.server 文件夹若没有则创建该文件夹并存放 frida-agent.so 等文件,该so会出现在 maps 文件中。

​ 注: frida 在注入 App 后会在 maps 中显示 frida 的 frida-agent.so 的内存信息,可以通过搜索特征字符串来检测frida

Frida-Server 为什么要放在这个目录?

​ 将 Frida-Server 放在 /data/local/tmp 目录下是为了确保它具有足够的权限运行并且可以被设备上的其他应用程序访问。Android 设备中的 /data/local/tmp 目录是一个特殊的目录,它具有较高的读写权限,并且对于应用程序来说是可访问的。因此,将 Frida-Server 放置在该目录下可以确保它能够在 Android 设备上正常运行,并且其他应用程序可以通过执行 Frida-Server 来与目标应用程序进行通信。此外, /data/local/tmp 目录下的文件在设备重启后不会被清除,这意味着 Frida-Server 在设备重启后仍然可用,而不需要重新安装。总之,将 Frida-Server 放置在 /data/local/tmp 目录下是为了方便其在 Android 设备上的部署和使用,并确保它具有足够的权限和持久性。除了 /data/local/tmp 目录,您还可以将 Frida-Server 放置在其他路径中,只要该路径对应的目录具有足够的权限供 Frida-Server 运行和其他应用程序访问即可。

绕过方式

  1. 更改默认启动的协议端口

    1
    2
    3
    4
    /data/local/tmp/fs64 -l 0.0.0.0:9998
    adb forward tcp:9998 tcp:9998
    # 测试京东app
    frida -H 127.0.0.1:9998 -f com.jingdong.app.mall -l tmp.js --no-pause -o data.txt
  2. 下载编译好的frida服务端(hluda-server)

1
2
3
4
# github地址:https://github.com/hzzheyang/strongR-frida-android/releases?page=5
# 如下载 hluda-server-15.1.28-android-arm64
pip install frida==15.1.28
pip install frida-tools==11.0.0

检测的apk

1
2
# 百度网盘链接: https://pan.baidu.com/s/1cifltwjaLpfCUaZnPmaBCw?pwd=1234 提取码: 1234 
FridaEnvCheck_1.0.apk

Frida脚本

获取Frida检测的so脚本

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
26
// 来源app:dewu5.6.0.apk
var dlopen = Module.findExportByName(null, "dlopen");
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");

Interceptor.attach(dlopen, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
console.log("[dlopen:]", path);
},
onLeave: function (retval) {

}
});
Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
console.log("[dlopen_ext:]", path);
},
onLeave: function (retval) {
}
});


// frida -U -f com.shizhuang.duapp -l load_so.js

hook NewStringUTF脚本

通用的hook脚本如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 后期任何app,都可以使用这个代码--》hook--so返回的字符串

//1 加载安卓手机底层包,系统自带的库,我们hook的NewStringUTF在这个包中
var symbols = Module.enumerateSymbolsSync("libart.so");
//2 定义一个变量,用来接收一会找到的NewStringUTF的地址
var addrNewStringUTF = null;
//3 循环找出libart.so中所有成员,匹配是NewStringUTF的函数,取出地址,赋值给上面的变量
for (var i = 0; i < symbols.length; i++) {
//3.1 取出libart.so的一个个方法对象
var symbol = symbols[i];
//3.2 判断方法对象的名字是不是包含 NewStringUTF和CheckJNI---》因为在真正底层,函数名不叫NewStringUTF,前后有别的字符串
// 实际它真正的名字:asdfa_NewStringUTF_dadsfasfd
if (symbol.name.indexOf("NewStringUTF") >= 0 && symbol.name.indexOf("CheckJNI") < 0) {
// 3.3 找到后,把地址赋值个上面的变量
addrNewStringUTF = symbol.address;
// 3.4 控制台打印一下
console.log("NewStringUTF is at ", symbol.address, symbol.name);
break
}
}
// 4 如果不为空,我们开始hook它(通过地址hook,有onEnter和onExit,所有的参数都给了args,通过位置取到每个参数)
if (addrNewStringUTF != null) {
Interceptor.attach(addrNewStringUTF, {
onEnter: function (args) {
// 4.1 取出NewStringUTF传入的第一个参数
var c_string = args[1];
// 4.2 第一个参数是c的字符串,我们把它转一下,变成真正的字符串
var dataString = c_string.readCString();
// 4.3 改字符串不为空,且长度为32,我们输出一下,并且打印出它的调用栈
if (dataString) {
if (dataString.length === 32) { //后期只要改这里 代表筛选条件
console.log(dataString);
// 4.4 读取当前在执行那个so文件,及so文件中的地址
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
// 4.5 打印调用栈
console.log(Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()));
}
}

}
});
}

// frida -UF -l 18.通用hook_NewStringUTF.js
// 末尾加上-o v1.txt代表日志输出到文档

Frida获取动态注册对应关系

这个用脚本的作用就是找到动态注册中的全部对应关系,这个对我们十分方便我这里给出hook代码以及详细注释,后期直接用,只需要改类名就好。

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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
function hook_RegisterNatives() {
//1 加载安卓手机底层包,系统自带的库,我们hook的RegisterNatives在这个包中
var symbols = Module.enumerateSymbolsSync("libart.so");
//2 定义一个变量,用来接收一会找到的addrRegisterNatives的地址
var addrRegisterNatives = null;
// 3 循环找到RegisterNatives的地址,赋值给变量
//注意:此处可能找出多个RegisterNatives的地址,由于咱们是for循环,会把之前的覆盖掉,所有如果hook没反应,尝试加break,使用第一个找到的
for (var i = 0; i < symbols.length; i++) {
var symbol = symbols[i];
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
break
}

}
// 4 找到后开始hook
if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
// 4.1 当进入RegisterNatives时执行
// RegisterNatives(env, 类型, Java和C的对应关系,个数)
onEnter: function (args) {
// 4.2 第0个参数是env
var env = args[0];
// 4.3 第1个参数是类型
var java_class = args[1];
// 4.4 通过类型得到具体的类名
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);
// 只有类名为com.bilibili.nativelibrary.LibBili,才打印输出
//后期只需要改这里,这里就是在jadx找到so文件下的类名
var taget_class = "com.bilibili.nativelibrary.LibBili";
if (class_name === taget_class) {
//4.5 只有类名为com.bilibili.nativelibrary.LibBili,再取出第四个参数
console.log("\n[RegisterNatives] method_count:", args[3]);
// 4.6 第2个参数是:Java和C的对应关系,我们转成指针
/*
static JNINativeMethod gMethods[] = {
{"add", "(III)I", (void *) plus},
{"add", "(II)I", (void *) plus},
{"add", "(II)I", (void *) plus},
};
*/
var methods_ptr = ptr(args[2]);
// 4.7 java和c函数对应关系的个数
var method_count = parseInt(args[3]);
// 4.8 我们循环这个个数,依次移动指针methods_ptr,通过readPointer,往后读取 {"add", "(III)I", (void *) plus},依次读出Java中函数名字,签名和C中的函数指针
for (var i = 0; i < method_count; i++) {
// 4.8.1 读取Java中函数名字的
var name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
// 4.8.2 读取签名, 参数和返回值类型
var sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
// 4.8.3 读取 C中的函数指针
var fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

// 4.8.4 读取java中函数名 字符串名
var name = Memory.readCString(name_ptr);
// 4.8.5 参数和返回值类型 字符串名
var sig = Memory.readCString(sig_ptr);
// 4.5.6 根据C中函数指针获取模块
var find_module = Process.findModuleByAddress(fnPtr_ptr); // 根据C中函数指针获取模块


// 4.8.7 得到该函数的偏移量:ptr(fnPtr_ptr)函数在内存中的地址 减去 该so文件的基地址(find_module.base)====得到偏移量
// 地址:函数在内存中的地址
// 偏移量:后期单独打开so文件后,可以根据偏移量 定位到函数位置
// 基地址:当前so文件从那个位置开始算地址
var offset = ptr(fnPtr_ptr).sub(find_module.base)
// console.log("[RegisterNatives] java_class:", class_name);
// 4.8.8 输出 函数名 参数和返回值类型 模块 偏移量
console.log("name:", name, "sig:", sig, "module_name:", find_module.name, "offset:", offset);

}
}
}
});
}
}

setImmediate(hook_RegisterNatives);

// frida -U -f 包名字 -l 19.Hook动态注册的对应关系.js
// frida -U -f tv.danmaku.bili -l 19.Hook动态注册的对应关系.js

Frida查找静态注册so脚本

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
26
27
28
// 处理海南航空apk9.0.0:hnairSign
Java.perform(function () {
var dlsymadd = Module.findExportByName("libdl.so", 'dlsym');
Interceptor.attach(dlsymadd, {
onEnter: function (args) {
this.info = args[1];

}, onLeave: function (retval) {
//那个so文件 module.name
var module = Process.findModuleByAddress(retval);
if (module == null) {
return retval;
}
// native方法
var funcName = this.info.readCString();
// 后期只需要改这里,对应的java方法名
if (funcName.indexOf("getHNASignature") !== -1) {
console.log(module.name);
console.log('\t', funcName);
}
return retval;
}
})
});


// frida -U -f 包名 -l 通用脚本_获取静态注册的so文件.js
// frida -U -f com.rytong.hnair -l 通用脚本_获取静态注册的so文件.js

延时hook脚本

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
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
52
53
54
55
56
57
58
59
// 唯品会的api_sign参数
function do_hook() {
setTimeout(function(){
var addr = Module.findExportByName("libkeyinfo.so", "getByteHash"); //后期改这里
console.log(addr); //0xb696387d


Interceptor.attach(addr, {
onEnter: function (args) {
this.x1 = args[2];
},
onLeave: function (retval) {
console.log("--------------------")
console.log(Memory.readCString(this.x1));
console.log(Memory.readCString(retval));
}
})
},10);

}

function load_so_and_hook() {
var dlopen = Module.findExportByName(null, "dlopen");
var android_dlopen_ext = Module.findExportByName(null, "android_dlopen_ext");

Interceptor.attach(dlopen, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();
// console.log("[dlopen:]", path);
this.path = path;
}, onLeave: function (retval) {
if (this.path.indexOf("libkeyinfo.so") !== -1) { //还有这里
console.log("[dlopen:]", this.path);
do_hook();

}
}
});

Interceptor.attach(android_dlopen_ext, {
onEnter: function (args) {
var path_ptr = args[0];
var path = ptr(path_ptr).readCString();

this.path = path;
}, onLeave: function (retval) {
if (this.path.indexOf("libkeyinfo.so") !== -1) { //还有这里
console.log("\nandroid_dlopen_ext加载:", this.path);
do_hook();

}
}
});
}

load_so_and_hook();

//frida -U -f com.achievo.vipshop -l 20.延迟hook_so.js