본문 바로가기
reversing

안티 디버깅 Static

by 코끼리_땃쥐 2022. 10. 4.
반응형

안티 디버깅 분류

    Static, Dynamic

        - Static 기법은 디버깅 시작할 때 한번만 해체를 해주는 기법

        - Dynamic 기법은 디버깅을 진행하면서 (해당 Anti기법을) 만날 때마다 해결하는 기법

 

 

Static 안티 디버깅의 목적

디버기 프로세스에서 자신이 디버깅 당하는지 여부를 파악하는 기법

만약 디버깅 중이라고 판단되면 일반 실행과는 다른 코드(주로 종료 코드)를 실행하는 것이 핵심입니다.

구현 방법으로는 디버거를 탐지하는 방법, 디버깅 환경을 탐지하는 방법, 디버거를 강제 분리시키는 방법등이 있습니다. 회피 방법은 탐지 코드에서 얻어 오는 정보를 파악하여 아예 그 정보 자체를 변경해버리는 것입니다.

 

PEB

현재 프로세스의 디버깅 여부를 판단하기 위해 PEB(Process Environment Block)구조체 정보를 이용할 수 있습니다. 정보를 신용할 수 있고 사용하기 쉽기 때문에 가장 널리 사용되는 안티 디버깅 기법입니다.

 

안티 디버깅 기법에 사용되는 PEB멤버
+0x002 BeingDebugged    : UChar
+0x00C Ldr                         : Ptr32 _PEB_LDR_DATA
+0x018 ProcessHeap         : Ptr32 Void
+0x068 NtGlobalFlag          : Uint4B

BeingDebugged 멤버는 디버깅 여부를 표시하는 Flag로 사용되며, 나머지 Ldr, Processheap, NtGlobalFlag 멤버들은 디버깅 프로세스의 독특한 힙(Heap) 메모리 특성과 관련되어 있습니다.

 

Beingdebugged(+0x002)

PEB.BeingDebugged 멤버(+0x002)의 값은 디버깅 중일 때 1(TRUE)로 세팅되고, 일반 실행인 경우 0(FALSE)으로 세팅 됩니다.

 

IsDebuggerPresent() API는 PEB.BeingDebugged 값을 참조하여 디버깅 여부를 판별합니다.

 

Ldr(+0x00C)

디버깅 프로세스는 힙(Heap) 메모리 영역에 자신이 디버깅 당하는 프로세스라는 표시를 합니다. 그중에서 아주 눈에 띄는 특징이 있는데요. 바로 힙 메모리의 사용이 되지 않는 영역을 0xFEEEFEEE 값으로 채워 버립니다. 이것은 프로세스가 디버깅 당할 때 나타나는 흔적입니다. 이 특징을 바탕으로 프로세스의 디버깅 여부를 판별할 수 있습니다.

PEB.Ldr 멤버는 _PEB_LDR_DATA 구조체를 가리키는 포인터 입니다. 마침 _PEB_LDR_DATA 구조체는 힙 메모리 영역에 생성되기 때문에 이 영역을 스캔(Scan)해보면 쉽게 알 수 있습니다.

 

Process Heap(+0x018)

PEB.ProcessHeap 멤버(+0x018)는 HEAP 구조체를 가리키는 포인터 입니다.

HEAP 구조체의 일부

+0x000 Entry                                : _HEAP_ENTRY 
+0x008 Signture                           : Uint4B
+0x00C Flags                               : Uint4B
+0x010 ForceFlags                       : Uint4B
+0x014 VirtualMemoryThreshold  : Uint4B
. . .

위 코드는 HEAP 구조체의 일부 입니다. 이중에서 Flags 멤버(+0x00C)와 ForceFlags 멤버(+0x010)는 디버깅 중에 특정한 값으로 세팅됩니다.

 

Flags(+0x00C) & Force Flags(+0x010)

