290 lines
7.8 KiB
Markdown
290 lines
7.8 KiB
Markdown
# Writeup: Metamorphic Core Crackme
|
||
|
||
## **Используемые инструменты**
|
||
- **DnSpy**
|
||
- **Detect It Easy**
|
||
- **UPX**
|
||
- **ExtremeDumper**
|
||
- **.NET Reactor Slayer**
|
||
|
||
---
|
||
|
||
## **Шаг 1. UPX упаковка**
|
||
|
||
Загружаем файл в **Detect It Easy** и видим, что он упакован **UPX**.
|
||
Попытка распаковать через `upx.exe -d <file>` неудачна — заголовок повреждён.
|
||
|
||
Открываем бинарь в **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 (<Module>{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 (<Module>{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 (<Module>{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 (<Module>{3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_61742430ed914e4ca05833d60e3db39b == 0)
|
||
{
|
||
break;
|
||
}
|
||
continue;
|
||
IL_A0:
|
||
if (num2 >= 0)
|
||
{
|
||
goto IL_06;
|
||
}
|
||
num = 0;
|
||
if (<Module>{3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_4090cbdba33d4db1ba74b2a9eac02aa1 == 0)
|
||
{
|
||
continue;
|
||
}
|
||
IL_B5:
|
||
this.stack_1.Push(struct2);
|
||
num = 3;
|
||
if (<Module>{3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_fe002c72199d46f0b4a52d0c2bff689e == 0)
|
||
{
|
||
goto Block_4;
|
||
}
|
||
}
|
||
continue;
|
||
IL_30:
|
||
Class3.Struct1 struct3;
|
||
struct2 = struct3;
|
||
num = 14;
|
||
if (<Module>{3a1b6496-e3bc-4dd9-bc7f-29db217cfc86}.m_0425df13f0c045d99d31a6db275fbd9e == 0)
|
||
{
|
||
goto IL_44;
|
||
}
|
||
goto IL_123;
|
||
IL_6B:
|
||
struct3.int_1 = 0;
|
||
num = 3;
|
||
if (<Module>{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 (<Module>{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}
|
||
```
|