정적 안티디버깅이란 처음에 한번 실행할 때 안티디버깅이 걸려있는 것으로 실행 전에 한번 해체를 하면 된다.
동적으로 매번 각각의 기법으로 해제해줘야하는 것들보다 훨씬 쉽겠다.
프로세스 정보를 담고 있는 구조체 PEB를 이용한 경우가 접근이 쉬워 많이 이용한다.
PEB 구조체 주소는 FS 세그먼트(또는 GS 세그먼트)를 통해 쉽게 구할 수 있다.
FS레지스터
- 예외 처리 및 스레드 관리
- 각 스레드 상태를 관리하는 TEB를 가리키며, 예외 처리, 스레드 관련 설정 및 정보를 관리하는 데 사용
GS 레지스터
- 디버깅 상태 및 TLS 관리
- 디버거 상태 플래그, 디버깅 관련 정보, TLS를 관리하는 데 사용
x86 시스템에서는 FS, x64에서는 GS 세그먼트를 이용하여 PEB에 접근한다.
x86에서 세그먼트를 이용한 TEB/PEB 주소
- SEH : FS:[0x00]
- TEB : FS:[0x18], GS:[0x00]
- PEB : FS:[0x30], FS:[0x18] 그리고 +0x30
x64에서 세그먼트를 이용한 TEB/PEB 주소
- TEB : GS:[0x30]
- PEB : GS:[0x60], GS:[0x30] 그리고 +0x60
PEB 구조체 중 안티 디버깅에 사용되는 멤버다.
+0x002 BeingDebugged : Uchar
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
+0x018 ProcessHeap : Ptr32 Void
+0x068 NtGlobalFlag : Uint4B
디버깅 중일 때 1, 아니면 0으로 세팅
오프셋: PEB 주소 + 0x2
해당 값을 0으로 변경하여 우회 가능
디버깅 중일 때: 0xFEEEFEEE
오프셋: PEB 주소 + 0xc
해당 값을 NULL로 변경하여 우회 가능
Windows Vista부터 제거됨
힙 구조체를 가리키는 포인터
오프셋: PEB 주소 + 0x18
힙 구조체의 Flags 및 Force 멤버가 디버깅 중일 때 특정 값으로 세팅됨
디버깅되지 않을 때 Flags는 0x2, Force는 0x0으로 설정됨
Flags는 0x2, Force는 0x0으로 변경하여 우회 가능
Windows 7부터는 제거됨
디버깅, 커널 모드 설정, 성능 추적 등의 작업에 사용
디버깅중일 때 0x70으로 세팅
오프셋: PEB 주소 + 0x68
해당 값으로 0으로 변경하여 우회 가능
IsDebuggerPresent()는 PEB를 이용한 안티 디버깅에 쓰이는 API다.
- 현재 프로세스가 디버깅 중인지 확인할 수 있는 함수
- 디버깅이 감지되면 특정 동작을 하지 않도록 하거나 예외를 발생시켜 디버깅을 방해
- 디버거가 프로세스에 연결 되었는지를 확인
IsDebuggerPresent API는 gs:[0x60]를 참고하여 TEB에 접근하고 BeingDebugged 필드인 rax+2 주소를 참조한다.
BeingDebugged를 0으로 바꿔서 우회시킨다.
NtQueryInformationProcess는 Windows API로 특정 프로세스에 대한 다양한 정보를 가져오는 데 사용된다. 프로세스의 상태, 핸들 정보, 메모리 정보 등을 확인할 수 있다.
구조를 본다면
NTSTATUS NtQueryInformationProcess(
HANDLE ProcessHandle, // 정보가 조회될 프로세스의 핸들
PROCESSINFOCLASS ProcessInformationClass, // 조회하고자 하는 정보의 종류
PVOID ProcessInformation, // 조회된 정보를 받을 버퍼
ULONG ProcessInformationLength, // 버퍼의 크기
PULONG ReturnLength // 반환된 데이터의 실제 길이
);
두 번째 파라미터 ProcessInformationClass에 원하는 정보를 입력하고 API를 호출하면 세 번째 파라미터인 ProcessInformation에 해당 정보가 세팅된다.
PROCESSINFOCLASS는 열거형(enum)으로 그 값들 중 디버거 탐지에 사용되는 것들이다.
typedef enum _PROCESSINFOCLASS{
...
ProcessDebugPort = 7, // 0x7
...
ProcessDebugObjectHandle = 30, // 0x1E
ProcessDebugFlags = 31, // 0x1F
} PROCESSINFOCLASS, * PPROCESSINFOCLASS;
CheckRemoteDebuggerPresent( )는 NtQueryInformationProcess를 이용한 안티 디버깅 API이다.
IsDebuggerPresent와 달리 다른 프로세스의 디버깅 여부까지 판단할 수 있다.
CheckRemoteDebuggerPresent()
- 프로세스 외부에서 디버거가 연결되었는지 확인
- 디버거가 연결되면 특정 플래그가 설정되기 때문에 이를 체크하여 디버깅 여부를 확인
GetCurrentProcess를 통해 현재 실행되는 프로세스를 확인한다.
CheckRemoteDebuggerPresent는 내부를 보면 NtQueryInformationProcess를 호출한다.
곧 NtQueryInformationProcess는 사용이 중지되어 IsDebuggerPresent 함수로 대체될 것이라고 한다.

