抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

oncreate分析

紧接前文,我们发现onCreate函数被注册为native函数

拦截JNI注册函数RegisterNative找onCreate函数的地址,使用了yang神的脚本

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
var so_name = "libjiagu_64.so";
function hook_RegisterNatives() {
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);
}
}

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

var methods_ptr = ptr(args[2]);

var method_count = parseInt(args[3]);
for (var i = 0; i < method_count; i++) {
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));
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);
console.log("[RegisterNatives] java_class:", class_name, "name:", name, "sig:", sig, "fnPtr:", fnPtr_ptr, "module_name:", find_module.name, "module_base:", find_module.base, "offset:", ptr(fnPtr_ptr).sub(find_module.base));

}
if (name == "onCreate") {
console.log(hexdump(fnPtr_ptr));
var code = Instruction.parse(fnPtr_ptr);
var next_code = code.next;
console.log(code.address, ":", code);
for (var i = 0; i < 11; i++) {
var next_c = Instruction.parse(next_code);
console.log(next_c.address, ":", next_c);
next_code = next_c.next;
}
var onCreate_fun_ptr = next_c.next.add(0xf);
var onCreate_fun_addr = Memory.readPointer(onCreate_fun_ptr);
console.log("onCreate_function_address:",onCreate_fun_addr,"offset:",ptr(onCreate_fun_addr).sub(module_base));
}
}
});
}
}

var zlib_count=0;
var next_in,avail_in,next_out,avail_out;
function hook_zlib(){
Interceptor.attach(Module.findExportByName(null, "inflate"), {
// fd, buff, len
onEnter: function (args) {
zlib_count+=1
if(zlib_count>1){
console.log("zlib");
hook_RegisterNatives();
}

},
onLeave: function (ret) {
}
});
}



function anti_frida_check(){
var module = Process.findModuleByName("libjiagu_64.so");
Interceptor.attach(module.base.add(0x1770C), {
onEnter: function (args) {
try{
var s = this.context.x6.readCString();
if (s.indexOf('frida')!==-1 ||
s.indexOf('gum-js-loop')!==-1 ||
s.indexOf('gmain')!==-1 ||
s.indexOf('linjector')!==-1 ||
s.indexOf('/proc/')!==-1){
//console.log(s)
Memory.protect(this.context.x0, Process.pointerSize, 'rwx');
var replace_str=""
for(var i=0;i<s.length;i++){
replace_str+="0"
}
this.context.x0.writeUtf8String(replace_str);
}
}
catch (e){
}
},
onLeave: function (ret) {
}
});
}

function hook_dlopen() {
//hook_call_constructors();
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
//console.log("dlopen "+path);
if (path.indexOf(so_name) >= 0) {
this.is_can_hook = true;
}
}
},
onLeave: function (retval) {
if (this.is_can_hook) {
hook_RegisterNatives();
hook_zlib();
anti_frida_check();
}
}
}
);
}
setImmediate(hook_dlopen);

都不行的话也可以试试:

[原创]告别RegisterNatives获取JNI函数绑定的地址,迎接最底层的方式获取!(三个案例)-Android安全-看雪-安全社区|安全招聘|kanxue.com

stackplz的使用技巧收集 - SeeFlowerX

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
[Nexus 5X::com.oacia.apk_protect]-> RegisterNatives is at  0x74149344d4 _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
[RegisterNatives] method_count: 0x9
[RegisterNatives] java_class: com.stub.StubApp name: interface14 sig: (I)Ljava/lang/String; fnPtr: 0x73fd09f694 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x11d694
[RegisterNatives] java_class: com.stub.StubApp name: mark sig: ()V fnPtr: 0x73fd101c80 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x17fc80
[RegisterNatives] java_class: com.stub.StubApp name: interface5 sig: (Landroid/app/Application;)V fnPtr: 0x73fd09f738 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x11d738
[RegisterNatives] java_class: com.stub.StubApp name: interface11 sig: (I)V fnPtr: 0x73fd0bb9d8 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x1399d8
[RegisterNatives] java_class: com.stub.StubApp name: interface12 sig: (Ldalvik/system/DexFile;)Ljava/util/Enumeration; fnPtr: 0x73fd09f73c module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x11d73c
[RegisterNatives] java_class: com.stub.StubApp name: interface21 sig: (Landroid/app/Application;)V fnPtr: 0x73fd0a2c30 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x120c30
[RegisterNatives] java_class: com.stub.StubApp name: interface7 sig: (Landroid/app/Application;Landroid/content/Context;)Z fnPtr: 0x73fd0aa1e4 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x1281e4
[RegisterNatives] java_class: com.stub.StubApp name: interface8 sig: (Landroid/app/Application;Landroid/content/Context;)Z fnPtr: 0x73fd0a00d8 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x11e0d8
[RegisterNatives] java_class: com.stub.StubApp name: interface22 sig: (I[Ljava/lang/String;[I)V fnPtr: 0x73fd0a267c module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x12067c
zlib
RegisterNatives is at 0x74149344d4 _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
[RegisterNatives] method_count: 0x1
[RegisterNatives] java_class: dalvik.system.DexFile name: getClassNameList sig: (Ljava/lang/Object;)[Ljava/lang/String; fnPtr: 0x73fd0a1a18 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x11fa18
[RegisterNatives] method_count: 0x1
[RegisterNatives] java_class: dalvik.system.DexFile name: getClassNameList sig: (Ljava/lang/Object;)[Ljava/lang/String; fnPtr: 0x73fd0a1a18 module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x11fa18
[RegisterNatives] method_count: 0x1
[RegisterNatives] java_class: com.stub.stub07.Stub01 name: mark1 sig: (Ljava/lang/String;)V fnPtr: 0x73fd0998fc module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x1178fc
[RegisterNatives] method_count: 0x1
[RegisterNatives] java_class: com.stub.stub07.Stub01 name: mark1 sig: (Ljava/lang/String;)V fnPtr: 0x73fd0998fc module_name: libjiagu_64.so module_base: 0x73fcf82000 offset: 0x1178fc
[RegisterNatives] method_count: 0x1
[RegisterNatives] method_count: 0x1

没有找到我们的onCreate注册地址。对比原文件观察,static多了一个interface11函数。

MainActivity第一次被加载到JVM时,这个 static 代码块会立即执行。interface11方法就会将onCreate注册某个native方法上,再根据输出来到:0x1399d8

我们直接来看汇编代码,前面都是一些线程检测,以及寄存器操作等,来到最后跳转的是x8寄存器的值,没法直接反编译。

通过分析,可能访问off_267220 或者off_267220 + 8 的地址。

off_267220 + 8来到sub_13A8DC,比较短,应该是见检测栈的操作

来到off_267220,分析,最后还是跳转地址或者地址+8

后面诸多如此,直接上frida-stalker,跟踪x8寄存器的变化。

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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
/**
功能: 跟踪native函数调用情况。
*/

var TargetLibName = 'libjiagu_64.so'; // 待分析的so库文件名
var so_name = TargetLibName;
var TargetFuncOffset = 0x1399D8; // 待分析的native函数偏移, 先在IDA里找到
var TraceCallNum = 1; // 默认只输出调用1次的函数


function hookTargetFunc() {
var baseAddr = Module.findBaseAddress(TargetLibName)
//console.log(TargetLibName + " Module Base: " + baseAddr);
if (baseAddr == null) {
console.log('Please makesure ' + TargetLibName + ' is Loaded, by setting extractNativeLibs true.');
return;
}

// hook指定的函数地址
Interceptor.attach(baseAddr.add(TargetFuncOffset), {
onEnter: function (args) {
console.log('\nCall ' + TargetFuncOffset.toString(16).toUpperCase() + ' In')
this.tid = Process.getCurrentThreadId();
var so_addr = Module.findBaseAddress(so_name);
var so_size = Process.getModuleByName(so_name).size;
Stalker.follow(this.tid, {
events: {
call: true, // CALL instructions: yes please

// Other events:
ret: false, // RET instructions
exec: false, // all instructions: not recommended as it's
// a lot of data
block: false, // block executed: coarse execution trace
compile: false // block compiled: useful for coverage
},

onReceive: function () {

},


transform: function (iterator) {
var instruction = iterator.next();
const startAddress = instruction.address;
if (startAddress){
do{
if (instruction.mnemonic.startsWith('br')||instruction.mnemonic.startsWith('blr')||instruction.mnemonic.startsWith('bl')) {
// var uuid = getUuid()
//console.log("uuid 1", uuid)
try {

iterator.putCallout(function (context) {
var pc = context.pc; //获取pc寄存器,当前地址
var lr = context.lr;
var x8 = context.x8; //获取x8寄存器,
var module = Process.findModuleByAddress(pc);
if (module) {

try{
console.log(module.name + "!" +Memory.readCString(ptr(x8)));

}
catch (e){

}

console.log("x8 ====" + DebugSymbol.fromAddress(ptr(x8)));


}
});

} catch (e) {
console.log("error", e)

}
}

iterator.keep();
} while ((instruction = iterator.next()) !== null);
}
},
onCallSummary(summary) {
}

})
}, onLeave: function (retVal) {
console.log('Call ' + TargetFuncOffset.toString(16).toUpperCase() + ' Out\n')
Stalker.unfollow(this.tid)
}
})
}

function hook(addr) {
Interceptor.attach(addr, {
onEnter: function (args) {
var module = Process.findModuleByAddress(addr);
this.args0 = args[0];
this.args1 = args[1];
this.args2 = args[2];
this.args3 = args[3];
this.args4 = args[4];
this.logs = []
this.logs.push("------------------------\n");
this.logs.push("call " + module.name + "!" + ptr(addr).sub(module.base) + "\n");
this.logs.push("onEnter args0: " + print_arg(this.args0));
this.logs.push("onEnter args1: " + print_arg(this.args1));
this.logs.push("onEnter args2: " + print_arg(this.args2));
this.logs.push("onEnter args3: " + print_arg(this.args3));
this.logs.push("onEnter args4: " + print_arg(this.args4));
}, onLeave: function (ret) {
this.logs.push("onLeave args0: " + print_arg(this.args0));
this.logs.push("onLeave args1:" + print_arg(this.args1));
this.logs.push("onLeave args2:" + print_arg(this.args2));
this.logs.push("onLeave args3:" + print_arg(this.args3));
this.logs.push("onLeave args4:" + print_arg(this.args4));
this.logs.push("onLeave return: " + print_arg(ret));
console.log(this.logs)
}
})
}

function print_arg(addr) {
var range = Process.findRangeByAddress(addr);
console.log(range)
if (range != null) {
return hexdump(addr) + "\r\n";
} else {
return ptr(addr) + "\r\n";
}
}


function main() {
// 需要hook的函数地址列表
var funcAddr = [
];

if (funcAddr.length == 0) {
hookTargetFunc();
} else {
var baseAddr = Module.findBaseAddress(TargetLibName);
console.log(TargetLibName + " Module Base: " + baseAddr);

for (var i = 0; i < funcAddr.length; i++) {
hook(baseAddr.add(funcAddr[i]));
}
}
}
var zlib_count=0;
var next_in,avail_in,next_out,avail_out;
function hook_zlib(){
Interceptor.attach(Module.findExportByName(null, "inflate"), {
// fd, buff, len
onEnter: function (args) {
zlib_count+=1
if(zlib_count>1){
main();
}

},
onLeave: function (ret) {
}
});
}



function anti_frida_check(){
var module = Process.findModuleByName("libjiagu_64.so");
Interceptor.attach(module.base.add(0x1770C), {
onEnter: function (args) {
try{
var s = this.context.x6.readCString();
if (s.indexOf('frida')!==-1 ||
s.indexOf('gum-js-loop')!==-1 ||
s.indexOf('gmain')!==-1 ||
s.indexOf('linjector')!==-1 ||
s.indexOf('/proc/')!==-1){
//console.log(s)
Memory.protect(this.context.x0, Process.pointerSize, 'rwx');
var replace_str=""
for(var i=0;i<s.length;i++){
replace_str+="0"
}
this.context.x0.writeUtf8String(replace_str);
}
}
catch (e){
}
},
onLeave: function (ret) {
}
});
}

function hook_dlopen() {
//hook_call_constructors();
Interceptor.attach(Module.findExportByName(null, "android_dlopen_ext"),
{
onEnter: function (args) {
var pathptr = args[0];
if (pathptr !== undefined && pathptr != null) {
var path = ptr(pathptr).readCString();
//console.log("dlopen "+path);
if (path.indexOf(so_name) >= 0) {
this.is_can_hook = true;
}
}
},
onLeave: function (retval) {
if (this.is_can_hook) {
//you can do any thing before stalker trace so
//trace_so();
hook_zlib();
anti_frida_check();

//hook_C918();
}
}
}
);
}
setImmediate(hook_dlopen);

根据trcace发现了两处oncreate

先来看看的后一个地址干了什么

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
x8 ====0x7bfee7fe0c libjiagu_64.so!0x139e0c
x8 ====0x7bfee7fe64 libjiagu_64.so!0x139e64
x8 ====0x7bfee7fe7c libjiagu_64.so!0x139e7c//似乎是重点的
x8 ====0x7bfee80460 libjiagu_64.so!0x13a460//拷贝操作
x8 ====0x7bfee7fe8c libjiagu_64.so!0x139e8c
x8 ====0x7bfee7ff60 libjiagu_64.so!0x139f60 //来到复杂函数 sub_19DCA4
x8 ====0x7bfee7ffa0 libjiagu_64.so!0x139fa0//拷贝
x8 ====0x7bfee7ffc4 libjiagu_64.so!0x139fc4
x8 ====0x7bfee8010c libjiagu_64.so!0x13a10c
x8 ====0x7bfee80140 libjiagu_64.so!0x13a140
x8 ====0x7bfee80170 libjiagu_64.so!0x13a170
//oncreate
x8 ====0x7bfee80194 libjiagu_64.so!0x13a194
x8 ====0x7bfee801fc libjiagu_64.so!0x13a1fc
x8 ====0x7bfee804e4 libjiagu_64.so!0x13a4e4
x8 ====0x7bfee8024c libjiagu_64.so!0x13a24c
x8 ====0x7bfee80264 libjiagu_64.so!0x13a264//进程检测mutex_lock
//前面似乎一些栈的操作,然后寄存器发生变化
x8 ====0x7bfee802e8 libjiagu_64.so!0x13a2e8//
x8 ====0x7bfee80328 libjiagu_64.so!0x13a328//内存
x8 ====0x7bfee7fe64 libjiagu_64.so!0x139e64
x8 ====0x7bfee80600 libjiagu_64.so!0x13a600
x8 ====0x7bfee8061c libjiagu_64.so!0x13a61c
//然后出现下一个oncreate

看看后一个oncreate,都是一些内存和栈的操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
x8 ====0x7bfee807ac libjiagu_64.so!0x13a7ac//下一步跳转
x8 ====0x7bfee807b8 libjiagu_64.so!0x13a7b8
x8 ====0x7bfee807f4 libjiagu_64.so!0x13a7f4
x8 ====0x7bfee80808 libjiagu_64.so!0x13a808
x8 ====0x7bfee80810 libjiagu_64.so!0x13a810
x8 ====0x7bfee8082c libjiagu_64.so!0x13a82c
x8 ====0x7bfee80854 libjiagu_64.so!0x13a854
x8 ====0x7bfee80854 libjiagu_64.so!0x13a854
x8 ====0x7bfee80838 libjiagu_64.so!0x13a838
x8 ====0x7bfee807e0 libjiagu_64.so!0x13a7e0
x8 ====0x7bfee807f4 libjiagu_64.so!0x13a7f4
x8 ====0x7bfee80870 libjiagu_64.so!0x13a870
x8 ====0x7bfee8087c libjiagu_64.so!0x13a87c
x8 ====0x7bfee80894 libjiagu_64.so!0x13a894
x8 ====0x7bfee80894 libjiagu_64.so!0x13a894
x8 ====0x7bfee80894 libjiagu_64.so!0x13a894
x8 ====0x7bfee80894 libjiagu_64.so!0x13a894
x8 ====0x7bfee808c4 libjiagu_64.so!0x13a8c4
x8 ====0x7bfee808dc libjiagu_64.so!0x13a8dc
x8 ====0x7bfee808e8 libjiagu_64.so!0x13a8e8

0x13a170函数之后出现了第一个onCreate,blr x8进入关键部分。

jnitrace看一下流程

1
jnitrace -l libjiagu_64.so com.oacia.apk_protect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
x8 ====0x7c166e43c0 libart.so!_ZN3art3JNI11GetMethodIDEP7_JNIEnvP7_jclassPKcS6_//JNIEnv->GetMethodID()
x8 ====0x7c164d6a1c libart.so!_ZN3art11ClassLinker15InitializeClassEPNS_6ThreadENS_6HandleINS_6mirror5ClassEEEbb
libart.so!<clinit> //在类加载过程中执行
libart.so!<clinit>
libart.so!<init>
libart.so!<init>
bytesToHex
bytesToHex
libart.so!enc2//似乎是mute进程检测的关键数据
libart.so!enc2
libart.so!onCreate
libart.so!onCreate
libart.so!Landroid/os/Bundle;
libart.so!Landroid/os/Bundle;

接下来关键操作分析0x13a61c函数之后出现了第二个oncreate,同样是blr x8,大概率是进入jni函数了。

1
2
3
4
5
6
7
8
9
10
x8 ====0x7c1671e4d4 libart.so!_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi//JNIEnv->RegisterNatives()
libart.so!enc2
libart.so!enc2
libart.so!onCreate
libart.so!onCreate
libart.so!Landroid/os/Bundle;
libart.so!Landroid/os/Bundle;
x8 ====0x7c169d0160 libart.so!_ZN3art7Runtime9instance_E
x8 ====0x7c169d0160 libart.so!_ZN3art7Runtime9instance_E
x8 ====0x7c16723374 libart.so!_ZN3art3JNI14ExceptionCheckEP7_JNIEnv// JNIEnv->ExceptionCheck()

我们去hook一下 _ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi

未尝试的办法:

stackplz的使用技巧收集 - SeeFlowerX

定制art沙箱。

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
function traceRegisterNatives() {
var RegisterNativesaddr = null;
var libartmodule = Process.getModuleByName("libart.so");
libartmodule.enumerateSymbols().forEach(function (symbol) {
if (symbol.name == "_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi") {
RegisterNativesaddr = symbol.address;
}
})
console.log("RegisterNativesaddr->" + RegisterNativesaddr);
if (RegisterNativesaddr != null) {
Interceptor.attach(RegisterNativesaddr, {
onEnter: function (args) {
console.log("go into RegisterNativesaddr");
var methodsptr = ptr(args[2]);
var method_count = args[3];
var i = 0;
console.log("[" + Process.getCurrentThreadId() + "]RegisterNatives->" + method_count);
for (i = 0; i < method_count; i++) {
var name = methodsptr.add(i * Process.pointerSize * 3 + 0).readPointer().readUtf8String();
var signature = methodsptr.add(i * Process.pointerSize * 3 + 1 * Process.pointerSize).readPointer().readUtf8String();
var fnPtr = methodsptr.add(i * Process.pointerSize * 3 + 2 * Process.pointerSize).readPointer();
var find_module = Process.findModuleByAddress(fnPtr);
console.log("[" + Process.getCurrentThreadId() + "]RegisterNatives->name:" + name + ",sig:" + signature + ",addr:" + fnPtr);

}
if (name == "onCreate") {
console.log(hexdump(fnPtr));
var code = Instruction.parse(fnPtr);
var next_code = code.next;
console.log(code.address, ":", code);
for (var i = 0; i < 10; i++) {
var next_c = Instruction.parse(next_code);
console.log(next_c.address, ":", next_c);
next_code = next_c.next;
}
var onCreate_fun_ptr = next_c.next.add(0xf);
var onCreate_fun_addr = Memory.readPointer(onCreate_fun_ptr);
console.log("onCreate_function_address:",onCreate_fun_addr,"offset:",ptr(onCreate_fun_addr));
}
}, onLeave: function (retval) {


}
})
}

}


function main() {
var module = Module.findBaseAddress("libjiagu_64.so");
console.log("libjiagu_64.so base address: " + module);
traceRegisterNatives();
}

setImmediate(main);

找到了oncreate

但是好像是动态注册,找不到moudel的名字,我们直接去so里面寻找。在0x178c50+0xe7000=0x26FC50

在ida中找到

来到sub_1A1908,他有两个交叉引用

我们来到12DFE4,参数0ff_265F20,指向sub_137978,

进入

sub_137978这个函数是关键操作,可以Edit->other->specify switch idiom,修复。参考ida pro switch 修复 - DirWangK - 博客园

使用hyperpwn调试

启动frida-server

进行端口转发

1
adb forward tcp:1234 tcp:1234

我们把gdbserver存入手机(NDK23C)

1
adb push gdbserver /data/local/tmp

运行我们上一次的脚本

1
frida -U -f  com.oacia.apk_protect -l vm.js --no-pause

查看pid

1
ps -ef|grep com.oacia

然后attach

1
./gdbserver :1234 --attach 1729
1
2
set arch aarch64
//set arch arm set arm fallback-mode thumb

这个指令的作用是 在不确定当前指令模式时,优先回退到 Thumb 指令集。在 ARM 设备上,可能有 ARM 模式Thumb 模式

然后等待hyperpwn的pwndbg连接

1
target remote 127.0.0.1:1234

查看oncreate处的十条汇编代码

1
x/10i 0x705123b000

我们在这里的主线程下个断点,然后c运行。

1
2
3
b *0x705123b000 thr 1
info breakpoints//查看断点信息
delete 3//按编号删除断点

看看线程调用

1
2
3
info threads//查看线程
bt//查看当前调用栈
thread <id>//切换线程

gdb 默认不会跟踪 fork() 出来的新进程,但你可以启用 follow-fork-mode 来指定如何处理 fork:

1
2
3
4
5
catch fork
//catch fork 命令会让 GDB 在被调试的程序调用 fork() 时暂停。
catch vfork
//catch vfork 命令会让 GDB 在程序调用 vfork() 时暂停。
set follow-fork-mode child
  • set follow-fork-mode parent(默认)→ 继续调试父进程,忽略子进程
  • set follow-fork-mode child → 继续调试子进程,detach 父进程

如果你希望同时调试父进程和子进程,可以使用:

1
set detach-on-fork off

这样 gdb 不会自动 detach 任何 fork 出来的进程

遇到问题:

似乎是线程问题,

遇到问题

1
maintenance flush registers

oncreate分析2

sub_137978的逻辑大概是先保存栈帧,然后开辟新的空间,跳转跟之前一样打印x8的变化

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
Call 137978 In
x8 ====0x7164d7d568 libjiagu_64.so!0x139568
x8 ====0x7164d7b9c0 libjiagu_64.so!0x1379c0
x8 ====0x7164d7b9e0 libjiagu_64.so!0x1379e0
x8 ====0x7164d7ba04 libjiagu_64.so!0x137a04
x8 ====0x7164d7ba7c libjiagu_64.so!0x137a7c
x8 ====0x717c4e9ec4 libart.so!_ZN3art3JNI14PushLocalFrameEP7_JNIEnvi
x8 ====0x7164d7bbe8 libjiagu_64.so!0x137be8 //存在异或
x8 ====0x7164d7cd58 libjiagu_64.so!0x138d58
x8 ====0x7164d7cd68 libjiagu_64.so!0x138d68
x8 ====0x7164d7cdf4 libjiagu_64.so!0x138df4
x8 ====0x7164d7cf04 libjiagu_64.so!0x138f04
x8 ====0x7164d7cf0c libjiagu_64.so!0x138f0c
x8 ====0x717c4eb8ec libart.so!_ZN3art3JNI11NewLocalRefEP7_JNIEnvP8_jobject
x8 ====0x7164d7cde8 libjiagu_64.so!0x138de8
x8 ====0x7164d7cdf4 libjiagu_64.so!0x138df4
x8 ====0x7164d7d70c libjiagu_64.so!0x13970c //来到了sub_144EF0
x8 ====0x7164d7d70c libjiagu_64.so!0x13970c //来到了sub_144EF0
x8 ====0x7164d88f4c libjiagu_64.so!0x144f4c //把sj8uwp\}{}r赋值给寄存器
x8 ====0x717c52d374 libart.so!_ZN3art3JNI14ExceptionCheckEP7_JNIEnv
x8 ====0x717c52d374 libart.so!_ZN3art3JNI14ExceptionCheckEP7_JNIEnv
x8 ====0x7164d89018 libjiagu_64.so!0x145018
x8 ====0x7164da3a54 libjiagu_64.so!0x15fa54 //寄存器赋值
x8 ====0x717c4eaa68 libart.so!_ZN3art3JNI12NewGlobalRefEP7_JNIEnvP8_jobject
x8 ====0x7164d89024 libjiagu_64.so!0x145024
x8 ====0x7164d89040 libjiagu_64.so!0x145040
x8 ====0x7164d89058 libjiagu_64.so!0x145058
x8 ====0x7164d890b8 libjiagu_64.so!0x1450b8
x8 ====0x7164d890fc libjiagu_64.so!0x1450fc //存在EOR W8, W8, #31,存入 X0 + 8 位置。把x0的值给了x28
x8 ====0x7164d89148 libjiagu_64.so!0x145148
x8 ====0x7164d9e630 libjiagu_64.so!0x15a630//跳转到sub_16CE0C 存在花指令,似乎是重要函数,调用sub_15FC34
x8 ====0x7164d89154 libjiagu_64.so!0x145154//CMP W0, #129
x8 ====0x7164d8916c libjiagu_64.so!0x14516c/CMP W0, #191
x8 ====0x7164d89184 libjiagu_64.so!0x145184//CMP W0, #224
x8 ====0x7164d89508 libjiagu_64.so!0x145508//来到switch的分发快 CMP W0, #206
x8 ====0x7164d89520 libjiagu_64.so!0x145520 //CMP W0, #215
x8 ====0x7164d8a274 libjiagu_64.so!0x146274//CMP W0, #210
x8 ====0x7164d8a28c libjiagu_64.so!0x14628c//CMP W0, #212
x8 ====0x7164d8cca4 libjiagu_64.so!0x148ca4 //CMP W0, #211 很像二分法有没有
x8 ====0x7164d8f3cc libjiagu_64.so!0x14b3cc //来到sub_160D7C解释器,且之前的x28给了x7,也就是参数a8
x8 ====0x717c52d374 libart.so!_ZN3art3JNI14ExceptionCheckEP7_JNIEnv
x8 ====0x717c52d374 libart.so!_ZN3art3JNI14ExceptionCheckEP7_JNIEnv
x8 ====0x717c4eaa68 libart.so!_ZN3art3JNI12NewGlobalRefEP7_JNIEnvP8_jobject
x8 ====0x717c52d374 libart.so!_ZN3art3JNI14ExceptionCheckEP7_JNIEnv
x8 ====0x717c4ee3c0 libart.so!_ZN3art3JNI11GetMethodIDEP7_JNIEnvP7_jclassPKcS6_
x8 ====0x717c52d374 libart.so!_ZN3art3JNI14ExceptionCheckEP7_JNIEnv
x8 ====0x717c52d374 libart.so!_ZN3art3JNI14ExceptionCheckEP7_JNIEnv
libjiagu_64.so!p:}|q
libjiagu_64.so!p:}|q
libjiagu_64.so!p:}|q
libjiagu_64.so!p:}|q
x8 ====0x7164da9f8c libjiagu_64.so!0x165f8c
x8 ====0x7164da5df0 libjiagu_64.so!0x161df0
x8 ====0x717c5093dc libart.so!_ZN3art3JNI25CallNonvirtualVoidMethodAEP7_JNIEnvP8_jobjectP7_jclassP10_jmethodIDP6jvalue
//奔溃

