一套强劲的 Il2cpp Hook 框架

前言

前一段时间研究了一下Android的Native层Hook,结果踩了一脚的雷
这里直接使用现成框架 VirtualApp 来实现Native层Hook
对于il2cpp的Hook则采用的是替换类加载器进行针对性的HOOK
萌新第一次写文章,哪里写的不好可以提出来

工具的准备

VirtualApp源码一份:GayHub
AndroidStudio和SDK:AS官网
NDK低版本一份:NDK官方下载

以上工具获取后请先自行编译vapp的源码,能够正常编译出签名版本即可

通过vapp的接口进行拓展

定位到目标接口

定位到 VirtualApp/lib/src/main/jni/Foundation/IOUniformer.cpp 文件
其中 onSoLoaded 这个函数是vapp预留给我们的接口,只要有lib加载就会走这个方法
第一个形参是文件路径,第二个形参是句柄 (就是说dlopen调用完后紧接着会走这个函数?)

过滤无效的文件

只需要判断 文件路径 是否包含我们需要的lib就好了

if (!strstr(name, "libil2cpp.so"))
    return;

通过dlsym定位函数

查看Unity项目源码中Class的定义,会发现几个有意思的函数

namespace il2cpp {
namespace vm{
class Class{
    ...
    static TypeInfo* FromName (Il2CppImage* image, const char* namespaze, const char *name);
    static const MethodInfo* GetMethodFromName (TypeInfo *klass, const char* name, int argsCount);
    static const MethodInfo* GetMethodFromNameFlags (TypeInfo *klass, const char* name, int argsCount, int32_t flags);
    ...
};
} /* namespace vm */
} /* namespace il2cpp */

