今日内容

1 unidbg是什么

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
unidbg是一个Java开源项目,可以帮助我们去模拟一个安卓或IOS设备,用于去执行so文件中的算法,从而不需要再去逆向他内部的算法

它是一个基于 unicorn 的逆向工具,可以直接调用Android和iOS中的 so 文件

Allows you to emulate an Android native library, and an experimental iOS emulation
允许您模拟 Android native library 和 实验性的 iOS 模拟


# 安卓开发流程
- 我们写了java代码,写了c代码--》使用java调用 c写的so文件中某个加密方式--》完成加密--》打包成apk

-破解app:
-1 普通app---》加密算法只是用java加密了---》直接使用jadx反编译apk--》阅读java代码--》复现过程即可
-2 高级app---》加密算法使用c写的,打包到so文件中---》我们使用java调用so文件中的某个加密方法,完成加密(B站,得物)

# 针对于高级app---》破解so文件加密的时候
-1 硬核破解--》直接反编译so文件(IDA)---》读它的c代码---》硬核破解加密方式【难度很大--》一旦读完--》使用python复现--》交付给用户方便】
-2 frida-rpc--》远程远程调用---》在电脑端--》传入加密函数的参数--》直接调用手机上的加密方法--》得到结果【破解简单,不需要硬核读逻辑---》交付麻烦--》没法把手机和app交付给用户--》咱们提供接口供用户调用】
-3 自己编写apk---》调用别人的so文件---》传入参数--》直接返回加密结果【破解简单---》交付麻烦】
-4 unidbg:集成了12,3的优点 交付方便,破解简单[不需要硬核破解]---》模拟手机运行环境--》运行apk和so文件--》传入该传的参数----》返回加密后的结果--》交付时---》直接打包


# 总结:
-unidbg 可以模拟手机执行环境--》把apk和so放到环境中---》它就能模拟运行这个apk---》我们只需要调用so中某个加密方法---》传入参数---》最终它就能把执行结果返回
-由于unidbg是使用java写的---》写逻辑需要用java代码写---》最终打包成jar包---》使用python调用jar包---》传入参数--》得到结果--》交付时--》python脚本,unidbg的jar包

2 下载和使用

1
2
3
4
5
6
7
# 开源项目:github地址:https://github.com/zhkl0228/unidbg
# 下载地址:https://github.com/zhkl0228/unidbg/releases
下载最新:v0.9.7版本
# 解压:【路径中不能有中文】
# 使用IDEA打开
# 确认,unidbg能不能正常使用---》直接运行它的示例代码--》如果能顺利运行,就ok了
找到 unidbg-android/src/test/java/com/anjuke.mobile.sign/SignUtil 右键运行,如果能正常打印出结果,说明unidbg环境搭建完成

image-20240517184242014

image-20240517184252324image-20240517184302547

3 unidbg补环境

1
2
3
4
5
6
7
8
9
10
11
# unidbg 模拟了手机设备---》apk的so文件加密---》有两种方式
- so中的加密算法---》所有加密逻辑都在so中,都是用c写的 大姨妈案例---》不需要补环境的
- so中加密算法---》加密逻辑在so中,使用c调用了java中的某些类,又回到so中执行 唯品会--》需要补环境[java的环境--》java的类]



# 所谓的补环境--》实际是补上 c调用java的时候的一些类
-本身unidbg就是用java写的---》c调用的时候,缺了java的这部分--》c在调用时候,就掉不到---》我们需要把缺的java类,补充上


# 下图中:1图不需要补环境,2图需要补环境

image-20240517184312793

image-20240517184321502

image-20240517184328137

4 车智赢案例

1
2
3
4
5
6
7
8
9
10
11
# 第14天学的


# 我们在破解 udid的时候---》udid使用des加密得到的---》des的明文我们知道了--》des的加密秘钥,使用jni--》返回的

# 我们当时没有去破解so--》我们hook得到了密码--》猜测秘钥是固定的--》直接拿着用了
-通过hook得到:appapiche168comappapiche168comap
-硬核读so文件得到了

