본문 바로가기

About Programing/04. Device Driver

SDT 수정을 이용한 Native API Hooking


OS : Windows XP SP3
Tool : Microsoft Visual Studio 2008 SP1 /  Windows Driver Kit (WDK) Version 7.1.0



Native API Hooking은 SDT를 수정하는 방법을 통해서 이루어 집니다.

SDT에 대하여 이야기 하기 전에 User Application Programming을 할때 우리는 WinXX API를 많은 DLL들,
예를들면 KERNEL32,GDI32,USER32같은 것들이 Export해주는 API들을 호출하여 씁니다.

이런 DLL들이 Export해주는 API들을 Disassemble하여 보면,
예를들어서 KERNEL32가 Export해주는 API인 WriteFile()를 Disassemble하여 보면,
내부적으로는 NTDLL.DLL에서 Export해주는 ZwWriteFile()라는 Native API를 사용함을 볼수 있습니다.

또 ZwWriteFile()를 Disassemble하여 보면,

MOV EAX, 0ED 



LEA EDX, DWORD PTR SS:[ESP+4] 



INT 2E 



RETN 24 


위와 같은 부분이 보이게 됩니다.

첫번쨰 줄의 0x0ED는 ZwWriteFile의 ServiceNumber이며,
세번쨰 줄의 INT 2E(System Call)은 UserMode에서 KernelMode로 전환되는 진입점이라고 보면 됩니다.
(아쉽게도 이것은 Windows 2000까지 해당되는 것이며, Windows XP System에서는 INT 2E가 아닌 SYSENTER를 사용합니다.)

이것은 프로세서 차원에서 제공되는 새로운 명령어로서,
기존의 방식보다 커널모드로의 전환이 훨씬 빠르다고 합니다.

위의 Disassemble과정으로 알수 있었던 Call 과정을 도식화 하면,

WinXXAPI - > Native API - > INT2E(Interruptl)

이 Call과정의 중간 단계에 있는 Native API를 Hooking하는 것입니다.

이제 Call과정에 대해서 어느정도 알게 되었으니 SDT에 대하여 알아보면,
SDT란 Service Descriptor Table의 약자로서 SDE를 묶어놓은 구조체입니다.

typedef struct ServiceDescriptorTable {




SDE ServiceDescriptor[4]; 




} SDT;


위와 같이 선언되어 있으며, 위에서 쓰인 SDE라는것은,
ServiceDescriptorEntry의 약자로서 SDT의 구성요소로서,
Native API에 대한 자세한 정보가 담겨진 구조체 입니다.

typedef struct ServiceDescriptorEntry { 




PDWORD ServiceTable; //




PDWORD CounterTableBase;



DWORD ServiceLimit; 



PBYTE ArgumentTable;




} SDE;


실제 Hooking에서는 SDE의 ServiceTable(System Service Dispatch Table)을 우리의 Driver내에 존재하는 함수의 주소로 바꾸어
해당API가 호출될떄 우리의 함수가 실행되도록 한후, 우리의 함수 내부에서 원하는 작업을 처리하고
원래해당 API를 호출해줌으로써, 정상적으로 작동하게 하는 구조입니다.

물런 Hooking을 끝내고자 할때는 Driver를 Unload만 해서는 안되며,
SDE의 해당API의 ServiceTable을 원상태로 돌려놓아야 합니다.

돌려놓지 않았을때는 아름다운 Blue Screen을 만나 보실수 있을겁니다ㅋㅋ



먼저 간단한 Driver의 뼈대를 작성합니다.

Application을 제작할떄 가장 먼저 작성하는 Entry Point가 Console App에서는 main()이고,
Win App에서는 WinMain()이며, 우리가 지금부터 작성하고자 하는 Driver에서는 DriverEntry() 입니다.

밑에와 같이 간단한 DriverEntry()를 작성하여 보았습니다.

NTSTATUS DriverEntry ( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath )
{ 
	NTSTATUS status;
	int i;

	for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
	{ 
		pDriverObject->MajorFunction[i] = DispatchPassThru;
	} 

	SetupSTBHook();

	pDriverObject->DriverUnload = DriverUnload;

	status = STATUS_SUCCESS;

	return status;
}

}

