이전에 설명한 IRP Hook 코드와는 크게 다른 부분은 없고 추가적으로
완료 루틴을 훅킹하는 예제를 보여줍니다.
완료 루틴을 훅킹함으로써 하드 디스크에서 어떠한 데이터를 읽었는지 알아내는 예제입니다.
완료루틴에서 데이터를 가져올 때 지금과 같이 ATAPI.sys에 경우
Irp->MdlAddress 로부터 읽은 섹터를 가져오는데 이 때 MmGetSystemAddressForMdlSafe 함수를
사용하면 안되고 자체적으로 해당 매크로를 구현해주었습니다.
주석에도 나왓듯이 간혹 MDL_MAPPED_TO_SYSTEM_VA 플래그가 MDL에 포함 된 상태로
오는 경우가 있기 때문입니다.
만약 그런 경우가 발생하게 되면 다음과 같이 블루 스크린이 뜰 수 있습니다.

그리고 현재 ATAPI.sys를 훅킹하기 위해 \\Device\\Ide\\IdeDeviceP1T0L0-17 요런식으로
디바이스 이름을 가져오는데 이렇게 가져오지 않고 다음과 같이 드라이버 오브젝트를 가져오는
방식으로 해도 상관이 없습니다.
UNICODE_STRING uniname;
PDRIVER_OBJECT atapi_dev;
NTSTATUS ntStatus;
RtlInitUnicodeString(&uniname , L"\\Driver\\Atapi");
ntStatus = ObReferenceObjectByName( &uniname ,
OBJ_CASE_INSENSITIVE ,
NULL ,
0,
*IoDriverObjectType ,
KernelMode ,
NULL ,
(PVOID*)&atapi_dev);
참고로 특정 PC에 경우에는 ATAPI.sys를 안 쓰고 자체 드라이버를 쓰는 경우도 있으니 참고바랍니다.
IRP Completion Hook에 대해 어려운 부분이 없으므로 잡담은 그만하고 소스코드올립니다.
#include <Wdm.h>
#include <Scsi.h>
//
// 이 구조체에 필요한 데이터 변수들을 추가합니다.
//
typedef struct _MY_CONTEXT_DATA
{
//
// 이곳에 추가..
//
PVOID pNotUsedData;
} MY_CONTEXT_DATA, *PMY_CONTEXT_DATA;
typedef struct _HOOK_CONTEXT_DATA
{
PIO_COMPLETION_ROUTINE pOrgCompletionRoutine;
PVOID pOrgContext;
MY_CONTEXT_DATA MyContextData;
} HOOK_CONTEXT_DATA, *PHOOK_CONTEXT_DATA;
PDEVICE_OBJECT g_HookDeviceObject = NULL;
PDRIVER_DISPATCH g_OrgHandler = NULL;
UCHAR g_HookMajorFunctionIndex = 0xFF;
LONG g_IRPEnterCount = 0;
PDEVICE_OBJECT MyIoGetDeviceObjectPointer(__in WCHAR *DeviceObjectName, __in BOOLEAN bRelated);
BOOLEAN
IrpHook(__in PDEVICE_OBJECT DeviceObject, __in UCHAR
MajorFunctionIndex, __out PDRIVER_DISPATCH *pOrgHandler,
PDRIVER_DISPATCH pNewHandler);
BOOLEAN IrpHookFormName(__in WCHAR
*DeviceObjectName, __in UCHAR MajorFunctionIndex, __out PDRIVER_DISPATCH
*pOrgHandler, PDRIVER_DISPATCH pNewHandler);
VOID IrpUnhook();
NTSTATUS NewIrpHandler(__in PDEVICE_OBJECT DeviceObject, __in PIRP Irp);
PDEVICE_OBJECT MyIoGetDeviceObjectPointer(__in WCHAR *DeviceObjectName, __in BOOLEAN bRelated)
{
NTSTATUS ntStatus;
PFILE_OBJECT FileObject;
PDEVICE_OBJECT DeviceObject;
UNICODE_STRING Name;
OBJECT_ATTRIBUTES ObjectAttributes;
HANDLE FileHandle;
IO_STATUS_BLOCK IoStatus;
DeviceObject = NULL;
RtlInitUnicodeString(&Name, DeviceObjectName);
if (bRelated == TRUE)
{
// IoGetDeviceObjectPointer는 내부적으로 IoGetRelatedDeviceObject 함수를 호출합니다.
// 따라서 DeviceObjectName 에 해당하는 디바이스 오브젝트 포인터를 얻어오는게 아니라
// 해당 디바이스 오브젝트에 대한 최상위 디바이스 오브젝트를 얻어옵니다.
ntStatus = IoGetDeviceObjectPointer(&Name, FILE_READ_ATTRIBUTES, &FileObject, &DeviceObject);
if (NT_SUCCESS(ntStatus) == TRUE)
{
return DeviceObject;
}
else
{
return NULL;
}
}
else
{
// 실제 해당 이름을 가진 디바이스 오브젝트를 얻옵니다.
InitializeObjectAttributes(&ObjectAttributes,
&Name,
0,
(HANDLE) NULL,
(PSECURITY_DESCRIPTOR)NULL);
ntStatus = ZwOpenFile(&FileHandle,
FILE_READ_ATTRIBUTES,
&ObjectAttributes,
&IoStatus,
0,
FILE_NON_DIRECTORY_FILE);
if (NT_SUCCESS(ntStatus) == FALSE)
{
// 해당 디바이스 오브젝트를 열 수 없습니다.
return NULL;
}
ntStatus = ObReferenceObjectByHandle(FileHandle,
0,
*IoFileObjectType,
KernelMode,
(PVOID *)&FileObject,
NULL);
if (NT_SUCCESS(ntStatus) == TRUE)
{
// Windows 2000 소스코드 참고
// 실제 내부 구현도 이와 비슷하게 되어 있습니다.
//
// If the file object was taken out against the mounted file system, it
// will have a Vpb. Traverse it to get to the DeviceObject. Note that in
// this case we should never follow FileObject->DeviceObject, as that
// mapping may be invalid after a forced dismount.
//
if (FileObject->Vpb != NULL && FileObject->Vpb->DeviceObject != NULL)
{
DeviceObject = FileObject->Vpb->DeviceObject;
//
// If a driver opened a disk device using direct device open and
// later on it uses IoGetRelatedTargetDeviceObject to find the
// device object it wants to send an IRP then it should not get the
// filesystem device object. This is so that if the device object is in the
// process of being mounted then vpb is not stable.
//
}
else if (!(FileObject->Flags & FO_DIRECT_DEVICE_OPEN) &&
FileObject->DeviceObject->Vpb != NULL &&
FileObject->DeviceObject->Vpb->DeviceObject != NULL)
{
DeviceObject = FileObject->DeviceObject->Vpb->DeviceObject;
//
// This is a direct open against the device stack (and there is no mounted
// file system to strain the IRPs through).
//
}
else
{
DeviceObject = FileObject->DeviceObject;
}
ZwClose(FileHandle);
return DeviceObject;
}
else
{
ZwClose(FileHandle);
return NULL;
}
}
}
VOID DriverUnload(__in PDRIVER_OBJECT DriverObject)
{
KdPrint(("Driver unload.\n"));
// 드라이버 언로드 루틴에서 호출됩니다.
IrpUnhook(TRUE);
}
BOOLEAN
IrpHook(__in PDEVICE_OBJECT DeviceObject, __in UCHAR
MajorFunctionIndex, __out PDRIVER_DISPATCH *pOrgHandler,
PDRIVER_DISPATCH pNewHandler)
{
g_IRPEnterCount = 0;
*pOrgHandler = DeviceObject->DriverObject->MajorFunction[MajorFunctionIndex];
if (*pOrgHandler == NULL)
{
return FALSE;
}
g_HookDeviceObject = DeviceObject;
g_HookMajorFunctionIndex = MajorFunctionIndex;
InterlockedExchangePointer(&(DeviceObject->DriverObject->MajorFunction[MajorFunctionIndex]), pNewHandler);
return TRUE;
}
BOOLEAN
IrpHookFormName(__in WCHAR *DeviceObjectName, __in UCHAR
MajorFunctionIndex, __out PDRIVER_DISPATCH *pOrgHandler,
PDRIVER_DISPATCH pNewHandler)
{
PDEVICE_OBJECT DeviceObject;
DeviceObject = MyIoGetDeviceObjectPointer(DeviceObjectName, FALSE);
if (DeviceObject != NULL)
{
return (IrpHook(DeviceObject, MajorFunctionIndex, pOrgHandler, pNewHandler));
}
else
{
return FALSE;
}
}
VOID IrpUnhook(BOOLEAN DriverUnloadRoutine)
{
if ((g_HookDeviceObject == NULL)
|| (g_OrgHandler == NULL))
{
KdPrint(("Not hooked.\n"));
}
else
{
// IRP 핸들러를 원래의 핸들러로 되돌려줍니다.
InterlockedExchangePointer(&(g_HookDeviceObject->DriverObject->MajorFunction[g_HookMajorFunctionIndex]),
g_OrgHandler);
if (DriverUnloadRoutine == TRUE)
{
LARGE_INTEGER WaitTime;
WaitTime.QuadPart = -1 * 10 * 1000 * 1000;
//
// 드라이버 언로드 루틴에서 이 함수가 호출되었다면
// InterlockedExchangePointer 후에 바로 드라이버가 언로드 되므로
// 이 때 InterlockedExchangePointer를 하기전에 훅킹 핸들러가 실행되었고
// 그 후에 InterlockedExchangePointer함수가 리턴하게 되면
// 훅킹 핸들러를 원래대로 되돌려주었지만 현재 루틴이 실행중일 수 있으므로 이에 대한 동기화 처리를
// 진행합니다.
//
while (g_IRPEnterCount > 0)
{
// 현재 훅킹루틴이 실행중에 있으므로 잠시 대기합니다.
KeDelayExecutionThread(KernelMode ,FALSE, &WaitTime);
}
// 마지막으로 한 번 더 대기합니다.
KeDelayExecutionThread(KernelMode ,FALSE, &WaitTime);
}
}
}
NTSTATUS NewIoCompletionRoutine(__in PDEVICE_OBJECT DeviceObject, __in PIRP Irp, __in_opt PVOID Context)
{
PHOOK_CONTEXT_DATA pHookContextData;
PIO_COMPLETION_ROUTINE pOrgCompletionRoutine;
PVOID pOrgContext;
PIO_STACK_LOCATION pIoStackLocation;
UCHAR *pBuffer;
ULONG i, DataLength;
BOOLEAN bUnlock;
pHookContextData = (PHOOK_CONTEXT_DATA)Context;
pOrgCompletionRoutine = pHookContextData->pOrgCompletionRoutine;
pOrgContext = pHookContextData->pOrgContext;
pIoStackLocation = IoGetNextIrpStackLocation(Irp);
if (pIoStackLocation->MajorFunction == g_HookMajorFunctionIndex)
{
if (Irp->MdlAddress != NULL)
{
KdPrint(("Irp->MdlAddress->MdlFlags : %u\n", Irp->MdlAddress->MdlFlags));
// 이 접근 방식을 사용하지 않습니다.
// 특정 상황에 경우 블루 스크린이 뜰 수 있습니다.
// 왜냐하면 간혹 MDL이 이미 맵핑되어 있는 상태로 올 수가 있는데 이 때
// MmUnmapLockedPages 함수를 호출해버리면 훅킹루틴에서 맵핑을 해제해 버리기 때문에
// 블루 스크린이 발생합니다.
// ( 정확하게 말해서 Irp->MdlAddress->MdlFlags에 MDL_MAPPED_TO_SYSTEM_VA 플래그가
// 포함 된 상태인 경우가 어쩌다가 한 번씩 존재합니다. )
//pBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);
if (Irp->MdlAddress->MdlFlags &
(MDL_MAPPED_TO_SYSTEM_VA |
MDL_SOURCE_IS_NONPAGED_POOL))
{
pBuffer = Irp->MdlAddress->MappedSystemVa;
bUnlock = FALSE;
}
else
{
pBuffer = MmMapLockedPagesSpecifyCache(Irp->MdlAddress,
KernelMode,
MmCached,
NULL,
FALSE,
NormalPagePriority);
bUnlock = TRUE;
}
if (pBuffer != NULL)
{
DataLength = Irp->IoStatus.Information;
KdPrint(("ATAPI read databuffer. read length : %u\n", DataLength));
if (DataLength > 5)
{
// 읽은 데이터의 일부분만 출력합니다.
DataLength = 5;
}
for (i = 0; i < DataLength; i++)
{
KdPrint(("%#x\n", pBuffer[i]));
}
if (bUnlock == TRUE)
{
MmUnmapLockedPages(pBuffer, Irp->MdlAddress);
}
}
}
}
KdPrint(("NewIoCompletionRoutine\n"));
return (pOrgCompletionRoutine(DeviceObject, Irp, pOrgContext));
}
NTSTATUS NewIrpHandler(__in PDEVICE_OBJECT DeviceObject, __in PIRP Irp)
{
NTSTATUS ntStatus;
// 동기화를 위한 전역 카운트를 증가시킵니다.
InterlockedIncrement(&g_IRPEnterCount);
// 디바이스 오브젝트에 대한 필터링을 하였지만 상황에 따라 하지 않아야 할 수도 있습니다.
//if (DeviceObject == g_HookDeviceObject)
{
// 우리가 원하는 DeviceObject에서 발생한 Irp입니다.
PIO_STACK_LOCATION pIoStackLocation;
pIoStackLocation = IoGetCurrentIrpStackLocation(Irp);
if (pIoStackLocation->MajorFunction == g_HookMajorFunctionIndex)
{
//
// 이곳에 원하는 코드를 작성합니다.
// 예제코드에서는 간단히 CDB에 대해서 출력하겠습니다.
//
switch (pIoStackLocation->Parameters.Scsi.Srb->Cdb[0])
{
case SCSIOP_READ:
// ATAPI.sys에 기본적인 읽기명령이 발생하였습니다.
if (pIoStackLocation->CompletionRoutine != NULL)
{
// 예제에서는 완료루틴이 등록되어 있을 경우에만 완료 루틴을 훅킹합니다.
PHOOK_CONTEXT_DATA pHookContextData;
pHookContextData = ExAllocatePoolWithTag(NonPagedPool, sizeof(HOOK_CONTEXT_DATA), 'tseT');
if (pHookContextData != NULL)
{
pHookContextData->pOrgCompletionRoutine = pIoStackLocation->CompletionRoutine;
pHookContextData->pOrgContext = pIoStackLocation->Context;
pIoStackLocation->CompletionRoutine = NewIoCompletionRoutine;
pIoStackLocation->Context = pHookContextData;
// pIoStackLocation->Control = SL_INVOKE_ON_SUCCESS;
}
}
break;
}
}
}
ntStatus = g_OrgHandler(DeviceObject, Irp);
// 동기화를 위한 전역 카운트를 감소합니다.
InterlockedDecrement(&g_IRPEnterCount);
return (ntStatus);
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)
{
NTSTATUS ntStatus;
PDEVICE_OBJECT DeviceObject;
// 드라이버가 언로드 될 수 있도록 합니다.
DriverObject->DriverUnload = DriverUnload;
// 예제로 훅킹하는 디바이스는 하드 디스크입니다.
// ( 실제 하드 디스크에 섹터를 읽어주는 디바이스입니다. )
//
// 해당 디바이스 이름은 시스템에 따라서 다릅니다.
// IRP_MJ_SCSI 를 훅킹합니다.
if (IrpHookFormName(L"\\Device\\Ide\\IdeDeviceP1T0L0-17", IRP_MJ_SCSI, &g_OrgHandler, NewIrpHandler) == TRUE)
{
KdPrint(("IrpHookFormName successed.\n"));
}
else
{
KdPrint(("IrpHookFormName failed.\n"));
}
return STATUS_SUCCESS;
}