首页
社区
课程
招聘
[原创]U3D游戏的另类汉化思路
发表于: 2020-9-25 20:10 23579

[原创]U3D游戏的另类汉化思路

2020-9-25 20:10
23579

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

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

(emmm 其实也不是不行,咋们的frida也是提供了本地hook的能力的,只是这里咋们考虑使用dobby进行hook,还有就是js在这里使用不是很方便文件操作,实现通用化的汉化模式,测试学习玩玩还是可以的 所以 嗯...,具体怎么用frida本地hook,参考我的另一篇文章---> 见文章底部

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

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

UnityGameLocalizationDemo

const-string v1, "inject"
invoke-static {v1}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
const-string v1, "inject"
invoke-static {v1}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
//运行以下脚本输出日志到文本
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())
        }
    })
}
//运行以下脚本输出日志到文本
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())
        }
    })
}
 
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);
    }
}
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);

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-11-24 15:37 被唱过阡陌编辑 ,原因: 更新新的想法,完善文章
收藏
免费 4
支持
分享
最新回复 (12)
雪    币: 5
活跃值: (1045)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
老哥,使用的游戏名字叫什么
2020-9-28 16:33
0
雪    币: 5
活跃值: (1045)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
激光步枪
就是这个游戏是哪款
2020-9-28 16:36
0
雪    币: 1441
活跃值: (5660)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
4
恋一世的爱 激光步枪 就是这个游戏是哪款
欸  这个只是个举例嘛   u3d游戏都差不多
2020-9-28 21:03
0
雪    币: 67
活跃值: (369)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
思路清奇,有帮助!
2020-10-24 02:06
0
雪    币: 79
活跃值: (853)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
试试基于FakerAndroid
2020-10-24 10:13
0
雪    币: 1441
活跃值: (5660)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
7
tiantianfen 试试基于FakerAndroid
可以这玩意非常香,用上了,就是arm64的hook总会出点问题,32没问题
2020-11-24 15:10
0
雪    币: 1422
活跃值: (1578)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
8
建议用 il2cpp_string_new 函数直接构建新的字符串,有GC, 项目写的乱糟糟的....建议分一下文件
2020-11-25 11:29
0
雪    币: 1441
活跃值: (5660)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
9
Himeko 建议用 il2cpp_string_new 函数直接构建新的字符串,有GC, 项目写的乱糟糟的....建议分一下文件
大佬指点的是,我也是用frida搞安卓游戏的时候意外的注意到这个函数,就这点c语言我也是现学现用,代码里诸多不妥的谢大佬指出    
2020-11-28 08:48
0
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
10
Hook il2cpp_class_get_methods 该方法的时候怎么没有获取到UnityEngine.UI中Button的点击事件呢 ,点击事件该怎么获取
2022-10-30 01:57
0
雪    币: 370
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
11
直接改资源文件会乱码怎么回事呢
2022-11-29 10:36
0
雪    币: 1441
活跃值: (5660)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
12
奶桃 直接改资源文件会乱码怎么回事呢
改错了呗   
2022-11-29 12:43
0
雪    币: 233
活跃值: (2323)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
13
大佬,游戏中文不显示有比较好的解决方法么
2023-2-6 21:59
0
游客
登录 | 注册 方可回帖
返回
//