(1)에서는 MajorFunction들을 모두 PassThru루틴으로 채우는것을 볼수 있습니다.

MajorFunction들 예를들면 IRP_MJ_WRITE,IRP_MJ_CREATE 같은 것들을 처리하기 위해 존재하는것인데,
드라이버의 기본 뼈대를 작성하는 단계임으로 PassThru로 모두 채웁니다.

(2)에서는 Unload함수를 채워주는것이 나오는데,
Unload로 채워주는 함수인 DriverUnload()가 하는 역활은 Driver가 Unload될떄 처리 할것이 있다면,
그것들을 처리하는 부분입니다.

NTSTATUS DispatchPassThru( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
	Irp->IoStatus.Status = STATUS_SUCCESS; 
	IoCompleteRequest(Irp,IO_NO_INCREMENT);

	return Irp->IoStatus.Status; 
}


보시다시피 아무것도 수행되지 않는 루틴임을 볼수 있습니다.

DriverUnload()의 코드는 다음과 같습니다.

VOID DriverUnload ( IN PDRIVER_OBJECT pDriverObject )
{ 
	DbgPrint("---My Driver Unloadded---\n");    //언로드시 출력 

	_asm 
	{ 
		CLI 
			MOV    EAX, CR0     
			AND EAX, NOT 10000H 
			MOV    CR0, EAX 
	} 
	(ZWWRITEFILE)
		(SYSTEMSERVICE(ZwWriteFile))   //OldZwWriteFile으로 채운다. 
		= OldZwWriteFile; 
	_asm 
	{ 
		MOV    EAX, CR0         
			OR    EAX, 10000H         
			MOV    CR0, EAX             
			STI     
	}   
}


이제 Hooking기능을 집어넣기 위해 해더파일을 만든후 다음을 집어넣습니다.

#pragma once 
#include 
#include 

#define DWORD unsigned long 
#define WORD unsigned short 
#define BOOL unsigned long 

