首页
社区
课程
招聘
4
[原创]基于inlinehook免重打包实现持久化NativeHook
发表于: 2020-9-4 21:44 13459

[原创]基于inlinehook免重打包实现持久化NativeHook

2020-9-4 21:44
13459

两个主角函数:

目的:

通过这两个函数,以及inlinehook我们可以实现root下不修改smali,不重打包完成持久化native hook

实现原理(arm32):

众所周知,System.loadLibrary()可以根据名称加载/data/app/com.xxx/lib/arm/目录下的so,而inlinehook也是通过加载so来动态修改原来的汇编代码,正常使用的话我们考虑修改smali代码加上一句System.loadLibrary()来实现自定义so的加载,但是我们不正常!所以我们想着能不能不修改smali完成加载
我们使用inlinehook编译一个so并把名称改为他原来的so的名称,把他原来的so改个名字,由我们编译的so来加载,嗯 。。。这就是本文的大概原理

需要处理的问题:

(这里涉及了两个so,统一一下名称便于后文描述,原app的so称为 '原so' ,我们用inlinehook生成的so称为 '新so' )

 

1.替换以后我们需要知道由系统调用的dlopen获得的 新so 的handle

 

2.替换以后原本的函数不管是静态注册还是动态注册的必然在新so里面是找不到的

解决办法:

1.最开始想尝试使用frida去实践一下想法

  • ①hook android_dlopen_ext
  • ②第一个参数路径包含 新so,记录handle
  • ③第一个参数路径包含 原so,拦截dlsym()替换第一个参数为上handle

发现死活不行,想到可能是连续调用导致frida性能问题(欢迎大佬给我普及一下为什么),后面改用Module.findExportByName()返回的pointer直接去替换dlsym返回值,这就行了(没读过frida源码,但是这里看参数和结果似乎frida的这个api就是调用的dlsym返回)
然后后面又想着用inlinehook去实现
但是系统连新so都还没加载进去,谈何hook,emmm
最后想着我在调用一次dlopen加载新so不就行了么……

 

2.我们需要稍微了解一下他为什么调用不起来
调用细节看图
其实注意的就是刚才说的dlopen返回的handle,dlsym传递的两个参数,第一个就是dlopen返回的handle,第二个是我们需要调用的函数符号
这个时候使用inlinehook去hook一下这两个函数,在手动加载 原so 后,触发一下原so的Jni_onload(),再启动inlinehook当dlsym第一个参数是 新so 的dlopen handle时就替换为 原so 的handle

举例

我们这里就用最右(cn.xiaochuangkeji.tieba)来举例
替换以前


[注意]看雪招聘,专注安全领域的专业人才平台!

最后于 2020-10-8 10:18 被唱过阡陌编辑 ,原因: 更新 纠正错误
收藏
免费 4
支持
分享
赞赏记录
参与人
雪币
留言
时间
PLEBFE
为你点赞~
2022-7-30 12:03
0x指纹
为你点赞~
2020-9-7 15:43
天边之云
为你点赞~
2020-9-5 20:27
唱过阡陌
为你点赞~
2020-9-4 22:04
最新回复 (29)
雪    币: 1432
活跃值: (5800)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
2
typedef int (*dlopen_tp)(const char* __filename, int __flag);
dlopen_tp my_dlopen = (dlopen_tp)dlopen;

    func_dlopen = base + 0x2F00+1;
    func_dlsym = base + 0x2FB8+1;

    LOGD("DLOPEN = %p   ?   %p  %p",my_dlopen,func_dlopen,func_dlsym);

