目前只有プリンセスコネクト!Re:Dive 无法 dump,这玩意的 crackproof 不知道改了什么东西,会把完整的 PE 切成几百个分页,不过可以分析安卓版本的,壳子难度中等。
由于 Unity 的源代码得买,所以这里只能用反编译器 + pdb 来分析

LoadScriptingRuntime 这个函数加载了 GameAssembly.dll,相关的加载逻辑在 LoadIl2Cpp 里面,LoadIl2Cpp 的返回值是 GameAssembly.dll 的 handle

解法就很明显了,可以用 frida 拦截 LoadIl2Cpp 返回时候的动作,这时候 GameAssembly.dll 刚刚被 LoadLibraryW 加载上去,并且完成了一些初始化(crackproof 修复导入表,解密解压代码段等等),但是没有执行任何 il2cpp 部分的代码,dump 下来就能获得完全干净的 GameAssembly.dll 了。
dump 下来以后还需要简单的修复一下 PE 头,完整代码如下:

'use strict';

const UNITY_PLAYER = "UnityPlayer.dll";
const TARGET_RVA = ;
const GAMEASSEMBLY = "GameAssembly.dll";
const DUMP_PATH = "D:\\Reverse\\Frida_Hook\\GameAssembly_dump_fix.dll";
const CHUNK_SIZE = ;


function dumpModule(moduleName, outPath) {
    try {
        const m = Process.getModuleByName(moduleName);
        console.log("[*] Found module:", m.name, "Base:", m.base, "Size:", m.size);

        const size = m.size;
        const base = m.base;

        // raw→virtual const localCopy = fixPEHeader(base, size);
        if (localCopy === null) {
            console.error("[!] Fix PE Header failed");
            return;
        }

        const file = new File(outPath, "wb");
        console.log("[*] Output:", outPath);

        let offset = 0;
        while (offset < size) {
            const chunk = Math.min(CHUNK_SIZE, size - offset);
            const buf = localCopy.add(offset).readByteArray(chunk);
            file.write(buf);
            offset += chunk;
        }

        file.flush();
        file.close();
        console.log("[*] Dump finished:", outPath);

    } catch (e) {
        console.error("[!] Dump exception:", e);
    }
}

function hookAfterUnityPlayerLoaded(module) {
    if (module.name !== UNITY_PLAYER) return;
    console.log("[+] UnityPlayer.dll loaded @", module.base);

    const targetAddr = module.base.add(TARGET_RVA);
    console.log("[*] Hooking LoadDynamicLibrary @", targetAddr);

    Interceptor.attach(targetAddr, {
        onLeave(retval) {
            console.log("[*] LoadDynamicLibrary returned:", retval);

            try {
                const found = Process.findModuleByName(GAMEASSEMBLY);
                if (found) {
                    console.log("[*] GameAssembly.dll loaded -> dumping...");
                    dumpModule(GAMEASSEMBLY, DUMP_PATH);
                } else {
                    console.warn("[!] GameAssembly.dll not found yet");
                }
            } catch (e) {
                console.error("[!] Dump error:", e);
            }
        }
    });
}


function fixPEHeader(base, size) {
    try {
        const localBuf = Memory.alloc(size);
        Memory.copy(localBuf, base, size);

        const dos = localBuf.readPointer();
        const e_lfanew = localBuf.add().readU32();
        const nt = localBuf.add(e_lfanew);

        const numSections = nt.add().readU16();
        const optSize = nt.add().readU16();
        const firstSec = nt.add( + optSize);

        console.log("[*] Sections:", numSections, "First section @", firstSec);

        let secPtr = firstSec;
        for (let i = 0; i < numSections; i++) {
            const virtualAddress = secPtr.add(0xC).readU32();
            const virtualSize = secPtr.add().readU32();

            // 把 raw data 指向 virtual
            secPtr.add().writeU32(virtualAddress);       // PointerToRawData
            secPtr.add().writeU32(virtualSize);          // SizeOfRawData

            secPtr = secPtr.add(); // 下一节
        }

        return localBuf;

    } catch (e) {
        console.error("[!] fixPEHeader exception:", e);
        return null;
    }
}


setImmediate(() => {
    console.log("[*] Script started.");

    Process.attachModuleObserver({
        onAdded(module) {
            console.log("[*] Module loaded:", module.name);
            if (module.name === UNITY_PLAYER) {
                hookAfterUnityPlayerLoaded(module);
            }
        },
        onRemoved(module) { }
    });

    try {
        const existing = Process.getModuleByName(UNITY_PLAYER);
        if (existing) hookAfterUnityPlayerLoaded(existing);
    } catch (e) { }
});

由于 crackproof hook 了自身的 openprocess 并且进行的 handle 权限过滤,frida-server 是肯定不行了,但是 Windows 这玩意相当开放,有以下方法能把 frida-gadget.dll 塞进去:

  1. 劫持 version.dll
  2. 修改 UnityPlayer.dll 的导入表,把 frida-gadget.dll 导出表的任意函数塞进去。
  3. 搓一个 ring0 驱动,从内核用 APC 方法把 frida-gadget.dll 强行塞进去。

frida-gadget.dll 塞进去了以后还需要写一个配置文件,名称命名为 frida-gadget.config

{
  "interaction": {
    "type": "script",
    "path": "D:\\Reverse\\Frida_Hook\\crackproof\\1.js"
  }
}

这样 frida-gadget.dll 加载后就能自动执行脚本,手动连接执行肯定是来不及的,因为 GameAssembly.dll 加载时机非常早。

对于ウマ娘 プリティーダービー这种会检查目录下面有没有多余的 dll,可以把带 crackproof 但是不检查 dll 的启动器复制过去,然后就能随意改导入表了。(不带 crackproof 不检查 dll 的启动器貌似不行,疑似启动器上面的壳子有额外检测)


📌 转载信息
转载时间:
2026/1/12 17:05:44

标签: 逆向工程, Unity, Frida, GameAssembly.dll, PE修复

添加新评论