SiNiSistar2 AssetBundle加密分析

该话题被推 逆向工程il2cppUnity
浏览数 - 91发布于 - 2026-03-01 - 22:15

经典il2cpp,通用流程先来一遍,把符号解析出来再丢IDA里面

PixPin_2026-03-01_22-00-00.jpg继续观察发现该文件夹下面存放了加密后的ab包

PixPin_2026-03-01_22-01-36.jpgebda798b17101f9d355678df086dd340.jpeg搜索字符串game_bin,xref到SiNiSistar2::AssetBundleLoader::cctor函数

C
// AssetBundleLoader()
void SiNiSistar2::AssetBundleLoader::cctor(MethodInfo *method)
{
  String *streamingAssetsPath; // rax
  String *v2; // rax

  if ( !byte_1830E545A )
  {
    sub_18038C080(&TypeInfo::UnityEngine::Application);
    sub_18038C080(&TypeInfo::SiNiSistar2::AssetBundleLoader);
    sub_18038C080(&StringLiteral__game_bin);
    byte_1830E545A = 1;
  }
  if ( !TypeInfo::UnityEngine::Application->_1.cctor_finished_or_no_cctor )
    il2cpp_class_get_assemblyname_1(TypeInfo::UnityEngine::Application);
  streamingAssetsPath = UnityEngine::Application::get_streamingAssetsPath(nullptr);
  v2 = System::String::Concat(streamingAssetsPath, StringLiteral__game_bin, nullptr);
  TypeInfo::SiNiSistar2::AssetBundleLoader->static_fields->BasePath = v2;
  sub_18038B400(TypeInfo::SiNiSistar2::AssetBundleLoader->static_fields, v2);
}

所以TypeInfo::SiNiSistar2::AssetBundleLoader->static_fields->BasePath == /game_bin

继续xref到SiNiSistar2::DLC::DLCManager::FindDlcAbsolutePathFromAssetBundles函数,这里做了路径拼接

C
// String FindDlcAbsolutePathFromAssetBundles(String)
String *SiNiSistar2::DLC::DLCManager::FindDlcAbsolutePathFromAssetBundles(String *assetBundleName, MethodInfo *method)
{
  String *persistentDataPath; // rax
  String *v4; // rdi
  struct AssetBundleLoader__Class *v5; // rcx

  if ( !byte_1830E5D31 )
  {
    sub_18038C080(&TypeInfo::UnityEngine::Application);
    sub_18038C080(&TypeInfo::SiNiSistar2::AssetBundleLoader);
    sub_18038C080(&StringLiteral__Contents_FilteredContents_Asset);
    sub_18038C080(&StringLiteral___41);
    byte_1830E5D31 = 1;
  }
  if ( !TypeInfo::UnityEngine::Application->_1.cctor_finished_or_no_cctor )
    il2cpp_class_get_assemblyname_1(TypeInfo::UnityEngine::Application);
  persistentDataPath = UnityEngine::Application::get_persistentDataPath(nullptr);
  v4 = System::String::Concat(persistentDataPath, StringLiteral__Contents_FilteredContents_Asset, assetBundleName, nullptr);
  if ( System::IO::File::Exists(v4, nullptr) )
    return v4;
  v5 = TypeInfo::SiNiSistar2::AssetBundleLoader;
  if ( !TypeInfo::SiNiSistar2::AssetBundleLoader->_1.cctor_finished_or_no_cctor )
  {
    il2cpp_class_get_assemblyname_1(TypeInfo::SiNiSistar2::AssetBundleLoader);
    v5 = TypeInfo::SiNiSistar2::AssetBundleLoader;
  }
  return System::String::Concat(v5->static_fields->BasePath, StringLiteral___41, assetBundleName, nullptr);
}

返回/game_bin/{assetBundleName}

往上跟踪一层到SiNiSistar2::AssetBundleLoader::TryLoadManifest,可以看出LoadByeStatic::OutAllData函数开始处理AB包

