[已解决]夜莺逆向指令UniyuCN Key问题

b站的古早游戏,一测之后就砍了,整理云盘的时候发现还留着安装包,所以尝试解一下资源.
拖进as发现是unitycn,用了frida dump SetAssetBundleKeySetAssetBundleDecryptKey的传入,没有任何结果,怀疑是没有调用.
把global-metadata.dat和il2cpp.so导进 il2CppDumper 里面也有报错没法生成dump.cs

il2cpp bridge 在我这边还有点问题用不了,所以没法追踪函数调用,所以我上传到这里希望大家帮我拿到key :pray:
安装包以及一些其他东西都在谷歌云盘

安装包的game.dat应该加密了。
ida看的时候sub_8EC128应该就是MetadataLoader::LoadMetadataFile
也就是加载global-metadata.dat或game.dat的地方。

sub_8EC128
  __int64 __fastcall sub_8EC128(__int64 a1)
{
  unsigned __int64 v2; // x8
  const char *v3; // x9
  char **v4; // x1
  __int64 v5; // x0
  char *v6; // x8
  unsigned __int64 v7; // x9
  const char *v8; // x0
  __int64 v9; // x19
  __int64 v10; // x0
  const char *v11; // x1
  __int64 v12; // x20
  char *v14; // [xsp+0h] [xbp-60h] BYREF
  __int64 v15; // [xsp+8h] [xbp-58h]
  _QWORD v16[2]; // [xsp+10h] [xbp-50h] BYREF
  const char *v17; // [xsp+20h] [xbp-40h]
  _QWORD v18[2]; // [xsp+28h] [xbp-38h] BYREF
  char *v19; // [xsp+38h] [xbp-28h]
  char *v20; // [xsp+40h] [xbp-20h] BYREF
  unsigned __int64 v21; // [xsp+48h] [xbp-18h]

  ((void (__fastcall *)(_QWORD *__return_ptr))loc_92EF38)(v16);
  v14 = "Metadata";
  v15 = 8LL;
  v2 = (unsigned __int64)LOBYTE(v16[0]) >> 1;
  if ( (v16[0] & 1) != 0 )
    v3 = v17;
  else
    v3 = (char *)v16 + 1;
  if ( (v16[0] & 1) != 0 )
    v2 = v16[1];
  v20 = (char *)v3;
  v21 = v2;
  sub_8CA04C(v18, &v20, &v14);
  if ( (v16[0] & 1) != 0 )
    sub_6DC6D0(v17);
  strtod((const char *)a1, v4);
  if ( (v18[0] & 1) != 0 )
    v6 = v19;
  else
    v6 = (char *)v18 + 1;
  if ( (v18[0] & 1) != 0 )
    v7 = v18[1];
  else
    v7 = (unsigned __int64)LOBYTE(v18[0]) >> 1;
  v14 = (char *)a1;
  v15 = v5;
  v20 = v6;
  v21 = v7;
  sub_8CA04C(v16, &v20, &v14);
  if ( off_2F2F2D0 && *(_BYTE *)(a1 + 1) == 97 )
  {
    if ( (v16[0] & 1) != 0 )
      v8 = v17;
    else
      v8 = (char *)v16 + 1;
    v9 = off_2F2F2D0(v8);
  }
  else
  {
    LODWORD(v20) = 0;
    v10 = ((__int64 (__fastcall *)(_QWORD *, __int64, __int64, __int64, _QWORD, char **))dword_8BF9EC)(
            v16,
            3LL,
            1LL,
            1LL,
            0LL,
            &v20);
    if ( (_DWORD)v20 )
    {
      if ( (v16[0] & 1) != 0 )
        v11 = v17;
      else
        v11 = (char *)v16 + 1;
      sub_92E5A8("ERROR: Could not open %s", v11);
    }
    else
    {
      v12 = v10;
      v9 = sub_92E764();
      ((void (__fastcall *)(__int64, char **))((char *)&qword_8BFD90[9] + 4))(v12, &v20);
      if ( !(_DWORD)v20 )
        goto LABEL_28;
      sub_92E774(v9);
    }
    v9 = 0LL;
  }
LABEL_28:
  if ( (v16[0] & 1) != 0 )
    sub_6DC6D0(v17);
  if ( (v18[0] & 1) != 0 )
    sub_6DC6D0(v19);
  return v9;
}

.
sub_8EBF18是MetadataCache::Initialize