프로세스가 정살 실행 중일 때 Heap.Flags 멤버 (+0x00C)의 값은 0x2이고, Heap.ForceFlags 멤버(+0x010) 값은 0x0입니다. 디버깅 중일 때는 이 값들이 변경됩니다.

따라서 이 값들을 비교하면 프로세스의 디버깅 여부를 판별할 수 있습니다.

 

NtGlobalFlags(+0x068)

프로세스가 디버깅 중일 때 PEB.NtGlobalFlags 멤버 (+0x068)의 값은 0x70으로 세팅 됩니다. 이 값을 확인하면 디버깅 여부를 판별할 수 있습니다.

 

NtQueryInformationProcess()

 

디버거를 탐지해내는 또 다른 기법에 대한 설명입니다.

ntdll!NtQueryinformationProcess() API를 이용하면 프로세스의 디버깅 관련 정보를 비롯하여 매우 다양한 정보를 얻을 수 있습니다. 

 

NTSTATUS WINAPI NtQueryInformationProcess(
  __in      HANDLE ProcessHandle,
  __in      PROCESSINFOCLASS ProcessinformationClass,
  __out     PVOID Processinformation,
  __in      ULONG ProcessInformationLength,
  __out_opt PULONG ReturnLength
)

두 번째 파라미터 PROCESSINFOCLASS ProcessInformationClass에 원하는 정보형식을 입력한 후 NtQueryInformationProcess() 를 호출하면 세 번째 파라미터 PVOID ProcessInformation에 해당 정보가 세팅됩니다. PROCESSINFOCLASS는 열거형(enum) 으로써 다음과 같은 값을 가집니다.

enum PROCESSINFOCLASS
{
	ProcessBasicInformation = 0,
    ProcessquotaLimits,
    ProcessIoCounters,
    ProcessVmCounters,
    ProcessTimes,
    ProcessBasePriority,
    ProcessRaisePriority,
    ProcessDebugPort = 7                // 0x7
    ProcessExceptionPort,
    ProcessAccessToken,
    ProcessLdtInformation,
    ProcessLdtSize,
    ProcessDefaultHardErrorMode,
    ProcessIoPortHandlers,
    ProcessPooledUsageAndLimits,
    ProcessWorkingSetWatch,
    ProcessUserModeIOPL,
    ProcessEnableAlignmentFaultFixup,
    ProcessPriorityClass,
    ProcessWx86Information,
    ProcessHandleCount,
    ProcessAffinityMask,
    ProcessPriorityBoost,
    MaxProcessInfoClass,
    ProcessWow64Information = 26,
    ProcessImageFileName = 27,
    ProcessDebugObjecthandle = 30,      // 0x1E
    ProcessDebugFlags = 31              // 0x1F
}

위 코드에서 디버거 탐지에 사용 되는 것은 ProcessDebugProt(0x7), ProcessDebugObjectHandle(0x1E), ProcessDebugFlags(0x1F) 입니다.

 

ProcessDeubgPort(0x7)

프로세스가 디버깅 중일 때 Debug Port가 할당됩니다. ProcessInformationClass파라미터에 ProcessDebugPort(0x7) 값을 입력하면 DebugPort를 얻을 수 있습니다. 만약 프로세스가 디버깅 중이 아니라면 dwDebugPort 변수에 0이 세팅됩니다.

디버깅중이라면 0xFFFFFFFF 이 세팅됩니다.

 

CheckRemoteDebuggerPresent()

CheckRemoteDebuggerPresent() API는 IsDebuggerPresent() API와 비슷하게 디버깅 여부를 판별해 주는 함수입니다.

둘의 차이점은 CheckRemoteDebuggerPresent()는 다른 프로세스의 디버깅 여부까지 판단할 수 있다는 것입니다.

CheckRemoteDebuggerPresent() API 내부 코드를 보면 NtQueryInformationProcess(ProcessDebugPort) API를 이용하고 있습니다.

 

 