# 咱们今天,也不去破解so---》使用unidbg 直接运行得到秘钥
-直接运行--》public static native String get3desKey(Context context)--》传入该传的参数--》拿到结果

4.1 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
import frida
import sys

rdev = frida.get_remote_device()

session = rdev.attach("车智赢+")

scr = """
Java.perform(function () {
// 包.类
var AHAPIHelper = Java.use("com.autohome.ahkit.AHAPIHelper");

// Hook,替换
AHAPIHelper.getDesKey.implementation = function(ctx){
// 执行原来的方法
var res =this.getDesKey(ctx);
console.log("DesKey值:",res);
return res;
}
});
"""

script = session.create_script(scr)

script.load()
sys.stdin.read()

4.2 使用unidbg得到jni方法的结果

4.2.1 在unidbg的代码中新建一个类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 在unidbg-android/src/test/java/com/nb/demo/CheZhiYing.class
package com.nb.demo;

public class CheZhiYing {
// 1 构造方法---》只要类实例化,就会执行构造方法
public CheZhiYing(){

}

// 2 sign成员方法---》破解加密
public void sign(){

}

// 3 main方法--》运行代码
public static void main(String[] args) {
//1 new出一个对象,调用构造方法
CheZhiYing che =new CheZhiYing();
//2 通过对象,调用sign方法-完成破解加密
che.sign();
}
}

4.2.2 设备初始化–》构造方法中–>以后基本不需要懂

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
package com.nb.demo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.AbstractJni;
import com.github.unidbg.linux.android.dvm.DalvikModule;
import com.github.unidbg.linux.android.dvm.VM;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class CheZhiYing extends AbstractJni {
public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用
public static Memory memory;
public static VM vm;
public static Module module;
// 1 构造方法---》只要类实例化,就会执行构造方法-->设备初始化
public CheZhiYing(){
// 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
// 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的
// 这个名字可以随便写,一般写成app的包名 以后可能会动
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.che168.autotradercloud").build();

// 2 获取内存对象(可以操作内存)
memory = emulator.getMemory();

// 3.设置安卓sdk版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));

// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动
vm = emulator.createDalvikVM(new File("apks/che/che3.32.1.apk"));
vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni
//vm.setVerbose(true); //是否展示调用过程的细节

// 5.加载so文件
// 以后会动,只要懂so文件路径即可
DalvikModule dm = vm.loadLibrary(new File("apks/che/libnative-lib.so"), false);
dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册

// 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件

}

// 2 sign成员方法---》破解加密
public void sign(){

}

// 3 main方法--》运行代码
public static void main(String[] args) {
//1 new出一个对象,调用构造方法
CheZhiYing che =new CheZhiYing();
//2 通过对象,调用sign方法-完成破解加密
che.sign();
}
}

image-20240517184343046