#pragma pack(1) 
typedef struct ServiceDescriptorEntry { 
	unsigned int *ServiceTableBase; 
	unsigned int *ServiceCounterTableBase; 
	unsigned int NumberOfServices; 
	unsigned char *ParamTableBase; 
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t; 
#pragma pack() 

typedef struct _SRVTABLE { 
	PVOID       *ServiceTable; 
	ULONG       LowCall;     
	ULONG       HiCall; 
	PVOID        *ArgTable; 
} SRVTABLE, *PSRVTABLE; 

typedef NTSTATUS (*ZWWRITEFILE)( 
    IN HANDLE hFile, 
    IN HANDLE hEvent OPTIONAL, 
    IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL, 
    IN PVOID IoApcContext OPTIONAL, 
    OUT PIO_STATUS_BLOCK pIoStatusBlock, 
    IN PVOID WriteBuffer, 
    IN ULONG WriteBufferLength, 
    IN PLARGE_INTEGER FileOffset OPTIONAL, 
    IN PULONG LockOperationKey OPTIONAL 
); 
ZWWRITEFILE    OldZwWriteFile; 

__declspec(dllimport) ULONG NtBuildNumber; 
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable; 

#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)] 
#define SYSTEMSERVICEIDX(_index) KeServiceDescriptorTable.ServiceTableBase[_index] 


먼저 NTDDK.H라는 해더 파일을 Include함을 볼수 있습니다.
NTDDK.H는 Driver에서 핵심적인 것들이 선언된 해더파일임으로 반드시 Include해주어야 합니다.

SDE(ServiceDescriptorEntry) 구조체를 선언하는것도 볼수 있습니다.
NtBuildNumber를 통해 현재 운영체제의 빌드넘버를 구해올수 있습니다.

후에 Hooking을 할떄에 Exported by Name이 되지 않은 Native API들이 몇개 존재합니다. ZwDebugActiveProcess(),ZwWriteVirtualMemory()등등의 API입니다.

메크로 함수인 SYSTEMSERVICE와 SYSTEMSERVICEIDX는 얻고자 하는 값이 같지만, 쓰이는 상황이 다릅니다.

SYSTEMSERVICE같은 경우는 Native API의 이름으로 ServiceTableBase를 구해오고자 할떄 쓰이며,
SYSTEMSERVICEIDX같은 경우는 Service Number로서 ServiceTableBase를 얻어오고자 할때 쓰입니다.

위에서 말한 Exported by Name이 되지 않은 몇몇의 Native API들은 SYSTEMSERVICEIDX를 어쩔수 없이 써야 하는데
이때 운영체제의 빌드버젼에 따라서 Service Number가 다를수 있습니다.

그래서 NtBuildNumber로 switch처리를 해주어야 합니다.

지금까지 선언한것들이 Hooking을 하는데 필요한 기본적인 구조체와,메크로함수, 변수들의 선언입니다.
이제 필요한 것들을 선언했으니 Hooking하는 실질적인 Code를 작성하여 보도록 하겠습니다.

Hooking을 하는 방법은 위에서 언급했다싶이 SDE의 ServiceTableBase를 조작하는 것입니다.
이 ServiceTableBase를 조작하는 부분을 처리하는 별도의 루틴을 SetupSTBHook()이라는 이름으로 만들겠습니다.

VOID SetupSTBHook( void )
{
	DbgPrint("---SetupHook---\n");    //STBHook시 출력     
	OldZwWriteFile = 
		(ZWWRITEFILE) 
		(SYSTEMSERVICE(ZwWriteFile));  //(1)기존의 ZwWriteFile의 주소구함 

	DbgPrint("OldZwWriteFile : %Xn", 
		OldZwWriteFile);           //기존 주소 DebugMessage로 출력 

	_asm 
	{ 
		CLI              //인터럽트 일시 중지 
			MOV    EAX, CR0     
			AND EAX, NOT 10000H 
			MOV    CR0, EAX 
	} 

	(ZWWRITEFILE) 
		(SYSTEMSERVICE(ZwWriteFile))   //ServiceTableBase의값을 우리의 
		= NewZwWriteFile;          //루틴에 주소로 교체 

	_asm 
	{ 
		MOV    EAX, CR0      //인터럽트 일시 중지 해제         
			OR    EAX, 10000H         
			MOV    CR0, EAX             
			STI     
	} 
} 


코드가 그렇게 어렵지 않음으로 별도의 설명이 길게 필요하진 않을것입니다.

원래 Native API의 주소를 저장시켜둔후(복구 할떄를 위해서) 우리의 루틴에 주소로 바꿔치기 합니다.
이렇게 함으로써 ZwWriteFile()이 호출될떄 실제적으로 호출되는 함수는 우리 Driver내의 NewZwWriteFile()라는 함수가 되는것입니다.

그렇다면 NewZwWrtieFile()이라는 함수는 어떻게 구성되어 있어야 할까요?

기존의 함수의 처리를 완벽하게 해내야 함으로 전달받는 Parameter가 같아야 할것입니다.
그럼으로 Hooking을 하고자 할떄는 Hooking되는 대상 API의 전달되는 Parameter가 뭔지 미리 알아야 된다는 말이 됩니다.
ZwWrtieFile()의 인자전달 형태를 알아낸 분들께 감사드립니다. :)

NTSYSAPI 
NTSTATUS 
NTAPI 
ZwWriteFile( 
    IN HANDLE hFile, 
    IN HANDLE hEvent OPTIONAL, 
    IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL, 
    IN PVOID IoApcContext OPTIONAL, 
    OUT PIO_STATUS_BLOCK pIoStatusBlock, 
    IN PVOID WriteBuffer, 
    IN ULONG WriteBufferLength, 
    IN PLARGE_INTEGER FileOffset OPTIONAL, 
    IN PULONG LockOperationKey OPTIONAL 
); 


호출될때 전달되는 인자는 앞에 IN이 붙으며,
API가 처리된후 어떠한 결과를 돌려주는 인자는 OUT이 붙습니다.
NewZwWriteFile()함수 역시 위에와 같은 인자전달 형태를 취하고 있어야 합니다.