ProcessDebugObjectHandle(0x1E)

프로세스가 디버깅될 때 Debug Object가 생성됩니다.

ProcessDebugObjectHandle(0x1E)을 입력하면 Debug Obejct Handle을 구할 수 있습니다. 프로세스가 디버깅 중이라면 Debug Object Handle은 값이 존재할 것이고, 디버깅 중이 아니라면 DebugObjectHandle은 NULL 입니다.

 

ProcessDebugFlags(0x1F)

DebugFlags를 확인해서 프로세스의 디버깅 여부를 판별합니다. ProcessDebugFlags(0x1F)를 입력하여 DebugFlags를 구합니다. DebugFlags값이 0이면 디버깅 상태이고, 1이면 디버깅이 아닙니다.

 

NtQuerySystemInformation()

이 기법은 현재 OS가 Debug Mode로 부팅 되엇는지를 판단하는 안티 디버깅 기법입니다.

ntdll!NtQuerySystemInformation() API는 현재 동작 중인 OS시스템에 대한 다양한 정보를 수할 수 있는 시스템 함수입니다.

 

NtQuerySystemInformation() API
NTSTATUS WINAOI NtQuerySystemInformation(
	__in		SYSTEM_INFORMATION_CLASS SystemInformationClass,
    __in		PVOID SystemInformation,
    __in		ULONG SystemInformationLength,
    __out_opt	PULONG ReturnLength
)

SYSTEM_INFORMATION_CLASS SystemInformationClass 파라미터에 원하는 시스템 정보를 입력하고 PVOID SystemInformation 파라미터에 관련 구조체 주소를 넘겨주면 API가 리턴하면서 그 구조체에 관련 정보를 채워줍니다.

SYSTEM_INFORMATION_CLASS는 열거형(enum) 으로 다음과 같은 값을 가집니다.

typedef enum _SYSTEM_INFORMATION_CLASS {
	SystemBasicInformation = 0,
    SystemPerformanceinformation =2,
    SystemTimeOfDayInformation = 3,
    SystemProcessInformation = 5,
    SystemProcessPerformanceInformation = 8,
    SystemInterruptInformation = 23,
    SystemExceptioninformation = 33,
    SystemKernelDebuggerInformation = 35, 	// 0x23
    SystemRegistryQuotaInformation = 37,
    SystemLookasideInformation = 45
} SYSTEM_INFORMATION_CLASS;

SystemInformationClass 파라미터에 SystemKernelDebuggerInformation(0x23) 값을 입력하면 현재 OS시스템이 디버그 모드로 부팅되었는지 알 수 있습니다.

 

NtQueryObject()

시스템에서 어떤 디버거가 다른 프로세스를 디버깅 중이라면 그때 DebugObject타입의 커널 객체가 생성되는데, 그 DebugObject의 존재를 확인 하는 것입니다.

ntdll!NtQueryObject() API는 시스템의 다양한 종류의 커널 객체 정보를 구해오는 함수 입니다.

NtQueryObject() API
NTSTATUS NtQueryObject(
	__in_opt	HANDLE Handle,
    __in		OBJECT_INFORMATION_CLASS ObjectInformationClass,
    __out_opt	PVOID ObjectInformation,
    __in		ULONG ObjectInformationLength,
    __out_opt	PULONG ReturnLength
);

두 번째 파라미터 OBJECT_INFORMATION_CLASS ObjectInformationClass에 원하는 값을 입력하고 API를 호출하면, 세번째 파라미터 PVOID ObjectInformation에 관련 정보의 구조체 포인터를 리턴합니다.

먼저 OBJECT_INFORMATION_CLASS 는 열거형(enum)으로써 다음과 같은 값을 가집니다.

typedef enum _OBJECT_INFORMATION_CLASS{
	ObjectBasicInformation,
	ObjectNameInformation,
	ObjectTypeInformation,
    ObjectAllTypesInformation,		// 3
    ObjectHandleInformation
} OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;

