- 前面写过一个应用层的查找谁占用了我们的文件的程序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 使用了一种更加巧妙的方法:
// 这是上面的作者给出的代码
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 中则为直接的实现者,直接调用内核函数。