先从oncreat函数处断下寻找opcode

ida9 动态调试存在bug,建议切换为8.3调试

关键从0x1450fc处开始 ,从这里开始hook会遇到反调试,会引导我们进入错误的分支。

直接hook0x15a630,从这里开始调试,他决定了二分查找的目标值

第一部分解密

sub_16CE0C存在花指令,直接nop掉,重新定义为函数,似乎是对数据进行操作。

步入跟踪,来到花指令后的代码

1
2
3
4
5
6
loc_16CF9C
ADR X8, loc_16D09C
SUB X8, X8, #236
LDR X30, [SP,#0xA0+var_A0]
MOV X30, X8
RET

ADR(Address of Label) 是 ARM64 特有的伪指令,用于将当前 PC 相对的某个地址加载到寄存器

这里,它将 loc_16D09C 的地址加载到 X8,然后让 X8 的值减少 236(0xEC)

然后从栈中保存x30的地址,X30 通常用于存储返回地址,把 X8赋值给 X30,ret时就会返回我们设定的地址

进入真正的解密,解密逻辑为

从加密的opcode读取第一字节给w8,再从存加密opcode地址的地方读取第八个字节给w13 (c5)

w9存取的是加密的opcode读取第二字节,再减去w12(存加密opcode地址的地方读取第八个字节,和w13相等),这里结果为7

赋值,比较,与取W9低八位(07),相减,如果 EQ(ZF=1,前面 TST 结果是 0)W8 = W11(0xE2)否则W8 = W10(0xE7)

然后异或得w9=e5,保留 W13 低 8 位,来到左边分支,异或得到w10=6B

然后和之前一样的操作来到另外一部分解密操作,w8恢复到c5,把w9插入到w10的高位,然后把w8的低位复制到高位

然后w8和w10异或得到解密的opcode 20ae

第二部分解密

x8赋值为主dex文件的地址,w9是存加密opcode地址+18的值(A0AF),然后从主dex文件加载值w10 w8

W11 = W9 / W10 (整数除法)
W9 = W9 - (W11 * W10)(求余数)
W9 = W9 << 8 (左移 8 位)
然后X9 的低 8 位用 X19 的低 8 位替换得到最后值23AE,得到偏移

最后再从他查询得到值 D2赋值给w0

接着会根据w0的值来进行二分查找。且这个值是会变化的,如果从前面的函数开始调试,会出现错误的值,查找来到解释器。

解释器函数入参如下,重点关注a5 a6(加密的opcode)和a7 (已经解密的opcode),并且这里的a4就是我们之前得到的解密的主dex文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
x0 MOV             W0, #3  ; n4 
1 MOV W1, WZR ; a2
2 MOV X2, X20 ; JNIEnv
3 MOV X3, X21 ; a4
4 MOV X4, X19 ; a5
5 MOV X5, X22 ; a6
6 MOV W6, W25 ; a7
7 MOV X7, X28 ; a8
X0 0000000000000003
X1 0000000000000000
X2 0000007FC4CEE0A0 -> 0000007971A27800 -> 000000797C933118 (libart.so ! _ZN3art3JNI14NewObjectArrayEP7_JNIEnviP7_jclassP8_jobject)
X3 000000797CC43680 -> 0000007962CE6000 (debug003) -> ("dex\n038")
X4 0000007971A26600 ([anon:libc_malloc]) -> 0000000000000000
X5 000000797CD00380 -> 00000079632ACEE0 (debug003) -> EA743718C721CC4E
X6 00000000000020AE
*X7 000000797CCE6C40 -> 0000007FC4CEE0A0 -> 0000007971A27800 -> [...] (libart.so ! _ZN3art3JNI14NewObjectArrayEP7_JNIEnviP7_jclassP8_jobject)

通过二分查找来到sub_14B3CC,我们在sub_14B3CC发现了解释器sub_160D7C的调用,按 y把a3的结构体定义为JNIEnv*,

第三处解密

sub_16CB4C 存在同样的花指令,去除,函数和sub_16CE0C,套路都是一样的,直接从最后的真实逻辑开始分析

X8 = X20 << 1(2),读取 X19+8 处的字节到 W13(c5),从 X26 + X8 处读取 1 字节到 W8(就是第三个加密的opcode21)

W9 = W9 - W12(c7-c5=2),赋值,测试 W9 的 bit 5 是否为 1,W12 = W9 & 0xFF,W13 = W8 - W13,W8 = (Z == 1) ? W11 : W10

W9 = W8 XOR W12,W8 = W13 & 0xFF,W10 = W8 XOR W11(8E)

再来到,和之前的逻辑差不多,最后返回0x154b=5451

而5451对应method_id 正是onCreate

第四处opcode解密

sub_16C918发现也存在异或,分析关键部分

w8读取第五六位的加密opcode,(3718)w9取其高位。

这里w12依旧是之前的c5,四轮解密异或都是用的同一个密钥。

W9 = W9 - W12,W12 = W8 - W12,W8 = W9 & 0xFF,W9 = (Z == 1) ? W11 : W10,W8 = W9 XOR W8

W9 = W12 & 0xFF,后面的逻辑跟前面基本上一样,最后返回0x0021。

以上就是我们得到最后解密的opcode,逆序一下

ae20 (d2) 4b15 2100

除了作为操作码的首字节及部分操作数外,其余均相同

评论