解决了上面的问题  这样会得到一个和dlopen地址(my_dlopen)
2020-9-6 10:20
0
雪    币: 2090
活跃值: (3948)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
以前做了一个一键生成so、dll劫持源码的小工具,对这种可以说非常有用。
缺点就是 有的so自带JNI_OnLoad时 一键链接Java层所有的native函数,这个部分支持依然有点问题,需要用hook去直接hook register native,然后一个一个 链接到自己的函数上,偷懒没做。
2020-9-7 00:39
0
雪    币: 11317
活跃值: (7764)
能力值: ( LV12,RANK:219 )
在线值:
发帖
回帖
粉丝
4
感谢分享 之前用inject感觉也挺方便的 
2020-9-7 10:59
0
雪    币: 15407
活跃值: (6673)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
“我们使用inlinehook编译一个so并把名称改为他原来的so的名称,把他原来的so改个名字,由我们编译的so来加载.”
不重新打包,你怎么替换/data/app/com.xxx/lib/目录下的so?难道在root下使用copy来替换?
2020-9-7 15:35
0
雪    币: 120
活跃值: (56)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
都root了还用inlineHook实乃不明智之举。
2020-9-7 15:50
0
雪    币: 1432
活跃值: (5800)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
7
tDasm “我们使用inlinehook编译一个so并把名称改为他原来的so的名称,把他原来的so改个名字,由我们编译的so来加载.” 不重新打包,你怎么替换/data/app/com.xxx/lib/目录下 ...

adb push xxx.so /data/app/com.xxx/lib/arm/

最后于 2020-9-7 16:15 被唱过阡陌编辑 ,原因:
2020-9-7 16:11
0
雪    币: 15407
活跃值: (6673)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
唱过阡陌 tDasm “我们使用inlinehook编译一个so并把名称改为他原来的so的名称,把他原来的so改个名字,由我们编译的so来加载.” 不重新打包,你怎么替换 ...
root下使用copy来替换,猜对了
2020-9-7 16:31
0
雪    币: 1146
活跃值: (645)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
牛皮牛皮
2020-9-8 10:17
0
雪    币: 1434
活跃值: (1699)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
10
dlopen地址自动获取其实有更高明的手段,比如vapp里用的那玩意
或者我之前写的方式http://www.52pojie.cn/thread-1226153-1-1.html
2020-9-18 16:33
0
雪    币: 1434
活跃值: (1699)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
11
知道windos有dll代理,也想了想应该linux的也能代理吧,但是确实没想到原来替换个handle就可以用了
2020-9-18 16:35
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
这边试了下,没成功,有两个疑问:
1、"手动加载 原so 后,触发一下原so的Jni_onload()",这个没太明白
程序启动,应该是runtime 先dloepn libmarsxlog.so,在进入JNI_OnLoad函数,此时在JNI_OnLoad函数中dlopen libmarsxlogcp.so,只是获取了libmarsxlogcp.so的handle
System.loadLibrary(libName)
->JavaVMExt.loadNativieLibrary:
     ->dlopen
     ->JNI_OnLoad
2、下面的代码仅仅打印下吗
 //手动调用原so的JNI_OnLoad()
    void *p2 = dlsym(p1, "JNI_OnLoad");
    LOGD("called dlsym JNI_OnLoad 0x%p",p2);
2020-9-18 21:14
0
雪    币: 1432
活跃值: (5800)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
13
wx_havenow 这边试了下,没成功,有两个疑问: 1、"手动加载 原so 后,触发一下原so的Jni_onload()",这个没太明白 程序启动,应该是runtime 先dloepn libm ...

第一个问题:

LoadNativeLibrary调用了android_dlopen_ext()和dlsym(),并由dlsym()调用了JNI_Onload(),所以我们手动dlopen打开一个so得手动去调用dlsym(),来触发他的JNI_Onload(),具体可以参见另一篇文章:(https://www.jianshu.com/p/5252b62aa9d2



第二个问题:

void *p2 = dlsym(p1, "JNI_OnLoad"); //就是上述我们说的要手动调用他的JNI_Onload()

LOGD("called dlsym JNI_OnLoad 0x%p",p2); //这里只是打印看一下调用情况

补充:

至于试了没有成功,可能不是这个问题,举例说的话,这种方式调用不是什么情况下都能成功的,比如Unity游戏中libil2cpp.so,具体也没研究(欢迎大佬来补充),但是众所周知这个so哪怕是改了一个字节他都会报错的,so里面做了其他处理吧,一般情况替换工具so是没问题的,建议动态用frida先跑通了在尝试inlinehook


2020-9-19 08:46
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
14
感觉这个方法确实不是什么情况下都能成功,我这边失败的原因暂时没找到。
我看了下打印的日志,我这边的app,老so的部分jni方法确实是有被dlsym,老so的jni方法为什么会被dlsym,而且只是部分被dlsym,具体的原因还有待验证,有可能是反射机制,java层调用的时候才会去dlsym

作者说的  “所以我们手动dlopen打开一个so得手动去调用dlsym(),来触发他的JNI_Onload()”,个人觉得是有问题的:
libdl.cpp --->dlsym() //加载jni_onload() 
对于这个一个流程的个人觉得应该是在libdl.cpp中先dlysm了jni_onload在去调用jni_onload

本身dlsym仅仅是获取jni_onload这个这个函数的handle
如果要调用,应该是  p2(vm,  reserved);
2020-9-19 22:46
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
15
这边搜索了下代码 JavaVMExt::LoadNativeLibrary,楼主写的 “所以我们手动dlopen打开一个so得手动去调用dlsym(),来触发他的JNI_Onload()” 应该是有问题的

JavaVMExt::LoadNativeLibrary
{
void* sym;  
 sym = library->FindSymbol("JNI_OnLoad", nullptr);  
  if (sym == nullptr) {  
    VLOG(jni) << "[No JNI_OnLoad found in \"" << path << "\"]";  
    was_successful = true;  
  } else {  
    // Call JNI_OnLoad.  We have to override the current class  
    // loader, which will always be "null" since the stuff at the  
    // top of the stack is around Runtime.loadLibrary().  (See  
    // the comments in the JNI FindClass function.)  
    ScopedLocalRef<jobject> old_class_loader(env, env->NewLocalRef(self->GetClassLoaderOverride()));  
    self->SetClassLoaderOverride(class_loader);  
   
    VLOG(jni) << "[Calling JNI_OnLoad in \"" << path << "\"]";  
    typedef int (*JNI_OnLoadFn)(JavaVM*, void*);  
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);  
    int version = (*jni_on_load)(this, nullptr);  
}

注意关键点:
sym = library->FindSymbol("JNI_OnLoad", nullptr);  //这里应该是dlsym JNI_OnLoad函数
    JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);  
    int version = (*jni_on_load)(this, nullptr);  //这里是根据函数地址做函数调用,这个调用就会进入so的JNI_OnLoad

2020-9-19 22:54
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
16

猜测失败的原因:
老的so里面函数dlsym应该是都会成功的,但是调用可以会有问题
vm是唯一的,但是env是线程相关的,如果JNI_OnLoad函数里面用到了env,可能会出问题

最后于 2020-9-20 21:38 被wx_havenow编辑 ,原因:
2020-9-19 23:07
0
雪    币: 1432
活跃值: (5800)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
17
wx_havenow 猜测失败的原因:1、老的so里面部分函数dlsym失败;不确定用registerNative注册的函数和直接用Java_com_xxxx映射的函数 dlsym有没有区别2、vm是唯一的,但是env是线 ...
大佬指出的是,我刚才重新看了一下源码,文章这里确实是写错了 ,压根就没有调用jni_onload   /xk
是在调用FindSymbol()拿到JNI_Onload地址之后,做了一次函数的调用
```
JNI_OnLoadFn jni_on_load = reinterpret_cast<JNI_OnLoadFn>(sym);
int version = (*jni_on_load)(this, nullptr);
```
实际操作中不管是动态注册还是静态注册的函数dlsym只会调用一次
这里的this,都是一个同一个javaVM,但是函数里用到的env确实可能拿到不一样的env导致出问题
要真是线程相关的问题还不好处理