C
// Boolean TryLoadManifest(AssetBundleManifest ByRef)
bool SiNiSistar2::AssetBundleLoader::TryLoadManifest(AssetBundleManifest **manifest, MethodInfo *method)
{
  String *v3; // rdi
  String *DlcAbsolutePathFromAssetBundles; // rdi
  Byte__Array *v5; // rax
  Object_1 *v6; // rdi
  Object_1 *v8; // rbx
  AssetBundle *v9; // [rsp+50h] [rbp+18h] BYREF

  if ( !byte_1830E5452 )
  {
    sub_18038C080(&TypeInfo::SiNiSistar2::AssetBundleLoader);
    sub_18038C080(&MethodInfo::UnityEngine::AssetBundle::LoadAsset<UnityEngine::AssetBundleManifest>);
    sub_18038C080(&TypeInfo::UnityEngine::Debug);
    sub_18038C080(&TypeInfo::UnityEngine::Object);
    sub_18038C080(&StringLiteral_AssetBundleManifest);
    sub_18038C080(&StringLiteral_Manifest_u306E_AssetBundle_u304C_u8AADu307Fu8FBCu3081u307Eu305Bu3093);
    sub_18038C080(&StringLiteral_AssetBundle_u306Eu4E2Du306E_Manifest_u304C_u8AADu307Fu8FBCu3081u307E);
    sub_18038C080(&StringLiteral_StreamingAssets);
    byte_1830E5452 = 1;
  }
  v9 = nullptr;
  *manifest = nullptr;
  sub_18038B400(manifest);
  if ( !TypeInfo::SiNiSistar2::AssetBundleLoader->_1.cctor_finished_or_no_cctor )
    il2cpp_class_get_assemblyname_1(TypeInfo::SiNiSistar2::AssetBundleLoader);
  v3 = StringLiteral_StreamingAssets;
  if ( !byte_1830E5453 )
  {
    sub_18038C080(&TypeInfo::LoadByeStatic);
    byte_1830E5453 = 1;
  }
  sub_18038B400(&v9);
  DlcAbsolutePathFromAssetBundles = SiNiSistar2::DLC::DLCManager::FindDlcAbsolutePathFromAssetBundles(v3, nullptr);
  if ( !System::IO::File::Exists(DlcAbsolutePathFromAssetBundles, nullptr) )
  {
    if ( !TypeInfo::UnityEngine::Debug->_1.cctor_finished_or_no_cctor )
      il2cpp_class_get_assemblyname_1(TypeInfo::UnityEngine::Debug);
    UnityEngine::Debug::LogError(StringLiteral_Manifest_u306E_AssetBundle_u304C_u8AADu307Fu8FBCu3081u307Eu305Bu3093, nullptr);
    return 0;
  }
  if ( !TypeInfo::LoadByeStatic->_1.cctor_finished_or_no_cctor )
    il2cpp_class_get_assemblyname_1(TypeInfo::LoadByeStatic);
  v5 = LoadByeStatic::OutAllData(DlcAbsolutePathFromAssetBundles, nullptr);
  v9 = UnityEngine::AssetBundle::LoadFromMemory(v5, nullptr);
  sub_18038B400(&v9);
  if ( !v9 )
    sub_18038C2D0();
  *manifest = UnityEngine::AssetBundle::LoadAsset<System::Object>(v9, StringLiteral_AssetBundleManifest, MethodInfo::UnityEngine::AssetBundle::LoadAsset<UnityEngine::AssetBundleManifest>);
  sub_18038B400(manifest);
  v6 = *manifest;
  if ( !TypeInfo::UnityEngine::Object->_1.cctor_finished_or_no_cctor )
    il2cpp_class_get_assemblyname_1(TypeInfo::UnityEngine::Object);
  if ( UnityEngine::Object::op_Equality(v6, nullptr, nullptr) )
  {
    if ( !TypeInfo::UnityEngine::Debug->_1.cctor_finished_or_no_cctor )
      il2cpp_class_get_assemblyname_1(TypeInfo::UnityEngine::Debug);
    UnityEngine::Debug::LogError(StringLiteral_AssetBundle_u306Eu4E2Du306E_Manifest_u304C_u8AADu307Fu8FBCu3081u307E, nullptr);
    return 0;
  }
  if ( !v9 )
    sub_18038C2D0();
  UnityEngine::AssetBundle::Unload(v9, 0, nullptr);
  v8 = *manifest;
  if ( !TypeInfo::UnityEngine::Object->_1.cctor_finished_or_no_cctor )
    il2cpp_class_get_assemblyname_1(TypeInfo::UnityEngine::Object);
  return UnityEngine::Object::op_Inequality(v8, nullptr, nullptr);
}

