심플스 - 프로그램과 책 이야기로 가득한 곳, (Simples.Kr)

  

IRP Completion Hook

Windows Research 조회 수 13768 추천 수 0 2010.09.04 10:05:51
 

이전에 설명한 IRP Hook 코드와는 크게 다른 부분은 없고 추가적으로
완료 루틴을 훅킹하는 예제를 보여줍니다.
완료 루틴을 훅킹함으로써 하드 디스크에서 어떠한 데이터를 읽었는지 알아내는 예제입니다.

완료루틴에서 데이터를 가져올 때 지금과 같이 ATAPI.sys에 경우
Irp->MdlAddress 로부터 읽은 섹터를 가져오는데 이 때 MmGetSystemAddressForMdlSafe 함수를
사용하면 안되고 자체적으로 해당 매크로를 구현해주었습니다.
주석에도 나왓듯이 간혹 MDL_MAPPED_TO_SYSTEM_VA 플래그가 MDL에 포함 된 상태로
오는 경우가 있기 때문입니다.
만약 그런 경우가 발생하게 되면 다음과 같이 블루 스크린이 뜰 수 있습니다.


b0072382_4a6ad2e2e6dc6.png


그리고 현재 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;
}

List of Articles
번호 제목 글쓴이 날짜 조회 수
163 Linux Tip 리눅스 기반 오픈 소스 사이트 모음 삽질신 2009-11-16 70508
162 Linux Tip VirtualBox에 우분투 설치 후에 내부 네트워크 접속 설정하기 file esniper 2010-09-10 35137
161 Windows Research IE Cache 경로 변경하기 lain 2011-06-09 30643
160 Windows Research VMWare 탐지 기법 우회 - 2 [2] lain 2011-04-04 28612
159 Windows Research 화면캡쳐방지는 어떻게 구현될까? [3] lain 2010-09-04 28392
158 PHP Tip php 파싱 라이브러리 - snoopy esniper 2009-03-26 20563
157 Linux Tip Directory index forbidden by Options directive 삽질신 2009-11-16 20097
156 Linux Tip VirtualBox에서 오른쪽 CTRL키 사용하기(VitualBox 호스트키) file [2] esniper 2010-09-10 19521
155 Windows Tip SSH 터널링 - putty의 plink 사용예 esniper 2009-03-26 19421
154 문서자료 고품질의 무료 아이콘들 [3] esniper 2010-09-08 19296
153 문서자료 json 문법 체크 사이트 Lyn 2012-02-28 18726
152 PHP Tip mysql_insert_id() - 마지막 insert 된 아이디 값 esniper 2009-03-26 18263
151 Linux Tip 우분투 설치 후 putty에서 한글 안 깨지도록 설정하기 file esniper 2010-09-10 16994
150 Linux Tip 우분투 ssh or mysql 서버 접속지연이 있는 경우 해결책 esniper 2010-09-10 16360
149 Windows Research 커널레벨에서 IAT Hook lain 2011-06-11 16276
148 Linux Tip 우분투에 IRC서버 설치후 닉네임 길이와 동접자 조절. [1] 오랑캐꽃 2010-11-26 15912
147 Windows Research 유저 레벨에서 API Hook 1부 file [1] lain 2010-09-03 15657
146 Windows Research SetEvent 함수는 언제 실패할까? lain 2010-09-04 15584
145 Windows Research Sysinternals - Filemon source code file [5] lain 2010-09-04 15323
144 Windows Research Sysinternals - Regmon source code file [1] lain 2010-09-04 14915

  • 이용약관
  • 개인정보취급방침
  • 사이트맵