4.2.3 执行签名

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
package com.nb.demo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class CheZhiYing extends AbstractJni {
public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用
public static Memory memory;
public static VM vm;
public static Module module;

// 1 构造方法---》只要类实例化,就会执行构造方法-->设备初始化
public CheZhiYing() {
// 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
// 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的
// 这个名字可以随便写,一般写成app的包名 以后可能会动
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.che168.autotradercloud").build();

// 2 获取内存对象(可以操作内存)
memory = emulator.getMemory();

// 3.设置安卓sdk版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));

// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动
vm = emulator.createDalvikVM(new File("apks/che/che3.32.1.apk"));
vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni
//vm.setVerbose(true); //是否展示调用过程的细节

// 5.加载so文件
// 以后会动,只要懂so文件路径即可
DalvikModule dm = vm.loadLibrary(new File("apks/che/libnative-lib.so"), false);
dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册

// 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件

}

// 2 sign成员方法---》破解加密--》执行签名
public void sign() {
// 1 找到java中 jni的类 native 类,必须用固定的写法写
// 只要拿类,就要使用这个方法写,使用resolveClass把它包裹起来,中间用 / 区分
DvmClass CheckSignUtil = vm.resolveClass("com/autohome/ahkit/jni/CheckSignUtil");

// 2 找到类中的方法--》固定写法
// 方法名(参数签名)返回值签名--》jni签名---》day13天学的
String method = "get3desKey(Landroid/content/Context;)Ljava/lang/String;";

// 3 执行方法---》get3desKey--》传入参数
// 第一个参数:设备对象emulator
// 第二个参数:方法的签名字符串 method
// 再往后的参数,是 执行方法时,需要传入的参数,按位置一个个传即可
StringObject obj = CheckSignUtil.callStaticJniMethodObject(
emulator,
method,
// so文件中,接受了context对象,可能使用了,也可能没使用,如果使用了,但是我们穿的是空 ,就可能会报错
// 现在先设置为null--》它是不报错的--》so内部,没有使用它
// 否则你需要真正的传这个类的对象
vm.resolveClass("android/content/Context").newObject(null)

);

// 4 执行,得到结果--》拿到真正的字符串
String result = obj.getValue(); //拿到真正的字符串
System.out.println(result);


}

// 3 main方法--》运行代码
public static void main(String[] args) {
//1 new出一个对象,调用构造方法
CheZhiYing che = new CheZhiYing();
//2 通过对象,调用sign方法-完成破解加密
che.sign();
}
}

image-20240517184355673

4.2.4 调用方法返回结果

1
2
3
4
5
6
7
// 3 main方法--》运行代码
public static void main(String[] args) {
//1 new出一个对象,调用构造方法
CheZhiYing che = new CheZhiYing();
//2 通过对象,调用sign方法-完成破解加密
che.sign();
}

4.3 调用方法–》参数和返回值

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
### 调用  jni中 Native方法时,传入的参数和返回值,不是java的 类型,而是需要使用unidbg提供的类型

# 签名有对应关系
String method = "get3desKey(Landroid/content/Context;)Ljava/lang/String;";

# 调用方法--》 传入参数 和返回值
StringObject obj = CheckSignUtil.callStaticJniMethodObject(
emulator,
method, vm.resolveClass("android/content/Context").newObject(null)
);


## 传参过程
Java类型 包裹 unidbg使用
字符串:"justin" StringObject("justin") 使用
字节数组:{11,22} ByteArray({11,22}) 使用
----------------------------------------------------------------------
布尔:True/False True/False 使用
数字:19 19 使用
空:null null 使用
---------------------------自定义的类型------------------------------------------
自定义类型:Info cls = vm.resolveClass("com/nb/utils/Info"); 使用
cls.newObject(对象)


# 返回值:如果是字符串类型
StringObject 接收---》需要调用 对象.getValue() 得到真正的字符串

5 大姨妈案例

1
2
3
4
5
6
7
8
9
10
11
12
# day22 天讲的

# 咱们在破解 sign时---》找到了jni的Native方法encrypt_data--》当时看了一眼,觉得很难--》没有硬核破解--》使用frida-rpc和自己写app调用so---》两种方案得到的结果


# 我们今天使用unidbg跑
如果传入的参数三个参数:
0
"bcae572b84d20c385d6d9d2d7d9e645da29da3c0user/login18953675221WA89qByLlDeaGjmVNzXm/w=="
85
# 返回结果是:
20d97aad1b9ba75db3e0901e8c3b11d5