ObjectAllTypeInformation 항목을 이용하여 시스템의 모든 객체 정보를 구한다음 그 중에 DebugObject가 있는지 확인합니다.

 

ZwSetInformationThread()

디버깅 당하는 쪽(디버기)에서 강제로 디버거를 떼어내는(Detach) 기법에 대한 설명입니다.

ZwSetInformationThread() API를 사용하면 자신을 디버깅 하고 있는 디버거를 떼어낼 수 있습니다.

ZwSetInformationThread() API
typedef enum _THREAD_INFORMATION_CLASS {
	ThreadBasicInformation,
    ThreadTimes,
    ThreadPriority,
    Threadbasepriority,
    ThreadAffinityMask,
    ThreadimpersonationToken,
    ThreadDescriptortableEntry,
    ThreadEnableAlignmentFaultFixup,
    ThreadEventPair,
    ThreadQuerySetwin32StartAddress,
    ThreadZeroTlsCell,
    ThreadperformanceCount,
    ThreadAmILastThread,
    ThreadIdealProcessor,
    ThreadPriorityBoost,
    ThreadSetTlsArrayAddress,
    ThreadIsIoPending,
    ThreadHideFromDebugger			// 17 (0x11)
} THREAD_INFORMATION_CLASS, *PTHREAD_INFORMATION_CLASS;

NTSTATUS ZwSetInformationThread(
	__in HANDLE ThreadHandle,
    __in THREADINFOCLASS ThreadInformationClass,
    __in PVOID ThreadInformation,
    __in ULONG ThreadInformationLength
);

ZwSetInformationThread() 함수는 이름 그대로 스레드에게 정보를 세팅하는 System Native API 입니다. 첫 번째 파라미터 ThreadHandle에 현재 스레드의 핸들을 넘겨주고, 두 번째 파라미터 ThreadInformationClass에 ThreadHideFrom Debugger (0x11) 값을 입력하면 디버거 프로세스가 분리됩니다.ZwSetInformationThread() API는 일반 실행의 경우 프로그램에 아무런 영향을 주지 않고, 디버거 실행인 경우에는 디버거를 종료시켜 버립니다.

 

TLS 콜백 함수

TLS 콜백 함수는 유용하게 사용되는 안티 디버깅 기법입니다.

TLS 콜백은 그 자체로 안티 디버깅 기법이라고 볼수 없지만 프로그램의 EntryPoint 코드보다 먼저 실행되는 특징 때문에 안티 디버깅에 많이 활용됩니다.

TLS콜백 함수 내에서 IsDebuggerPresent() 등의 함수로 간단히 디버깅 여부를 판별하여 프로그램을 계속 실행할지 말지 결정할 수 있습니다.

 

ETC

안티 디버깅 기법을 사용하는 목적은 특정 프로그램이 리버싱을 당하지 않도록 하기 위한 것입니다. 이러한 목적을 위해서라면 굳이 자신의 프로세스가 디버깅 당하는지 어렵게 판단할 필요가 없습니다. 간단히 현재 시스템이 (일반적인 시스템이 아니라) 리버싱을 위한 전용 시스템이라는 판단이 선다면 바로 프로그램의 실행을 멈춰버리는 것도 좋은 방법입니다. 이를 위해서 시스템에서 쉽게 구할 수 있는 일반적인 정보(Process, File, Window, Registry, host name, computer name, user name, Environment Variable등)들을 활용 하는 다양한 안티 디버깅 기법들이 존재 합니다.

반응형

'reversing' 카테고리의 다른 글

고급 안티 디버깅  (0) 2022.10.20
안티 디버깅 Dynamic  (1) 2022.10.05
NtQueryInformationProcess  (0) 2022.09.29
IA-32 Instruction 포맷  (0) 2022.09.27
TEB  (0) 2022.09.16

댓글