Unity 视觉小说逆向小结

该话题被推 逆向工程Unityil2cpp
浏览数 - 232发布于 - 2025-11-22 - 03:24

0x0 前言

用Unity做的视觉小说基本上没啥会上高强度的加密,顶多搞点自定义封包乱七八糟的,对Unity底层动刀的几乎没有,和国内外二游比起来简直是眉清目秀(乐

0x1 Mono

以二分之一为例,这样的目录结构就是典型的mono打包方式,具体表现为只有UnityPlayer.dll和启动器,没有GameAssembly.dll:
PixPin_2025-11-22_01-52-26.jpg要逆向这个游戏用dnspyEX反编译二分之一_Data\Managed文件夹下面的Assembly-CSharp.dll即可,非常明了的C#代码:
PixPin_2025-11-22_01-57-30.jpg

0x2 il2cpp

这玩意用一张图来解释大概是这样

bfloat16-1763748216739-16a6417333d452b594e4309e520f09dapngC#代码由C#编译器编译成托管代码,然后切割掉没有使用的托管代码,再转换成C++代码,和libil2cpp一起编译成Native DLL。

尽管被编译成了Native代码,但是C#的一些语言特性(反射GC等)依然要实现,所以 C# 中的类名、方法名、属性名、字符串等东西会额外写入global-metadata.dat

以Clover Days + Steam版本为例,这样的目录结构就是典型的il2cpp打包方式:
PixPin_2025-11-22_02-09-29.jpg要逆向il2cpp首先得把函数名,结构体,字符串等全部解析出来,可以用Il2CppDumperIl2CppInspectorRedux来完成全部流程

然后把在反编译器里面跑一下生成的脚本就能恢复全部符号和结构体以及字符串:

PixPin_2025-11-22_02-32-16.jpg后续只要注意常量一般在.cctor里面填充就行

PixPin_2025-11-22_02-35-02.jpg比如我要逆Clover Days + Steam的system.dat文件的加密,只需要搜system.dat,定位到反编译器里面对应的位置,然后查交叉引用抠出整个调用链即可PixPin_2025-11-22_02-39-51.jpg

C
System_IO_Stream_o *PkMain__OpenInternal(PkMain_o *this, System_String_o *fpath, const MethodInfo *method)
{
  int (*v3)(void); // eax
  int v4; // ebx
  System_IO_FileStream_o *v5; // esi
  int32_t v6; // ecx
  int v8; // eax
  int v9; // eax
  System_IO_FileNotFoundException_o *v10; // esi
  int v11; // eax

  if ( !byte_A2B20B )
  {
    sub_B6D00(&System_IO_FileStream_TypeInfo);
    byte_A2B20B = 1;
  }
  v3 = dword_A2B7B8;
  if ( !dword_A2B7B8 )
  {
    v3 = sub_B8B20("UnityEngine.SystemInfo::GetPhysicalMemoryMB()");
    if ( !v3 )
    {
      v8 = sub_B5CF0("UnityEngine.SystemInfo::GetPhysicalMemoryMB()");
      sub_B43F0(v8, 0);
    }
    dword_A2B7B8 = v3;
  }
  v4 = v3();
  if ( !System_IO_File__Exists(fpath, 0) )
  {
    v9 = sub_F0E70(&System_IO_FileNotFoundException_TypeInfo);
    v10 = sub_F1070(v9);
    System_IO_FileNotFoundException___ctor(v10, 0);
    v11 = sub_F0E70(&Method_PkMain_OpenInternal__);
    sub_F1080(v10, v11);
  }
  v5 = sub_BD210(System_IO_FileStream_TypeInfo);
  v6 = 0x100000;
  if ( v4 <= 0xA00 )
    v6 = 0x1000;
  System_IO_FileStream___ctor_276732752(v5, fpath, 3, 1, 5, v6, 0, 0x10000000, 0);
  return v5;
}
C
void PRead___ctor(PRead_o *this, System_IO_Stream_o *fs, System_String_o *fn, const MethodInfo *method)
{
  bool v4; // zf
  int v5; // eax
  unsigned int v6; // edx
  int v7; // ecx
  System_String_o *v8; // eax
  System_Collections_Generic_Dictionary_string__PRead_fe__o *ti; // eax

  if ( !byte_A2B3B5 )
  {
    sub_B6D00(&Method_System_Collections_Generic_Dictionary_string__PRead_fe__Remove__);
    sub_B6D00(&str_adult_dat);                  // adult.dat
    sub_B6D00(&StringLiteral_795);              // def/version.txt
    byte_A2B3B5 = 1;
  }
  v4 = dword_A27CC4 == 0;
  this->fields.fs = fs;
  if ( !v4 )
  {
    do
      LOBYTE(v5) = sub_D61C0();
    while ( v5 == 0xFF );
    dword_A4FE90[v6 >> 5] |= 1 << (v6 & 0x1F);
    byte_A27898 = 0;
  }
  PRead__Init(this, 0);
  if ( !fn )
    goto LABEL_12;
  v8 = System_String__ToLower(fn, 0);
  if ( !v8 )
    goto LABEL_12;
  if ( !System_String__EndsWith_274357920(v8, str_adult_dat, 0, 0) )
    return;
  ti = this->fields.ti;
  if ( !ti )
LABEL_12:
    sub_F1090(v7);
  System_Collections_Generic_Dictionary_string__PRead_fe___Remove(ti, StringLiteral_795, Method_System_Collections_Generic_Dictionary_string__PRead_fe__Remove__);// def/version.txt
}
C
void PRead__Init(PRead_o *this, const MethodInfo *method)
{
  struct System_IO_Stream_o *fs; // ecx
  int *v3; // edi
  bool v4; // zf
  int v5; // eax
  unsigned int v6; // edx
  int v7; // eax
  System_Byte_array *v8; // ebx
  int v9; // edi
  int32_t i; // esi
  int32_t v11; // eax
  System_Byte_array *v12; // esi
  uint32_t v13; // eax
  System_Byte_array *v14; // edi
  uint32_t v15; // eax
  int v16; // eax
  int32_t v17; // esi
  System_String_o *v18; // eax
  void (__cdecl **v19)(struct System_Collections_Generic_Dictionary_string__PRead_fe__o *, System_String_o *, uint32_t, uint32_t, uint32_t, int, _DWORD); // ecx
  int v20; // edx
  void (__cdecl **v21)(int *, _DWORD, _DWORD, _DWORD); // [esp-4h] [ebp-44h]
  int32_t startIndex; // [esp+10h] [ebp-30h]
  System_Byte_array *value; // [esp+14h] [ebp-2Ch]
  int v24; // [esp+18h] [ebp-28h]
  System_String_o *v25; // [esp+18h] [ebp-28h]
  int L; // [esp+1Ch] [ebp-24h]
  int32_t La; // [esp+1Ch] [ebp-24h]
  int v28; // [esp+20h] [ebp-20h]
  struct System_Collections_Generic_Dictionary_string__PRead_fe__o **p_ti; // [esp+24h] [ebp-1Ch]
  uint32_t v30; // [esp+28h] [ebp-18h]
  uint32_t v31; // [esp+2Ch] [ebp-14h]
  uint32_t v32; // [esp+30h] [ebp-10h]

  if ( !byte_A2B3B6 )
  {
    sub_B6D00(&System_BitConverter_TypeInfo);
    sub_B6D00(&byte___TypeInfo);
    sub_B6D00(&Method_System_Collections_Generic_Dictionary_string__PRead_fe__Add__);
    sub_B6D00(&Method_System_Collections_Generic_Dictionary_string__PRead_fe___ctor__);
    sub_B6D00(&System_Collections_Generic_Dictionary_string__PRead_fe__TypeInfo);
    byte_A2B3B6 = 1;
  }
  v3 = sub_BD210(System_Collections_Generic_Dictionary_string__PRead_fe__TypeInfo);
  if ( !v3 )
    goto LABEL_35;
  v21 = **(*(Method_System_Collections_Generic_Dictionary_string__PRead_fe___ctor__ + 0xC) + 0x60);
  (*v21)(v3, 0, 0, v21);
  v4 = dword_A27CC4 == 0;
  p_ti = &this->fields.ti;
  this->fields.ti = v3;
  if ( !v4 )
  {
    do
      LOBYTE(v5) = sub_D61C0();
    while ( v5 == 0xFF );
    dword_A4FE90[v6 >> 5] |= 1 << (v6 & 0x1F);
    byte_A27898 = 0;
  }
  fs = this->fields.fs;
  if ( !fs )
    goto LABEL_35;
  (fs->klass->vtable._12_unknown.methodPtr)(fs, 0, 0, fs->klass->vtable._12_unknown.method);
  v7 = sub_AE200(byte___TypeInfo, 0x400);
  fs = this->fields.fs;
  v8 = v7;
  if ( !fs )
    goto LABEL_35;
  (fs->klass->vtable._22_unknown.methodPtr)(fs, v7, 0, 0x400, fs->klass->vtable._22_unknown.method);
  v9 = 0;
  for ( i = 0x10; i < 0x3FC; i += 4 )
  {
    if ( (System_BitConverter_TypeInfo->_2.bitflags2 & 4) != 0 && !System_BitConverter_TypeInfo->_2.cctor_finished )
      sub_C32F0(System_BitConverter_TypeInfo);
    v11 = System_BitConverter__ToInt32(v8, i, 0);
    v9 += v11;
  }
  v28 = v9;
  v12 = sub_AE200(byte___TypeInfo, 0x10 * v9);
  value = v12;
  if ( !v12 )
    goto LABEL_35;
  fs = this->fields.fs;
  if ( !fs )
    goto LABEL_35;
  (fs->klass->vtable._22_unknown.methodPtr)(fs, v12, 0, v12->max_length, fs->klass->vtable._22_unknown.method);
  if ( (System_BitConverter_TypeInfo->_2.bitflags2 & 4) != 0 && !System_BitConverter_TypeInfo->_2.cctor_finished )
    sub_C32F0(System_BitConverter_TypeInfo);
  v13 = System_BitConverter__ToUInt32(v8, 0xD4, 0);
  PRead__dd(v12, 0x10 * v9, v13, 0, 0, 0);
  L = System_BitConverter__ToInt32(v12, 0xC, 0) - 0x10 * (v9 + 0x40);
  v14 = sub_AE200(byte___TypeInfo, L);
  if ( !v14 )
    goto LABEL_35;
  fs = this->fields.fs;
  if ( !fs )
    goto LABEL_35;
  (fs->klass->vtable._22_unknown.methodPtr)(fs, v14, 0, v14->max_length, fs->klass->vtable._22_unknown.method);
  v15 = System_BitConverter__ToUInt32(v8, 0x5C, 0);
  PRead__dd(v14, L, v15, 0, 0, 0);
  v24 = 0;
  La = 0;
  if ( v28 > 0 )
  {
    v16 = 8;
    for ( startIndex = 8; ; startIndex += 0x10 )
    {
      if ( (System_BitConverter_TypeInfo->_2.bitflags2 & 4) != 0 && !System_BitConverter_TypeInfo->_2.cctor_finished )
      {
        sub_C32F0(System_BitConverter_TypeInfo);
        v16 = startIndex;
      }
      v31 = System_BitConverter__ToUInt32(v12, v16 - 8, 0);
      v17 = System_BitConverter__ToInt32(v12, startIndex - 4, 0);
      v32 = System_BitConverter__ToUInt32(value, startIndex, 0);
      v30 = System_BitConverter__ToUInt32(value, startIndex + 4, 0);
      while ( v17 < v14->max_length && sub_3060(v17) )
        ++v17;
      if ( !System_Text_Encoding__get_UTF8(0) )
        break;
      v18 = sub_315A0(v14, v24, v17 - v24);
      if ( !v18 )
        break;
      v25 = System_String__ToLower(v18, 0);
      if ( !*p_ti )
        break;
      v19 = *(*(*(Method_System_Collections_Generic_Dictionary_string__PRead_fe__Add__ + 0xC) + 0x60) + 0x48);
      (*v19)(*p_ti, v25, v30, v31, v32, 2, v19);
      v20 = v17 + 1;
      v12 = value;
      v16 = startIndex + 0x10;
      v24 = v20;
      if ( ++La >= v28 )
        return;
    }
LABEL_35:
    sub_F1090(fs);
  }
}
C
void PRead__dd(System_Byte_array *b, int32_t L, uint32_t k, int64_t ro, int32_t wo, const MethodInfo *method)
{
  _DWORD *v6; // edi
  unsigned int v7; // ecx
  int v8; // esi
  int i; // edx
  uint32_t v10; // ecx
  int32_t v12; // eax
  int64_t v13; // kr00_8
  int v14; // eax
  int *v15; // eax
  uint8_t v16; // [esp+Fh] [ebp-11h]
  char v17; // [esp+Fh] [ebp-11h]
  int32_t v18; // [esp+10h] [ebp-10h]
  char v19; // [esp+1Ch] [ebp-4h]

  if ( !byte_A2B3B7 )
  {
    sub_B6D00(&byte___TypeInfo);
    sub_B6D00(&Method_PRead_dd__);
    byte_A2B3B7 = 1;
  }
  v6 = sub_AE200(byte___TypeInfo, 0x100);
  v7 = 0x1CDF * k + 0xA74C;
  v8 = v7 ^ (v7 << 0x11);
  for ( i = 0; i < 0x100; ++i )
  {
    v10 = v8 - k + v7;
    v8 = v10 + 0x38;
    v7 = ((v10 + 0x38) & 0xEF) * v10;
    if ( !v6 )
      goto LABEL_14;
    if ( i >= v6[3] )
      goto LABEL_16;
    *(v6 + i + 0x10) = v7;
    v7 >>= 1;
  }
  if ( L > 0 )
  {
    v12 = 0;
    v18 = 0;
    if ( b )
    {
      while ( wo < b->max_length )
      {
        v13 = ro + v12;
        if ( v13 % 0xFD > 0x7FFFFFFF || (v16 = b->m_Items[wo], v17 = sub_3060(v6, v13 % 0xFD) ^ v16, v13 % 0x3B > 0x7FFFFFFF) )
        {
          v14 = sub_F10C0();
          sub_F1080(v14, Method_PRead_dd__);
        }
        v19 = (v17 + sub_3060(v6, v13 % 0x3B)) ^ 0x99;
        sub_1BD30(b, wo++, v19);
        v12 = v18 + 1;
        v18 = v12;
        if ( v12 >= L )
          return;
      }
LABEL_16:
      v15 = sub_F11D0();
      sub_F1080(v15, 0);
    }
LABEL_14:
    sub_F1090(v7);
  }
}

PixPin_2025-11-22_03-21-05.jpg

PixPin_2025-11-22_03-24-14.jpg

本文版权遵循 CC BY-NC 协议 本站版权政策

(。>︿<。) 已经一滴回复都不剩了哦~