可以附一下报错嘛,大家一起找问题
2020-9-20 20:39
0
雪    币: 1432
活跃值: (5800)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
18
还有一个小问题,在自己写的小demo里面,java层的点击事件触发一个静态注册的native函数,hook dlsym的时候发现其实注册函数只是被调用了一次,即使后面再点击按钮不会再出发dlsym获取函数地址,可能是某种缓存机制?就是说要想hook到注册函数的调用时机得早,在他调用之后你再去改返回值其实也就不生效了,反正他也不会调用了 
2020-9-20 20:48
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
19
我这边确认过了:
java端调用System.loadLibrary()后,
1、dlopen so
2、dlsym so中的JNI_OnLoad函数,并且调用JNI_OnLoad函数
3、如果JNI_OnLoad函数中有registerNatives,对本地方法进行注册,这里是不会调用dlsym的,因为是将函数指针保存起来了
4、如果有Java_com.xxxx_fun的jni函数,在调用jni函数时,vm会dlysm jni函数,获取到jni函数地址,在调用jni函数,只会dlsym一次,
因为获取到jni函数的地址后会存起来,就不用在dlsym了

你做hook,肯定是要在so load结束,才能hook成功
比如你的System.loadLibrary不是在static块里面,用一个按钮去触发,app启动了,hook jni函数肯定是失败的
2020-9-20 21:47
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
20
13030-13057/? E/inlinehook: new_func_dlsym from 0x7a45cdaf, glSamplerParameteriv
13030-13057/? D/inlinehook: 0x0 = __loader_dlsym('0x7a45cdaf','glSamplerParameteriv')
13030-13057/? E/inlinehook: new_func_dlsym from 0xc68f21d3, glSamplerParameteriv
13030-13057/? D/inlinehook: 0xe5ce11a0 = __loader_dlsym('0xc68f21d3','glSamplerParameteriv')
13030-13077/? I/Process: Sending signal. PID: 13030 SIG: 9
1635-3586/? D/ActivityManager: report kill process: killerPid is:13030, killedPid is:13030

只能可看到 Sending signal. PID: 13030 SIG: 9
2020-9-20 22:19
0
雪    币: 1109
活跃值: (3626)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
21

还是 lief 比较香

`pip install lief`

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3    
# Description    
# -----------    
# Hook the 'cos' function from the standard math library (libm)    
import lief    
libm = lief.parse("/usr/lib/libm.so.6")    
hook = lief.parse("hook")    
cos_symbol  = libm.get_symbol("cos")    
hook_symbol = hook.get_symbol("hook")    
code_segment = hook.segment_from_virtual_address(hook_symbol.value)    
segment_added = libm.add(code_segment)    
print("Hook inserted at VA: 0x{:06x}".format(segment_added.virtual_address))    
# Offset of the function 'hook' within the CODE segment    
hook_offset = hook_symbol.value - code_segment.virtual_address    
new_addr    = segment_added.virtual_address + hook_offset    
print(f"Change {cos_symbol.name}!{cos_symbol.value:x} -> {cos_symbol.name}!{new_addr:x}")    
cos_symbol.value = new_addr    
libm.write("libm.so.6")


2020-9-21 11:32
0
雪    币: 3110
活跃值: (4257)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
tql
2020-9-21 12:06
0
雪    币: 1
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
23
这个根本不必要这样搞啊,直接hook system.loadlibrary,然后先注入自己的inlinehook,再加载原有lib就可以了
2020-10-29 16:43
0
雪    币: 1432
活跃值: (5800)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
24
huangbof 这个根本不必要这样搞啊,直接hook system.loadlibrary,然后先注入自己的inlinehook,再加载原有lib就可以了
1.这篇文章是在不修改smali代码的情况下完成的,当你遇到指令抽取的时候可以绕过修改smali
2.发文就是想给大家分享思路,大家各抒己见就行,但是既然看到这篇文章,如果觉得思路有意思,咋不妨就去谈论一下这种思路的可行性,可能遇到的问题,听大佬的发言一起完善它,这才能有进步嘛
2020-10-30 18:02
0
雪    币: 1432
活跃值: (5800)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
25
发现一个python的工具lief(https://github.com/lief-project/LIEF)
这玩意修改一下依赖,添加上我们的inject.so就完事,这更简单
2020-11-24 15:08
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册