2020-01-02 12:46:52 DaoDivDiFang 阅读数 42

Android9.0中在activity的onCreate之前hook dlopen函数,如果需要返回值(即修改了LR寄存器),那么会触发:E/libEGL: EGL_ANDROID_blob_cache advertised, but unable to get eglSetBlobCacheFuncsANDROID。不会crash,但是界面不会绘制出来。

是因为dlopen(libEGL_adreno.so, 2)、dlopen(libGLESv2_adreno.so, 2)返回都是null。理论上其他任何hook框架也都存在这个问题,测试了几个也确实都存在。

dlopen函数实现在libdl.so,

.text:0000000000000EF8                 WEAK dlopen
.text:0000000000000EF8 dlopen                                  ; DATA XREF: LOAD:0000000000000508↑o
.text:0000000000000EF8
.text:0000000000000EF8 var_s0          =  0
.text:0000000000000EF8
.text:0000000000000EF8 ; __unwind {
.text:0000000000000EF8                 STP             X29, X30, [SP,#-0x10+var_s0]!
.text:0000000000000EFC                 MOV             X29, SP
.text:0000000000000F00                 MOV             X2, X30 ; a3
.text:0000000000000F04                 BL              .__loader_dlopen
.text:0000000000000F08                 LDP             X29, X30, [SP+var_s0],#0x10
.text:0000000000000F0C                 RET
.text:0000000000000F0C ; } // starts at EF8
.text:0000000000000F0C ; End of function dlopen

这里把LR寄存器当作第三个参数传递给.__loader_dlopen,而我们hook时为了能拿到返回值是修改了LR寄存器的,这是不可避免的。如果不修改LR寄存器,除非我们知道函数结尾,在函数结尾修改LR寄存器或者跳到shellcode/hook函数,但是通常是不现实的,函数开头容易确认,函数结尾很难自动化确认。

// Proxy calls to bionic loader
__attribute__((__weak__))
void* dlopen(const char* filename, int flag) {
  const void* caller_addr = __builtin_return_address(0);
  return __loader_dlopen(filename, flag, caller_addr);
}

__builtin_return_address函数应该是gcc内部函数,0获取的是被调用函数返回后执行的指令地址,至于1之后的数字是否能获取到函数调用栈待测试。已测试:
1、0获取的就是进入函LR寄存器的值。
2、1获取的是r7(thumb)/r12(arm),即ip寄存器的值,所以能不能获取到正确的值取决于上层是否使用了ip寄存器暂存sp,且ip寄存器之后(栈上)就是在栈上存储的LR寄存器的值。所以不满足这些条件的函数是获取不到的甚至是错误的。

爷爷函数:
.text:0000C250 ; __unwind {
.text:0000C250                 PUSH            {R4,R5,R7,LR}
.text:0000C252                 ADD             R7, SP, #8
...
.text:0000C292                 BLX             j__Z12test_replacev ; test_replace(void)

--------------------------------------

父函数:
.text:0000B648                 PUSH            {R7,LR}
.text:0000B64A                 MOV             R7, SP
...
.text:0000B6A6                 BLX             j__Z12dump_replacePvS_PFvP10my_pt_regsP11STR_HK_INFOES5_PKc ;

--------------------------------------

子函数:
.text:0000D268                 PUSH            {R4-R7,LR}
.text:0000D26A                 ADD             R7, SP, #0xC
...
.text:0000D2DA                 MOV             R1, R7
.text:0000D2DC                 LDR             R1, [R1];    父函数传过来的R7的值
.text:0000D2DE                 LDR             R3, [R1,#4];r3寄存器为__builtin_return_address(1),即取父函数放在R7之后的LR寄存器的值

.__loader_dlopen就是一个跳板

.plt:0000000000000DD8 ; __int64 __fastcall __loader_dlopen(__int64 a1, __int64 a2, __int64 a3)
.plt:0000000000000DD8 .__loader_dlopen                        ; CODE XREF: dlopen+C↓p
.plt:0000000000000DD8                 ADRP            X16, #off_1FF70@PAGE
.plt:0000000000000DDC                 LDR             X17, [X16,#off_1FF70@PAGEOFF]
.plt:0000000000000DE0                 ADD             X16, X16, #off_1FF70@PAGEOFF
.plt:0000000000000DE4                 BR              X17     ; __loader_dlopen
.plt:0000000000000DE4 ; End of function .__loader_dlopen

导入__loader_dlopen函数,实现在linker/64,所以从libdl.so的got表中可以获取__loader_dlopen函数的绝对地址。

static void* dlopen_ext(const char* filename,
                        int flags,
                        const android_dlextinfo* extinfo,
                        const void* caller_addr) {
  ScopedPthreadMutexLocker locker(&g_dl_mutex);
  g_linker_logger.ResetState();
  void* result = do_dlopen(filename, flags, extinfo, caller_addr);
  if (result == nullptr) {
    __bionic_format_dlerror("dlopen failed", linker_get_error_buffer());
    return nullptr;
  }
  return result;
}

void* __loader_android_dlopen_ext(const char* filename,
                           int flags,
                           const android_dlextinfo* extinfo,
                           const void* caller_addr) {
  return dlopen_ext(filename, flags, extinfo, caller_addr);
}

void* __loader_dlopen(const char* filename, int flags, const void* caller_addr) {
  return dlopen_ext(filename, flags, nullptr, caller_addr);
}

dlopen_ext函数被__loader_dlopen函数内联了,所以__loader_dlopen函数足够被inline hook了。

__int64 __fastcall _loader_dlopen(__int64 a1, unsigned int a2, __int64 a3)
{
  _BYTE *v3; // x21
  __int64 v4; // x19
  unsigned int v5; // w20
  __int64 v6; // x19
  __int64 v7; // x0

  v3 = (_BYTE *)a1;
  v4 = a3;
  v5 = a2;
  _dl_pthread_mutex_lock(&_dl__ZL10g_dl_mutex);
  _dl__ZN12LinkerLogger10ResetStateEv(&_dl_g_linker_logger);
  v6 = _dl__Z9do_dlopenPKciPK17android_dlextinfoPKv(v3, v5, 0LL, v4);
  if ( !v6 )
  {
    v7 = _dl__Z23linker_get_error_bufferv(0LL);
    _dl__ZL23__bionic_format_dlerrorPKcS0_("dlopen failed", v7);
  }
  _dl_pthread_mutex_unlock(&_dl__ZL10g_dl_mutex);
  return v6;
}

首先为了验证是不是因为LR寄存器导致的问题,使用dump函数"(dump((void*)dlopen, onPreCallBack, NULL, “dlopen”)😉",只在函数之前打印下参数寄存器,之后调用原函数,发现可以正常运行了。

使用导入表/导出表hook dlopen也出现该问题。

hook __loader_dlopen代替dlopen,发现dump、replace都可以正常运行,至此可以确定确实是修改了LR寄存器的问题。

而__loader_dlopen函数导出在linker,通过dlsym(RTLD_DEFAULT, “__loader_dlopen”)是获取不到的,linker不是动态库,这里采用

//高版本尽量使用,没有详细看各个版本的Android,不过记忆中从Android8.0使用的libdl.so,所以应该8.0及以上
//或者一步到位hook所有dlopen(包括dlopen_ext)都调用的函数do_dlopen,这个函数在linker中,所以需要在linker中找到函数地址,并没有导出,但是没有去符号,可以自己解析
//    void *do_dlopen = dlsym(RTLD_DEFAULT, "__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv");
//    LE("do_dlopen=%p", do_dlopen);
//    dump((void*)do_dlopen, onPreCallBack, onCallBack, "do_dlopen");
void test__loader_dlopen(){
    //因为__loader_dlopen只在libdl.so导入,真正的导出函数是在linker中
    void *__loader_dlopen = dlsym(RTLD_DEFAULT, "__loader_dlopen");//0x7d3ff19dd8
    LE("__loader_dlopen=%p", __loader_dlopen);
    if (!__loader_dlopen) {
        //通过libdl.so拿到的__loader_dlopen地址就是linker导出的函数地址。看源码的话这个__loader_dlopen函数只是调用dlopen_ext,应该因为
        //太短无法hook,不过就是因为太短被和dlopen_ext内联成一个函数了,dlopen_ext未导出。所以可以hook。
        void *dl = dlopen("libdl.so", RTLD_LAZY);
        LE("libdl.so=%p", dl);
        __loader_dlopen = dlsym(dl, "__loader_dlopen");//0x7d3ff19dd8
        LE("__loader_dlopen=%p", __loader_dlopen);
    }

    if (!__loader_dlopen) {
        return;
    }

    //    dump((void*)__loader_dlopen, onPreCallBack, onCallBack, "__loader_dlopen");
    const RetInfo info = dump_replace((void *) __loader_dlopen, (void *) (my__loader_dlopen), NULL,
                                       NULL, "__loader_dlopen");
    if (info.status != success) {
        LE("hook __loader_dlopen error=%d", info.status);
    }

}

通过第三个参数确定libEGL_adreno.so是被libGLESv2_adreno.so内的函数dlopen;libGLESv2_adreno.so应该是被libadreno_utils.so内的函数dlopen;依此类推libgrallocutils.so、libadreno_utils.so、libboost.so、libgui.so。可能有误,因为这个其实不关键,我也不关心gui流程,有谁关心这个的可以分析整个流程。

因为传入的p(LR寄存器的值)导致返回null,即使不为null,后面对应的android_namespace_t肯定也不一致。

soinfo* find_containing_library(const void* p) {
  ElfW(Addr) address = reinterpret_cast<ElfW(Addr)>(p);
  for (soinfo* si = solist_get_head(); si != nullptr; si = si->next) {
    if (address >= si->base && address - si->base < si->size) {
      return si;
    }
  }
  return nullptr;
}

最终结果就是已加载的so是其他命名空间、classloader加载的,所以查找不到最终返回null。看到这发现和7.0之后的限制私有api是一回事。那么即然是根据第三个参数判断的,那么hook __loader_dlopen函数传入一个符合的地址是不是就可以绕过了。

进行测试:
在这里插入图片描述
看到日志linker: library “/system/lib/libnativehelper.so” ("/system/lib/libnativehelper.so") needed or dlopened by可以确定就是android7.0开始采用的命名空间限制私有api调用。

对其进行过滤,把LR寄存器的值改为符合地址范围的,这里我使用dlerror的地址,只要符合即可。通过日志可以看到成功了。
在这里插入图片描述

所以这就是我早期针对Android7.0限制私有api的一种绕过方式,比起自己加载系统so,自己解析再通过偏移值确定地址好些。。。,通过这还可以延伸出我动态注入时做的一件事情全局绕过私有api限制。以前frida是没有这么做过的,没看最近的代码,不确定是否现在也采用这样的方法了没有。

所以对于hook dlopen可以这么划分:
1、低于7.0的版本只hook dlopen即可。
2、8.0以上最好hook __loader_dlopen或者do_dlopen。或者hook dlopen过滤发现是系统so(限制的私有so)不取返回值(即不修改LR寄存器),或者自行解析、重组dlopen的指令传入原来的LR寄存器(不推荐)。
3、7.0-7.1的hook do_dlopen,因为没有采用libdl.so做中转。
4、全局关闭命名空间限制使用私有api,不是特别推荐吧,但其实我是这么做的。
5、更投机取巧的方法,即然只要LR寄存器合乎规则即可,那么可以针对dlopen、dlsym从BL指令转成先设置LR寄存器为一个libc等so里面的一个不常用的函数c(常用的也不是不行,但是对效率可能有影响),之后使用B指令跳转到备份/修复的dlopen原函数执行。c函数进行hook,使用一样的跳板,只是shellcode有些不一样。因为dlopen等函数执行完会跳回c函数,那么LR寄存器就是c函数的地址,只要c函数不是一个递归函数,那么就可以通过判断LR寄存器区别出是dlopen等函数的返回还是其他函数正常调用的c函数。当是dlopen函数返回时再跳回dlopen的shellcode处完成返回。

未解决的疑问:
我记得梆梆也hook了dlopen、dlsym,arm使用的是Cydia Substrate框架,在高版本上应该也存在这个问题的,但是以前没仔细看,我记得应该没特殊处理的,奇怪。抽时间再逆下,除非他改了生成的shellcode,非自己要hook的so就过滤掉,不修改LR寄存器。

已解决:后来抽时间看下,原来也是解析了__loader_dlopen、__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv等,

if ( sdk_0 >= 24 )
      {
        if ( g_addr_do_dlopen )
        {
            p_dlopen = (void *(**)(const char *, int))g_addr_do_dlopen;

所以也是参照上面的3来做的。不过看其定位了一些安卓源码中没有的符号,所以也存在兼容性问题?看来也是存在风险的。毕竟不是标准c/c++ api,确实有可能被魔改。

dlsym等其他几个函数也是类似的处理,甚至如果只是为了调用系统的私有api,只hook __loader_dlsym即可,使用dlsym(RTLD_DEFAULT, “xx”)即可,把__loader_dlsym的第三个参数改为合法的地址。

2016-06-24 14:32:01 zhuanshenai 阅读数 7143

android 逆向分析过程有时候需要hook dlopen和dlsym函数,打印调用的库或者函数名。
利用cydia substrate的动态库,或者ThomasKing大大的ELF-ARM-HOOK-Library 两个都行,但是cydia 支持x86的hook,模拟器hook 比较方便(个人见解)。
我用的是cydia 实现的hook。具体如下,

//substrate.cpp
void *(*MSFindSymbol)(MSImageRef image, const char *name);
MSImageRef(*MSGetImageByName)(const char *file);
void(*MSJavaHookMethod)(JNIEnv *jni, jclass _class, jmethodID methodID, void *function, void **result);
void(*MSHookFunction)(void *symbol, void *replace, void **result);
void(*MSJavaHookClassLoad)(JNIEnv *jni, const char *name, void(*callback)(JNIEnv *, jclass, void *), void *data);
__attribute__((__constructor__)) static void _MSInitialize()
{
    void* handlesubdsub = dlopen("/system/lib/libsubstrate.so", RTLD_NOW);
    void* handlesubdvm = dlopen("/system/lib/libsubstrate-dvm.so", RTLD_NOW);
    if (handlesubdsub != NULL)
    {
        LOGD("***start dlsym methond***");
        MSGetImageByName = (MSImageRef(*)(const char *file))dlsym(handlesubdsub, "MSGetImageByName");
        MSFindSymbol = (void*(*)(MSImageRef image, const char *name))dlsym(handlesubdsub, "MSFindSymbol");
        MSHookFunction = (void(*)(void *symbol, void *replace, void **result))dlsym(handlesubdsub, "MSHookFunction");
        MSJavaHookClassLoad = (void(*)(JNIEnv *jni, const char *name, void(*callback)(JNIEnv *, jclass, void *), void *data))dlsym(handlesubdvm, "MSJavaHookClassLoad");
        MSJavaHookMethod = (void(*)(JNIEnv *jni, jclass _class, jmethodID methodID, void *function, void **result)) dlsym(handlesubdvm, "MSJavaHookMethod");
        LOGD("***end dlsym methond***");
    }
    else
    {
        LOGE("***open so file fail or can't find file!***");
    }

}

以上代码:可以不用安装cydia的apk,把libsubstrate.so、 libsubstrate-dvm.so 两个动态库复制到 /system/lib/目录下。 直接调用 相关函数,ELF-ARM-HOOK-Library 也有同样的方法自己实现。
hook dlopen 函数 就要获取 函数的地址:

    //这个方法来自 android inject 用于获取地址
    void* get_module_base(int pid, const char* module_name)
{
    FILE *fp;
    long addr = 0;
    char *pch;
    char filename[32];
    char line[1024];
    if (pid < 0) {
        /* self process */
        snprintf(filename, sizeof(filename), "/proc/self/maps", pid);
    }
    else {
        snprintf(filename, sizeof(filename), "/proc/%d/maps", pid);
    }
    fp = fopen(filename, "r");
    if (fp != NULL) {
        while (fgets(line, sizeof(line), fp)) {
            if (strstr(line, module_name)) {
                pch = strtok(line, "-");
                addr = strtoul(pch, NULL, 16);

                if (addr == 0x8000)
                    addr = 0;

                break;
            }
        }
        fclose(fp);
    }
    return (void *)addr;
}
//这个方法来自 android inject 用于获取地址
void* get_remote_addr(int target_pid, const char* module_name, void* local_addr)
{
    void* local_handle, *remote_handle;

    local_handle = get_module_base(-1, module_name);
    remote_handle = get_module_base(target_pid, module_name);

    LOGI("[+] get_remote_addr: local[%x], remote[%x]\n", local_handle, remote_handle);

    void * ret_addr = (void *)((uint32_t)local_addr + (uint32_t)remote_handle - (uint32_t)local_handle);

#if defined(__i386__)    
    if (!strcmp(module_name, "/system/lib/libc.so")) {
        ret_addr += 2;
    }
#endif    
    return ret_addr;
}

hook方法

    //声明各个变量存放地址
    void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;
    //获取dlopen地址
    dlopen_addr = get_remote_addr(getpid(), "/system/bin/linker", (void *)dlopen);
    LOGI("[+] dlopen_addr: [%x]", dlopen_addr);
    //hook dlopen方法  下面方法类似
    MSHookFunction((void*)dlopen_addr, (void*)newdlopen, (void**)&olddlopen);

    dlsym_addr = get_remote_addr(getpid(), "/system/bin/linker", (void *)dlsym);
    LOGI("[+] dlsym_addr: [%x]", dlsym_addr);
    MSHookFunction(dlsym_addr, (void*)newdlsym, (void**)&olddlsym);
    dlclose_addr = get_remote_addr(getpid(), "/system/bin/linker", (void *)dlclose);
    dlerror_addr = get_remote_addr(getpid(), "/system/bin/linker", (void *)dlerror);
//hook方法
void* (*olddlsym)(void*  handle, const char*  symbol);
void* newdlsym(void*  handle, const char*  symbol) {
    LOGD("the handle [0x%x] symbol name:%s",handle,symbol);
    return olddlsym(handle, symbol);
}
void* (*olddlopen)(const char* filename, int myflags);
void* newdlopen(const char* filename, int myflags) {
    LOGD("the dlopen name :%s",filename);
    return olddlopen(filename, myflags);
}

效果图

2016-08-13 21:06:22 jltxgcy 阅读数 3774

   0x00 进程注入

    通过ptrace机制,调用目标进程的dlopen来加载我们的so,实现进程注入。

    这部分知识在Android中的so注入(inject)和挂钩(hook) - For both x86 and arm,有详细的解释,请读者先阅读这篇文章。

    那么被注入的进程是如何返回到我们的进程中呢?答案在regs->ARM_lr = 0。具体的分析请参考Android注入完全剖析


   0x01 要被注入的so

    这个so主要的任务是hook住Zygote的Native Method。基于HOOK的Anti-debug调用点trace和Anti-anti这篇文章讲解了原理和对应的工程。为了方便起见,我上传了源码工程,这个被注入的so的工程可以在http://download.csdn.net/detail/jltxgcy/9602656下载。

    下面是工程目录:

    

    由于我们要注入的so是两个,一是libTKHooklib.so,一个是libtrace_anti.so,而入口函数hook_entry在libtrace_anti.so中,由于Android中的so注入(inject)和挂钩(hook) - For both x86 and arm这篇文章只介绍了注入一个so的源码,所以我们要修改源码,注入两个so。

    那么怎么样才能注入两个so呢?很简单,libTKHooklib.so是libtrace_anti.so所依赖的,所以先注入libTKHooklib.so,然后再注入libtrace_anti.so,并调用hook_entry,即可。

    我们也可以先在java层用System.load来先后加载这两个so,看看会不会报错。如果不报错,就放心的加载吧。注入这两个so,修改Android中的so注入(inject)和挂钩(hook) - For both x86 and arm后的代码如下:

int inject_remote_process(pid_t target_pid, const char *library_path, const char *depend_path ,const char *function_name, const char *param, size_t param_size)
{
    int ret = -1;
    void *mmap_addr, *dlopen_addr, *dlsym_addr, *dlclose_addr, *dlerror_addr;
    void *local_handle, *remote_handle, *dlhandle;
    uint8_t *map_base = 0;
    uint8_t *dlopen_param1_ptr, *dlsym_param2_ptr, *saved_r0_pc_ptr, *inject_param_ptr, *remote_code_ptr, *local_code_ptr;

    struct pt_regs regs, original_regs;
    extern uint32_t _dlopen_addr_s, _dlopen_param1_s, _dlopen_param2_s, _dlsym_addr_s, \
        _dlsym_param2_s, _dlclose_addr_s, _inject_start_s, _inject_end_s, _inject_function_param_s, \
        _saved_cpsr_s, _saved_r0_pc_s;

    uint32_t code_length;
    long parameters[10];

    DEBUG_PRINT("[+] Injecting process: %d\n", target_pid);

    if (ptrace_attach(target_pid) == -1)
        goto exit;

    if (ptrace_getregs(target_pid, &regs) == -1)
        goto exit;

    /* save original registers */
    memcpy(&original_regs, &regs, sizeof(regs));

    mmap_addr = get_remote_addr(target_pid, libc_path, (void *)mmap);
    DEBUG_PRINT("[+] Remote mmap address: %x\n", mmap_addr);

    /* call mmap */
    parameters[0] = 0;  // addr
    parameters[1] = 0x4000; // size
    parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC;  // prot
    parameters[3] =  MAP_ANONYMOUS | MAP_PRIVATE; // flags
    parameters[4] = 0; //fd
    parameters[5] = 0; //offset

    if (ptrace_call_wrapper(target_pid, "mmap", mmap_addr, parameters, 6, &regs) == -1)
        goto exit;

    map_base = ptrace_retval(&regs);

    dlopen_addr = get_remote_addr( target_pid, linker_path, (void *)dlopen );
    dlsym_addr = get_remote_addr( target_pid, linker_path, (void *)dlsym );
    dlclose_addr = get_remote_addr( target_pid, linker_path, (void *)dlclose );
    dlerror_addr = get_remote_addr( target_pid, linker_path, (void *)dlerror );

    DEBUG_PRINT("[+] Get imports: dlopen: %x, dlsym: %x, dlclose: %x, dlerror: %x\n",
            dlopen_addr, dlsym_addr, dlclose_addr, dlerror_addr);
    printf("depend path = %s\n", depend_path);
    ptrace_writedata(target_pid, map_base, depend_path, strlen(depend_path) + 1);

    parameters[0] = map_base;
    parameters[1] = RTLD_NOW| RTLD_GLOBAL;

    if (ptrace_call_wrapper(target_pid, "dlopen", dlopen_addr, parameters, 2, &regs) == -1) //先注入libTKHooklib.so
    	goto exit;

    mmap_addr = get_remote_addr(target_pid, libc_path, (void *)mmap);
    DEBUG_PRINT("[+] Remote mmap address: %x\n", mmap_addr);

	/* call mmap */
	parameters[0] = 0;  // addr
	parameters[1] = 0x4000; // size
	parameters[2] = PROT_READ | PROT_WRITE | PROT_EXEC;  // prot
	parameters[3] =  MAP_ANONYMOUS | MAP_PRIVATE; // flags
	parameters[4] = 0; //fd
	parameters[5] = 0; //offset

	if (ptrace_call_wrapper(target_pid, "mmap", mmap_addr, parameters, 6, &regs) == -1)
		goto exit;

	map_base = ptrace_retval(&regs);
    printf("library path = %s\n", library_path);
    ptrace_writedata(target_pid, map_base, library_path, strlen(library_path) + 1);

    parameters[0] = map_base;
    parameters[1] = RTLD_NOW| RTLD_GLOBAL;

    if (ptrace_call_wrapper(target_pid, "dlopen", dlopen_addr, parameters, 2, &regs) == -1) //然后再注入libtrace_anti.so
        goto exit;

    void * sohandle = ptrace_retval(&regs);

#define FUNCTION_NAME_ADDR_OFFSET       0x100
    ptrace_writedata(target_pid, map_base + FUNCTION_NAME_ADDR_OFFSET, function_name, strlen(function_name) + 1);
    parameters[0] = sohandle;
    parameters[1] = map_base + FUNCTION_NAME_ADDR_OFFSET;

    if (ptrace_call_wrapper(target_pid, "dlsym", dlsym_addr, parameters, 2, &regs) == -1)
        goto exit;

    void * hook_entry_addr = ptrace_retval(&regs);
    DEBUG_PRINT("hook_entry_addr = %p\n", hook_entry_addr);

#define FUNCTION_PARAM_ADDR_OFFSET      0x200
    ptrace_writedata(target_pid, map_base + FUNCTION_PARAM_ADDR_OFFSET, param, strlen(param) + 1);
    parameters[0] = map_base + FUNCTION_PARAM_ADDR_OFFSET;

    if (ptrace_call_wrapper(target_pid, "hook_entry", hook_entry_addr, parameters, 1, &regs) == -1)
        goto exit;

    printf("Press enter to dlclose and detach\n");
    getchar();
    parameters[0] = sohandle;

    if (ptrace_call_wrapper(target_pid, "dlclose", dlclose, parameters, 1, &regs) == -1)
        goto exit;

    /* restore */
    ptrace_setregs(target_pid, &original_regs);
    ptrace_detach(target_pid);
    ret = 0;

exit:
    return ret;
}

int main(int argc, char** argv) {
    pid_t target_pid;
    target_pid = find_pid_of("zygote");
    if (-1 == target_pid) {
        printf("Can't find the process\n");
        return -1;
    }
    //target_pid = find_pid_of("/data/test");
    inject_remote_process(target_pid, argv[1], argv[2], "hook_entry",  "I'm parameter!", strlen("I'm parameter!"));
    return 0;
}

    在代码中,先注入libTKHooklib.so,然后再注入libtrace_anti.so。    


   0x02 过反调试和过防dump

    在基于HOOK的Anti-debug调用点trace和Anti-anti已经讲过了如何过防dump,代码如下:

static int new_inotify_add_watch(int fd, const char *pathname, uint32_t mask){
	unsigned lr;
	GETLR(lr);
	LOGD("[*] Traced-inotify_add_watch Call function: 0x%x\n", lr);

	LOGD("[*] Traced-inotify_add_watch --> %s, 0x%x", pathname, mask);
	return old_inotify_add_watch(fd, pathname, 0x00000200);
}
    过反调试,只给出了部分代码,没有给出完整代码,这里给出完整的代码:

bool HasGenFile(const char *re_path) {
	FILE *fpr;
	char buffer[BUFFERSIZE];
	fpr = old_fopen(re_path, "r");
	if (fpr == NULL) {
		return false;
	}
	while (fgets(buffer, BUFFERSIZE, fpr) != NULL) {
		if (strstr(buffer, "State") != NULL) {
			return true;
		}
		if (strstr(buffer, "TracerPid") != NULL) {
			return true;
		}
	}
	LOGD("[*] HasGenFile return false");
	return false;
}

FILE* hookToNewFile(const char *path, const char * mode) {
	char re_path[256];
	sprintf(re_path, "/data/local/tmp/status");
	if (!HasGenFile(re_path)) {
		char buffer[BUFFERSIZE];
		FILE *fpr, *fpw;
		fpr = old_fopen(path, "r");
		fpw = old_fopen(re_path, "w");
		if (fpr == NULL || fpw == NULL) {
			LOGD("[E] re-path [%s]failed", path);
			return old_fopen(path, mode);
		}
		while (fgets(buffer, BUFFERSIZE, fpr) != NULL) {
			if (strstr(buffer, "State") != NULL) {
				fputs("State:\tS (sleeping)\n", fpw);
			}
			if (strstr(buffer, "TracerPid") != NULL) {
				fputs("TracerPid:\t0\n", fpw);
			} else {
				fputs(buffer, fpw);
			}
		}
		fclose(fpr);
		fclose(fpw);
	}
	LOGD("[*] hookToNewFile Success");
	return old_fopen(re_path, mode);
}

FILE* new_fopen(const char *path,const char * mode){
	unsigned lr;
	GETLR(lr);

	if(strstr(path, "status") != NULL){
		LOGD("[*] Traced-fopen Call function: 0x%x\n", lr);
		if(strstr(path, "task") != NULL){
			LOGD("[*] Traced-anti-task/status");
		}else {
			LOGD("[*] Traced-anti-status");
			return hookToNewFile(path, mode);
		}
	}else if(strstr(path, "wchan") != NULL){
		LOGD("[*] Traced-fopen Call function: 0x%x\n", lr);
		LOGD("[*] Traced-anti-wchan");
	}
	return old_fopen(path, mode);
}
    使读取的文件实现重定向到/data/local/tmp/status。

2018-11-19 14:27:04 u014288349 阅读数 678

http://blog.51cto.com/haidragon/2135226

https://github.com/gaffe23/linux-inject

 

目的:将动态库so注入到目标程序中

核心原理:1、获取目标程序函数(__libc_dlopen_mode、malloc、free)的地址;2、获取一段可执行的内存地址;3、将汇编注入代码写入这段内存地址;4、通过int3断点来查看函数调用返回值和恢复写入内存数据。

1、通过读取(/proc/%d/maps)获取libc的基地址,通过dlopen和dlsym获取当前程序中需要获取的函数(__libc_dlopen_mode、malloc、free)的地址,函数如下:

long getFunctionAddress(char* funcName)

{

void* self = dlopen("libc.so.6", RTLD_LAZY);

void* funcAddr = dlsym(self, funcName);

return (long)funcAddr;

}

因为函数(__libc_dlopen_mode、malloc、free)都在libc库中,所以用获取的地址-基地址=偏移地址;

同样读取目标程序的(/proc/%d/maps)获取libc的基地址,用此基地址+偏移地址=函数地址;

所以:偏移地址 = (当前函数地址)-(当前基地址);

目标函数地址 = (目标库基地址) + (获取的偏移地址)

2、读取目标程序(/proc/%d/maps)一段可执行(属性为x)内存地址,此内存地址需要计算最佳位置:

// find a good address to copy code to

long addr = freespaceaddr(target) + sizeof(long);

 

// now that we have an address to copy code to, set the target's rip to

// it. we have to advance by 2 bytes here because rip gets incremented

// by the size of the current instruction, and the instruction at the

// start of the function to inject always happens to be 2 bytes long.

regs.rip = addr + 2;

,计算注入函数的大小,注入函数的最后一个返回值写为int3用于恢复现场等,将需要写入内存大小的数据保存,写入注入的汇编函数(malloc分配注入so路径大小内存->使用dlopen打开注入的so->free分配内存)

3、写入汇编代码后,运行,在第一个int3中断,向malloc返回值寄存器rax赋值注入so的路径,运行,第二个int3中断,判断寄存器rax返回的dlopen函数打开的so地址是否为空,运行,第三个int3中断,判断free是否成功,运行,中断后恢复写入的内存数据,并恢复原始寄存器值,之后停止附加,退出.

 

注:arm通过raise替换int3 

// call raise(SIGTRAP) to get back control of the target.

asm(

// pop off the stack to get the address of raise()

"pop {r1} \n"

// specify SIGTRAP as the first argument

"mov r0, #5 \n"

// call raise()

"blx r1"

);

https://github.com/crmulliner/adbi

https://blog.csdn.net/roland_sun/article/details/34109569

 

adbi分为so注入程序和hook程序:

adbi计算偏移值有两种方法:

1、和linux-inject一样采用(1)偏移地址 = (当前函数地址)-(当前基地址);

目标函数地址 = (目标库基地址) + (获取的偏移地址);

(2)通过读取目标so库中的符号表来获取偏移值

2、注入方式和linux-inject不一样,adbi通过将汇编注入代码写入栈中:

// write code to stack

codeaddr = regs.ARM_sp - sizeof(sc);

通过获得mprotect函数地址来改写整个栈的读写保护,最后执行栈中的代码来调用dlopen打开so路径,之后恢复寄存器,如下:

unsigned int sc[] = {

0xe59f0040, //        ldr     r0, [pc, #64]   ; 48 <.text+0x48>

0xe3a01000, //        mov     r1, #0  ; 0x0

0xe1a0e00f, //        mov     lr, pc

0xe59ff038, //        ldr     pc, [pc, #56]   ; 4c <.text+0x4c>

0xe59fd02c, //        ldr     sp, [pc, #44]   ; 44 <.text+0x44>

0xe59f0010, //        ldr     r0, [pc, #16]   ; 30 <.text+0x30>

0xe59f1010, //        ldr     r1, [pc, #16]   ; 34 <.text+0x34>

0xe59f2010, //        ldr     r2, [pc, #16]   ; 38 <.text+0x38>

0xe59f3010, //        ldr     r3, [pc, #16]   ; 3c <.text+0x3c>

0xe59fe010, //        ldr     lr, [pc, #16]   ; 40 <.text+0x40>

0xe59ff010, //        ldr     pc, [pc, #16]   ; 44 <.text+0x44>

0xe1a00000, //        nop                     r0

0xe1a00000, //        nop                     r1

0xe1a00000, //        nop                     r2

0xe1a00000, //        nop                     r3

0xe1a00000, //        nop                     lr

0xe1a00000, //        nop                     pc

0xe1a00000, //        nop                     sp

0xe1a00000, //        nop                     addr of libname

0xe1a00000, //        nop                     dlopenaddr

};

sc[11] = regs.ARM_r0;

sc[12] = regs.ARM_r1;

sc[13] = regs.ARM_r2;

sc[14] = regs.ARM_r3;

sc[15] = regs.ARM_lr;

sc[16] = regs.ARM_pc;

sc[17] = regs.ARM_sp;

sc[19] = dlopenaddr;

libaddr = regs.ARM_sp - n*4 - sizeof(sc);

sc[18] = libaddr;

将此段数据压入栈后,调用mprotect来改写读写保护:

regs.ARM_sp = regs.ARM_sp - n*4 - sizeof(sc);

 

// call mprotect() to make stack executable

regs.ARM_r0 = stack_start; // want to make stack executable

//printf("r0 %x\n", regs.ARM_r0);

regs.ARM_r1 = stack_end - stack_start; // stack size

//printf("mprotect(%x, %d, ALL)\n", regs.ARM_r0, regs.ARM_r1);

regs.ARM_r2 = PROT_READ|PROT_WRITE|PROT_EXEC; // protections

 

// normal mode, first call mprotect

if (nomprotect == 0) {

if (debug)

printf("calling mprotect\n");

regs.ARM_lr = codeaddr; // points to loading and fixing code

regs.ARM_pc = mprotectaddr; // execute mprotect()

}

// no need to execute mprotect on old Android versions

else {

regs.ARM_pc = codeaddr; // just execute the 'shellcode'

}

对比:adbi注入方式和linux-inject优缺点:

adbi优点:adbi注入在栈中恢复现场,不需要保存上下文寄存器,将代码写入栈中后,只需要继续运行代码

adbi缺点:代码冗余,很多bug,获取符号表时没判断重定位等

linux-inject优点:直接读取可执行段,来执行程序,通过int3来赋值,判断成功等

linux-inject缺点:注入时需要中途与可执行程序交互。会暂停程序执行等

 

 

 

 

https://blog.csdn.net/roland_sun/article/details/36049307

adbi的hook分析:

arm指令和Thumb区别等:

1、BX利用Rn寄存器中目的地址值的最后一位来判断跳转后的状态。当最后一位为0时,表示转移到ARM状态;当最后一位为1时,表示转移到Thumb状态

2、无论是ARM还是Thumb,其指令在存储器中都是边界对齐的。(2字节或者4字节对齐,最低位不起作用!)因此,在执行跳转过程中,PC寄存器中的最低位被舍弃,不起作用。在BX指令的执行过程中,最低位正好被用作状态判断的标志,不会造成存储器访问不对齐的错误

**重点分析链接**

https://blog.csdn.net/xiaoi123/article/details/80365732  猫神分析的,在hook中的那个thumb指令分析有点少(爱破解群里那位)

Hook 流程大概是:

读取elf取函数符号地址->(保存函数代码)修改函数地址处的代码跳转到我们自己的处理函数->自己处理函数->恢复原函数地址与代码->调用原函数

Hook.c的重点还是arm与thumb指令的处理了

arm处理很简单:

LDR pc, [pc, #0],由于pc寄存器读出的值实际上是当前指令地址加8,所以这里是把jump[2]的值加载进pc寄存器中,而jump[2]处保存的是hook函数的地址

thumb指令处理就需要注意了:

首先thumb指令是两字节,pc寄存器指向的是顺数第二条指令的取指,需要明白arm的三级流水线,取指-译指-执行,所以thumb的pc指向的是当前指令加4,arm是pc指向的当前指令加8,thumb这里的处理是:

h->jumpt[1] = 0xb4;

h->jumpt[0] = 0x60; // push {r5,r6}

h->jumpt[3] = 0xa5;

h->jumpt[2] = 0x03; // add r5, pc, #12

h->jumpt[5] = 0x68;

h->jumpt[4] = 0x2d; // ldr r5, [r5]

h->jumpt[7] = 0xb0;

h->jumpt[6] = 0x02; // add sp,sp,#8

h->jumpt[9] = 0xb4;

h->jumpt[8] = 0x20; // push {r5}

h->jumpt[11] = 0xb0;

h->jumpt[10] = 0x81; // sub sp,sp,#4

h->jumpt[13] = 0xbd;

h->jumpt[12] = 0x20; // pop {r5, pc}

h->jumpt[15] = 0x46;

h->jumpt[14] = 0xaf; // mov pc, r5 ;

 

但这里也有个假设,就是被hook函数的起始地址必须是4字节对齐的,哪怕被hook函数使用Thumb指令集写的

所以这里通过将自己函数地址压栈后pop弹到pc从而跳转到我们自己的处理函数

 

以上基本原理就是这些,我将通过修改此hook来hook实验下android虚拟机davlik中libvm.so中的解释函数dvmInvokeMethod,来获取method方法,从来获取android函数调用流程与参数arg等。

dlopen错误

阅读数 9263

dlopen example

阅读数 440

dlopen test in linux

博文 来自: adream307

dlopen 介绍

阅读数 427

没有更多推荐了,返回首页