U3D游戏的另类汉化思路

2020-09-25  本文已影响0人  约你一起偷西瓜

(本文是针对U3D编译为libil2cpp的情况做分析)

cache目录下一个文本以下格式创建文本

  1. 第一行为手动指定get_text(),set_text()的地址
  2. 左边列原来的文字,右边是替换成的文字


    准备文本
  3. 打包出来的so放在lib目录下重打包apk


    200925-183422.png
  4. 记得重打包的时候加上一句smali
    (代码里虽然是对加载时机做了判断,但实际建议是尽早加载inject,先于libil2cpp,避免部分汉化无效)
    const-string v1, "inject"
    invoke-static {v1}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V

首先是为了找到get_text和set_text的地址,具体可以参见我的另一篇文章 Unity游戏逆向的小工具 后文中用到的脚本
具体为什么是这样写的可以参考源码,这里不细说:
Unity\Editor\Data\il2cpp\libil2cpp\vm\Class.cpp
SetupMethods ——> SetupMethodsLocked
里面去找他的结构体即可分析得出以下脚本

//运行以下脚本输出日志到文本
frida -U -f <pkgName> -l hook.js --no-pause -o c:\temp.txt

function hook(){
    var p_size = Process.pointerSize
    var soAddr = Module.findBaseAddress("libil2cpp.so")
    Interceptor.attach(Module.findExportByName("libil2cpp.so","il2cpp_class_get_methods"),{
        onEnter:function(args){
        },
        onLeave:function(ret){
            console.error("--------------------------------------------------------")
            console.log(hexdump(ret,{length:16}))
            console.log("methodPointer => \t"+ret.readPointer() +"\t ===> \t"+ret.readPointer().sub(soAddr))
            console.log("invoker_method => \t"+ret.add(p_size*1).readPointer() +"\t ===> \t"+ret.add(p_size*1).readPointer().sub(soAddr))
            console.log("MethodName => \t\t"+ret.add(p_size*2).readPointer().readCString())
            var klass = ret.add(p_size*3).readPointer()
            console.log("namespaze => \t\t"+klass.add(p_size*3).readPointer().readCString()+"."
                +klass.add(p_size*2).readPointer().readCString())
        }
    })
}

导出到文本里就可以快速找到实际运行时候的地址以及ida里面分析的地址


当然我们inlinehook自然不可能使用js代码,那就翻译成c代码
知道这些后我们就用c去申请空间,按照这样的规则去构造一块内存区域,替换原先的返回值或者参数指针即可实现内容的替换
void *new_func_set(void *arg, void *arg1, void *arg2, void *arg3) {
    LOGD("Enter new_func_set %d",current_get_index);
    current_set_index++;
    //set的时候第二个参数可能为0,就像get的时候返回值可能为0一样
    if (arg1 == 0) return old_func_set(arg, arg1, arg2, arg3);
    try {
        memset(header_set, 0, HeaderSize);
        memcpy(header_set, arg1, HeaderSize);
        memset(end, 0, EndSize);
        memset(middle_set, 0, SplitSize);
        //以八个0作为结束,拷贝以返回值偏移12个字节的作为开始的内存数据,其实就是中间文字部分
        memccpy(middle_set, (char *) arg1 + sizeof(char) * HeaderSize, reinterpret_cast<int>(end), SplitSize);
        void* p_le =memchr(middle_set,reinterpret_cast<int>(end),SplitSize);
        //原返回值中间文字部分的长度
        int src_length = (char*)p_le - (char*)middle_set;

        int current_lines = 0;
        //初始化解析文本以“|”作为分割左边 右边部分缓存指针
        char *left = static_cast<char *>(calloc(SplitSize, sizeof(char)));
        char *right = static_cast<char *>(calloc(SplitSize, sizeof(char)));

        //读取文件后删除了源文件的,从这里的buffer拷贝一个备份来操作
        char *temp_buffer = (char *) malloc(sizeof(char) * file_size + sizeof(int));
        memcpy(temp_buffer, buffer, sizeof(char) * file_size + sizeof(int));
        char *p = strtok(temp_buffer, "\r\n");
        while (p != NULL) {
            memset(left, 0, SplitSize);
            memset(right, 0, SplitSize);
            char *s = strstr(p, "|");
            static_cast<char *>(memcpy(left, p, strlen(p) - strlen(s)));
            right = strcpy(right, s + sizeof(char));
            if (current_lines != 0) {
                char *convert_str = static_cast<char *>(malloc(strlen(left) * 2));
                memset(convert_str, 0, strlen(left) * 2);
                int length = UTF8_to_Unicode(convert_str, left);
                //length == (src_length - EndSize) &&
//                LOGD("src_length - EndSize  %d" , src_length - EndSize);
                //内存字节的比较
                if (memcmp(middle_set, convert_str, src_length - EndSize) == 0) {
                    LOGE("---> called set_text replace %s to %s   times:%d",left,right,current_set_index);
                    LOGD("Original str hex at %p === >",&middle_set);
                    hexDump(reinterpret_cast<const char *>(middle_set), src_length);
                    void *p = malloc(strlen(right) * 2);
                    int le = UTF8_to_Unicode(static_cast<char *>(p), right);
                    LOGD("Replacement str hex at %p === >",&le);
                    hexDump(reinterpret_cast<const char *>(p), le);
                    //申请空间来重新组合返回值
                    void *temp = malloc(static_cast<size_t>(HeaderSize + le + EndSize));
                    memset(temp, 0, static_cast<size_t>(HeaderSize + le + EndSize));
                    memcpy(temp, header_set, HeaderSize);
                    memcpy((char *) temp + HeaderSize, p, static_cast<size_t>(le));
                    memcpy((char *) temp + HeaderSize + le, end, EndSize);
                    LOGD("Return str hex at %p === >",&temp);
                    hexDump(static_cast<const char *>(temp), static_cast<size_t>(HeaderSize + le + EndSize));
                    free(convert_str);
                    free(left);
                    free(right);
                    free(temp_buffer);
                    return old_func_set(arg, temp, arg2, arg3);
                }
            }
            p = strtok(NULL, "\r\n");
            current_lines++;
        }
        free(left);
        free(right);
        free(temp_buffer);
        return old_func_set(arg, arg1, arg2, arg3);
    }catch (...){
        LOGE("ERRR MENORY");
        return old_func_set(arg, arg1, arg2, arg3);
    }
}

其实这里还有问题没考虑到(随便举例一个):

大概就这意思,平时空余时间写的demo,欢迎大佬来fork修改提交共同完善这个小工具,这里不适合贴太多代码,详细代码移步

UnityGameLocalizationDemo

上一篇下一篇

猜你喜欢

热点阅读