继续跟,LoadByeStatic::OutAllData读取了ab包,交给了Util::OutDeCreateByte处理

C
// Byte[] OutAllData(String)
Byte__Array *LoadByeStatic::OutAllData(String *path, MethodInfo *method)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

  if ( !byte_1830E4F26 )
  {
    sub_18038C080(&TypeInfo::System::Byte);
    sub_18038C080(&TypeInfo::UnityEngine::Debug);
    sub_18038C080(&TypeInfo::System::IDisposable);
    sub_18038C080(&TypeInfo::System::Int32);
    sub_18038C080(&TypeInfo::LoadByeStatic);
    sub_18038C080(&StringLiteral__0__u306E_u30D5u30A1u30A4u30EBu30B5u30A4u30BAu306Fu000Au60F3u5B9A__1_MBu000Au3092u8D85u3048u307Eu3057u305Fu000A_);
    sub_18038C080(&StringLiteral_u62E1u5F35u3057u307Eu3057u305F_0_MB);
    byte_1830E4F26 = 1;
  }
  Util::Update(nullptr);
  v21[0] = System::IO::File::OpenRead(path, nullptr);
  v21[2] = 0;
  v21[3] = v21;
  if ( !v21[0] )
    sub_18038C2D0();
  v3 = (*(*v21[0] + 0x1F8LL))(v21[0], *(*v21[0] + 0x200LL));
  v4 = TypeInfo::LoadByeStatic;
  if ( !TypeInfo::LoadByeStatic->_1.cctor_finished_or_no_cctor )
  {
    il2cpp_class_get_assemblyname_1(TypeInfo::LoadByeStatic);
    v4 = TypeInfo::LoadByeStatic;
  }
  m_LoadTmpByteArray = v4->static_fields->m_LoadTmpByteArray;
  if ( !m_LoadTmpByteArray )
    sub_18038C2D0();
  if ( v3 >= m_LoadTmpByteArray->max_length.size )
  {
    if ( !v4->_1.cctor_finished_or_no_cctor )
    {
      il2cpp_class_get_assemblyname_1(v4);
      v4 = TypeInfo::LoadByeStatic;
    }
    v6 = v4->static_fields->m_LoadTmpByteArray;
    if ( !v6 )
      sub_18038C2D0();
    v22 = v6->max_length.size / 0x100000;
    v7 = il2cpp_value_box(TypeInfo::System::Int32, &v22);
    v23 = v3 / 0x100000;
    v8 = il2cpp_value_box(TypeInfo::System::Int32, &v23);
    v9 = System::String::Format(StringLiteral__0__u306E_u30D5u30A1u30A4u30EBu30B5u30A4u30BAu306Fu000Au60F3u5B9A__1_MBu000Au3092u8D85u3048u307Eu3057u305Fu000A_, path, v7, v8, nullptr);
    if ( !TypeInfo::UnityEngine::Debug->_1.cctor_finished_or_no_cctor )
      il2cpp_class_get_assemblyname_1(TypeInfo::UnityEngine::Debug);
    UnityEngine::Debug::LogWarning(v9, nullptr);
    v10 = sub_18038B480(TypeInfo::System::Byte, (2 * v3));
    TypeInfo::LoadByeStatic->static_fields->m_LoadTmpByteArray = v10;
    sub_18038B400(TypeInfo::LoadByeStatic->static_fields, v10);
    v11 = TypeInfo::LoadByeStatic->static_fields->m_LoadTmpByteArray;
    if ( !v11 )
      sub_18038C2D0();
    v20 = v11->max_length.size / 0x100000;
    v12 = il2cpp_value_box(TypeInfo::System::Int32, &v20);
    v13 = System::String::Format(StringLiteral_u62E1u5F35u3057u307Eu3057u305F_0_MB, v12, nullptr);
    UnityEngine::Debug::LogWarning(v13, nullptr);
    v4 = TypeInfo::LoadByeStatic;
  }
  v14 = v21[0];
  v15 = v21[0];
  if ( !v4->_1.cctor_finished_or_no_cctor )
  {
    il2cpp_class_get_assemblyname_1(v4);
    v14 = v21[0];
    v4 = TypeInfo::LoadByeStatic;
  }
  v16 = v4->static_fields->m_LoadTmpByteArray;
  if ( !v14 )
    sub_18038C2D0();
  v17 = (*(*v14 + 0x1F8LL))(v14, *(*v14 + 0x200LL));
  if ( !v15 )
    sub_18038C2D0();
  (*(*v15 + 0x348LL))(v15, v16, 0, v17, *(*v15 + 0x350LL));
  if ( v21[0] )
    sub_180002B60(0, TypeInfo::System::IDisposable);
  v18 = TypeInfo::LoadByeStatic;
  if ( !TypeInfo::LoadByeStatic->_1.cctor_finished_or_no_cctor )
  {
    il2cpp_class_get_assemblyname_1(TypeInfo::LoadByeStatic);
    v18 = TypeInfo::LoadByeStatic;
  }
  return Util::OutDeCreateByte(v18->static_fields->m_LoadTmpByteArray, v3, nullptr);
}