image-20240517184408682

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
package com.nb.demo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class DYM extends AbstractJni {
public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用
public static Memory memory;
public static VM vm;
public static Module module;

public DYM() {
// 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
// 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的
// 这个名字可以随便写,一般写成app的包名 以后可能会动
emulator = AndroidEmulatorBuilder.for64Bit().setProcessName("com.dym").build();

// 2 获取内存对象(可以操作内存)
memory = emulator.getMemory();

// 3.设置安卓sdk版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));

// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动
vm = emulator.createDalvikVM(new File("apks/dym/dymv8.6.0.apk"));
vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni
//vm.setVerbose(true); //是否展示调用过程的细节

// 5.加载so文件
// 以后会动,只要懂so文件路径即可
DalvikModule dm = vm.loadLibrary(new File("apks/dym/libCrypt.so"), false);
dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册

// 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件
}

public void sign() {
// 1 找到类
DvmClass Crypt = vm.resolveClass("com/yoloho/libcore/util/Crypt");
// 2 方法签名字符串
String method = "encrypt_data(JLjava/lang/String;J)Ljava/lang/String;";
// 3 执行方法传入参数
StringObject obj = Crypt.callStaticJniMethodObject(
emulator,
method,
0,
new StringObject(vm, "fd6d0ce86280f3fa11a0a0774c06fef2446aee82user/login18953675221WA89qByLlDeaGjmVNzXm/w=="),
85
);
// 4 打印结果
String result = obj.getValue();
System.out.println(result);


}

public static void main(String[] args) {
DYM dym = new DYM();
dym.sign();
}
}

6 得物案例

1
2
3
# day16 

# unidbg -->只能跑jin方法--》so中的方法---》

image-20240517184423300

image-20240517184430803

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
package com.nb.demo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.linux.android.dvm.array.ByteArray;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class Du extends AbstractJni {
public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用
public static Memory memory;
public static VM vm;
public static Module module;

public Du() {
// 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
// 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的
// 这个名字可以随便写,一般写成app的包名 以后可能会动
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.du").build();

// 2 获取内存对象(可以操作内存)
memory = emulator.getMemory();

// 3.设置安卓sdk版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));

// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动
vm = emulator.createDalvikVM(new File("apks/du/du-4.74.5.apk"));
vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni
//vm.setVerbose(true); //是否展示调用过程的细节

// 5.加载so文件
// 以后会动,只要懂so文件路径即可
DalvikModule dm = vm.loadLibrary(new File("apks/du/libJNIEncrypt.so"), false);
dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册

// 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件
}

public void sign() {
//1 找到类
DvmClass AESEncrypt = vm.resolveClass("com/duapp/aesjni/AESEncrypt");
// 2 找到方法 getByteValues
String method = "getByteValues()Ljava/lang/String;";
// 3 调用方法
StringObject byteValues = AESEncrypt.callStaticJniMethodObject(
emulator,
method
);

// 4 拿到真正的字符串
String byteValuesString = byteValues.getValue();
// 5 执行 把0变成 1,把1变成0
StringBuilder sb = new StringBuilder();
for (int i = 0; i < byteValuesString.length(); i++) {
if (byteValuesString.charAt(i) == '0') {
sb.append('1');
} else {
sb.append('0');
}
}
String sbString = sb.toString();
//System.out.println(sbString);

// 6 找到方法 encodeByte
String methodEncodeBytes = "encodeByte([BLjava/lang/String;)Ljava/lang/String;";
String body = "abRecReason0abRectagFengge0abTypesocial_brand_strategy_v454abValue1abVideoCover2deliveryProjectId0lastIdlimit20loginTokenplatformandroidtimestamp1704204080619uuidee13885e68d76ed4v4.74.5";
//7 执行方法
StringObject data = AESEncrypt.callStaticJniMethodObject(
emulator,
methodEncodeBytes,
new ByteArray(vm, body.getBytes()),
new StringObject(vm, sbString)
);

System.out.println(data.getValue());

}

public static void main(String[] args) {
Du du = new Du();
du.sign();
}
}

7 海南航空案例

1
2
3
4
# 海南航空app---》hnairSign---》好多接口,都需要携带这个字段--》如果不带--》接口会报错
# hnairSign--》找到最后--》是jni方法得到

# 使用 unidbg 跑出结果

7.1 抓包

image-20240517184443234

7.2 反编译搜索

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
# 1 搜索 hnairSign 
# 2 搜到了
String signRequest = signRequest(aVar);
u.a i10 = request.j().i();
i10.b("hnairSign", signRequest);

