日常笔记,水平有限,小白学习,大佬移步,有错误欢迎指出。
1.什么是静态注册 or 动态注册?
关于so文件当中的函数,分为导出函数
和未导出函数
两种,导出函数打开IDA后能够在Exports导出表
中找到的就是导出函数,通常是"Java_"
开头的,未导出函数则在导出表中寻找不到。
一般来说静态编写的native函数都能在导出表中寻找到,而动态加载的则无法在导出表中找到。
静态注册的so函数命名格式是这样的:
Java_包名_类名_函数名
仔细观察下规律:
Java_com_rytong_hnair_HNASignature_getHNASignature
java层是这样的
ida-pro 导出表是这样的:
而动态注册的在导出表找不到对应的函数名,通常会看到JNI_OnLoad
。
2.咋看是Thumb指令 or Arm指令?
ida里依次找到Options
----> General
然后 Number of opcode bytes(non-graph)
设置为4 ,点击ok.
然后在IDA View
中查看opcode 的长度, 如果出现 2 个字节和 4 个字节
的, 说明为 thumb 指令集。
如果都是 4
个字节的, 说明是 arm 指令集。
在 Thumb 指令集下, inline hook 的需要进行偏移地址 +1
操作;
示例如图:
3.so函数地址的确认
这里分为两种情况
so基地址在
/proc/<pid>/maps
中可查看
情况1-导出函数: 导出函数地址 = so文件名 + 函数名
注: 函数名以汇编中出现的为准
let funcAddr = Module.findExportByName('libencryptlib.so', '_ZN7MD5_CTX11MakePassMD5EPhjS0_')
// 返回的是函数地址 第二个参数根据汇编中为准
console.log("so函数地址:", funcAddr);
// 通过Interceptor.attach来对函数进行hook
Interceptor.attach(funcAddr, {
onEnter: function (args) {
console.log('args[1]: ', hexdump(args[1])) // 打印参数的地址 通过hexdump打印16进制
console.log(this.context.x1) // 打印寄存器内容
console.log('args[2]: ', args[2].toInt32()) // 默认显示16进制,这里转为10进制
this.args3 = args[3] // 将args[3]值保存到this上
},
onLeave: function (retval) {
console.log('args[3]: ', hexdump(this.args3))
},
})
情况2 - 非导出函数:非导出函数地址 = so基地址 + 函数偏移量
注意如果Thumb 指令, hook 的偏移地址需要进行 +1 操作
代码如下:
var libbili = Module.findBaseAddress("libbili.so"); # 获取so文件的基地址
var so_func = libbili.add(0x22B0 + 1); # 如果是Thumb指令下,执行 +1 操作
var so_func = libbili.add(0x22B0); # 如果是Arm指令下,直接操作即可,不再 +1 操作
console.log("so函数地址:", so_func);
总结一下就是:
安卓位数 | 指令 | 计算方式 |
---|---|---|
32 位 | thumb | so 基址 + 函数在 so 中的偏移 + 1 |
64 位 | arm | so 基址 + 函数在 so 中的偏移 |
可以看到libbili.so
就是so文件的基地址,0x22B0
就是对应的偏移地址,而这个0x22B0
在so文件对应的写法是sub_22B0
,并且不区分大小写,也就是说0x22B0
或者0x22b0
都可以。
4.枚举导入表
let improts = Module.enumerateImports('libencryptlib.so')
for (let iterator of improts) {
console.log(JSON.stringify(iterator))
// {"type":"function","name":"__cxa_atexit","module":"/apex/com.android.runtime/lib64/bionic/libc.so","address":"0x778957bd34"}
}
5.枚举导出表
const exports = Module.enumerateExports('libencryptlib.so')
for (const iterator of exports) {
console.log(JSON.stringify(iterator))
// {"type":"letiable","name":"_ZTSx","address":"0x74d594b1c0"}
}
6.枚举符号表
// 列举 libart.so 中的所有导出函数(成员列表)
let symbols = Module.enumerateSymbolsSync("libart.so");
for (let i = 0; i < symbols.length; i++) {
let symbol = symbols[i];
console.log(symbol.name)
7.枚举进程中已加载的模块
const modules = Process.enumerateModules()
console.log(JSON.stringify(modules[0].enumerateExports()[0]))
8.常见的参数方法
// args[0] 是一个内存地址
hexdump(args[0]) // 打印参数的所在内存区域的字节数据
args[0].readCString() // 读取参数所对应的C字符串 (前提: 参数是一个可见字符串)
args[0].readPointer() // 用读地址方式去读取参数所对应的值 (如果参数是一个指针的话可能就需要使用)
9.内存读写
// 1. 读取指定地址的字符串
let baseAddr = Module.findBaseAddress('libxiaojianbang.so')
console.log(baseAddr.add(0x2c00).readCString())
// 2. dump指定地址的内存
console.log(hexdump(baseAddr.add(0x2c00)))
// 3. 读指定地址的内存
console.log(baseAddr.add(0x2c00).readByteArray(16))
console.log(Memory.readByteArray(baseAddr.add(0x2c00), 16)) //原先的API
// 4. 写指定地址的内存
baseAddr.add(0x2c00).writeByteArray(stringToBytes('xiaojianbang'))
console.log(hexdump(baseAddr.add(0x2c00)))
// 5. 申请新内存写入
Memory.alloc()
Memory.allocUtf8String()
// 6. 修改内存权限
Memory.protect(ptr(libso.base), libso.size, 'rwx')
10.确认 native 函数在哪个 so
静态分析查看静态代码块中加载的 so,但并不靠谱,因为 native 函数声明在一个类中,so 加载可以在其他的类中此外还可以在另外的类中,一次性加载所有的 so
hook 系统函数来得到绑定的 native 函数地址,然后再得到 so 地址
注册方式 | hook 点 |
---|---|
jni 函数动态注册 | hook RegisterNatives |
jni 函数静态注册 | hook dlsym |
hook RegisterNatives(动态注册)
直接上yang神的hook 脚本(地址:https://github.com/lasting-yang/frida_hook_libart )来打印注册的native函数和so文件。
代码down下来,再稍微修改下:
function find_RegisterNatives(params) {
// 列举 libart.so 中的所有导出函数(成员列表)
let addrRegisterNatives = null;
let symbols = Module.enumerateSymbolsSync("libart.so");
for (let i = 0; i < symbols.length; i++) {
let symbol = symbols[i];
console.log(symbol.name)
//_ZN3art3JNI15RegisterNativesEP7_JNIEnvP7_jclassPK15JNINativeMethodi
if (symbol.name.indexOf("art") >= 0 &&
symbol.name.indexOf("JNI") >= 0 &&
symbol.name.indexOf("RegisterNatives") >= 0 &&
symbol.name.indexOf("CheckJNI") < 0) {
// 获取 RegisterNatives 函数的内存地址,并赋值给addrRegisterNatives。
addrRegisterNatives = symbol.address;
console.log("RegisterNatives is at ", symbol.address, symbol.name);
hook_RegisterNatives(addrRegisterNatives)
}
}
}
function hook_RegisterNatives(addrRegisterNatives) {
if (addrRegisterNatives != null) {
// RegisterNatives(env, 类型, Java和C的对应关系,个数)
Interceptor.attach(addrRegisterNatives, {
onEnter: function (args) {
// console.log("[RegisterNatives] method_count:", args[3]);
let env = args[0]; // jni对象
let java_class = args[1]; // 类
let class_name = Java.vm.tryGetEnv().getClassName(java_class);
console.log("java-类名:", class_name)
let taget_class = "com.bilibili.nativelibrary.LibBili";
if (class_name === taget_class) {
//只找我们自己想要类中的动态注册关系
console.log("\n[RegisterNatives] method_count:", parseInt(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, "name:", sig, "module_name:", find_module.name, "offset:", offset);
console.log("java函数名:", name + ",", "参数和返回值:", sig + ",", "偏移地址offset:", offset);
}
}
}
});
}
}
setImmediate(find_RegisterNatives);
然后输入命令启动:
frida -U --no-pause -f package_name -l hook_RegisterNatives.js
hook_dlsym(静态注册)
function hook_dlsym() {
let dlsymAddr = Module.findExportByName('libdl.so', 'dlsym')
console.log(dlsymAddr)
Interceptor.attach(dlsymAddr, {
onEnter: function (args) {
this.args1 = args[1]
},
onLeave: function (retval) {
let module = Process.findModuleByAddress(retval)
if (module == null) return
console.log(this.args1.readCString(), module.name, retval, retval.sub(module.base))
},
})
}
1.so源码实例
#include <string.h>
#include <jni.h>
#include"test.h"
jstring JNICALL Java_com_example_mi_demoso_JNITest_getStringFromJNI(JNIEnv* env, jobject jo)
{
char str[] = "x HelloWorld from JNI12345!";
int c = test_add(97,1);
str[0] = (char)c;
return (*env)->NewStringUTF(env, str);
}
int test_add(int a,int b){
return a+b;
}
这里将对test_add
函数进行hook
2.寻找要hook函数的偏移地址
将要hook的so(libjnitest.so
)拖进ida
上图可以看到:在导出函数窗口可以直接看到函数"test_add"
的偏移地址和函数名(这里可以通过函数名或者地址进行hook),非导出函数只能通过地址hook;这里我们用地址hook的方法(图中表明hook的函数偏移为0x00000680
,函数地址 = so基地址 + 函数偏移,
要注意:如果Thumb 指令, hook 的偏移地址需要进行 +1 操作, )
**!!
3.编写frida脚本
import frida
import sys
jscode = """
Java.perform(function(){
var str_name_so = "libjnitest.so"; //需要hook的so名
var n_addr_func_offset = 0x00000680; //需要hook的函数的偏移
var n_addr_so = Module.findBaseAddress(str_name_so); //加载到内存后 函数地址 = so地址 + 函数偏移
var n_addr_func = parseInt(n_addr_so, 16) + n_addr_func_offset;
var ptr_func = new NativePointer(n_addr_func);
//var ptr_func = Module.findExportByName("libjnitest.so","test_add") //对函数名hook
Interceptor.attach(ptr_func,{
//onEnter: 进入该函数前要执行的代码,其中args是传入的参数,一般so层函数第一个参数都是JniEnv,第二个参数是jclass,从第三个参数开始是我们java层传入的参数
onEnter: function(args) {
send("Hook start");
send("args[2]=" + args[2]); //第一个传入的参数
send("args[3]=" + args[3]); //第二个参数
},
onLeave: function(retval){ //onLeave: 该函数执行结束要执行的代码,其中retval参数即是返回值
send("return:"+retval); //返回值
retval.replace(100); //替换返回值为100
}
});
});
"""
def printMessage(message,data):
if message['type'] == 'send':
print('[*] {0}'.format(message['payload']))
else:
print(message)
process = frida.get_remote_device().attach('com.example.testso') #进程名
script = process.create_script(jscode)
script.on('message',printMessage)
script.load()
sys.stdin.read()
4.效果验证
启动frida-server
后进行,启动端口转发
adb forward tcp:27043 tcp:27043
adb forward tcp:27042 tcp:27042
然后执行python脚本(frida脚本),接着启动应用:
符合retval.replace(100)
; //替换返回值为100
预期,即小写x处显示为d,即100.
参考:
https://www.cnblogs.com/aWxvdmVseXc0/p/12463319.html
https://refate.github.io/2019/01/13/rida_install/