解密方法为RijndaelEngine(256) + CBC + PKCS7,IV = x + "01127802CDEF00BC",Key = y + "06789412023ED45"

xy从未加密的ab包内读取

C
// Byte[] OutDeCreateByte(Byte[], Int32)
Byte__Array *Util::OutDeCreateByte(Byte__Array *inByte, int32_t byteSize, MethodInfo *method)
{
  RijndaelManaged *aesManaged; // rax
  __int64 *v6; // rax
  __int64 v8; // [rsp+20h] [rbp-18h]

  if ( !byte_1830E4F22 )
  {
    sub_18038C080(&TypeInfo::System::Security::Cryptography::ICryptoTransform);
    byte_1830E4F22 = 1;
  }
  aesManaged = Util::get_aesManaged(nullptr);
  if ( !aesManaged  (v6 = (aesManaged->klass->vtable.CreateDecryptor.methodPtr)(aesManaged, aesManaged->klass->vtable.CreateDecryptor.method)) == nullptr )
    sub_18038C2D0();
  LODWORD(v8) = 0;
  return sub_180233820(4u, TypeInfo::System::Security::Cryptography::ICryptoTransform, v6, inByte, v8, byteSize);
}

// RijndaelManaged get_aesManaged()
RijndaelManaged *Util::get_aesManaged(MethodInfo *method)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

  if ( !byte_1830E4F1F )
  {
    sub_18038C080(&TypeInfo::System::Security::Cryptography::RijndaelManaged);
    sub_18038C080(&TypeInfo::Util);
    byte_1830E4F1F = 1;
  }
  v1 = TypeInfo::Util;
  if ( !TypeInfo::Util->static_fields->aes )
  {
    Util::Update(nullptr);
    v2 = sub_18033E930(TypeInfo::System::Security::Cryptography::RijndaelManaged);
    System::Security::Cryptography::RijndaelManaged::RijndaelManaged(v2, nullptr);
    TypeInfo::Util->static_fields->aes = v2;
    sub_18038B400(&TypeInfo::Util->static_fields->aes);
    aes = TypeInfo::Util->static_fields->aes;
    if ( !aes )
      goto LABEL_14;
    (aes->klass->vtable.set_KeySize.methodPtr)(aes, 0x100, aes->klass->vtable.set_KeySize.method);
    v4 = TypeInfo::Util->static_fields->aes;
    if ( !v4 )
      goto LABEL_14;
    (v4->klass->vtable.set_BlockSize.methodPtr)(v4, 0x100, v4->klass->vtable.set_BlockSize.method);
    v5 = TypeInfo::Util->static_fields->aes;
    if ( !v5 )
      goto LABEL_14;
    (v5->klass->vtable.set_Mode.methodPtr)(v5, 1, v5->klass->vtable.set_Mode.method);
    v6 = TypeInfo::Util->static_fields->aes;
    UTF8 = System::Text::Encoding::get_UTF8(nullptr);
    if ( !UTF8
       (v8 = (UTF8->klass->vtable.GetBytes_1.methodPtr)(UTF8, TypeInfo::Util->static_fields->IV, UTF8->klass->vtable.GetBytes_1.method), !v6)
       ((v6->klass->vtable.set_IV.methodPtr)(v6, v8, v6->klass->vtable.set_IV.method), v9 = TypeInfo::Util->static_fields->aes, (v10 = System::Text::Encoding::get_UTF8(nullptr)) == nullptr)
       (v11 = (v10->klass->vtable.GetBytes_1.methodPtr)(v10, TypeInfo::Util->static_fields->K, v10->klass->vtable.GetBytes_1.method), !v9)
      || ((v9->klass->vtable.set_Key.methodPtr)(v9, v11, v9->klass->vtable.set_Key.method), (v12 = TypeInfo::Util->static_fields->aes) == nullptr) )
    {
LABEL_14:
      sub_18038C2D0();
    }
    (v12->klass->vtable.set_Padding.methodPtr)(v12, 2, v12->klass->vtable.set_Padding.method);
    v1 = TypeInfo::Util;
  }
  return v1->static_fields->aes;
}