# 3 signRequest是 signRequest(aVar)执行结果

# 4 看 signRequest(aVar)
private final String signRequest(v.a aVar) {
String str;
ApiInterceptor.Companion companion = ApiInterceptor.Companion;
companion.getRequestTag(aVar);
y request = aVar.request();
String headersForSign = headersForSign(request.f());
String queryForSign = queryForSign(request.j());
String requestBodyForSign = requestBodyForSign(request);
if (companion.needAuth(request)) {
str = Companion.extraSecret(request);
if (str == null && (str = this.userManager.get().getSecret()) == null) {
str = this.salt;
}
} else {
str = this.salt;
}
String a10 = AppUtil.a(this.context);
if (a10 == null) {
a10 = "";
}
# 返回了i.p().get(0)
# i.p()返回 ArrayList --》获取ArrayList 第0个位置
# HNASignature.getHNASignature() 执行结果是字符串,字符串中有 >> ,把字符串按 >> 分割成 ArrayList--》取了第0个位置
# asdfas>>aaa>>bbb>>ccc
# [asdfas,aaa,bbb,ccc]-->asdfas

return (String) i.p(HNASignature.getHNASignature(headersForSign, queryForSign, requestBodyForSign, str, a10), new String[]{">>"}).get(0);
}

# 5 查看 HNASignature.getHNASignature()--->返回结果是什么字符串--》取出字符串 >> 之前的第一个即可
public class HNASignature {
public static native String getHNASignature(String str, String str2, String str3, String str4, String str5);
}

# 6 getHNASignature 需要5个参数

# 7 接下来的目标:
1 确认好5个参数是什么:String str, String str2, String str3, String str4, String str5
2 确认好so文件是什么

7.3 确认参数–>直接hook

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Java.perform(function () {
var HNASignature = Java.use("com.rytong.hnair.HNASignature");
HNASignature.getHNASignature.implementation = function (str,str2,str3,str4,str5) {
console.log("---------------------")
console.log("参数",str);
console.log("参数",str2);
console.log("参数",str3);
console.log("参数",str4);
console.log("参数",str5);
var res = this.getHNASignature(str,str2,str3,str4,str5);
console.log("返回值=",res);
return res;
}
});


//frida -U -f com.rytong.hnair -l hook-js.js

7.4 确认so文件

7.4.1 如果是动态注册(B站–》偏移量)

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
var symbols = Module.enumerateSymbolsSync("libart.so");
var addrRegisterNatives = null;
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);
}
}
console.log("addrRegisterNatives=", addrRegisterNatives);

if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
var env = args[0];
var java_class = args[1];
var class_name = Java.vm.tryGetEnv().getClassName(java_class);
// 只有类名为com.rytong.hnair.HNASignature,才打印输出

var taget_class = "com.rytong.hnair.HNASignature";

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); // 读取java中函数名
var sig = Memory.readCString(sig_ptr); // 参数和返回值类型
var find_module = Process.findModuleByAddress(fnPtr_ptr); // 根据C中函数指针获取模块

var offset = ptr(fnPtr_ptr).sub(find_module.base) // fnPtr_ptr - 模块基地址
// console.log("[RegisterNatives] java_class:", class_name);
console.log("name:", name, "sig:", sig, "module_name:", find_module.name, "offset:", offset);
//console.log("name:", name, "module_name:", find_module.name, "offset:", offset);

}
}
}
});
}

// frida -U -f com.rytong.hnair -l hook22.js

7.4.2 如果是静态注册

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
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();
if (funcName.indexOf("getHNASignature") !== -1) {
console.log(module.name);
console.log('\t', funcName);
}
return retval;
}
})
});

// Application(identifier="com.rytong.hnair", name="海南航空", pid=14958, parameters={})
// frida -U -f com.rytong.hnair -l static_find_so.js

image-20240517184501808

7.5 使用unidbg运行

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
package com.nb.demo;

