首页
社区
课程
招聘
[原创]zygisk对抗绕过soinfo检测
发表于: 2025-5-27 01:01 995

[原创]zygisk对抗绕过soinfo检测

2025-5-27 01:01
995

上一篇博客写了老版本zygisk 使用dlclose 关闭so,所导致的问题,但是起始有些问题没有写清楚,所以有了第二篇

上一篇博客中的错误和解决

libart加载之后注入

libart.so 加载之后注入so,时机太早,是无法规避直接调用dlclose带来的soinfo空隙问题,上一篇博客写的确实有问题,当时我也有些疑问的,但是没有深入测试,而且在测试前期有些代码由bug导致从开始就造成了认知错误。

正确的时机和做法

我在脚本中有写,_ZN3artL25ZygoteHooks_nativePreForkEP7_JNIEnvP7_jclass 这个时机是我开始测试的时机,是没有问题的,他会在nativeForkSystemServer这个函数的前面

所以正确的时机是在 _ZN3artL25ZygoteHooks_nativePreForkEP7_JNIEnvP7_jclass 这个函数执行的时候,注入zygisk.so文件,这样只需要稍微修改一下zygisk的代码便可直接使用

第二种绕过方式

在我发博客以后,有兄弟直接进行了测试,后来还直接发给我解决这个问题的项目,这里很感谢。
参考了这个项目的代码(33eK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6v1K9h3&6Y4e0h3q4@1M7X3W2^5i4K6u0r3e0X3g2G2h3Y4W2Y4K9i4y4C8i4K6t1&6i4@1f1K6i4K6R3H3i4K6R3J5

在上一篇中,因为zygisk使用了dlopen加载so,导致了zygisk的soinfo加入到linker 的soinfo列表里,在应用进程启动的时候,调用dlclose 从soinfo的列表中删除zygisk的soinfo,因为so加载时机太早,zygisk 的soinfo位置太靠前,所以删除soinfo的时候导致出现了空隙。我采用了将libzygisk加载时机后延的方法。

其实防止检测的思想就是防止linker soinfo的列表出现空隙的。也可以说这个空隙是专门针对zygisk通过dlclose自卸载的检测。

所以我们在完成对抗的同时需要达到以下条件

  • zygisk 的soinfo必须从linker 的soinfo列表中删除
  • 不能在soinfo中留下空隙
  • libzygisk.so 需要进行在用户进程启动以后完成自卸载

对抗思路和原理

在libzygisk.so加载以后,直接删除linker 中zygisk.so的soinfo,并且不关闭libzygisk.so,同时更换自卸载函数,使用munmap完成自卸载。
这个思路从自卸载的角度来看,和dlclose大同小异,但是在操作上可控性更高。调用dlclose完成卸载,相当于同时删除soinfo,然后munmap删除已经加载到内存中libzygisk.so。但是由于so加载时机很早,卸载时机又很晚,导致linker的soinfo出现了空隙。现在将这个两个操作分开来,加载以后,直接删除soinfo,但是不释放已经加载到内存中的so,这样后续的so加载就会覆盖当前的位置,就不会出现空隙了,然后在应用进程启动以后在使用munmap 关闭soinfo。

代码分析

替换自卸载函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
DCL_HOOK_FUNC(static int, pthread_attr_setstacksize, void *target, size_t size) {
    int res = old_pthread_attr_setstacksize((pthread_attr_t *) target, size);
 
    LOGV("pthread_attr_setstacksize called in [tid, pid]: %d, %d", gettid(), getpid());
 
    // Only perform unloading on the main thread
    if (gettid() != getpid()) return res;
 
    if (g_hook->should_unmap) {
        g_hook->restore_plt_hook();
        if (g_hook->should_unmap) {
            void *start_addr = g_hook->start_addr;
            size_t block_size = g_hook->block_size;
            delete g_hook;
 
            // Because both `pthread_attr_setstacksize` and `munmap` have the same function
            // signature, we can use `musttail` to let the compiler reuse our stack frame and thus
            // `munmap` will directly return to the caller of `pthread_attr_setstacksize`.
            LOGD("unmap libzygisk.so loaded at %p with size %zu", start_addr, block_size);
            [[clang::musttail]] return munmap(start_addr, block_size);
        }
    }
 
    delete g_hook;
    return res;
}

删除soinfo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
bool dropSoPath(const char *target_path) {
    bool path_found = false;
    if (solist == nullptr && !initialize()) {
        LOGE("failed to initialize solist");
        return path_found;
    }
    for (auto *iter = solist; iter; iter = iter->getNext()) {
        if (iter->getPath() && strstr(iter->getPath(), target_path)) {
            SoList::ProtectedDataGuard guard;
            LOGD("dropping solist record for %s with size %zu", iter->getPath(), iter->getSize());
            if (iter->getSize() > 0) {
                iter->setSize(0);
                SoInfo::soinfo_free(iter);
                path_found = true;
            }
        }
    }
    return path_found;
}

删除计数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void resetCounters(size_t load, size_t unload) {
    if (solist == nullptr && !initialize()) {
        LOGE("failed to initialize solist");
        return;
    }
    if (g_module_load_counter == nullptr || g_module_unload_counter == nullptr) {
        LOGD("g_module counters not defined, skip reseting them");
        return;
    }
    auto loaded_modules = *g_module_load_counter;
    auto unloaded_modules = *g_module_unload_counter;
    if (loaded_modules >= load) {
        *g_module_load_counter = loaded_modules - load;
        LOGD("reset g_module_load_counter to %zu", (size_t) *g_module_load_counter);
    }
    if (unloaded_modules >= unload) {
        *g_module_unload_counter = unloaded_modules - unload;
        LOGD("reset g_module_unload_counter to %zu", (size_t) *g_module_unload_counter);
    }
}

最后

就写到这吧,感谢开源项目提供的解决方案,我也不知都那位大佬搞得这个方案,只能说思路很新颖,写的很好。
这是大佬项目地址 c4cK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6v1K9h3&6Y4e0h3q4@1M7X3W2^5i4K6u0r3e0X3g2G2h3Y4W2Y4K9i4y4C8i4@1g2r3i4@1u0o6i4K6S2o6i4@1f1$3i4K6R3^5i4K6V1I4i4@1f1#2i4@1t1%4i4@1t1J5i4@1f1%4i4@1u0n7i4K6S2r3i4@1f1$3i4K6S2m8i4K6R3@1i4@1f1^5i4@1p5J5i4@1q4p5i4@1f1#2i4K6R3^5i4@1t1H3i4@1f1$3i4K6R3^5i4K6V1I4i4@1f1%4i4K6W2m8i4K6R3@1i4@1f1&6i4@1p5I4i4@1t1&6i4@1f1%4i4K6W2n7i4@1q4q4i4@1f1@1i4@1t1^5i4K6S2m8i4@1f1@1i4@1u0m8i4K6R3$3
我的项目上一次已经放过地址了,这次就不放了


[培训]科锐逆向工程师培训第53期2025年7月8日开班!

收藏
免费 4
支持
分享
最新回复 (1)
雪    币: 3101
活跃值: (5899)
能力值: ( LV11,RANK:185 )
在线值:
发帖
回帖
粉丝
2
这不是我研究的,我只是分享。soinfo其实还可以填坑的,方法很多
2025-6-7 18:58
0
游客
登录 | 注册 方可回帖
返回