FromName 函数是用来获取类 (字节码文件?没学过C#蛋疼T T)
GetMethodFromName 则是用来获取指定的方法
可以直接通过 onSoLoaded 接口传入的 句柄 定位这两个函数

void *get_method_addr = dlsym(handle, "il2cpp_class_get_method_from_name");
void *get_class_addr = dlsym(handle, "il2cpp_class_from_name");

处理偏移

由于这两个函数都只有单条无条件跳转指令 B指令


如果不做偏移直接进行Hook会导致覆盖了过多的指令导致闪退 (还是B指令偏移有问题?)

inline void* correctGetMethodAddr(void *symbol) {
    return ((char*)symbol + (*(int32_t*)(symbol) << 8 >> 6) + (*(int32_t*)((char*)symbol + (*(int32_t*)(symbol) << 8 >> 6) + 12) << 8 >> 6) + 20);
}
inline void* correctGetClassAddr(void *symbol) {
    return ((char*)symbol + (*(int32_t*)(symbol) << 8 >> 6) + (*(int32_t *)((char*)symbol + (*(int32_t *)(symbol) << 8 >> 6) + 8) << 8 >> 6) + 16);
}

使用上面的函数就可以修正辣
唉?你问我这啥玩意?
拿最基本的的 getBTarget 开刀吧

inline void* getBTarget(void *symbol){
    return ((char*)symbol + (*(int32_t*)(symbol) << 8 >> 6) + 8);
}
  1. *(int32_t *)(symbol) 获取内存中指令的HEX
  2. << 8 抹除B指令的HEX,但是内存中的HEX跟IDA中看到HEX是倒过来的,所以不能用右移
  3. >> 6 初步解析指令的偏移
  4. + 8 人工计算具体偏移. 初步的解析指令偏移并不能一次性到点,还是得靠人工修正一下
  5. (char*)symbol + 初始的地址加上偏移的地址就是目标的地址

若不止跳转一次,按照上面的步骤继续解析加人工偏移就好了

开始Hook

创建要Hook的函数原型,还有要替换进去的新函数
要替换进去的函数的参数必须跟原函数一样,否则会替换失败
那些特殊的 类指针/结构体指针/等等 直接写 void* 就好了

void* (*orig_getMethod)(void*, const char*, int, int32_t);
void* (*orig_getClass)(void*, const char*, const char*);

void* new_getMethod(void* klass, const char* name, int argsCount, int32_t flags) {
    return orig_getMethod(klass, name, argsCount, flags);
}

void* new_getClass(void* image, const char* namespaze, const char* name) {
    //LOGE("[namespaze]-> %s [name]-> %s", namespaze, name);
    return orig_getClass(image, namespaze, name);
}

注意:GetMethodFromName方法其实调用的是GetMethodFromNameFlags,但是这个方法并不能直接通过dlsym获取,所以原型必须为 GetMethodFromNameFlags 的函数原型
写好函数原型后就可以进行HOOK了,直接使用vapp集成好的 MSHookFunction 直接HOOK就完事

MSHookFunction(correctGetMethodAddr(get_method_addr),(void*)new_getMethod, (void**)&orig_getMethod);
MSHookFunction(correctGetClassAddr(get_class_addr),(void*)new_getClass, (void**)&orig_getClass);

正常启动一遍vapp,将目标应用安装进去后启动,若无 闪退/卡壳 现象即为Hook成功
不放心的话可以在替换进去的函数加上几句打印LOG的语句查看

修改替换进去的函数实现针对性Hook

可以选择判 断名称空间 或者 类名 进行下一步操作
因为后面还有Hook,不能重复执行,来一个全局变量限制一下就好

bool (*orig_get_isAutoBattle)(void*);
bool new_get_isAutoBattle(void* self) {return true;}

bool isHookAgain = false;
void* new_getClass(void* image, const char* namespaze, const char* name) {
    void* result = orig_getClass(image, namespaze, name);
    //限制Hook
    if (isHookAgain || strcmp(namespaze, "MoleMole"))
        return result;
    isHookAgain = true;

    //通过具体的 名称空间 还有 类名,获取到对应的类
    void* AvatarManager = orig_getClass(image, "MoleMole", "AvatarManager");
    //通过指定的类去Hook指定的方法
    hook_func(AvatarManager, "get_isAutoBattle", 0, 0, (void *) new_get_isAutoBattle, (void **) &orig_get_isAutoBattle);

    return result;
}

注意一下:由于C#是面向对象的语言,普通成员函数第一个参数始终是实例自身,也就是this,写代码的时候并不需要自己加速,但是Hook的时候必须加上,否则会找不到

hook_func 函数是用来通过 类的image 获取对应的 方法 并且进行Hook

void hook_func(void* class_image, const char* func_name, int argsCount, int flags, void* new_func, void** orig_func) {
    if (class_image) {
        int32_t* addr = (int32_t*)orig_getMethod(class_image, func_name, argsCount, flags);
        if (addr){
            //这里需要获取指针的内容,否则Hook不到
            MSHookFunction((void*)(*addr), new_func, orig_func);
            LOGE("HOOK SUCCESS >>> [%s]", func_name);
        } else
            LOGE("HOOK not find>>> [%s] ", func_name);
    }
}

argsCount 是函数的参数个数,静态成员方法为-1,普通成员方法不带参数为0,带参数的多少个就是多少个
flags 目前不清楚啥作用,不过IDA看了一下 GetMethodFromName 函数的调用是直接给0

想要Hook其他方法,只需要写上对应的 函数原型要Hook进去的新函数函数参数个数具体的类image,并在 new_getClass 方法里面进行Hook,就ok辣

总结

这套方案只要实现成功后基本上不需要过多的维护
不过缺点也是有的,遇到那种对li2cpp进行了加壳加密,就嗝屁了

相关 教程/工具

unity游戏生成与修改so文件教程
Il2CppDumper工具

THE END
喜欢就支持以下吧
点赞1
分享
评论 抢沙发
  • 管埋员

    昵称

  • 取消

    请填写用户信息: