ISCC2025睡蕉小猴frida调用genPvKey

clev1L Lv3

出题人代码写的太抽象了,看到大部分人使用unidbg,我做题时用的安卓调用第三方so调用的函数,但感觉还是太麻烦了,尝试用frida。

首先删掉广告

1
2
3
4
5
let MainActivity = Java.use("com.example.mobile03.MainActivity");
MainActivity["showAd"].implementation = function () {
console.log(`MainActivity.showAd is called`);
// this["showAd"]();
};

拷贝dex

出题人甚至没有拷贝完dex文件,而且没有.dex后缀会导致动态加载报错,因为我们需要的逻辑在swan中,将rabbit替换成swan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var File = Java.use("java.io.File");
File.$init.overload('java.io.File', 'java.lang.String').implementation = function (dir, name) {
if(name=="rabbit"){
name="swan.dex";
console.log(name);
}

return this.$init(dir, name);
};
var AssetManager = Java.use("android.content.res.AssetManager");

AssetManager.open.overload('java.lang.String').implementation = function (filename) {
console.log("open",filename);
if(filename=="rabbit"){
filename="swan";
}

return this.open(filename);
};

动态加载dex文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();

var DexClassLoader = Java.use('dalvik.system.DexClassLoader');
var File = Java.use('java.io.File');
var dexPath = context.getDir("dex", 0).getAbsolutePath() + "/swan.dex";
console.log(context.getDir("dex", 0).getAbsolutePath());
var optimizedDir = context.getCodeCacheDir().getAbsolutePath();
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var packageCodePath = currentApplication.getPackageCodePath().toString();
var libPath = packageCodePath + "!/lib/arm64-v8a/";
try{
var classLoader = DexClassLoader.$new(dexPath, optimizedDir, libPath, context.getClassLoader());

}
catch(e){

}

反射调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var targetClass = classLoader.loadClass("com.example.mobile03.swan");
var clazz = Java.cast(targetClass, Java.use("java.lang.Class"));

var method = clazz.getDeclaredMethod("b", Java.array("java.lang.Class", []));

var result = method.invoke(null, Java.array("java.lang.Object", []));

console.log("✅ com.example.mobile03.swan.a.b() returned: " + result);


var targetClass = classLoader.loadClass("com.example.mobile03.swan");
var clazz = Java.cast(targetClass, Java.use("java.lang.Class"));

var paramTypes = Java.array("java.lang.Class", [Java.use("java.lang.String").class]);
var method = clazz.getDeclaredMethod("genPvKey", paramTypes);
method.setAccessible(true);


var args = Java.array("java.lang.Object", [Java.use("java.lang.String").$new("test_input")]);
var result = method.invoke(null, args);

console.log("✅ genPvKey('test_input') returned: " + result);

修改签名

so文件里面的动态注册的函数签名也是错的,得patch一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
if (name === "getPvKey" && sig === "()Ljava/lang/String;") {
const newSig = "(Ljava/lang/String;)Ljava/lang/String;";
const newSigBytes = Memory.alloc(newSig.length); // 为新签名分配内存
Memory.writeUtf8String(newSigBytes, newSig); // 写入新的签名到新内存
Memory.protect(sig_ptr, newSig.length, 'rw');
// 直接覆盖原来 sig_ptr 地址处的签名
Memory.writeByteArray(sig_ptr, new Array(newSig.length+1).fill(0).map((_, i) => newSig.charCodeAt(i)));

const newname = "genPvKey";
const newnameBytes = Memory.alloc(newname.length); // 为新签名分配内存
Memory.writeUtf8String(newnameBytes, newname); // 写入新的签名到新内存
Memory.protect(name_ptr, newname.length, 'rw');
// 直接覆盖原来 sig_ptr 地址处的签名
Memory.writeByteArray(name_ptr, new Array(newname.length+1).fill(0).map((_, i) => newname.charCodeAt(i)));
console.log(hexdump(sig_ptr, {length: 100, header: true, raw: true}));
console.log(`✅ Replaced signature of getPvKey with: ${newSig}`);
// sig = Memory.readCString(newSigPtr);
}

完整代码

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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
function find_RegisterNatives(params) {
let symbols = Module.enumerateSymbolsSync("libart.so");
let addrRegisterNatives = null;
for (let i = 0; i < symbols.length; i++) {
let symbol = symbols[i];

//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
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);
hook_RegisterNatives(addrRegisterNatives)
}
}

}

