Files
Kubok-Regionov/MetamorphicCore-Reverse/solve/writeup.md
2025-12-22 05:19:38 +03:00

7.8 KiB
Raw Blame History

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. Поиск вызовов функций

Находим функцию, отвечающую за вызов методов:

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:

[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
}

Ставим брейкпоинты и видим сравнение строк:

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}