免重打包实现持久化NativeHook

两个主角函数:

目的:

通过这两个函数,以及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性能问题(欢迎大佬给我普及一下为什么)
所以又想着用inlinehook去实现
但是系统连新so都还没加载进去,谈何hook,emmm
最后想着我在调用一次dlopen加载新so不就行了么……

补充:
后面重写了一下也是能实现的


补充用frida控制dlsym实现函数重定向

2.我们需要稍微了解一下他为什么调用不起来

调用细节看图

其实注意的就是刚才说的dlopen返回的handle,dlsym传递的两个参数,第一个就是dlopen返回的handle,第二个是我们需要调用的函数符号
这个时候使用inlinehook去hook一下这两个函数,在手动加载 原so 后,触发一下原so的Jni_onload(),再启动inlinehook当dlsym第一个参数是 新so 的dlopen handle时就替换为 原so 的handle

举例

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


替换以前
操作步骤
  • 把原来的libmarsxlog.so改名为libmarsxlogcp.so,
  • 然后编译出so,修改名称为libmarsxlog.so,并移动到该目录下
  • 启动查看日志

inlinehook替换函数前8字节(2条arm指令或者4条thumb指令)为跳转,而frida hook在这里是enter时候的参数自然是原来的,所以看起来是用了错误的handle加载function还不崩掉


替换以后

这里这个手机没有开全局调试 只有将就用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是变长参数为了能展示全部参数,只好多填几个参数,各自眼神忽略把


替换参数

至于这里为什么dlopen头文件定义的两个参数,在这里为什么是三个参数,就是第二个参数才是路径,看汇编F5,同理dlsym


dlopen参数

对加壳的app可以使用这种方式来hook关键点
OpenCommon / OpenMemory
C实现,替换原so就能在启动时hook从而dumpdex,这种基于inlinehook的操作不像xp框架,frida之类的容易被反调试,这种操作基本上没有什么反调试,比较香

附源码 inlinehook

#include <stdio.h>
#include <jni.h>

#include "../inlineHook/include/inlineHook.h"
#include <android/log.h>

#include <unistd.h>
#include <stdlib.h>
#include <dlfcn.h>

#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 handle
void *p0;
//原so handle
void *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 JNICALL
JNI_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似乎也能做点事情了
hook思路?
  • 关于dlopen以及dlsym函数地址的获取,目前这里是用的基地址加偏移

  • 或者是采用写一个函数只用于调用dlopen,这样再去hook这个函数拿到一个类似于dlopen的代理地址,算是比较通用


    代理这两个函数
  • 再或者是用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);
dlopen

这样得到的dlopen就是原dlopen地址,这样写最通用吧

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345