WhoUseMe 驱动实现

  • 前面写过一个应用层的查找谁占用了我们的文件的程序WhoUseMe
    http://blog.csdn.net/qq_18218335/article/details/62884657
    从文中截图可以看出来,普通的应用层程序虽然可以达到枚举句柄的目的,但是对于一部分具有特殊权限的文件及端口句柄来说,调用NtQueryObject函数可能导致线程挂起,对此我们的解决办法是让一个单独的线程执行NtQueryObject函数,然后定时检查其运行状况,如果阻塞的话,killthread 并重新生成一个新的线程来执行NtQueryObject操作。这样做虽然可以避免整个程序的死锁。但是没有驱动的帮助,有些时候依然无法查找到我们所需要找的文件。今天我们就来实现这个WhoUseMe的驱动版本。

对象相关知识回顾

  • 首先复习一下关于句柄和对象管理的知识
  • 每个内核对象都只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问。这个内存块是一个数据结构,其成员维护着与对象相关的信息。
  • 应用程序不能在内存中定位这些数据结构并直接更改其内容。应用程序对不同类型的对象的操作是通过特定的例程进行的。如:NtCreateObject 对应创建Job 对象,NtCreateFile 对应创建文件对象等等。
  • 句柄值进程相关—每个进程有一个进程句柄表。进程间可以共享同一个内核对象。
  • 内核对象的所有者是操作系统内核,而非普通进程。
  • 对象的生存期:操作系统销毁使用计数为0的内核对象,系统中不存在没有被任何进程引用的内存对象。必须了解特定函数对于对象的操作是否会改变其引用计数,并严格遵守使用规则,在使用前增加引用计数,释放后减少引用计数。
  • 常用的操作函数
函数名作用注意
ObReferenceObjectByHandle在当前进程的句柄表或内核句柄表中找到相应的表项该函数会增加对象的引用计数
ObReferenceObjectByPointer递增对象的指针引用计数PointerCount
ObpLookupEntryDirectory给定一个对象名,在特定的目录节点下面寻找目标对象单层搜索,找到则增加对象的引用计数
ObpLookupObjectName同上,逐层搜索
ObOpenObjectByName根据对象名打开一个对象里面会调用ObpLookupObjectName 函数,返回目标对象的句柄,会增加引用计数
ObReferenceObjectByName同上,返回的是对象的指针会增加引用计数
ObDereferenceObject递减对象的引用计数传入的是对象指针

为达成目标而进行的小测试

  • 我们在之前的文章中已经知道了枚举系统所有句柄的方法,如今的问题就是对于部分句柄调用NtQueryObject 会造成线程阻塞,驱动要解决的就是这个问题,那么,驱动中如何得到对象的名称呢?
    http://bbs.pediy.com/thread-118325.htm
    WinXP 对象头中有NameInfoOffset域,而Win7 使用了一种更加巧妙的方法:
    Win7 对象头->IofoMask域
// 这是上面的作者给出的代码
PVOID ObQueryNameInfo(IN PVOID Object)
{
    POBJECT_HEADER ObjectHeader=OBJECT_TO_OBJECT_HEADER(Object);
    BYTE InfoMask=ObjectHeader->InfoMask;
    ULONG NameInfo=0;
    if(InfoMask & OB_INFOMASK_NAME)
    {
        NameInfo=(ULONG)ObjectHeader - ObpInfoMaskToOffset[InfoMask & (OB_INFOMASK_NAME+OB_INFOMASK_CREATOR_INFO)];
    }
    else
    {
        NameInfo=0;
    }
    return (PVOID)NameInfo;
}
// 这是ntoskernel 逆向得到的代码,互相印证
if ( *(In_Object - 22) & 2 )
    v9 = (signed __int64)&v7->PointerCount - ObpInfoMaskToOffset[*(In_Object - 22) & 3];
  • 探索用户线程调用不成功的原因
  • 代码如下,当我们线程阻塞时,得到导致阻塞的句柄值
