枚举句柄-查找谁占用了我们的文件
平时偶尔会遇见文件无法删除的情况,通常使用PE 或者 PH 来查找谁占用了我们的文件,然后有一天就想自己实现查询占用文件情况的功能。
占用文件或目录,就会打开文件或目录(加载的DLL 例外),然后我们的任务变成:枚举所有的句柄,如果不是自己进程的句柄,先DuplicateObject然后得到句柄类型(或者设置全局的类型数据,然后根据TypeIndex进行类型比较即可,请参考上一篇文章)找到File 类型的句柄,得到对象名,即文件名然后进行比较即可。
枚举句柄的方法很简单:NtQueryObject(NULL,SystemHandleInformation/SystemExtendHandleInformation(Xp+),),问题是:当遇到访问权限为0x0012019f 0x001a019f 0x0016019f 还有 0 (或者其它还没发现的访问权限)时会出现NtQueryObject 获取对象名称将线程挂起的情况。有人说GetFileInformationByHandleEx 并不会将线程挂起,但是经过测试,函数依然会挂起。
上两张图展示了PE 在普通模式和UAC 模式下搜索令WhoUseMe 线程挂起的对象的结果,我们发现,PE 也无法在普通权限下获得该类型的对象的名称。而在UAC模式下可以搜索到该对象,分别在两个进程中,一个是NVDisplay.Container.exe进程,即原来的进程,WhoUseMe.exe 则是在DuplicateObject之后才获得的这个句柄,然后在NtQueryObject获得对象名称的时候线程被挂起。
那么UAC 代表权限,而我们自己的程序UAC 也会挂起,之后想起了驱动,只要有自己的驱动在挂载,就可以直接通过对象数据结构来获得对象名,使用Everything 搜索发现:
然后分别在普通权限和UAC 搜索该驱动:
而用更方便的PH 查找即可发现:
PE 在UAC 权限确实加载了该驱动,然后PE 通过驱动来获得对象名,而不会遇见线程挂起的情况。
另外,我们发现并不是所有的具有上述罗列的访问权限的文件对象都会导致线程挂起,为了在普通权限可以得到尽量全的文件名,我们不可以仅仅过滤那些访问权限。
那么现在如果我们想要得到文件对象的名称而不至于让线程挂起有两种解决方案:
1. 普通权限,让单独的线程执行NtQueryObject 获得对象名,如果线程挂起,直接杀死线程,为了避免频繁的线程创建和退出操作,我们将任务放在一个while(1)循环中,然后等待一个通知的同步事件,主线程如果需要执行操作,设置一个变量指示句柄并触发事件并在一个事件上等待(设置超时),然后子线程执行NtQueryObject 操作,成功后触发主线程所等待的事件,如果超时,KillThread并新生成一个线程。
2. UAC,加载驱动,通过句柄获得对象,通过对象获得对象名即可。
普通模式代码如下
头文件
#pragma once
#ifndef UNICODE
#define UNICODE
#endif
#include <afxstr.h>
#include <windows.h>
#define NTSTATUS long
#define NT_SUCCESS(x) ((x) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH 0xc0000004
#define SystemHandleInformation 16
#define SystemExtendedHandleInformation 64
#define ObjectBasicInformation 0
#define ObjectNameInformation 1
#define ObjectTypeInformation 2
typedef NTSTATUS(NTAPI *_NtQuerySystemInformation)(
ULONG SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);
typedef NTSTATUS(NTAPI *_NtDuplicateObject)(
HANDLE SourceProcessHandle,
HANDLE SourceHandle,
HANDLE TargetProcessHandle,
PHANDLE TargetHandle,
ACCESS_MASK DesiredAccess,
ULONG Attributes,
ULONG Options
);
typedef NTSTATUS(NTAPI *_NtQueryObject)(
HANDLE ObjectHandle,
ULONG ObjectInformationClass,
PVOID ObjectInformation,
ULONG ObjectInformationLength,
PULONG ReturnLength
);
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
{
PVOID Object;
ULONG_PTR UniqueProcessId;
HANDLE HandleValue;
ULONG GrantedAccess;
USHORT CreatorBackTraceIndex;
USHORT ObjectTypeIndex;
ULONG HandleAttributes;
ULONG Reserved;
} SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX, *PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX;
typedef struct _SYSTEM_HANDLE_INFORMATION_EX
{
ULONG_PTR NumberOfHandles;
ULONG_PTR Reserved;
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX Handles[1];
} SYSTEM_HANDLE_INFORMATION_EX, *PSYSTEM_HANDLE_INFORMATION_EX;
typedef enum _POOL_TYPE
{
NonPagedPool,
PagedPool,
NonPagedPoolMustSucceed,
DontUseThisType,
NonPagedPoolCacheAligned,
PagedPoolCacheAligned,
NonPagedPoolCacheAlignedMustS
} POOL_TYPE, *PPOOL_TYPE;
typedef struct _OBJECT_TYPE_INFORMATION
{
UNICODE_STRING Name;
ULONG TotalNumberOfObjects;
ULONG TotalNumberOfHandles;
ULONG TotalPagedPoolUsage;
ULONG TotalNonPagedPoolUsage;
ULONG TotalNamePoolUsage;
ULONG TotalHandleTableUsage;
ULONG HighWaterNumberOfObjects;
ULONG HighWaterNumberOfHandles;
ULONG HighWaterPagedPoolUsage;
ULONG HighWaterNonPagedPoolUsage;
ULONG HighWaterNamePoolUsage;
ULONG HighWaterHandleTableUsage;
ULONG InvalidAttributes;
GENERIC_MAPPING GenericMapping;
ULONG ValidAccess;
BOOLEAN SecurityRequired;
BOOLEAN MaintainHandleCount;
USHORT MaintainTypeList;
POOL_TYPE PoolType;
ULONG PagedPoolUsage;
ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
VOID WhoUsesFile(CString trFilePath, CString& strShow);
实现
#include "stdafx.h"
#include "QuerySystemHandleInformationAndFindWhoUseMe.h"
#include "F:\Common\\fordebug.h"
#include <Psapi.h>
TCHAR szNtPath[MAX_PATH] = { 0 };
WCHAR processName[0x200] = { 0 };
PVOID GetLibraryProcAddress(PSTR LibraryName, PSTR ProcName)
{
return GetProcAddress(GetModuleHandleA(LibraryName), ProcName);
}
_NtQueryObject NtQueryObject =
(_NtQueryObject)GetLibraryProcAddress("ntdll.dll", "NtQueryObject");
void ToCaps(PWCHAR lpFileName)
{
if (!lpFileName)
{
return;
}
PWCHAR travel = lpFileName;
WCHAR wChar = *travel;
while (wChar != 0)
{
if (wChar >= 0x61 && wChar <= 0x7a)
{
*travel = wChar - 0x20;
}
travel += 1;
wChar = *travel;
}
}
BOOL DosPathToNtPath(LPTSTR pszDosPath, LPTSTR pszNtPath)
{
TCHAR szDriveStr[500];
TCHAR szDrive[3];
TCHAR szDevName[100];
INT cchDevName;
INT i;
//检查参数
if (!pszDosPath || !pszNtPath)
return FALSE;
//获取本地磁盘字符串
if (GetLogicalDriveStrings(sizeof(szDriveStr), szDriveStr))
{
for (i = 0; szDriveStr[i]; i += 4)
{
if (!lstrcmpi(&(szDriveStr[i]), _T("A:\\")) || !lstrcmpi(&(szDriveStr[i]), _T("B:\\")))
continue;
szDrive[0] = szDriveStr[i];
szDrive[1] = szDriveStr[i + 1];
szDrive[2] = '\0';
if (!QueryDosDevice(szDrive, szDevName, 100))//查询 Dos 设备名
return FALSE;
cchDevName = lstrlen(szDevName);
if (_tcsnicmp(pszDosPath, szDevName, cchDevName) == 0)//命中
{
lstrcpy(pszNtPath, szDrive);//复制驱动器
lstrcat(pszNtPath, pszDosPath + cchDevName);//复制路径
return TRUE;
}
}
}
lstrcpy(pszNtPath, pszDosPath);
return FALSE;
}
typedef struct _THREAD_CONTEXT_
{
HANDLE CompletedEventHandle;
HANDLE StartEventHandle;
HANDLE TargetHandle;
HANDLE ThreadHandle;
PVOID ObjectName;
}THREAD_CONTEXT,*PTHREAD_CONTEXT;
DWORD WINAPI CallWithTimeoutThreadStart(
_In_ PVOID Parameter
)
{
PTHREAD_CONTEXT threadContext = (PTHREAD_CONTEXT)Parameter;
DWORD dwReturnedLength = 0x200;
DWORD dwNewLength = 0x200;
NTSTATUS status = 0;
while (TRUE)
{
if (WaitForSingleObject(threadContext->StartEventHandle, INFINITE) != STATUS_WAIT_0)
continue;
PUNICODE_STRING objectName = (PUNICODE_STRING)threadContext->ObjectName;
objectName->Length = objectName->MaximumLength = 0;
objectName->Buffer = NULL;
if (!NT_SUCCESS(status = NtQueryObject(
threadContext->TargetHandle,
ObjectNameInformation,
threadContext->ObjectName,
dwReturnedLength,
&dwNewLength
)))
{
if (status == 0xc0000008) // STATUS_INVALID_HANDLE
{
objectName->Length = 0;
}
else if (status == 0xC0000039) // STATUS_OBJECT_PATH_INVALID
{
objectName->Length = 0;
}
else if (status == 0xC00000BB)// STATUS_NOT_SUPPORTED
{
objectName->Length = 0;
}
else
{
// 仅仅作为测试,其实失败了就应该将Length 置0
}
}
SetEvent(threadContext->CompletedEventHandle);
}
return 0;
}
#define MAX_TYPE_COUNT 80
struct _TYPE_AND_INDEX_
{
BOOL bNew;
BOOL bTarget;
}g_Type[MAX_TYPE_COUNT];
VOID WhoUsesFile(CString strFileName, CString& strShow)
{
NTSTATUS Status;
PSYSTEM_HANDLE_INFORMATION_EX handleInfo;
HANDLE processHandle = NULL;
static ULONG initialBufferSize = 0x10000;
PVOID buffer;
ULONG bufferSize;
ToCaps(strFileName.GetBuffer());
_NtQuerySystemInformation NtQuerySystemInformation =
(_NtQuerySystemInformation)GetLibraryProcAddress("ntdll.dll", "NtQuerySystemInformation");
_NtDuplicateObject NtDuplicateObject =
(_NtDuplicateObject)GetLibraryProcAddress("ntdll.dll", "NtDuplicateObject");
for (int i = 0; i < MAX_TYPE_COUNT; i++)
{
g_Type[i].bTarget = FALSE;
g_Type[i].bNew = TRUE;
}
bufferSize = initialBufferSize;
buffer = malloc(bufferSize);
while ((Status = NtQuerySystemInformation(
SystemExtendedHandleInformation,
buffer,
bufferSize,
NULL
)) == STATUS_INFO_LENGTH_MISMATCH)
{
free(buffer);
bufferSize *= 2;
// Fail if we're resizing the buffer to something very large.
if (bufferSize > 256 * 1024 * 1024)
{
MessageBox(NULL, _T("需要的内存太大了"), NULL, MB_OK);
return;
}
buffer = malloc(bufferSize);
}
if (!NT_SUCCESS(Status))
{
free(buffer);
return;
}
if (bufferSize <= 0x200000) initialBufferSize = bufferSize;
handleInfo = (PSYSTEM_HANDLE_INFORMATION_EX)buffer;
DWORD dwThreadId;
ULONG objectNameInfo_returnLength = 0x10000;
ULONG objectTypeInfo_returnLength = 0x10000;
POBJECT_TYPE_INFORMATION objectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(objectTypeInfo_returnLength);
PFILE_NAME_INFO ObjectName = (PFILE_NAME_INFO)malloc(0x1000);
PTHREAD_CONTEXT ThreadContext = (PTHREAD_CONTEXT)malloc(sizeof(THREAD_CONTEXT));
ThreadContext->ThreadHandle = ThreadContext->CompletedEventHandle = ThreadContext->StartEventHandle = ThreadContext->TargetHandle = NULL;
ThreadContext->ObjectName = malloc(0x200);
for (ULONG i = 0; i < handleInfo->NumberOfHandles; i++)
{
PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handle = (PSYSTEM_HANDLE_TABLE_ENTRY_INFO_EX)&handleInfo->Handles[i];
HANDLE dupHandle = NULL;
UNICODE_STRING objectName;
if (!g_Type[handle->ObjectTypeIndex].bNew && !g_Type[handle->ObjectTypeIndex].bTarget)
{
continue;
}
/* 拷贝过来. */
if (handle->UniqueProcessId == GetCurrentProcessId())
{
continue;
}
processHandle = OpenProcess(PROCESS_DUP_HANDLE | PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, handle->UniqueProcessId);
if (processHandle == NULL)
{
continue;
}
if (!NT_SUCCESS(NtDuplicateObject(
processHandle,
handle->HandleValue,
GetCurrentProcess(),
&dupHandle,
0,
0,
DUPLICATE_SAME_ACCESS
)))
{
continue;
}
/* 得到对象类型 */
if (g_Type[handle->ObjectTypeIndex].bNew)
{
g_Type[handle->ObjectTypeIndex].bNew = FALSE;
if (!NT_SUCCESS(NtQueryObject(
dupHandle,
ObjectTypeInformation,
objectTypeInfo,
objectTypeInfo_returnLength,
NULL
)))
{
MessageBox(NULL, _T("NtQueryObject失败"), NULL, MB_OK);
CloseHandle(dupHandle);
continue;
}
/*
if (wcsicmp(objectTypeInfo->Name.Buffer, L"DmaAdapter") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"DmaDomain") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"DxgkSharedResource") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"DxgkSharedSyncObject") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"DxgkSharedSwapChainObject") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"FilterCommunicationPort") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"FilterConnectionPort") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"NdisCmState") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"PcwObject") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"VirtualKey") == 0 ||
wcsicmp(objectTypeInfo->Name.Buffer, L"VRegConfigurationContext") == 0)
{
//if (wcsicmp(objectTypeInfo->Name.Buffer, L"DxgkSharedResource") != 0)
{
//if (wcsicmp(objectTypeInfo->Name.Buffer, L"DxgkSharedSyncObject") != 0)
{
//GetModuleFileNameEx(processHandle, NULL, processName, MAX_PATH);
//MessageBox(NULL, _T("找到了"), NULL, MB_OK);
}
}
//continue;
}
*/
if (wcsicmp(objectTypeInfo->Name.Buffer, L"File") != 0 && wcsicmp(objectTypeInfo->Name.Buffer, L"Directory") != 0)
{
CloseHandle(dupHandle);
CloseHandle(processHandle);
g_Type[handle->ObjectTypeIndex].bTarget = FALSE;
continue;
}
g_Type[handle->ObjectTypeIndex].bTarget = TRUE;
}
else
{
if (!g_Type[handle->ObjectTypeIndex].bTarget)
{
CloseHandle(dupHandle);
CloseHandle(processHandle);
continue;
}
}
if (!ThreadContext->StartEventHandle)
{
if (!(ThreadContext->StartEventHandle = CreateEvent(NULL, FALSE, FALSE, NULL)))
{
MessageBox(NULL, L"创建事件失败", NULL, MB_OK);
free(ThreadContext->ObjectName);
break;
}
}
if (!ThreadContext->CompletedEventHandle)
{
if (!(ThreadContext->CompletedEventHandle = CreateEvent(NULL, FALSE, FALSE, NULL)))
{
MessageBox(NULL, L"创建事件失败", NULL, MB_OK);
free(ThreadContext->ObjectName);
break;
}
}
// 创建Query线程.
if (!ThreadContext->ThreadHandle)
{
if (!(ThreadContext->ThreadHandle = (HANDLE)_beginthreadex(NULL, 0,(_beginthreadex_proc_type) CallWithTimeoutThreadStart, ThreadContext, 0, (unsigned int*)&dwThreadId)))
{
MessageBox(NULL, L"创建线程失败", NULL, MB_OK);
break;
}
}
ThreadContext->TargetHandle = dupHandle;
SetEvent(ThreadContext->StartEventHandle);
if ((Status = WaitForSingleObject(ThreadContext->CompletedEventHandle, 100)) != WAIT_OBJECT_0)
{
// 操作超时或者发生错误,KillThread
TerminateThread(ThreadContext->ThreadHandle,0);
WaitForSingleObject(ThreadContext->ThreadHandle,0);
CloseHandle(ThreadContext->ThreadHandle);
CloseHandle(ThreadContext->CompletedEventHandle);
CloseHandle(ThreadContext->StartEventHandle);
CloseHandle(dupHandle);
CloseHandle(processHandle);
ThreadContext->CompletedEventHandle = NULL;
ThreadContext->StartEventHandle = NULL;
ThreadContext->ThreadHandle = NULL;
continue;
}
/* if (handle->GrantedAccess == 0x0012019f || handle->GrantedAccess == 0 || handle->GrantedAccess == 0x001a019f || handle->GrantedAccess == 0x0016019f)
{
CloseHandle(dupHandle);
continue;
}*/
/* Query the object name (unless it has an access of
0x0012019f, on which NtQueryObject could hang. */
// READ_CONTROL | SYNCHRONIZE[同步] | 读写执行,访问系统访问控制列表
// 100100000 0001 1001 1111(EWR)
// 1 0000 0000 1000 0000
// typedef struct _ACCESS_MASK {
// WORD SpecificRights; 12
// BYTE StandardRights; 1
// BYTE AccessSystemAcl : 1; 1
// BYTE Reserved : 3; 1
// BYTE GenericAll : 1; 1
// BYTE GenericExecute : 1; 1
// BYTE GenericWrite : 1; 1
// BYTE GenericRead : 1; 1
// } ACCESS_MASK;
PUNICODE_STRING strObjectName = (PUNICODE_STRING)ThreadContext->ObjectName;
if (strObjectName->Buffer && (strObjectName->Buffer == (PWCHAR)(strObjectName + 1)) && strObjectName->Length)
{
wcsncpy(processName, strObjectName->Buffer, 0x200);
ToCaps(strObjectName->Buffer);
if (wcsstr(strObjectName->Buffer, strFileName))
{
DosPathToNtPath(processName, szNtPath);
GetModuleFileNameEx(processHandle, NULL, processName, MAX_PATH);
strShow.AppendFormat(L"文件名:%s\r\n进程名:%s\r\n\r\n", szNtPath, processName);
}
}
CloseHandle(dupHandle);
CloseHandle(processHandle);
}
free(objectTypeInfo);
free(ThreadContext->ObjectName);
free(ThreadContext);
free(buffer);
free(ObjectName);
TerminateThread(ThreadContext->ThreadHandle, 0);// 这里我们就直接Kill了,不设置让线程自动退出了
WaitForSingleObject(ThreadContext->ThreadHandle, 0);
return;
}
使用
void CWhoUseMEDlg::OnBnClickedFind()
{
CString strFileName;
CString strShow;
GetDlgItemText(IDC_EDIT_FILE, strFileName);
if (strFileName != L"")
{
WhoUsesFile(strFileName, strShow);
}
else
{
AfxMessageBox(_T("请输入文件名"));
return;
}
SetDlgItemText(IDC_USE,strShow);
}
内核代码将来补上