[리버싱] : MS에서 지원하는 안티 디버깅 (IsDebuggerPresent)

#REVERSING

Microsoft에서 지원하는 안티 디버깅 IsDebuggerPresent의 작동 방식


x64dbg에 kernel32.dll을 올리고 CTRL+G를 눌러 IsDebuggerPresent을 검색하여 해당 명령어로 이동하면 아래와 같은 코드가 등장한다. 고작 3줄에 불가하는 코드이다.

IsDebuggerPresent

고작 3줄의 코드로 어떻게 디버깅을 감지하는 것인가

mov rax, qword ptr gs:[60]

gs레지스터는 Windows의 TEB(Thread Environment Block)를 가리킨다. TEB는 각 스레드마다 고유하게 존재하며, 여러 가지 스레드 관련 정보를 가지고 있다.

gs:[60]는 gs 레지스터가 가리키는 주소에 0x60바이트(10진수로 96바이트)만큼 떨어진 곳을 나타낸다.

그렇다면 우선 TEB의 구조를 알 필요가 있다. MSDN에서 TEB의 구조체를 보면 다음과 같다.

typedef struct _TEB {
  PVOID Reserved1[12]; // 64비트에서 PVOID의 크기는 8byte
  PPEB  ProcessEnvironmentBlock; // gs:[60]이 가리키는 곳
  PVOID Reserved2[399];
  BYTE  Reserved3[1952];
  PVOID TlsSlots[64];
  BYTE  Reserved4[8];
  PVOID Reserved5[26];
  PVOID ReservedForOle;
  PVOID Reserved6[4];
  PVOID TlsExpansionSlots;
} TEB, *PTEB;

PVOID 자료형의 크기는 실행되는 시스템에 따라 다른데 일반적으로 32bit의 경우 4바이트 64bit의 경우 8바이트라고 한다.

개별 원소가 8바이트인 배열의 크기가 12이므로 12 * 8 = 96 위에 나온 0x60바이트와 정확히 일치한다.

이로 인해 gs:[60]은 PEB(ProcessEnvironmentBlock)을 가리킨다는 것을 확인했다.

이로써 첫 번째 명령어의 의도를 파악했다.

첫 번째 명령어는 PEB의 주소를 rax 레지스터에 로드한다.

movzx eax, byte ptr ds:[rax+2]

현재 rax에는 PEB의 주소를 가지고 있다.

rax+2는 PEB의 특정 오프셋을 가리키는데 MSDN에서 PEB 구조체를 보면 다음과 같다.

typedef struct _PEB {
  BYTE                          Reserved1[2];
  BYTE                          BeingDebugged;
  BYTE                          Reserved2[1];
  PVOID                         Reserved3[2];
  PPEB_LDR_DATA                 Ldr;
  PRTL_USER_PROCESS_PARAMETERS  ProcessParameters;
  PVOID                         Reserved4[3];
  PVOID                         AtlThunkSListPtr;
  PVOID                         Reserved5;
  ULONG                         Reserved6;
  PVOID                         Reserved7;
  ULONG                         Reserved8;
  ULONG                         AtlThunkSListPtr32;
  PVOID                         Reserved9[45];
  BYTE                          Reserved10[96];
  PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;
  BYTE                          Reserved11[128];
  PVOID                         Reserved12[1];
  ULONG                         SessionId;
} PEB, *PPEB;

위로 알 수 있는 것은 rax+2의 오프셋은 BeingDebugged이다. 여기에 1이 들어가 있으면 디버깅 중인거고 0이 들어가 있으면 디버깅 중이 아닌 것이다.

ret

위의 BeingDebugged의 값이 eax 레지스터에 들어있으니 이대로 리턴하여 함수를 종료한다.

MS사의 IsDebuggerPresent 함수를 알아봤다. 간단하고 유명한 함수라서 우회하는 방법도 흔히 알려졌다.

그래도 정 쓰고 싶다면 kernel32.dll에서 로드해서 쓰는 것보다 아래와 같이 어셈블리로 직접 코딩해서 쓰는 편이 낫다.

BOOL debuggerPresent = FALSE;
__asm {
    mov rax, gs:[60h]
    movzx eax, byte ptr [rax+2]
    mov debuggerPresent, eax
}