// Void Update()
void Util::Update(MethodInfo *method)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]

  if ( !byte_1830E4F20 )
  {
    sub_18038C080(&MethodInfo::UnityEngine::Resources::Load<Unique>);
    sub_18038C080(&TypeInfo::Util);
    sub_18038C080(&StringLiteral__06789412023ED45);
    sub_18038C080(&StringLiteral_Manager_unique);
    sub_18038C080(&StringLiteral__01127802CDEF00BC);
    byte_1830E4F20 = 1;
  }
  if ( System::String::IsNullOrEmpty(TypeInfo::Util->static_fields->IV, nullptr) )
  {
    v1 = UnityEngine::Resources::Load<System::Object>(StringLiteral_Manager_unique, MethodInfo::UnityEngine::Resources::Load<Unique>);
    v2 = v1;
    if ( !v1 )
      sub_18038C2D0();
    TypeInfo::Util->static_fields->IV = System::String::Concat(v1[2].klass, StringLiteral__01127802CDEF00BC, nullptr);
    sub_18038B400(TypeInfo::Util->static_fields);
    TypeInfo::Util->static_fields->K = System::String::Concat(v2[2].monitor, StringLiteral__06789412023ED45, nullptr);
    sub_18038B400(&TypeInfo::Util->static_fields->K);
  }
}

最终代码

C#
using Org.BouncyCastle.Crypto;
using Org.BouncyCastle.Crypto.Engines;
using Org.BouncyCastle.Crypto.Modes;
using Org.BouncyCastle.Crypto.Paddings;
using Org.BouncyCastle.Crypto.Parameters;
using System.Text;