sub_8EBF18
__int64 __fastcall sub_8EBF18(_DWORD *a1, int *a2)
{
  const char *v4; // x0
  __int64 result; // x0
  unsigned __int64 v6; // kr00_8
  __int64 v7; // x0
  __int64 v8; // x13
  __int64 v9; // x8
  int *v10; // x13
  unsigned int v11; // w14
  int v12; // w14
  __int64 v13; // x14
  __int64 v14; // x14
  __int64 v15; // x14
  char v16[16]; // [xsp+0h] [xbp-30h] BYREF

  if ( (sub_8EBE7C() & 1) != 0 )
  {
    v4 = v16;
    strcpy(v16, "game.dat");
  }
  else
  {
    v4 = "global-metadata.dat";
  }
  result = sub_8EC128(v4);
  qword_2F2F2F0 = result;
  if ( result || (result = sub_8EC128("global-metadata.dat"), qword_2F2F2F0 = result, off_2F2F2D8 = 0LL, result) )
  {
    qword_2F2F2F8 = result;
    v6 = *(int *)(result + 172);
    *a1 = v6 / 0x28;
    dword_2F2F300 = v6 / 0x28;
    *a2 = *(int *)(result + 180) >> 6;
    qword_2F2F308 = sub_92E730((int)(v6 / 0x28), 24LL);
    qword_2F2F310 = sub_92E730(*(int *)(qword_2F2F2E8 + 48), 8LL);
    qword_2F2F318 = sub_92E730(*(int *)(qword_2F2F2F8 + 164) / 0x58uLL, 8LL);
    qword_2F2F320 = sub_92E730((unsigned __int64)*(int *)(qword_2F2F2F8 + 52) >> 5, 8LL);
    v7 = sub_92E730(*(int *)(qword_2F2F2E8 + 64), 8LL);
    v8 = qword_2F2F2E8;
    qword_2F2F328 = v7;
    result = 1LL;
    if ( *(int *)(qword_2F2F2E8 + 48) >= 1 )
    {
      v9 = 0LL;
      while ( 1 )
      {
        v10 = *(int **)(*(_QWORD *)(v8 + 56) + 8 * v9);
        v11 = *((unsigned __int8 *)v10 + 10);
        if ( v11 <= 0x1E )
        {
          v12 = 1 << v11;
          if ( (v12 & 0x13467FFE) != 0 )
          {
            v13 = *v10;
            if ( (_DWORD)v13 != -1 )
            {
              v14 = qword_2F2F2F0 + *(int *)(qword_2F2F2F8 + 160) + 88 * v13;
LABEL_16:
              *(_QWORD *)v10 = v14;
              goto LABEL_17;
            }
            goto LABEL_15;
          }
          if ( (v12 & 0x40080000) != 0 )
          {
            v15 = *v10;
            if ( (_DWORD)v15 != -1 )
            {
              v14 = qword_2F2F2F0 + *(int *)(qword_2F2F2F8 + 104) + 16 * v15;
              goto LABEL_16;
            }
LABEL_15:
            v14 = 0LL;
            goto LABEL_16;
          }
        }
LABEL_17:
        v8 = qword_2F2F2E8;
        if ( ++v9 >= *(int *)(qword_2F2F2E8 + 48) )
          return 1LL;
      }
    }
  }
  return result;
}

.
其中的os::File::Open函数调用

os::File::Open
v10 = ((__int64 (__fastcall *)(_QWORD *, __int64, __int64, __int64, _QWORD, char **))dword_8BF9EC)(
            v16,
            3LL,
            1LL,
            1LL,
            0LL,
            &v20);

v9 = off_2F2F2D0(v8);可能是解密位置。

但是我开frida-sever 就崩。现场学的,不知道有没有检测。

游戏闪退的话是检测开发者模式还有usb调试,隐藏一下就行了?
是加密了,所以我直接dump的global-metadata.dat,也在谷歌云盘里面,看起来应该是没啥问题


我的frida脚本

var FALLBACK_OFFSET = 0x8EC128;

var finded = false
var addr;
var notFirstTime = true
var s_GlobalMetadataHeader;
var completed = false;
var metadataLoaderFunc = null;
var searchAttempted = false;

