bytedance / bhook

:fire: ByteHook is an Android PLT hook library which supports armeabi-v7a, arm64-v8a, x86 and x86_64.
https://github.com/bytedance/bhook/tree/main/doc#readme
MIT License
2.05k stars 315 forks source link

自动hook新加载的动态库可能没被hook的问题 #26

Closed flx413 closed 7 months ago

flx413 commented 2 years ago

我在线上使用 bhook,版本 v1.0.3,想 hook 一个动态下载的 so,发现会有 so 已经加载好,但是 bhook 没有自动去 hook 的问题,出现概率比较低。

我找到了一种复现场景,发现是 bhook 没有立即监听到动态库的加载,所以也没有 hook 了: 1.线程 1 去初始化 bhook,调用 Bytehook.init,并且调用 bytehook_hook_single; 2.线程 2 加载某一个 so;

复现的环境和代码我也贴出来:

bhook 版本:v1.0.3 机型:OnePlus6,Android 10

我改了一些代码,让问题复现:

void bh_task_manager_hook(bh_task_manager_t *self, bh_task_t *task)
{
    if(bh_dl_monitor_is_initing())
    {
        static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
        static bool oneshot_refreshed = false;
        if(!oneshot_refreshed)
        {
            bool hooked = false;
            pthread_mutex_lock(&lock);
            if(!oneshot_refreshed)
            {
                BH_LOG_INFO("task_id:%u, hit init", task->id);
                bh_dl_monitor_dlclose_rdlock();
                bh_elf_manager_refresh(bh_core_global()->elf_mgr, false, NULL, NULL);
                BH_LOG_INFO("sleep start...");  // 获取完 maps 停一会,然后 APP 另一个线程去加载新的 so,模拟动态 so 随机加载的场景
                sleep(10);
                BH_LOG_INFO("sleep end...");
                bh_task_hook(task);
                bh_dl_monitor_dlclose_unlock();
                oneshot_refreshed = true;
                hooked = true;
            }
            pthread_mutex_unlock(&lock);
            if(hooked) return;
        }
    }
    else
    {
        // start & check dl-monitor
        if(0 != bh_task_manager_init_dl_monitor(self))
        {
            // For internal tasks in the DL monitor, this is not an error.
            // But these internal tasks do not set callbacks, so there will be no side effects.
            bh_task_hooked(task, BYTEHOOK_STATUS_CODE_INITERR_DLMTR, NULL, NULL);
            return;
        }
    }

    BH_LOG_INFO("task_id:%u start hook", task->id);

    bh_dl_monitor_dlclose_rdlock();
    bh_task_hook(task);
    bh_dl_monitor_dlclose_unlock();
}

大致的流程是 bhook 会先 hook dlopen 系列函数,会先调用一遍 bh_elf_manager_refresh 把当前的 so 信息缓存起来,然后执行 hook dlopen 的任务;如果中途有别的 so 加载,bhook 会漏掉这些新的 so,相当于没监听到。 有个补救措施是 如果后续有新的 so 加载会调用 dlopen,就会触发 bh_elf_manager_refresh 再刷一遍缓存,之前漏掉的 so 也会加进来并且执行它们的 hook 任务,这样表现的情况就是 hook 被延后了。假如后续没有触发 bh_elf_manager_refresh,那么这次的 hook 就失败了

我感觉这种情况无法完全避免,如果对可靠性要求高的话,我可以每隔一段时间调用 refresh 吗?就像 xhook 的那种用法一样

flx413 commented 2 years ago

还有个疑问是 dl_iterate_phdr 和 /proc/self/maps 可以拿到当前进程已经加载的所有共享对象,是不是也有可能不是最新的?比如我遍历 /proc/self/maps 的每一行,假如遍历的中途有个新的动态库加载,会出现遍历不到这个动态库的情况吗

caikelun commented 2 years ago

嗯,确实存在这个问题,感谢指出。这个其实是“首次refresh”和“dlopen监听机制完成启动之前”的间隙。也许可以在bytehook初始化的最后阶段调用一下bh_task_manager_init_dl_monitor,然后再调用一次refresh,这样在初始化完成后就不会出现这个问题了。

bytehook比较早的内部版本是在初始化时就执行dlopen监听和refresh的,后来为了加快初始化速度,才这样调整的。后续版本我研究下怎么弥补这个问题。。。

caikelun commented 2 years ago

还有个疑问是 dl_iterate_phdr 和 /proc/self/maps 可以拿到当前进程已经加载的所有共享对象,是不是也有可能不是最新的?比如我遍历 /proc/self/maps 的每一行,假如遍历的中途有个新的动态库加载,会出现遍历不到这个动态库的情况吗

是的,单纯遍历maps是有可能有这种问题。bytehook在dlopen监听机制启动后应该就不会有这个问题了。

cchhcchh commented 2 years ago

感觉作者一直在吃老本啊,都是之前开源工程的那一套