unity3d引擎的游戏的脚本DUMP及HOOK方案优化

对unity3d引擎的游戏,重要的资源就是C#脚本,脚本是被打包到APK的assets目录下的一些dll文件,有的APP可能会对其加密,运行的时候再动态解密。可以通过HOOK libmono.so中的函数mono_image_open_from_data_with_name就可以DUMP出原始内容,如果加入的有其他加解密代码,可以进一步地对解密函数进行HOOK,也是可以DUMP出内容的。

下面这个是以天天飞车为例进行的分析,先看一段LOG:

01-06 17:27:39.045 9550-9550/? I/Xposed: java.lang.Runtime loadLibrary() afterHookedMethod: tprt
01-06 17:27:39.155 9550-9550/? I/Xposed: Hid process: de.robv.android.xposed.installer
01-06 17:27:39.155 9550-9550/? I/Xposed: Hid process: com.netease.l10:PushService
01-06 17:27:39.155 9550-9550/? D/TssSDK: process_name: com.tencent.game.SSGame
01-06 17:27:39.155 9550-9550/? D/dalvikvm: Trying to load lib /data/app-lib/com.tencent.game.SSGame-1/libtprt.so 0x422f8cc0
01-06 17:27:39.155 9550-9550/? D/dalvikvm: Shared lib '/data/app-lib/com.tencent.game.SSGame-1/libtprt.so' already loaded in same CL 0x422f8cc0
01-06 17:27:39.155 9550-9550/? I/Xposed: java.lang.Runtime loadLibrary() afterHookedMethod: tprt
01-06 17:27:39.165 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: /data/app-lib/com.tencent.game.SSGame-1/libtersafe.so
01-06 17:27:39.655 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: /data/app-lib/com.tencent.game.SSGame-1/libmono.so
01-06 17:27:39.660 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: libmono.so
01-06 17:27:39.660 9550-9550/? D/SUBSTRATEHOOK: [newdlopen] hook libmono.so
01-06 17:27:39.660 9550-9550/? D/SUBSTRATEHOOK: [dumplua] libmono.so handle: 0x726c3924
01-06 17:27:39.660 9550-9550/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name_0 found: 0x7698fc4c
01-06 17:27:39.750 9550-9550/? I/Xposed: java.lang.Runtime loadLibrary() afterHookedMethod: main
01-06 17:27:39.755 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: /data/app-lib/com.tencent.game.SSGame-1/libmono.so
01-06 17:27:39.755 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: /data/app-lib/com.tencent.game.SSGame-1/libunity.so
01-06 17:27:39.775 9550-9550/? D/SUBSTRATEHOOK: the dlopen name =: libc.so

用IDA查看libmain.so发现有加载mono的逻辑,但是实际HOOK发现在main之前就已经加载了mono,原因是libtersafe.so里面有加载mono的逻辑,因为tersafe在main之前加载,所以才导致了mono比main更早地被加载了。通过上面的LOG时间顺序可以看出来。

而且这个mono并不是通过java层代码加载的,因此我们之前的xposed通过HOOK Runtime的load及loadLibrary是无法拦截mono的加载的(之前分析cocos的时候是通过拦截game这个so加载的时候注入的SO)。

//当目标SO加载时再注入
private void hookLoadSharedLibrary(final XC_LoadPackage.LoadPackageParam lpparam, final String hostSoName) {
    if (TextUtils.isEmpty(hostSoName) == true) {
        return;
    }

    /*
      xposed不能HOOK java.lang.System loadLibrary函数,参考:[Hooking System.loadLibrary causes crash. #87] https://github.com/rovo89/XposedBridge/issues/87
     */
    XC_MethodHook.Unhook unhook = findAndHookMethod(Runtime.class, "loadLibrary", String.class, ClassLoader.class, new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            //XposedBridge.log("java.lang.Runtime loadLibrary() beforeHookedMethod: " + param.args[0]);
        }

        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            super.afterHookedMethod(param);
            String target = (String) param.args[0];
            XposedBridge.log("java.lang.Runtime loadLibrary() afterHookedMethod: " + target);
            if (target.equals(hostSoName)) {
                loadInjectSoFile(lpparam);
            }
        }
    });

    if (unhook != null) {
        XposedBridge.log("java.lang.Runtime loadLibrary() hook ok");
    } else {
        XposedBridge.log("java.lang.Runtime loadLibrary() hook failed");
    }

    findAndHookMethod(Runtime.class, "load", String.class, new XC_MethodHook() {
        @Override
        protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
            //XposedBridge.log("java.lang.Runtime load() beforeHookedMethod: " + param.args[0]);
        }

        @Override
        protected void afterHookedMethod(MethodHookParam param) throws Throwable {
            super.afterHookedMethod(param);
            XposedBridge.log("java.lang.Runtime load() beforeHookedMethod: " + param.args[0]);
        }
    });
}                                                             