function get_self_process_name() {
    var openPtr = Module.getExportByName('libc.so', 'open');
    var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);

    var readPtr = Module.getExportByName("libc.so", "read");
    var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);

    var closePtr = Module.getExportByName('libc.so', 'close');
    var close = new NativeFunction(closePtr, 'int', ['int']);

    var path = Memory.allocUtf8String("/proc/self/cmdline");
    var fd = open(path, 0);
    if (fd != -1) {
        var buffer = Memory.alloc(0x1000);

        var result = read(fd, buffer, 0x1000);
        close(fd);
        result = ptr(buffer).readCString();
        return result;
    }

    return "-1";
}

var intervalId = setInterval(() => {
    if (completed) {
        clearInterval(intervalId);
        return;
    }

    if (finded) {
        if (addr != null && metadataLoaderFunc == null && !searchAttempted) {

            if (metadataLoaderFunc == null) {
                metadataLoaderFunc = addr.base.add(FALLBACK_OFFSET);
            }
        }

        if (metadataLoaderFunc != null) {
            if (notFirstTime) {
                notFirstTime = false;
                try {
                    Interceptor.attach(metadataLoaderFunc, {
                        onEnter: function (args) {
                            console.log("MetadataLoader::LoadMetadataFile 被调用");
                        },
                        onLeave: function (retval) {
                            console.log("s_GlobalMetadataHeader:" + retval.toString(16));
                            s_GlobalMetadataHeader = retval;
                            save(get_size());
                            completed = true;
                        }
                    });
                } catch (e) {
                    completed = true;
                }
            }
        }
    } else {
        try {
            addr = Process.findModuleByName("libil2cpp.so");
            if (addr != null) {
                console.log("找到libil2cpp.so模块,基址: " + addr.base.toString(16));
                finded = true;
            }
        } catch (e) {
        }
    }
}, 100);

function get_size() {
    const metadataHeader = s_GlobalMetadataHeader;
    let fileOffset = 0x10C;
    let lastCount = 0;
    let lastOffset = 0;
    while (true) {
        lastCount = Memory.readInt(ptr(metadataHeader).add(fileOffset));
        if (lastCount !== 0) {
            lastOffset = Memory.readInt(ptr(metadataHeader).add(fileOffset - 4));
            break;
        }
        fileOffset -= 8;
        if (fileOffset <= 0) {
            console.log("获取到的大小错误!");
            break;
        }
    }
    return lastOffset + lastCount;
}

function save(size) {
    var file = new File("/data/data/" + get_self_process_name() + "/global-metadata.dat", "wb");
    var contentBuffer = Memory.readByteArray(s_GlobalMetadataHeader, size);
    file.write(contentBuffer);
    file.flush();
    file.close();
    console.log("global-metadata已导出到/data/data/" + get_self_process_name() + "/global-metadata.dat")
}

有点神奇,我装了我不是开发者模块隐藏usb调试和开发者后可以在开frida-sever的情况下正常打开这个游戏,但是我只要frida -U -f com.bilibili.yeying.bilibili -o resume=true就直接白屏卡住。
frida的版本是16.7.07,cpu架构是arm64-v8a。

ida逆向.so没什么问题,可能是的有些参数不一致。

┗━━  ./UnityCNBurst "F:\workspace\yeying\global-metadata.dat"
Found valid key: Nightingale@2019
Elapsed time: 0.014814 sec
Total keys checked: 370461
Keys per second: 25007492.912110165

前两天数据填错了没跑出来难绷,直接暴搜global-metadat.dat就有key

2 个赞
import re
import sys
from UnityPy.helpers.ArchiveStorageManager import brute_force_key
from UnityPy.streams.EndianBinaryReader import EndianBinaryReader

if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python brute_force.py <path_to_bundlefile> <path_to_global_metadata.dat>")
        sys.exit(1)
    bundlefile_path = sys.argv[1]
    global_metadata_path = sys.argv[2]

    with open(bundlefile_path, "rb") as f:
        data = f.read()
        reader = EndianBinaryReader(data)

    _ = reader.read_string_to_null()
    _ = reader.read_u_int()
    _ = reader.read_string_to_null()
    _ = reader.read_string_to_null()
    reader.Position += 57

    signatureBytes = reader.read_bytes(0x10)
    signatureKey = reader.read_bytes(0x10)
    key = brute_force_key(global_metadata_path, signatureKey, signatureBytes, re.compile(rb"(?=([\x20-\x7E]{16}))"))
    print(key)

如果用UnityPy大概是这样的

3 个赞

感谢大佬分享思路 :+1:

好家伙,还能这样,学到了

学习了