sprintf函数实现_基于inlinehook免重打包实现持久化NativeHook

cafc20dea31dcfff867159c7079fdecd.png

本文为看雪论坛优秀文章

看雪论坛作者ID:唱过阡陌

两个主角函数 void dlopen(const char filename, int flag)
  • 功能是以指定模式打开指定的动态链接库文件

  • 返回一个句柄给dlsym()的调用进程

  • 使用dlclose()来卸载打开的库

void dlsym(void handle, const char * symbol )
  • 根据动态链接库操作句柄与符号,返回符号对应的地址

  • 获取变量地址

目的 通过这两个函数,以及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. 我们需要稍微了解一下他为什么调用不起来 3b150991bf6a13772b4d814906cb5ea1.png 其实注意的就是刚才说的dlopen返回的handle,dlsym传递的两个参数,第一个就是dlopen返回的handle,第二个是我们需要调用的函数符号。
这个时候使用inlinehook去hook一下这两个函数,在手动加载 原so 后,触发一下 原so 的Jni_onload(),再启动inlinehook当dlsym第一个参数是 新so 的dlopen handle时就替换为 原so 的handle。
举例 我们这里就用最右(cn.xiaochuangkeji.tieba)来举例: e5bfe68eaf32cfe757f0c53824bb3b71.png
操作步骤 1. 把原来的libmarsxlog.so改名为libmarsxlogcp.so 2. 然后编译出so,修 改名称为libmarsxlog.so,并移动到该目录下 3. 启动查看日志 inlinehook替换函数前8字节(2条arm指令或者4条thumb指令)为跳转,而frida hook在这里是enter时候的参数自然是原来的,所以看起来是用了错误的handle加载function还不崩掉。 90f59f020b590990390c996b03c2c976.png 这里这个手机没有开全局调试 只有将就用frida hook看一下打印日志:
function hookLog() {    var isFirst = true    Interceptor.attach(Module.findExportByName("liblog.so","__android_log_print"), {        onEnter: function (args) {            if(isFirst) {                console.log("\n")                isFirst = false            }            if(args[1].readCString().indexOf("ZZZ")!=-1)                console.log(args[1].readCString()+"\t"+args[2].readCString()+"\t"+args[3]+"\t"+args[4]+"\t"+args[5])        }    });}
这里__android_log_print是变长参数为了能展示全部参数,只好多填几个参数,各自眼神忽略吧。 f84c51d79b11163aaceafcaa997459f4.png   至于这里为什么dlopen头文件定义的两个参数,在这里为什么是三个参数,就是第二个参数才是路径,看汇编F5,同理dlsym。 9c351bf0f1169000b020cce995f1cebb.png   对加壳的app可以使用这种方式来hook关键点: OpenCommon / OpenMemory C实现,替换原so就能在启动时hook从而dumpdex,这种基于inlinehook的操作不像xp框架,frida之类的容易被反调试,这种操作基本上没有什么反调试,比较香。  

附源码 inlinehook:

#include #include  #include "../inlineHook/include/inlineHook.h"#include  #include #include #include  #define LOG_TAG "ZZZ"#define LOGD(fmt, args...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG,fmt, ##args)#define LOGE(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,fmt, ##args) //需要Hook的函数地址unsigned int func_dlopen = NULL;unsigned int func_dlsym = NULL; //新so handlevoid *p0;//原so handlevoid *p1;int *env; static unsigned long find_module_by_name(char *soName) {    char filename[32];    char cmdline[256];    sprintf(filename, "/proc/%d/maps", getpid());    LOGD("filename = %s", filename);    FILE *fp = fopen(filename, "r");    unsigned long revalue = 0;    if (fp) {        while (fgets(cmdline, 256, fp)) //逐行读取        {            if (strstr(cmdline, soName) && strstr(cmdline, "r-xp"))//筛选            {                LOGD("cmdline = %s", cmdline);                char *str = strstr(cmdline, "-");                if (str) {                    *str = '\0';                    char num[32];                    sprintf(num, "0x%s", cmdline);                    revalue = strtoul(num, NULL, 0);                    LOGD("revalue = %lu", revalue);                    return revalue;                }            }            memset(cmdline, 0, 256); //清零        }        fclose(fp);    }    return 0L;} //原函数指针void* (*old_func_dlopen)(const char* filename, int flags, const void* caller_addr,void* s) = NULL;void* (*old_fun_dlsym)(void* /*handle*/, const char* /*symbol*/) = NULL; void* new_func_dlopen(const char *filename, int flags, const void *caller_addr, void *s) {    void* p = old_func_dlopen(filename,flags,caller_addr,s);    LOGD("%p = __loader_dlopen('%s','%d','%p')",p,filename,flags,caller_addr);    return p;} void* new_func_dlsym(void *handle, const char *symbol){    if(handle == p0 && strstr(symbol,"JNI_OnLoad") == NULL){        handle = p1;        LOGD("change handle from %p to %p",p0,p1);    }    void* ret = old_fun_dlsym(handle,symbol);    LOGD("%p = __loader_dlsym('%p','%s')",ret,handle,symbol);    return ret;} jint JNICALLJNI_OnLoad(JavaVM *vm, void *reserved) {     LOGE("------------------- JNI_OnLoad -------------------");     if (vm->GetEnv( (void**)&env, JNI_VERSION_1_6) == JNI_OK) {        LOGD("GetEnv OK");    }     char *lib_name = const_cast<char *>("linker");    unsigned int base = find_module_by_name(lib_name);    LOGD("Find %s at 0x%x ", lib_name,base);     //计算需要Hook的函数地址偏移 (baseAddress + offset + thumb ? 1 : 0)    //这里的地址是pull出linker导出函数里面找的,不同系统版本不一样    //这里使用的是Neux 6P 安卓9    func_dlopen = base + 0x75A4+1;    func_dlsym = base + 0x76F0+1;     LOGE("------------------- InlineHook -------------------");     //注册Hook信息    registerInlineHook((uint32_t) func_dlopen, (uint32_t) new_func_dlopen, (uint32_t **) &old_func_dlopen)==ELE7EN_OK ?    LOGD("Success Hook func_dlopen at 0x%x",func_dlopen):LOGE("Fail Hook func_dlopen at 0x%x",func_dlopen);     registerInlineHook((uint32_t) func_dlsym, (uint32_t) new_func_dlsym, (uint32_t **) &old_fun_dlsym)==ELE7EN_OK ?    LOGD("Success Hook func_dlsym at 0x%x",func_dlsym):LOGE("Fail Hook func_dlsym at 0x%x",func_dlsym);     inlineHookAll();     //新so    p0 = dlopen("/data/data/cn.xiaochuankeji.tieba/lib/libmarsxlog.so", 0);    LOGD("dlopen libmarsxlog.so handle = 0x%p",p0);    //原so    p1 = dlopen("/data/data/cn.xiaochuankeji.tieba/lib/libmarsxlogcp.so", 0);    LOGD("dlopen libmarsxlogcp.so handle =  0x%p",p1);     //手动调用原so的JNI_OnLoad()    void *p2 = dlsym(p1, "JNI_OnLoad");    LOGD("called dlsym JNI_OnLoad 0x%p",p2);     LOGE("-------------------  Function  -------------------");     return JNI_VERSION_1_6;}
总结 这里既然可以通过dlsym让他调函数得到改变,所以这个可以当作一个hook思路?只要能修改dlsym就可以控制导出函数的调用。 这么看来基于PLT的hook似乎也能做点事情了。 76f1b5751fdf3c49dce179f9b4a4cdbc.png 关于dlopen以及dlsym函数地址的获取,目前这里是用的基地址加偏移,或者是采用写一个函数只用于调用dlopen,这样再去hook这个函数拿到一个类似于dlopen的代理地址,算是比较通用。 a0eb10380c629570808474662ec302b5.png 还有一个最通用的方法: 用typedef重定义一个dlopen,重新声明一个函数指针,并将这个函数指针指向dlopen并强转为重定义的dlopen。
typedef int (*dlopen_tp)(const char* __filename, int __flag);dlopen_tp my_dlopen = (dlopen_tp)dlopen; func_dlopen = base + 0x2F00+1;LOGD("DLOPEN = %p   -----   %p ",my_dlopen,func_dlopen);
my_dlopen = func_dlopen f0b92e87ac22d6f202465e611fbd50a7.gif - End -

45c094d781ffcc95f809c47659185ad5.png

看雪ID:唱过阡陌

https://bbs.pediy.com/user-home-868525.htm

  *本文由看雪论坛 唱过阡陌 原创,转载请注明来自看雪社区。

02f97de44f18e4d38f6902c9acfbb268.png

推荐文章++++

338fde9ca46eaec256448e5ea4fa40fd.png

* java类加载的过程概述

* Java序列化反序列化源码---Jackson反序列化漏洞源码分析

* PWN学习Use After Free

* 高版本64位Win10(RS2或更高)下枚举消息钩子的一种思路

* X64内核SMAP,SMEP浅析

554494a0346830202a0d6e8f5cc7cd1f.png 公众号ID:ikanxue 官方微博:看雪安全 商务合作:wsc@kanxue.com 0de54f23-2c30-eb11-8da9-e4434bdf6706.svg

求分享

24e54f23-2c30-eb11-8da9-e4434bdf6706.svg

求点赞

e54b8f35ca9d7ee88a8f19f55132831c.gif

求在看

8c7902c505630864337527dacfa497c0.gif
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值