VMP Anti Debugging

[0x00] Overview

VMP 는 유저모드, 커널모드, 유저모드+커널모드 디버깅에 대한 방지를 적용할 수 있습니다. 이전에 플러그인의 존재를 몰랐을 때 저는 직접 수동으로 하루종일 찾아 헤맸습니다. 기본적으로 유저모드 디버깅 방지에 어떤 기법이 적용되어 있는지 알아보겠습니다.

[0x01] VMP Packing

우선 예제 소스코드는 아래와 같습니다. 패킹 하기 전에 vmp 마커를 통해 가상화 구간을 지정했습니다. 또한 유저모드 안티 디버깅도 적용하여 패킹했습니다.

#include <stdio.h>
#include <Windows.h>
#include <VMProtectSDK.h>
#pragma warning(disable:4996)
#pragma comment(lib,"VMProtectSDK64.lib")

BOOLEAN ValidationCheck()
{
	int input = 0;
	int valid = 13579;
	VMProtectBegin("testvmp");
	printf("[+] Input Key : ");
	scanf("%d", &input);

	if (input == valid)
	{
		printf("[+] Correct!\n");
		return TRUE;
	}

	else
	{
		printf("[!] Incorrect!\n");
		return FALSE;
	}
	VMProtectEnd();
}

int main()
{
	printf("Ok, VMP Test\n");
	VMProtectBeginVirtualization("Virtual");
	
	if (ValidationCheck())
	{
		for (int i = 0; i < 10; i++)
		{
			printf("w0w..\n");
		}
	}
	else
	{
		exit(1);
	}
	VMProtectEnd();
}

[0x02] VMP Anti Debugging

처음에 플러그인에 존재를 몰랐기 때문에 직접 디버깅하며 찾았었습니다.(매우 어리석은 짓이지만 큰 도움이 됐었습니다.) VMP에 적용되어 있는 유저모드 안티 디버깅 기법과 이에 대한 우회방법에 대해 설명하겠습니다. 제가 사용하는 VMP 패커에서는 정확히 아래에 기법 순서대로 동작합니다.

[-] PEB.BeingDebugged

IsDebuggerPresent 함수로 더 잘 알려진 값입니다. 프로세스의 PEB 에 존재하는 멤버로써 디버깅 중인 경우 1로 설정됩니다. 간단하게 이 값을 0으로 설정하면 프로세스가 다시 실행되지 않는 한 바뀌지 않습니다.

x64 기준 gs:[60h]에 프로세스의 PEB가 존재하고 gs:[60h] + 2 위치에 BeingDebugged 멤버가 존재합니다.

[-] PEB.NtGlobalFlag

마찬가지로 PEB에 존재하는 값입니다. 문서화 되어있으며 GFLAGS.exe 도구를 이용해 확인할 수 있습니다. 해당 플래그 값을 통해 디버깅 여부를 판단할 수 있습니다.

x64 기준 gs:[60h] + 0xbc 위치에 해당 멤버가 존재하며 0x70인 경우 디버깅 중임을 의미합니다. 마찬가지로 0으로 설정해주면 우회가 가능합니다.

[-] NtQueryInformationProcess

__kernel_entry NTSTATUS NtQueryInformationProcess(
  IN HANDLE           ProcessHandle,
  IN PROCESSINFOCLASS ProcessInformationClass,
  OUT PVOID           ProcessInformation,
  IN ULONG            ProcessInformationLength,
  OUT PULONG          ReturnLength
);

PROCESSINFOCLASS 내 열거되어 있는 클래스 식별 값을 전달하여 이에 해당하는 프로세스 정보를 획득합니다. 열거되어 있는 클래스 중 ProcessDebugPort(0x7), ProcessDebugObjectHandle(0x1E), ProcessDebugFlags(0x1F)를 이용하여 디버깅 중인지 판별할 수 있습니다.

VMP 에서는 ProcessDebugPortProcessDebugObjectHandle 클래스를 이용하여 디버깅 여부를 판별합니다. 유저모드에서 syscall 을 호출하기 전에 클래스를 0으로 교체해주면 우회가 가능합니다.

[-] NtSetInformationThread

__kernel_entry NTSYSCALLAPI NTSTATUS NtSetInformationThread(
  HANDLE          ThreadHandle,
  THREADINFOCLASS ThreadInformationClass,
  PVOID           ThreadInformation,
  ULONG           ThreadInformationLength
);

THREADINFOCLASS 내 열거되어 있는 클래스 식별 값을 전달하여 이에 해당하는 값으로 스레드 상태를 설정합니다. 대표적으로 ThreadHideFromDebugger(0x11) 을 이용합니다. 말 그대로 디버거로 부터 스레드를 숨기기 때문에 디버깅이 불가능한 상태가 되며, 이미 어태치 된 디버거도 무용지물이 됩니다.

마찬가지로 호출하기 전에 클래스를 0으로 교체해주면 우회가 가능합니다.

[-] NtClose

__kernel_entry NTSYSCALLAPI NTSTATUS NtClose(
  HANDLE Handle
);

오브젝트 핸들을 닫는 루틴을 가지고 있습니다. 디버깅 중일 때 예외를 디버거에서 처리해야 된다는 점을 이용하여 0xDEADC0DE 라는 잘못된 핸들 값을 넘겨 디버깅이 불가능하도록 방지합니다.

마찬가지로 0으로 교체해주면 우회가 가능합니다.

[0x03] Plugins

개인적으로 ScyllaHide 내 HookLibrary 코드를 보는 것을 추천드립니다.

  • ScyllaHide : 안티 디버깅 관련된 여러가지 기법들을 선택적으로 우회할 수 있도록 만들어진 플러그인입니다.

    • https://github.com/x64dbg/ScyllaHide
  • SharpOD : 마찬가지로 선택이 가능합니다. 커널 단에서 사용 가능한 몇 가지 기법이 적용되어 강력합니다.

    • https://github.com/A-new/x64dbg_plugin/tree/master/x32

[0x04] Proof of Concept

영상의 내용은 위에서 말한 내용대로 안티 디버깅 기법을 우회하고 코드영역에 주요 코드가 풀리는 내용입니다.

[0x05] Conclusion

플러그인을 사용하는 것을 권장합니다. 하지만 플러그인을 만드는 것은 또 다른 도전입니다. 현재 InfinityHideTitanHide, ScyllaHide 의 소스코드를 이용하여 유저모드의 안티 디버깅을 우회할 수 있는 플러그인을 직접 제작하는 프로젝트를 진행하고 있습니다.