NTSTATUS NewZwWriteFile( IN HANDLE hFile, IN HANDLE hEvent OPTIONAL, IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
						IN PVOID IoApcContext OPTIONAL, OUT PIO_STATUS_BLOCK pIoStatusBlock, IN PVOID WriteBuffer, 
						IN ULONG WriteBufferLength, IN PLARGE_INTEGER FileOffset OPTIONAL, IN PULONG LockOperationKey OPTIONAL ) 
{ 
	NTSTATUS rc; 

	DbgPrint("ZwWriteFile is Hookedn"); //훅되고 있음을 알림 
	rc = ((ZWWRITEFILE)(OldZwWriteFile)) ( 
		hFile,               //원래 함수를 호출해서 
		hEvent OPTIONAL,         //정상적으로 처리되게함 
		IoApcRoutine OPTIONAL, 
		IoApcContext OPTIONAL, 
		pIoStatusBlock, 
		WriteBuffer, 
		WriteBufferLength, 
		FileOffset OPTIONAL, 
		LockOperationKey OPTIONAL );

	return rc; 
} 


먼저 DbgPrint()함수를 이용하여 훅되고 있음을 DebugMessage로 출력합니다.
그런후 기존의 ZwWriteFile()주소를 담고 있는 OldZwWriteFile()을 호출 함으로써 기존의 ZwWriteFile() API를 호출합니다.

OldZwWriteFile에 저장해두었던 원래의 값으로 다시 채워넣기만 하면, Hook해제는 끝입니다. 정말 쉽죠? :)



// Hooking.c

#include "Hooking.h"

NTSTATUS DriverEntry ( IN PDRIVER_OBJECT pDriverObject, IN PUNICODE_STRING pRegistryPath )
{ 
	NTSTATUS status;
	int i;

	for(i = 0; i < IRP_MJ_MAXIMUM_FUNCTION; i++)
	{ 
		pDriverObject->MajorFunction[i] = DispatchPassThru;
	} 

	SetupSTBHook();

	pDriverObject->DriverUnload = DriverUnload;

	status = STATUS_SUCCESS;

	return status;
}

NTSTATUS DispatchPassThru( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp )
{
	Irp->IoStatus.Status = STATUS_SUCCESS; 
	IoCompleteRequest(Irp,IO_NO_INCREMENT);

	return Irp->IoStatus.Status; 
}

VOID DriverUnload ( IN PDRIVER_OBJECT pDriverObject )
{ 
	DbgPrint("---My Driver Unloadded---\n");    //언로드시 출력 

	_asm 
	{ 
		CLI 
			MOV    EAX, CR0     
			AND EAX, NOT 10000H 
			MOV    CR0, EAX 
	} 
	(ZWWRITEFILE)
		(SYSTEMSERVICE(ZwWriteFile))   //OldZwWriteFile으로 채운다. 
		= OldZwWriteFile; 
	_asm 
	{ 
		MOV    EAX, CR0         
			OR    EAX, 10000H         
			MOV    CR0, EAX             
			STI     
	}   
}

VOID SetupSTBHook( void )
{
	DbgPrint("---SetupHook---\n");    //STBHook시 출력     
	OldZwWriteFile = 
		(ZWWRITEFILE) 
		(SYSTEMSERVICE(ZwWriteFile));  //(1)기존의 ZwWriteFile의 주소구함 

	DbgPrint("OldZwWriteFile : %Xn", 
		OldZwWriteFile);           //기존 주소 DebugMessage로 출력 

	_asm 
	{ 
		CLI              //인터럽트 일시 중지 
			MOV    EAX, CR0     
			AND EAX, NOT 10000H 
			MOV    CR0, EAX 
	} 

	(ZWWRITEFILE) 
		(SYSTEMSERVICE(ZwWriteFile))   //ServiceTableBase의값을 우리의 
		= NewZwWriteFile;          //루틴에 주소로 교체 

	_asm 
	{ 
		MOV    EAX, CR0      //인터럽트 일시 중지 해제         
			OR    EAX, 10000H         
			MOV    CR0, EAX             
			STI     
	} 
} 

