ntdll 관련 static 안티 디버 깅은 PEB관련 안티디버깅과는 다르게 좀 더 시스템적인 접근이 필요하고 복잡한면이 있어서 한번 그에 관해서 복습겸 정리를 해볼려 합니다.



1. NtQueryInformationProcess()


우선 함수정의는 다음과 같습니다.


 NTSTATUS WINAPI NtQueryInformationProcess(

    __in          HANDLE ProcessHandle,

    __in          PROCESSINFOCLASS ProcessInformationClass,

    __out        PVOID ProcessInformation

    __in          ULONG ProcessInformationLengh,

    __out_opt  PULONG ReturnLength

);


위의 인자에서 PROCESSINFOCLASS 는 열거형으로써 원하는 정보를 입력한수 API를 호출하면 세번째 인자에

관련 정보가 세팅됩니다. 


안티 디버깅에 사용되는것은 

ProcessDebugPort(0x7), ProcessDebugObjectHandle(0x1E), ProcessDebugFlags(0x1F) 

이렇게 3가지가 있습니다.


ProcessDebugPort(0x7)은 프로세스가 디버깅 중일 때 Debug Port가 할당되는데 프로세스가 디버깅 중이 아니라면 3번째 인자로 준값에 0이 세팅되고 디버깅 중이라면 0xFFFFFFFF로 세팅이 됩니다. CheckRemoteDebuggerPresent() 함수에서도 내부적으로 NtQueryInformationProcess() 를 이용하는데 어셈코드를 봐보면 push 7을 확인할 수 있습니다.


ProcessDebugObjectHandle(0x!E)는 프로세스가 디버깅될 때 Debug Object가 생성이 되는데 그 때 이 값을 인자로 넣어주면 Debug Object Handle을 구할 수 있습니다. 만약 디버깅 중이라면 값이 존재할 것이고 아니라면 값은 NULL이 될 것 입니다. 


ProcessDebugFlags(0x1F)는 Debug Flags를 확인해서 프로세스의 디버깅 여부를 판별하는데 이 값을 인자로 주어 Debug Flags를 구하고 그 값이 0이면 디버깅상태 1이면 디버깅이 아닌 상태입니다. 

다음 소스를 참조하시면 이해가 쉬울것입니다(이 포스팅에서 사용하는 소스는 핵심원리에서 퍼왔습니다)


// ProcessDebugFlags(0x1F)


BOOL bDebugFlag = TRUE;


pNtQueryInformationProcess(

    GetCurrentProcess(),

    ProcessDebugFlags,

   &bDebugFlag,

    sizeof(bDebugFlag),

    NULL

);


if( bDebugFlag == 0x0 ) printf(" ==> Debugging!!!\n\n");

else printf(" ==> Not Debugging...\n\n");


회피 방법은 간단합니다. 직접 인자로 들어가는 값들을 조작시켜주거나 계속적으로 호출이 되서 매번 바꾸기가 번거로우면 후킹을 시켜버리면 됩니다. 어떻게 후킹을 하는지에 대한 자세한 설명은 생략하도록 하겠지만 흐름만 말씀드리자면 API를 call하는 어셈코드를 적당한 영역으로 jmp 시켜준뒤 그 영역에서 직접 어셈코드를 작성해주면 됩니다. 



2. NtQuerySystemInformation()


NtQuerySystemInformation() API는 현재 동작 중인 OS에 대한 다양한 정보를 구해줍니다. 

함수의 정의는 다음과 같습니다.


NTSTATUS WINAPI NtQuerySystemInformation(

    __in          SYSTEM_INFORMATION_CLASS SystemInformationClass,

    __inout      PVOID SystemInformation,

    __in          ULONG SystemInformationLength,

    __out_opt  PULONG ReturnLength

); 


이 함수도 NtQueryInformationProcess() 와 비슷하게 열거형인 1번째 인자에 원하는 시스템 정보를 입력하고 2번째 인자에 관련 구조체 주소를 넘겨주면 API가 리턴하면서 그 구조체에 관련 정보를 채워줍니다. 