function hook_RegisterNatives(addrRegisterNatives) {

if (addrRegisterNatives != null) {
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
console.log("[RegisterNatives] method_count:", args[3]);
let java_class = args[1];
let class_name = Java.vm.tryGetEnv().getClassName(java_class);
//console.log(class_name);

let methods_ptr = ptr(args[2]);

let method_count = parseInt(args[3]);
for (let i = 0; i < method_count; i++) {
let name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
let sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
let fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));
console.log(name_ptr,sig_ptr,fnPtr_ptr);
console.log(hexdump(sig_ptr, {length: 100, header: true, raw: true}));
let name = Memory.readCString(name_ptr);
let sig = Memory.readCString(sig_ptr);
let symbol = DebugSymbol.fromAddress(fnPtr_ptr)
if (name === "getPvKey" && sig === "()Ljava/lang/String;") {
const newSig = "(Ljava/lang/String;)Ljava/lang/String;";
const newSigBytes = Memory.alloc(newSig.length); // 为新签名分配内存
Memory.writeUtf8String(newSigBytes, newSig); // 写入新的签名到新内存
Memory.protect(sig_ptr, newSig.length, 'rw');
// 直接覆盖原来 sig_ptr 地址处的签名
Memory.writeByteArray(sig_ptr, new Array(newSig.length+1).fill(0).map((_, i) => newSig.charCodeAt(i)));

const newname = "genPvKey";
const newnameBytes = Memory.alloc(newname.length); // 为新签名分配内存
Memory.writeUtf8String(newnameBytes, newname); // 写入新的签名到新内存
Memory.protect(name_ptr, newname.length, 'rw');
// 直接覆盖原来 sig_ptr 地址处的签名
Memory.writeByteArray(name_ptr, new Array(newname.length+1).fill(0).map((_, i) => newname.charCodeAt(i)));
console.log(hexdump(sig_ptr, {length: 100, header: true, raw: true}));
console.log(`✅ Replaced signature of getPvKey with: ${newSig}`);
// sig = Memory.readCString(newSigPtr);
}
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", symbol, " callee:", DebugSymbol.fromAddress(this.returnAddress));
}
for (let i = 0; i < method_count; i++) {
let name_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3));
let sig_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize));
let fnPtr_ptr = Memory.readPointer(methods_ptr.add(i * Process.pointerSize * 3 + Process.pointerSize * 2));

let name = Memory.readCString(name_ptr);
let sig = Memory.readCString(sig_ptr);
let symbol = DebugSymbol.fromAddress(fnPtr_ptr)
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, " fnOffset:", symbol, " callee:", DebugSymbol.fromAddress(this.returnAddress));
}
}
});
}
}
function hook(){
Java.perform(function(){

let MainActivity = Java.use("com.example.mobile03.MainActivity");
MainActivity["showAd"].implementation = function () {
console.log(`MainActivity.showAd is called`);
// this["showAd"]();
};

var File = Java.use("java.io.File");

File.$init.overload('java.io.File', 'java.lang.String').implementation = function (dir, name) {
// console.log(name);
if(name=="rabbit"){
name="swan.dex";
console.log(name);
}

// 继续执行原方法
return this.$init(dir, name);
};
var AssetManager = Java.use("android.content.res.AssetManager");

AssetManager.open.overload('java.lang.String').implementation = function (filename) {
// 获取调用栈,检查是否来自 cpdex()
console.log("open",filename);
if(filename=="rabbit"){
filename="swan";
}

// 继续执行原方法
return this.open(filename);
};

Java.choose("com.example.mobile03.MainActivity",{
onMatch:function(instance){
instance.cpdex();
},onComplete:function(){

}
});
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var context = currentApplication.getApplicationContext();

var DexClassLoader = Java.use('dalvik.system.DexClassLoader');
var File = Java.use('java.io.File');
var dexPath = context.getDir("dex", 0).getAbsolutePath() + "/swan.dex";
console.log(context.getDir("dex", 0).getAbsolutePath());
var optimizedDir = context.getCodeCacheDir().getAbsolutePath();
var currentApplication = Java.use('android.app.ActivityThread').currentApplication();
var packageCodePath = currentApplication.getPackageCodePath().toString();
var libPath = packageCodePath + "!/lib/arm64-v8a/";
try{
var classLoader = DexClassLoader.$new(dexPath, optimizedDir, libPath, context.getClassLoader());

}
catch(e){

}

var targetClass = classLoader.loadClass("com.example.mobile03.swan");
var clazz = Java.cast(targetClass, Java.use("java.lang.Class"));

var method = clazz.getDeclaredMethod("b", Java.array("java.lang.Class", []));

var result = method.invoke(null, Java.array("java.lang.Object", []));

console.log("✅ com.example.mobile03.swan.a.b() returned: " + result);


var targetClass = classLoader.loadClass("com.example.mobile03.swan");
var clazz = Java.cast(targetClass, Java.use("java.lang.Class"));

var paramTypes = Java.array("java.lang.Class", [Java.use("java.lang.String").class]);
var method = clazz.getDeclaredMethod("genPvKey", paramTypes);
method.setAccessible(true);


var args = Java.array("java.lang.Object", [Java.use("java.lang.String").$new("test_input")]);
var result = method.invoke(null, args);

console.log("✅ genPvKey('test_input') returned: " + result);

})
}



function main(){
find_RegisterNatives();
hook();
}

setImmediate(main,500);

成功调用,加密是异或,将密文和输入异或就是密钥流

  • Title: ISCC2025睡蕉小猴frida调用genPvKey
  • Author: clev1L
  • Created at : 2025-05-07 20:00:00
  • Updated at : 2025-05-07 19:00:41
  • Link: https://github.com/clev1l/2025/05/07/ISCC2025睡蕉小猴frida调用genPvKey/
  • License: This work is licensed under CC BY-NC-SA 4.0.
Comments