NTSTATUS NewZwWriteFile( IN HANDLE hFile, IN HANDLE hEvent OPTIONAL, IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
						IN PVOID IoApcContext OPTIONAL, OUT PIO_STATUS_BLOCK pIoStatusBlock, IN PVOID WriteBuffer, 
						IN ULONG WriteBufferLength, IN PLARGE_INTEGER FileOffset OPTIONAL, IN PULONG LockOperationKey OPTIONAL ) 
{ 
	NTSTATUS rc; 

	DbgPrint("ZwWriteFile is Hookedn"); //훅되고 있음을 알림 
	rc = ((ZWWRITEFILE)(OldZwWriteFile)) ( 
		hFile,               //원래 함수를 호출해서 
		hEvent OPTIONAL,         //정상적으로 처리되게함 
		IoApcRoutine OPTIONAL, 
		IoApcContext OPTIONAL, 
		pIoStatusBlock, 
		WriteBuffer, 
		WriteBufferLength, 
		FileOffset OPTIONAL, 
		LockOperationKey OPTIONAL );

	return rc; 
} 


// Hooking.h

#ifndef _HOOKING_H
#define _HOOKING_H

#pragma once 
#include 
#include 

#define DWORD unsigned long 
#define WORD unsigned short 
#define BOOL unsigned long 

#pragma pack(1) 
typedef struct ServiceDescriptorEntry { 
	unsigned int *ServiceTableBase; 
	unsigned int *ServiceCounterTableBase; 
	unsigned int NumberOfServices; 
	unsigned char *ParamTableBase; 
} ServiceDescriptorTableEntry_t, *PServiceDescriptorTableEntry_t; 
#pragma pack() 

typedef struct _SRVTABLE { 
	PVOID       *ServiceTable; 
	ULONG       LowCall;     
	ULONG       HiCall; 
	PVOID        *ArgTable; 
} SRVTABLE, *PSRVTABLE; 

typedef NTSTATUS (*ZWWRITEFILE)( 
    IN HANDLE hFile, 
    IN HANDLE hEvent OPTIONAL, 
    IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL, 
    IN PVOID IoApcContext OPTIONAL, 
    OUT PIO_STATUS_BLOCK pIoStatusBlock, 
    IN PVOID WriteBuffer, 
    IN ULONG WriteBufferLength, 
    IN PLARGE_INTEGER FileOffset OPTIONAL, 
    IN PULONG LockOperationKey OPTIONAL 
); 
ZWWRITEFILE    OldZwWriteFile; 

__declspec(dllimport) ULONG NtBuildNumber; 
__declspec(dllimport) ServiceDescriptorTableEntry_t KeServiceDescriptorTable; 

#define SYSTEMSERVICE(_function) KeServiceDescriptorTable.ServiceTableBase[ *(PULONG)((PUCHAR)_function+1)] 
#define SYSTEMSERVICEIDX(_index) KeServiceDescriptorTable.ServiceTableBase[_index] 

NTSTATUS DispatchPassThru( IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp );
VOID DriverUnload ( IN PDRIVER_OBJECT pDriverObject );
VOID SetupSTBHook( void );
NTSTATUS NewZwWriteFile( IN HANDLE hFile, IN HANDLE hEvent OPTIONAL, IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
						IN PVOID IoApcContext OPTIONAL, OUT PIO_STATUS_BLOCK pIoStatusBlock, IN PVOID WriteBuffer, 
						IN ULONG WriteBufferLength, IN PLARGE_INTEGER FileOffset OPTIONAL, IN PULONG LockOperationKey OPTIONAL );

#endif // _HOOKING_H


// sources
TARGETNAME=Hooking
TARGETPATH=obj
TARGETTYPE=DRIVER
SOURCES=Hooking.c 

'About Programing > 04. Device Driver' 카테고리의 다른 글

ZwCreateFile 함수  (0) 2010.07.29
OBJECT_ATTRIBUTES 구조체  (0) 2010.07.29
WinDbg 명령어 정리  (0) 2010.06.14
완전 신기!  (0) 2010.05.23
5월 22일 SOURCES 파일  (0) 2010.05.23