因为这里的mono是被别其他的SO在初始化的时候(JNI_OnLoad)通过dlopen加载的,所以就导致了在xposed中无法拦截目标SO的加载事件,很容易漏网。那么该怎么办呢?
- 想法一:如果APP有SO(一个或多个),即使有个别SO是在其他SO的JNI_OnLoad中通过dlopen加载的,那么至少要有一个SO是要通过java层代码加载。那就要面临一个问题,到底应该拦截哪个SO呢?就像上面的那个例子一样,本来以为只要拦截main这个so的加载,但是tersafe被加载的时候mono就已经被加载了。而且每次要具体分析哪个SO加载了目标SO,还是有点麻烦的,不够通用。
- 想法二:在xopsed层不拦截Runtime的load及loadLibrary函数了,只要拦截到APP启动,就load注入SO,也就是第一时间把SO注入到目标APP中去。然后被注入的SO HOOK掉dlopen函数来拦截目标SO的加载,当目标SO加载时(libmono.so或libgame.so)再去做其他HOOK操作。由于Runtime的load及loadLibrary函数最终还是要调用到JNI层的dlopen函数,因此该方法可行,且不会漏掉SO的加载。

想法二可行是可行,但是会引来两个个问题:
- 问题一:注入SO由于过早地被加载到目标进程,在JNI_OnLoad中动态获取目标APP的包名会失效。
- 问题二:dlopen被HOOK,自己的代码需要调用的时候需谨慎,以免进入死循环。

解决办法

  • 问题一:注入SO由于过早地被加载到目标进程,在JNI_OnLoad中动态获取目标APP的包名会失效。下面代码中getPackagePath的是获取/data/data/com.youzu.android.snsgz类似路径的。
extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)  
{
    JNIEnv* env = NULL;
    jint result = -1;  
    LOGD("[dumplua] %s begin", __FUNCTION__);
    if( vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
        LOGE("utility GetEnv error");
        return result;
    }

    g_thisenv = env;
    g_bDataPathGot = getPackagePath(env, g_strDataPath);
    LOGD("[dumplua] data path: %s", g_strDataPath.c_str());
    hook();

    LOGD("[dumplua] %s end", __FUNCTION__);
    return JNI_VERSION_1_6;  
}

解决办法:先缓存一个JNIEnv指针,记录路径是否获取成功的状态。等到后面保存文件的时候再判断一下,如果之前没有成功获取到路径,那么在保存文件之前获取一下即可。

string getNextFilePath(const char *fileExt) {
    char buff[100] = {0};
    ++g_nCount;
    if ( g_bDataPathGot==false ) {
        g_bDataPathGot = getPackagePath(g_thisenv, g_strDataPath);
    }
    sprintf(buff, "%s/cache/%d%s", g_strDataPath.c_str(), g_nCount, fileExt);
    return buff;
}

动态获取包名的代码可以参考:android jni签名验证(一)

  • 问题二:dlopen被HOOK,自己的代码需要调用dlopen的时候需谨慎,以免进入死循环。

解决办法:缓存目标SO的句柄,后期直接使用,规避对dlopen的调用或者调用olddlopen。参考:如何hook dlopen和dlsym底层函数

void* (*olddlopen)(const char* filename, int myflags) = NULL;
void* newdlopen(const char* filename, int myflags) {
    LOGD("the dlopen name =: %s",filename);
    void *handle = olddlopen(filename, myflags);

    if ( strcmp(filename, "libmono.so")==0 ) {
        if ( g_bU3dHooked==false ) {
            //libmono.so加载了,但是发现之前并没有HOOK成功
            g_handlelibMonoSo = handle;
            LOGD("[%s] hook libmono.so", __FUNCTION__);
            g_bU3dHooked = hookU3D();
        }
    }else if ( strcmp(filename, "libgame.so")==0 ) {
        if ( g_bCocosHooked==false ) {
            g_handlelibGameSo = handle;
            LOGD("[%s] hook libgame.so", __FUNCTION__);
            g_bCocosHooked = hookCocos();
        }
    }
    return handle;
}