import com.github.unidbg.AndroidEmulator;
import com.github.unidbg.Module;
import com.github.unidbg.linux.android.AndroidEmulatorBuilder;
import com.github.unidbg.linux.android.AndroidResolver;
import com.github.unidbg.linux.android.dvm.*;
import com.github.unidbg.memory.Memory;

import java.io.File;

public class HaiNan extends AbstractJni {
public static AndroidEmulator emulator; // 静态属性,以后对象和类都可以直接使用
public static Memory memory;
public static VM vm;
public static Module module;

public HaiNan() {
// 1.创建设备(32位或64位模拟器), 具体看so文件在哪个目录。 在armeabi-v7a就选择32位
// 传进设备时,如果是32位,后面so文件就要用32位,同理需要用64位的
// 这个名字可以随便写,一般写成app的包名 以后可能会动
emulator = AndroidEmulatorBuilder.for32Bit().setProcessName("com.hainan").build();

// 2 获取内存对象(可以操作内存)
memory = emulator.getMemory();

// 3.设置安卓sdk版本(只支持19、23)
memory.setLibraryResolver(new AndroidResolver(23));

// 4.创建虚拟机(运行安卓代码需要虚拟机,就想运行py代码需要python解释器一样) 以后会动
vm = emulator.createDalvikVM(new File("apks/hainan/v9.0.0.apk"));
vm.setJni(this); // 后期补环境会用,把要补的环境,写在当前这个类中,执行这个代码即可,但是必须继承AbstractJni
//vm.setVerbose(true); //是否展示调用过程的细节

// 5.加载so文件
// 以后会动,只要懂so文件路径即可
DalvikModule dm = vm.loadLibrary(new File("apks/hainan/libsignature.so"), false);
dm.callJNI_OnLoad(emulator); // jni开发动态注册,会执行JNI_OnLoad,如果是动态注册,需要执行一下这个,如果静态注册,这个不需要执行,车智赢案例是静态注册

// 6.dm代表so文件,dm.getModule()得到module对象,基于module对象可以访问so中的成员。
module = dm.getModule(); // 把so文件加载到内存后,后期可以获取基地址,偏移量等,该变量代指so文件
}

public void sign() {
// 1 找到类
DvmClass HNASignature = vm.resolveClass("com/rytong/hnair/HNASignature");
// 2 方法签名
String method = "getHNASignature(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;";
// 3 执行方法
StringObject obj = HNASignature.callStaticJniMethodObject(
emulator,
method,
new StringObject(vm, "{}"),
new StringObject(vm, "{}"),
new StringObject(vm, "{\"abuild\":\"64249\",\"akey\":\"184C5F04D8BE43DCBD2EE3ABC928F616\",\"aname\":\"com.rytong.hnair\",\"atarget\":\"standard\",\"aver\":\"9.0.0\",\"caller\":\"AD_H5\",\"did\":\"2c1c0689406f11f3\",\"dname\":\"Google_Pixel 2 XL\",\"gtcid\":\"f27ae4d8106eade7f93544fac0fa366a\",\"hver\":\"9.0.0.35417.7ac793f2e.standard\",\"mchannel\":\"huawei\",\"schannel\":\"AD\",\"slang\":\"zh-CN\",\"sname\":\"google\\/taimen\\/taimen:11\\/RP1A.201005.004.A1\\/6934943:user\\/release-keys\",\"stime\":\"1708611990194\",\"sver\":\"11\",\"system\":\"AD\",\"szone\":\"+0800\",\"riskToken\":\"65d75727UbwY9H3Ya4JUMA5JATe6L2ImaxZYHvH3\"}"),
new StringObject(vm, "21047C596EAD45209346AE29F0350491"),
new StringObject(vm, "F6B15ABD66F91951036C955CB25B069F")

);

// 4 打印结果
System.out.println(obj.getValue());

}

public static void main(String[] args) {

HaiNan hn = new HaiNan();
hn.sign();
}
}

__END__