1. 基础用法
(0)在so中找java层对应的native函数
-
① 看so的Export函数列表(找静态注册的情形)
若无,则可能为动态注册 -
② 看so的
.init_array
段中是否有函数
IDA中按Ctrl+S,进入.init_array
系统加载so,在完成装载、映射和重定向以后,就首先执行.init和.init_array段的代码,之后如果存在JNI_OnLoad就调用该函数.我们要对一个so进行分析,需要先看看有没有.init_array section和.init section,so加壳一般会在初始化函数进行脱壳操作。
这里的.init_array段中没有函数
参考:http://pwn4.fun/2020/07/29/so%E6%96%87%E4%BB%B6%E7%9A%84-init-array%E6%AE%B5%E4%B8%AD%E6%B7%BB%E5%8A%A0%E4%BB%A3%E7%A0%81/
初始化和终止例程
动态库可以提供用于运行时初始化和终止处理的代码。每次在进程中装入动态库时,都会执行一次动态库的初始化代码。每次在进程中卸载动态库或进程终止时,都会执行一次动态库的终止代码。
在将控制权转交给应用程序之前,运行时链接程序将处理应用程序中找到的所有初始化节及所有装入的依赖项。如果在进程执行期间装入新动态库,则会在装入该目标文件的过程中处理其初始化节。初始化节 .preinit_array、.init_array 和 .init 由链接编辑器在生成动态库时创建。
运行时链接程序执行的函数的地址包含在 .preinit_array 和 .init_array 节中。这些函数的执行顺序与其地址在数组中的显示顺序相同。运行时链接程序将 .init 节作为单独的函数执行。如果某目标文件同时包含 .init 节和 .init_array 节,则会首先处理 .init 节,然后再处理该目标文件的 .init_array 节定义的函数。
动态可执行文件可在 .preinit_array 节中提供预初始化函数。这些函数将在运行时链接程序生成进程映像并执行重定位之后但执行任何其他初始化函数之前执行。 预初始化函数不允许在共享库中执行。
- ③ 看有无JNI_OnLoad函数
基本就是这里了
重新定义g_env的类型为JNIEnv*
这样就能看到RegisterNatives函数了
RegisterNatives函数的第三参数是方法数组:
jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
调用时多一个env对象作为第一参数
env->RegisterNatives(clazz, gMethods, sizeof(gMethods) / sizeof(gMethods[0]))
四个参数:
env是他的第一个参数(这个要注意)
clazz:指定的类,即 native 方法所属的类
methods:方法数组,这里需要了解一下 JNINativeMethod 结构体
nMethods:方法数组的长度
JNINativeMethod 结构体:
方法数组体现在IDA中的参数off_5004处:
可以看到,方法数组中包含initSN、saveSN等方法,说明这些Java方法是被JNI_Onload动态注册的。
可以看到,saveSN
方法在n2+1处
我们进入到n2+1处,所看到的函数就是saveSN
方法注册到的native函数。
参考:
https://blog.csdn.net/afei__/article/details/81031965
https://www.jianshu.com/p/eacdb203ea21
(1)按方法名看参数、bt、返回值、output参数
// 按方法名看参数、bt、返回值
var arg8;
var arg9;
var MtdName = "TssAesGcmDecrypt";
Java.perform(function () {
Interceptor.attach(Module.findExportByName(SoName, MtdName), {
onEnter: function (args) {
send(MtdName + " hooked:");
send("called from\\n" + Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join("\\n"));
console.log("\\n");
send(MtdName + " args:");
for (var it = 0; it < 9; it++) {
send(MtdName + " args[" + it + "]=" + args[it]);
if (args[it] > 0x15000000)
{
var buffer = Memory.readByteArray(args[it],128);
}
else
{
var buffer = args[it];
}
console.log(buffer);
console.log("\\n");
}
arg8 = parseInt(args[7]);
arg9 = parseInt(args[8]);
},
onLeave: function (retval) {
send(MtdName + " RetVal : " + retval);
var buffer2 = Memory.readByteArray(retval, 128);
console.log(buffer2);
console.log("\\n");
send(MtdName + " arg[7] : 0x" + arg8.toString(16));
var buffer3 = Memory.readByteArray(ptr(arg8), 128);
console.log(buffer3);
console.log("\\n");
send(MtdName + " arg[8] : 0x" + arg9.toString(16));
var buffer3 = Memory.readByteArray(ptr(arg9), 128);
console.log(buffer3);
console.log("\\n");
send(MtdName + " finished \\n");
}
});
});
(2)打印任意地址的内容
例如,打印返回值(地址)附近的内容
onLeave: function (retval) {
send("RetVal : " + retval);
var buffer2 = Memory.readByteArray(retval, 128);
console.log(buffer2);
console.log("\\n");
var tmp = parseInt(retval)+1032;
send("RetVal + 1032: 0x" + tmp.toString(16));
var buffer2 = Memory.readByteArray(ptr(tmp), 128);
console.log(buffer2);
console.log("\\n");
var tmp = parseInt(retval)+1232;
send("RetVal + 1232: 0x" + tmp.toString(16));
var buffer2 = Memory.readByteArray(ptr(tmp), 128);
console.log(buffer2);
console.log("\\n");
send(MtdName + " finished \\n");
}
输出:
[*] RetVal : 0x730eb32c00
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 c2 34 2a bc bd ad 38 41 14 1e 72 16 87 9c f1 12 .4*...8A..r.....
00000010 1e 43 9b 50 de 6a 1a dc 22 c4 b8 82 d1 cc 3d 0b .C.P.j..".....=.
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
[*] RetVal + 1032: 730eb33008
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 96 ce b4 c4 9b b3 f4 f8 06 e4 91 13 00 00 00 00 ................
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
[*] RetVal + 1232: 730eb330d0
0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF
00000000 a6 70 89 84 62 e4 52 bf 8b 77 f5 b3 30 f9 c6 24 .p..b.R..w..0..$
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000020 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000030 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000040 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000050 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000060 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
00000070 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
[*] ParseGcmKey finished
(3)定位虚表函数偏移
//定位虚表函数偏移
Java.perform(function(){
var Offset=0x1516C;//BLR X8
Interceptor.attach(Module.findBaseAddress(SoName).add(ptr(Offset)),{
onEnter: function(args) {
console.log("\\n" + Offset + " - x8: " + this.context.x8);
console.log("\\n" + "base : " + Module.findBaseAddress(SoName));
var funcOff = parseInt(this.context.x8,16)-parseInt(Module.findBaseAddress(SoName),16);
console.log("\\n" + "funcOffset : " + funcOff.toString(16) );
},
onLeave: function(retval){
console.log(Offset +" finished \\n");
}
});
});
输出:
[*] Running hooking
86380 - x8: 0x7290b5c418
base : 0x7290b43000
funcOffset : 19418
ida中按g,跳转到相应函数的位置,定位到函数:
(4)判断地址是否能hook到
Java.perform(function(){
var Offset=0xF86C;//LDR X24, [X8,#8]
Interceptor.attach(Module.findBaseAddress(SoName).add(ptr(Offset)),{
onEnter: function(args) {
console.log("\\n" + Offset.toString(16) + " hooked ");
},
onLeave: function(retval){
console.log(Offset +" finished \\n");
}
});
});
(5)从地址读取ByteArray、String
var buffer = Memory.readByteArray(args[it],128);
var buffer = Memory.readCString(args[it]);
for (var it = 0; it < 2; it++)
{
send(MtdName4 + " args[" + it + "]=" + args[it]);
if (args[it] > 0x15000000)
{
if (it == 1)
{
var buffer = Memory.readByteArray(args[it],128);
}
else
{
var buffer = Memory.readCString(args[it]);
}
}
else
{
var buffer = args[it];
}
console.log(buffer);
console.log("\\n");
}
2. 进阶用法
(1)枚举so中的符号
function hook_libart() {
var proc = Process.findModuleByName("libart.so");
var symbols = proc.enumerateSymbols(); //枚举模块的符号
var addr_GetStringUTFChars = null;
var addr_FindClass = null;
var addr_GetStaticFieldID = null;
var addr_SetStaticIntField = null;
for (var i = 0; i < symbols.length; i++) {
var name = symbols[i].name;
if (name.indexOf("art") >= 0) {
if ((name.indexOf("CheckJNI") == -1) && (name.indexOf("JNI") >= 0)) {
if (name.indexOf("GetStringUTFChars") >= 0) {
console.log(name);
addr_GetStringUTFChars = symbols[i].address;
} else if (name.indexOf("FindClass") >= 0) {
console.log(name);
addr_FindClass = symbols[i].address;
} else if (name.indexOf("GetStaticFieldID") >= 0) {
console.log(name);
addr_GetStaticFieldID = symbols[i].address;
} else if (name.indexOf("SetStaticIntField") >= 0) {
console.log(name);
addr_SetStaticIntField = symbols[i].address;
}
}
}
}
}
(2)通过frida 的api来写文件
function write_reg_dat() {
//frida 的api来写文件
var file = new File("/sdcard/reg.dat", "w");
file.write("EoPAoY62@ElRD");
file.flush();
file.close();
}
(3)把C函数定义为NativeFunction来写文件
function write_reg_dat2() {
//把C函数定义为NativeFunction来写文件
var addr_fopen = Module.findExportByName("libc.so", "fopen");
var addr_fputs = Module.findExportByName("libc.so", "fputs");
var addr_fclose = Module.findExportByName("libc.so", "fclose");
console.log("addr_fopen:", addr_fopen, "addr_fputs:", addr_fputs, "addr_fclose:", addr_fclose);
var fopen = new NativeFunction(addr_fopen, "pointer", ["pointer", "pointer"]);
var fputs = new NativeFunction(addr_fputs, "int", ["pointer", "pointer"]);
var fclose = new NativeFunction(addr_fclose, "int", ["pointer"]);
var filename = Memory.allocUtf8String("/sdcard/reg.dat");
var open_mode = Memory.allocUtf8String("w+");
var file = fopen(filename, open_mode);
console.log("fopen file:", file);
var buffer = Memory.allocUtf8String("EoPAoY62@ElRD");
var ret = fputs(buffer, file);
console.log("fputs ret:", ret);
fclose(file);
}
3. 一些注意事项
关于so分析时的Memory.readByteArray
不要这么写:
console.log("\\n"+Memory.readByteArray(ptr(0x7751a537ec),128));
就是不要在Memory.readByteArray前加"\n",会报错
可以用send
也可以单独将"\n"输出