namespace Decrypt_AssetBundle
{
    internal static class Program
    {
        private const string X = "D9AB89AA56F56730";
        private const string Y = "0BFAB106A793DCA7F";
        private const string IvSuffix = "01127802CDEF00BC";
        private const string KeySuffix = "06789412023ED45";

        private static readonly byte[] Iv = Encoding.UTF8.GetBytes(X + IvSuffix);
        private static readonly byte[] Key = Encoding.UTF8.GetBytes(Y + KeySuffix);

        private static int Main(string[] args)
        {
            if (args.Length != 2)
            {
                Console.WriteLine("Usage: Decrypt_AssetBundle <input_folder> <output_folder>");
                return 1;
            }

            string inputFolder = args[0];
            string outputFolder = args[1];

            if (!Directory.Exists(inputFolder))
            {
                Console.WriteLine($"Input folder not found: {inputFolder}");
                return 1;
            }

            Directory.CreateDirectory(outputFolder);

            string[] inputFiles = Directory.GetFiles(inputFolder, "*", SearchOption.AllDirectories);
            if (inputFiles.Length == 0)
            {
                Console.WriteLine($"No files found in input folder: {inputFolder}");
                return 0;
            }

            int success = 0;
            int failed = 0;
            int maxDegree = Environment.ProcessorCount;
            object logLock = new();
            ThreadLocal<IBufferedCipher> threadDecryptor = new(CreateDecryptor, true);

            Parallel.ForEach(
                inputFiles,
                new ParallelOptions { MaxDegreeOfParallelism = maxDegree },
                inputFile =>
                {
                    IBufferedCipher decryptor = threadDecryptor.Value!;
                    string relativePath = Path.GetRelativePath(inputFolder, inputFile);
                    string outputFile = Path.Combine(outputFolder, relativePath);
                    string? outputDir = Path.GetDirectoryName(outputFile);
                    if (!string.IsNullOrEmpty(outputDir))
                    {
                        Directory.CreateDirectory(outputDir);
                    }

                    try
                    {
                        byte[] cipher = File.ReadAllBytes(inputFile);
                        byte[] plain = Decrypt(cipher, decryptor);
                        File.WriteAllBytes(outputFile, plain);
                        Interlocked.Increment(ref success);
                        lock (logLock)
                        {
                            Console.WriteLine($"[OK] {relativePath}");
                        }
                    }
                    catch (Exception ex)
                    {
                        Interlocked.Increment(ref failed);
                        lock (logLock)
                        {
                            Console.WriteLine($"[FAIL] {relativePath} -> {ex.Message}");
                        }
                    }
                }
            );

            threadDecryptor.Dispose();
            Console.WriteLine($"Threads: {maxDegree}");
            Console.WriteLine($"Done. Success: {success}, Failed: {failed}, Total: {inputFiles.Length}");
            return failed == 0 ? 0 : 2;
        }

        private static IBufferedCipher CreateDecryptor()
        {
            IBufferedCipher decryptor = new PaddedBufferedBlockCipher(
                new CbcBlockCipher(new RijndaelEngine(256)),
                new Pkcs7Padding()
            );
            decryptor.Init(false, new ParametersWithIV(new KeyParameter(Key), Iv));
            return decryptor;
        }

        private static byte[] Decrypt(byte[] cipher, IBufferedCipher decryptor)
        {
            decryptor.Reset();

            byte[] plain = new byte[decryptor.GetOutputSize(cipher.Length)];
            int processed = decryptor.ProcessBytes(cipher, 0, cipher.Length, plain, 0);
            processed += decryptor.DoFinal(plain, processed);

            if (processed != plain.Length)
            {
                Array.Resize(ref plain, processed);
            }

            return plain;
        }
    }
}

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

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