SetEvent(ThreadContext->StartEventHandle);
            if ((Status = WaitForSingleObject(ThreadContext->CompletedEventHandle, 100)) != WAIT_OBJECT_0)
            {
                // 操作超时或者发生错误,KillThread
                //TerminateThread(ThreadContext->TargetHandle, 0);
                char    szBuffer[0x10] = { 0 };
                sprintf(szBuffer, "%d", ThreadContext->ThreadHandle);
                MessageBoxA(NULL, szBuffer, NULL, 0);

得到如下截图:
阻塞原因
得到对象地址
       之后我们在WinDbg 中得到对象的内容如下。

1: kd> dt _FILE_OBJECT 0xfffffa8032880ca0
nt!_FILE_OBJECT
   +0x000 Type             : 0n5
   +0x002 Size             : 0n216
   +0x008 DeviceObject     : 0xfffffa80`3151e3c0 _DEVICE_OBJECT
   +0x010 Vpb              : (null) 
   +0x018 FsContext        : 0x00000000`00010101 Void
   +0x020 FsContext2       : 0xfffff8a0`0bc84d60 Void
   +0x028 SectionObjectPointer : (null) 
   +0x030 PrivateCacheMap  : 0x00000000`00000001 Void
   +0x038 FinalStatus      : 0n0
   +0x040 RelatedFileObject : (null) 
   +0x048 LockOperation    : 0 ''
   +0x049 DeletePending    : 0 ''
   +0x04a ReadAccess       : 0 ''
   +0x04b WriteAccess      : 0 ''
   +0x04c DeleteAccess     : 0 ''
   +0x04d SharedRead       : 0 ''
   +0x04e SharedWrite      : 0 ''
   +0x04f SharedDelete     : 0 ''
   +0x050 Flags            : 0x40082
   +0x058 FileName         : _UNICODE_STRING "\QQ_771765819_pipe"
   +0x068 CurrentByteOffset : _LARGE_INTEGER 0x0
   +0x070 Waiters          : 0
   +0x074 Busy             : 1
   +0x078 LastLock         : (null) 
   +0x080 Lock             : _KEVENT
   +0x098 Event            : _KEVENT
   +0x0b0 CompletionContext : (null) 
   +0x0b8 IrpListLock      : 0
   +0x0c0 IrpList          : _LIST_ENTRY [ 0xfffffa80`32880d60 - 0xfffffa80`32880d60 ]
   +0x0d0 FileObjectExtension : (null) 

       查看被阻塞线程的调用堆栈如下:
被阻塞线程的调用堆栈

  • 内核实现
#define IOCTL_WUM_GET_OBJ_NAME CTL_CODE(\
    FILE_DEVICE_UNKNOWN, \
    0x800, \
    METHOD_NEITHER, \
    FILE_ANY_ACCESS)
    // 使用的是自定义的I/O
typedef struct _DEVICE_EXTENSION {
    PDEVICE_OBJECT pDevice;
    UNICODE_STRING ustrDeviceName;  //设备名称
    UNICODE_STRING ustrSymLinkName; //符号链接名
    PUCHAR buffer;//缓冲区
} DEVICE_EXTENSION, *PDEVICE_EXTENSION;
typedef struct {
    ULONG   dwProcessId;
    HANDLE  hObjHandle;
    PUNICODE_STRING pUnicodeString;
}STRUCT_GET_OBJ_NAME,*PSTRUCT_GET_OBJ_NAME;

