Init. import
This commit is contained in:
289
MetamorphicCore-Reverse/solve/writeup.md
Normal file
289
MetamorphicCore-Reverse/solve/writeup.md
Normal file
@@ -0,0 +1,289 @@
|
||||
# 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}
|
||||
```
|
||||
Reference in New Issue
Block a user