Debug Port를 얻기 위해 ProcessDebugPort에 0x7 값을 세팅합니다.
API 호출 후 버퍼(R8)에 디버깅 정보가 저장되어 있습니다.
한번 그 주소로 가보자
0xFFFFFFFF면 디버깅 중이란거다.
이렇게 0으로 되어있으면 디버깅되괴 있지 않다는 것이다.
NtQueryInformationProcess의 3번째 파라미터에 저장된 주소, 프로세스 정보가 저장된 버퍼의 값을 0으로 바꿔서 우회하는 것이다.
앞선 방법들은 디버깅 중인지를 확인하는 직접적인 탐지 방법이었다면, 디버깅 환경을 확인하는 간접적인 탐지 방법도 있다.
NtQuerySystemInformation는 디버깅 환경을 확인하는 방법으로 현재 OS가 디버깅 모드로 부팅되었는지를 판단하는데, 디버그 모드인 경우 1로 세팅된다.
NTSTATUS NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass, // 요청 시스템 정보
PVOID SystemInformation, // 요청 정보가 저장될 버퍼
ULONG SystemInformationLength, // 요청 정보의 최대 크기
PULONG ReturnLength // 실제로 반환된 정보의 크기
);
SystemInformationClass 파라미터는 열거형으로 그 값들 중 디버거 탐지에 사용되는 것들이다.
typedef enum _SYSTEM_INFORMATION_CLASS { ... SystemKernelDebuggerInformation = 35, // 0x23 ... } SYSTEM_INFOMATION_CLASS;
SystemKernelDebuggerInformation(0x23) 값을 입력하면 현재 OS 시스템이 디버그 모드로 부팅되었는지 알 수 있다.
이 기법은 boot.ini 파일에서 '/debugport=com1 /baudrate=115200 /Debug' 값을 제거 및 부팅을 통해 일반 모드로 변경하여 회피할 수 있다.
어떤 디버거가 다른 프로세스를 디버깅 중이라면 DebugObject 타입의 커널 객체가 생성된다.
이 DebugObject의 존재를 확인하는 방법이다.
NTSTATUS NtQueryObject(
HANDLE ObjectHandle, // 객체 핸들
OBJECT_INFORMATION_CLASS ObjectInformationClass, // 조회할 정보 유형
PVOID ObjectInformation, // 반환된 정보가 저장될 버퍼
ULONG ObjectInformationLength, // 버퍼의 크기
PULONG ReturnLength // 반환된 정보의 크기
);
두 번째 ObjectInformationClass 파라미터는 열거형으로 그 값들 중 디버거 탐지에 사용되는 것들이다.
typedef enum _OBJECT_INFORMATION_CLASS { ... ObjectAllTypesInformation, // 3 ... } OBJECT_INFORMATION_CLASS, *POBJECT_INFORMATION_CLASS;
ObjectAllTypesInformation를 이용하여 시스템의 모든 객체 정보를 구한 다음 그 중에 DebugObject가 있는지 확인한다.
ObjectAllTypesInformation(3)을 0으로 변경하면 회피가 가능하다.
디버깅을 당하는 쪽에서 강제로 디버거를 떼어내는(Detach) 기법이다.
NTSTATUS ZwSetInformationThread(
HANDLE ThreadHandle, // 설정할 스레드의 핸들
THREAD_INFORMATION_CLASS ThreadInformationClass, // 설정할 정보의 종류
PVOID ThreadInformation, // 설정할 정보가 저장된 버퍼
ULONG ThreadInformationLength // 설정할 정보의 크기
);
두 번째 ThreadInformationClass 파라미터는 열거형으로 그 값들 중 디버거 탐지에 사용되는 것들이다.
typedef enum _THREAD_INFORMATION_CLASS { ... ThreadHideFromDebugger // 17 (0x11) } THREAD_INFORMATION_CLASS, *PTHREAD_INFORMATION_CLASS;
첫 번째 파라미터 ThreadHandel에 현재 스레드의 핸들을 넘겨주고, 두 번째 파라미터 ThreadInformationClass에게 ThreadHideFromDebugger(0x11) 값을 입력하면 디버거 프로세스가 분리된다. 디버거를 통한 실행일 때 디버거를 종료시키고 자신의 프로세스도 같이 종료.
ThreadHideFromDebugger 값을 0으로 변경해버리면 해당 기법을 우회할 수 있는 것이다.
프로세스의 스레드가 생성/종료될 때마다 자동으로 호출되는 TLS 콜백함수를 안티 디버깅에 사용할 수 있다.
프로그램의 Entry Point 코드보다 먼저 실행되는 특성이 있단 걸 전에 배웠다. 그럼 이 TLS 함수 내에서 IsDebuggerPresent() 등의 함수를 호출하여 간단히 디버깅 여부를 판별하고 프로그램을 계속 실행할지 말지를 결정할 수 있다.
결국 목적이 리버싱을 안 당하고 싶은 것이니까 지금 내(프로세스)가 돌아가는 시스템이 어딘가 리버싱 시스템 같은 쎄한 느낌만 알아차리면 되잖아.
시스템에서 구할 수 있는 정보로만 가지고 안티디버깅을 할 수 있다. 이런 정보는 대부분 Win32 API를 이용하여 구현된다.
잘 알려진 디버거 창의 이름 검색
FindWindow(), GetWindowText()
ex. OllyDbg, IDA pro, WinDbg
프로세스 이름 검색
CreateToolhelp32Snapshot()
리버싱 시스템에서 자주 사용되는 컴퓨터 이름 검색
GetComputerName()
ex. TEST, ANALYSIS
리버싱 시스템에서 자주 사용되는 프로그램 실행 경로를 검사
GetCommandLine()
ex. TEST, SAMPLE
가상머신에서 실행 중인지 확인
가상머신 특유의 프로세스 이름 확인
ex) VMWareService.exe, VMWareTray.exe, VMWareUser.exe
또 이런 방법이 아니더라도 문자열이 저장되는 버퍼를 NULL로 덮어씀으로써 API에서 탐지하지 못하게 하거나, 또는 조건 점프를 조작하여 이러한 기법들을 회피할 수 있다.