bool hookU3D() {
    void *handle = NULL;
    if ( g_handlelibMonoSo==NULL ) {
        if ( olddlopen==NULL ) {
            handle = dlopen("libmono.so", RTLD_NOW);
        }else{
            handle = olddlopen("libmono.so", RTLD_NOW);
        }
        if (handle == NULL) {
            LOGE("[dumplua]dlopen err: %s.", dlerror());
            return false;
        }
    }else{
        handle = g_handlelibMonoSo;
        LOGD("[dumplua] libmono.so handle: %p", handle);
    }

    void *mono_image_open_from_data_with_name = dlsym(handle, "mono_image_open_from_data_with_name");
    if (mono_image_open_from_data_with_name == NULL){
        LOGE("[dumplua] mono_image_open_from_data_with_name not found!");
        LOGE("[dumplua] dlsym err: %s.", dlerror());
    }else{
        LOGD("[dumplua] mono_image_open_from_data_with_name found: %p", mono_image_open_from_data_with_name);
        MSHookFunction(mono_image_open_from_data_with_name, (void *)&mono_image_open_from_data_with_name_mod, (void **)&mono_image_open_from_data_with_name_orig);
    }

    return true;
}

这里是拦截的天天飞车的LOG信息:

01-06 17:19:41.075 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/UnityEngine.dll, len: 310272, buff: MZ�
01-06 17:19:41.345 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/Assembly-CSharp-firstpass.dll, len: 2926592, buff: MZ�
01-06 17:19:41.625 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/Assembly-CSharp.dll, len: 7148544, buff: MZ�
01-06 17:19:41.690 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/UnityEngine.UI.dll, len: 171520, buff: MZ�
01-06 17:19:41.690 1286-1397/? D/SUBSTRATEHOOK: [dumplua] mono_image_open_from_data_with_name, name: /data/app/com.tencent.game.SSGame-1.apk/assets/bin/Data/Managed/poly2tri.dll, len: 43008, buff: MZ�
### 关于LOL(英雄联盟)服务器配置、状态及故障排查 #### 服务器配置的重要性 高效的游戏服务器是保障《英雄联盟》这类在线多人游戏稳定性与流畅性的核心要素[^2]。为了实现最佳用户体验,服务器需具备强大的计算能力、低延迟网络环境以及高可用性架构。 #### 当前已知的服务器问题描述 近期,“祖安、皮尔特沃夫、水晶之痕、巨神峰”四大电信大区出现了显著的网络异常波动现象,这可能导致部分玩家遇到匹配时间延长、登录困难甚至频繁掉线的情况[^3]。此外,在特定情况下,客户端文件损坏也可能引发诸如乱码显示等问题[^4]。 #### 解决方案概述 针对上述提到的各种潜在故障点,可以采取如下措施逐一排除并解决问题: 对于由官方维护引起的不可控因素造成的连接失败情况: - **等待官方修复**:密切关注官方网站发布的最新动态消息,耐心等候直至服务恢复正常为止; 如果怀疑个人设备存在兼容性或者软件层面的问题,则可尝试执行以下操作之一或多个组合方式来进行自我诊断和处理: 1. 更新至最新版本客户端程序以获取最新的错误修正补丁包。 ```bash cd C:\Riot Games\League of Legends\ del /F TerSafe.dll League\of\Legends.exe copy \\NormalPlayerPC\C$\CorrectFiles\*.* . ``` 2. 执行手动清理缓存数据命令清除旧有的临时存储资料从而减少干扰项影响加载效率。 当涉及到更复杂的后台管理领域时,例如容器化部署场景下微服务治理策略制定方面,则应遵循现代云原生理念指导原则合理规划资源调度机制设计思路。例如通过Kubernetes平台定义Pod健康探测规则确保应用层面上始终处于良好工作状态之中[^5]。 ```yaml livenessProbe: httpGet: path: / port: 8080 readinessProbe: tcpSocket: port: 9000 startupProbe: exec: command: ["cat", "/tmp/healthy"] initialDelaySeconds: 10 periodSeconds: 5 ``` 以上代码片段展示了如何利用 Kubernetes API 对 Pod 进行存活探针(liveness probe), 就绪探针(readiness probe) 及启动探针(startup probe) 的基本配置示例. #### 总结说明 综上所述,无论是面对外部依赖型还是内部可控范围内的各类挑战都需要保持冷静分析态度,并灵活运用各种技术和工具手段加以应对解决。只有这样才能够有效提升整体服务质量水平满足广大用户的实际需求预期目标达成一致共识共赢局面形成良性循环发展态势持续向前迈进不断创造新的辉煌成就!
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

asmcvc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值