# Writeup: Metamorphic Core Crackme ## **Используемые инструменты** - **DnSpy** - **Detect It Easy** - **UPX** - **ExtremeDumper** - **.NET Reactor Slayer** --- ## **Шаг 1. UPX упаковка** Загружаем файл в **Detect It Easy** и видим, что он упакован **UPX**. Попытка распаковать через `upx.exe -d ` неудачна — заголовок повреждён. Открываем бинарь в **Hex Editor** и сравниваем заголовок с эталонным. Находим строку `MCVM` вместо `UPX0`. Исправляем байты на **UPX0** и успешно распаковываем через `upx -d`. --- ## **Шаг 2. Native-загрузчик .NET Reactor** Повторная проверка в **Detect It Easy** показывает: код нативный, обфусцирован **.NET Reactor**. Это значит, что бинарь упакован в **native loader**. Открываем EXE в **DnSpy (32-bit)** и запускаем. На точке **CreateProcess** видно, что процесс распакован и висит в памяти. Используем **ExtremeDumper** → находим `task.exe` → делаем дамп. Получаем два файла: - **_.dll** - **VmHost.exe** --- ## **Шаг 3. Деобфускация .NET Reactor** Открываем **VmHost.exe** в **DnSpy** или **Detect It Easy** — видим сильную обфускацию. Запускаем **.NET Reactor Slayer**, выбираем все опции и запускаем деобфускацию. Теперь бинарь читаем и доступен для отладки. --- ## **Шаг 4. Отладка VM** Открываем **VmHost.exe** в **DnSpy**. Ставим брейкпоинт в произвольном месте и смотрим окно **Local Variables**. Видим переменные: - **this.dictionary_*** - **this.list_*** - **this.stack_*** - **this.object_*** Здесь хранятся строки с именами функций виртуальной машины: **SHA256**, **ExitProcess**, **sha256_hex**, **tohex** и др. Значит, вызовы идут через опкод **CALL** в VM. --- ## **Шаг 5. Поиск вызовов функций** Находим функцию, отвечающую за вызов методов: ```csharp private void method_3(int int_3, int int_4) { Class3.Struct0 @struct; for (;;) { IL_177: @struct = this.struct0_0[int_3]; int num = 0; <-------------------------- Тут ставим брейкпоинт if ({3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_b53c11d96ca540509b6b9be7f90e8419 != 0) { goto IL_F4; } Class3.Struct1 struct2; for (;;) { IL_123: int num2; switch (num) { case 0: goto IL_B5; case 1: goto IL_F4; case 2: goto IL_177; case 3: case 4: goto IL_A0; case 5: num2--; num = 2; if ({3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_656fb7edff9a4bc3a280e41bdde428a0 != 0) { goto IL_A0; } continue; case 6: goto IL_6B; case 7: goto IL_D4; case 8: goto IL_197; case 9: goto IL_4F; case 10: return; case 11: goto IL_44; case 12: goto IL_62; case 13: goto IL_47; case 14: num2 = @struct.int_1 - 1; num = 0; if ({3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_9fc4e4f74325466da9db2cc7afcf1357 == 0) { goto IL_A0; } continue; case 15: goto IL_1B4; case 16: goto IL_30; case 17: break; default: goto IL_B5; } IL_06: struct2.object_0[num2] = this.stack_0.Pop(); num = 5; if ({3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_61742430ed914e4ca05833d60e3db39b == 0) { break; } continue; IL_A0: if (num2 >= 0) { goto IL_06; } num = 0; if ({3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_4090cbdba33d4db1ba74b2a9eac02aa1 == 0) { continue; } IL_B5: this.stack_1.Push(struct2); num = 3; if ({3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_fe002c72199d46f0b4a52d0c2bff689e == 0) { goto Block_4; } } continue; IL_30: Class3.Struct1 struct3; struct2 = struct3; num = 14; if ({3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_0425df13f0c045d99d31a6db275fbd9e == 0) { goto IL_44; } goto IL_123; IL_6B: struct3.int_1 = 0; num = 3; if ({3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_d940aa626e044e7a9f22554ba2f6bb07 == 0) { goto IL_30; } goto IL_123; IL_62: int num3; struct3.int_0 = num3; goto IL_6B; IL_4F: struct3.object_0 = new object[@struct.int_2]; goto IL_62; IL_47: struct3 = default(Class3.Struct1); goto IL_4F; IL_45: int num4; num3 = num4; goto IL_47; IL_D4: num4 = this.int_0; goto IL_45; IL_F4: if (int_4 != @struct.int_1) { goto Block_6; } if (this.stack_1.Count != 0) { goto IL_D4; } num = 4; if ({3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_b0871c76b442400c9823273656177106 == 0) { goto IL_123; } IL_44: num4 = -1; goto IL_45; } Block_4: goto IL_1B4; Block_6: IL_197: throw new Exception(Class5.smethod_17(656) + @struct.string_0); IL_1B4: this.int_0 = @struct.int_0; } ``` В локальной переменной **@struct** появляются названия функций, которые будут выполняться. Здесь видим вызов **exists**, и в **object_0** лежит строка: ``` C:\Users\user\AppData\Local\Temp\.password\metamorphic.core ``` Значит, программа требует этот файл. Создаём его с любыми данными: ``` test1 test2 test3 ``` ## **Шаг 6. Работа с файлом** Перезапускаем отладку. Теперь программа не падает, т.к. файл найден. Следующий вызов — **"read_all_ascii"**. В **object_0** появляются строки из файла. Затем вызываются **slice** и вспомогательные функции, которые берут первые две строки. После вызывается **abort**, и программа завершается. ## **Шаг 7. Проверка условий (IF)** Ищем в коде VM функции условий IF: ```csharp [CompilerGenerated] private bool method_21(object object_2, object object_3) { return this.method_8(object_2, object_3); // Equals } [CompilerGenerated] private bool method_22(object object_2, object object_3) { return !this.method_8(object_2, object_3); // Equals } ``` Ставим брейкпоинты и видим сравнение строк: ```csharp object_2 = "test1" object_3 = "MetamorphicVirtualMachine" ``` Значит, первая строка файла должна быть MetamorphicVirtualMachine. ## **Шаг 8. XOR-дешифровка второй строки** Дальше вызывается функция **xorstr**. В **object_0** лежит строка **+)8$)/e%+':-zxz}** и ключ **0x48**. Расшифровка XOR даёт строку: ``` caplag-mcore2025 ``` Эта строка сравнивается со второй строкой файла. Итого, правильное содержимое файла **metamorphic.core**: ``` Metamorphic Core :: crackme Привет: DESKTOP-6IRVEAN, user user OK: заголовки валидны caplag{6ed9095fc6d38efbdea82031d16150c1b80c16cd641a135f117bdc411dbba68a} Готово ``` ✅ Флаг: ``` caplag{6ed9095fc6d38efbdea82031d16150c1b80c16cd641a135f117bdc411dbba68a} ```