NTSTATUS DispatchDeviceControl(PDEVICE_OBJECT  DeviceObject,PIRP Irp)
{
    NTSTATUS  Status       = STATUS_SUCCESS;
    PVOID     InputBuffer  = NULL;
    ULONG_PTR InputSize    = 0;
    ULONG_PTR IoControlCode = 0;
    PIO_STACK_LOCATION   IrpSp;
    PSTRUCT_GET_OBJ_NAME    pGetName;
    PEPROCESS               pprocess;
    HANDLE                  handle;
    KPROCESSOR_MODE         referenceMode;
    KAPC_STATE              apcState;
    PVOID                   object;
    IrpSp = IoGetCurrentIrpStackLocation(Irp);

    InputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;
    InputSize = IrpSp->Parameters.DeviceIoControl.InputBufferLength;

    IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;

    switch(IoControlCode)
    {
    case IOCTL_WUM_GET_OBJ_NAME:
        {
            if (InputBuffer!=NULL)
            {
                if (!MmIsAddressValid(InputBuffer))
                {
                    Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                    Irp->IoStatus.Information = 0;
                    break;
                }
                __try
                {
                    ProbeForRead(InputBuffer,sizeof(STRUCT_GET_OBJ_NAME),sizeof(ULONG_PTR));
                    pGetName = (PSTRUCT_GET_OBJ_NAME)InputBuffer;
                    if(!NT_SUCCESS(PsLookupProcessByProcessId(pGetName->dwProcessId,&pprocess)))
                    {
                        DbgPrint("Error: Unable to reference the target process.");
                        Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                        Irp->IoStatus.Information = 0;
                        break;
                    }

                    if (pprocess == PsInitialSystemProcess)
                    {
                        handle = MakeKernelHandle(pGetName->hObjHandle);
                        referenceMode = KernelMode;
                    }
                    else
                    {
                        if (IsKernelHandle(pGetName->hObjHandle))
                        {
                            ObDereferenceObject(pprocess);
                            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                            Irp->IoStatus.Information = 0;
                            break;
                        }
                        referenceMode = Irp->RequestorMode;
                        handle = pGetName->hObjHandle;
                    }

                    // 挂载到目标进程
                    KeStackAttachProcess(pprocess, &apcState);
                    // 得到句柄对应的对象
                    Status = ObReferenceObjectByHandle(
                        handle,
                        0,
                        NULL,
                        referenceMode,
                        &object,
                        NULL
                        );
                    KeUnstackDetachProcess(&apcState);

                    if(!NT_SUCCESS(Status))
                    {
                        ObDereferenceObject(pprocess);
                        Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                        Irp->IoStatus.Information = 0;
                        break;
                    }

                    QueryNameObject(object,pGetName->pUnicodeString);
                    Status = Irp->IoStatus.Status = STATUS_SUCCESS;
                    break;
                }
                __except(EXCEPTION_EXECUTE_HANDLER)
                {
                    Irp->IoStatus.Information = 0;
                    Status = Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
                    break;
                }
            }
            Irp->IoStatus.Information = 0;
            Status = Irp->IoStatus.Status = STATUS_SUCCESS;
            break;
        }
    default:
        {
            Irp->IoStatus.Status = STATUS_UNSUCCESSFUL;
            Irp->IoStatus.Information = 0;
            break;
        }
    }
    IoCompleteRequest(Irp,IO_NO_INCREMENT);
    return Status;
}

void QueryNameObject(PVOID object,PUNICODE_STRING pUnicodeString)
{
    POBJECT_TYPE    objectType;
    ULONG           dwRetLength = 0;
    int             i = 0;
    ProbeForRead(pUnicodeString,sizeof(UNICODE_STRING),sizeof(ULONG_PTR));
    ProbeForWrite(pUnicodeString->Buffer,pUnicodeString->MaximumLength,sizeof(WCHAR));
    ObQueryNameString(object,(POBJECT_NAME_INFORMATION)pUnicodeString,pUnicodeString->MaximumLength + sizeof(UNICODE_STRING),&dwRetLength);
}
  • 关于Zw 和 Nt 函数
           ntdll中,Nt 和 Zw 是一样的。Ntoskrnl.exe 中Zw 函数eax 为中断号,edx 为函数的参数的起始地址,之后中断,调用函数。Nt 函数则直接调用内核函数。即Nt在内核中为实现代码,Zw 仍然通过中断来实现功能。Zw 函数总是通过中断调用内核函数(一个是内核模式调用,一个是用户模式调用)。而Nt 函数在Ring3 显然必须通过中断进入内核。但在Ring0 中则为直接的实现者,直接调用内核函数。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值