첫번째 인자중에 디버깅과 관련된 인자인 SystemKernelDebuggerInformation(0x23) 값을 입력하면 현재 OS 시스템이 디버그 모드로 부팅되었는지 알 수 있는데 디버그 모드인 경우 SYSTEM_KERNEL_DEBUGGER_INFORMATION 구조체의 DebuggerEnabled가 1로 세팅됩니다. 


회피방법은 다른 안티디버깅들과 조금 다른데 Windows XP의 경우 boot.ini의 /debugport=com1 

/baudrate=115200 /Debug 값을 제거해주고 Windows 7의 경우 cmd에서 bcdedit /debugoff 명령을 내리면 됩니다. 



3. NtQueryObject()


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

다음은 API의 정의입니다.


NTSTATUS NtQueryObject(

    __in_opt     HANDLE Handle,

    __in           OBJECT_INFORMATION_CLASS ObjectInformationClass,

    __out_opt   PVOID ObjectInformation,

    __in           ULONG ObjectInformationLength,

    __out_opt   PULONG ReturnLength    


NtQueryObject()는 시스템의 다양한 종류의 커널 객체 정보를 구해오는데 두 번째 인자에 원하는 값을 입력하고 API를 호출하면 세 번째 인자에 관련 정보의 구조체 포인터를 리턴합니다. 두 번째 인자는 열거형인데 그중 ObejctAllTypesInformation 항목을 이용하여 시스템의 모든 객체 정보를 구한 다음 그 중에 DebugObject가 있는지 확인합니다. 사용법이 약간 복잡한데 소스와 함께 설명을 하겠습니다.


우선 커널 객체 정보 리스트의 크기를 얻습니다.


ULONG lSize = 0;

pNtQueryObject(NULL, ObjectAllTypesInformation, &lSize, sizeof(lsize), &lSize); 


다음 그 크기만큼 메모리를 할당시켜 줍니다.


void *pBuf = NULL;

pBuf = VirtualAlloc(NULL, lSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 


커널 객체 정보 리스트를 구합니다.


typedef struct _OBJECT_TYPE_INFORMATION {

    _STRING TypeName;

    ULONG TotalNumberOfHandles;

    ULONG TotalNumberOfObjects;

} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;


typedef struct _OBJECT_ALL_INFORMATION {

    ULONG                                  NumberOfObjectsTypes;

    OBJECT_TYPE_INFORMATION ObjectTypeInformation[1];

} OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;


pNtQueryObject((HANDLE)0xFFFFFFFF, ObjectAllTypesInformationk pBuf, lSize, NULL);


POBJECT_ALL_INFORMATION pObjectAllInfo = (POBJECT_ALL_INFORMATION)pBuf; 


회피방법은 상당히 간단한데 NtQueryInformationProcess()와 같이 직접 인자를 수정해주거나 API를 후킹해주면 됩니다.



4. ZwSetInformationThread()


ZwSetInformationThread()는 스레드에게 정보를 세팅하는 Native API인데 이를 이용하면 자신을 디버깅하고 있는 디버거를 떼어낼 수 있습니다.

다음은 함수 정의입니다.


NTSTATUS ZwSetInformationThread(

    __in  HANDLE ThreadHandle,

    __in  THREADINFOCLASS ThreadInformationClass,

   __in  PVOID ThreadInformation,

    __in  ULONG ThreadInformationLength

);


열거형인 두번째 인자에 ThreadHide From Debugger(0x11)을 입력하면 디버거 프로세스가 분리됩니다.

이 API는 일반실행시에는 아무영향을 주지않지만 디버거로 실행 했을 경우에 자신과 같이 종료 시켜 버립니다. 


회피는 역시 NtQueryInformationProcess()나 NtQueryObject()과 같이 인자를 직접 조작해주거나 API를 후킹하면 됩니다. 



'Security' 카테고리의 다른 글

ROP 자료  (1) 2013.11.20
64비트 calling convention + stack frame  (1) 2013.11.15
안티디버깅 간단 정리  (1) 2013.10.30
바인드쉘, 리버스쉘  (0) 2013.10.24
TLS에 관한 간단한 탐구  (0) 2013.10.21
Posted by xer0s :