# ring0解锁文件
# 背景
当我们要删除一个文件的的时候,有时候会出现“文件被占用”或是“无法删除”的提示框,这往往是由于该文件句柄已经在其它进程中打开未关闭的缘故。
本文要介绍的是关闭在其它进程中的文件句柄,对文件进行解锁。现在,我把实现过程和原理,整理成文档,分享给大家。
# 实现原理
要实现对指定文件进行解锁,我们首先要做的就是获取这个文件所有被打开的文件句柄,然后在关闭这些句柄即可。那么接下来,我就对指定文件句柄的遍历以及结束文件句柄的实现原理分别进行解析。
## 遍历指定文件句柄
1. 首先,我们使用 16 号功能调用 ZwQuerySystemInformation 函数,获取系统上所有的句柄数据。其中,ZwQuerySystemInformation 是一个未导出的内核函数,16号功能即 SystemHandleInformation 功能,用来获取系统上的所有句柄信息,根据 SYSTEM_HANDLE_INFORMATION 结构体来获取返回的数据信息。
2. 然后,遍历系统上每一个句柄并进行判断。
1> 首先,我们可以从上述 16 号功能中获取到句柄对应进程 ID 的信息,这样,可以先调用 ZwOpenProcess 打开句柄所在的进程;
2> 然后,调用 ZwDuplicateObject 函数将已打开进程中的文件句柄复制到当前进程 NtCurrentProcess 中;
3> 接着,使用1 号功能调用 ZwQueryObject 函数获取句柄的类型和名称;
4> 最后,根据句柄对应的文件名称判断是否是要解锁的文件,若是,则开始对文件进行解锁;若不是,则继续对下一个句柄进行判断。
3. 最后,释放内存。
## 结束指定文件句柄
1. 首先,我们根据要解锁的文件句柄对应的进程ID,调用内核函数 PsLookupProcessByProcessId 来获取进程结构对象 EPROCESS;
2. 然后,调用 KeStackAttachProcess 附件到目标进程中;
3. 接着,调用未导出函数 ObSetHandleAttributes 函数,对文件句柄的属性进行设置,将其文件句柄设置为“可以关闭”;
4. 接着,调用 ZwClose 函数,关闭文件句柄;
5. 最后,调用 KeUnstackDetachProcess 结束附加到目标进程。
# 编码实现
## 获取并遍历系统上所有句柄
```c++
// 遍历句柄, 解锁文件
BOOLEAN Unlockfile(UNICODE_STRING ustrUnlockFileName)
{
NTSTATUS status = STATUS_SUCCESS;
SYSTEM_HANDLE_INFORMATION tempSysHandleInfo = {0};
PSYSTEM_HANDLE_INFORMATION pSysHandleInfo = NULL;
ULONG ulSysHandleInfoSize = 0;
ULONG ulReturnLength = 0;
ULONGLONG ullSysHandleCount = 0;
PSYSTEM_HANDLE_TABLE_ENTRY_INFO pSysHandleTableEntryInfo = NULL;
ULONGLONG i = 0;
BOOLEAN bRet = FALSE;
// 调用ZwQuerySystemInformation的16号功能来枚举系统里的句柄
// 先获取缓冲区大小
ZwQuerySystemInformation(16, &tempSysHandleInfo, sizeof(tempSysHandleInfo), &ulSysHandleInfoSize);
if (0 >= ulSysHandleInfoSize)
{
ShowError("ZwQuerySystemInformation", 0);
return FALSE;
}
DbgPrint("ulSysHandleInfoSize=%d\n", ulSysHandleInfoSize);
// 申请缓冲区内存
pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)ExAllocatePool(NonPagedPool, ulSysHandleInfoSize);
if (NULL == pSysHandleInfo)
{
ShowError("ExAllocatePool", 0);
return FALSE;
}
RtlZeroMemory(pSysHandleInfo, ulSysHandleInfoSize);
// 获取系统中所有句柄的信息
status = ZwQuerySystemInformation(16, pSysHandleInfo, ulSysHandleInfoSize, &ulReturnLength);
if (!NT_SUCCESS(status))
{
ExFreePool(pSysHandleInfo);
ShowError("ZwQuerySystemInformation", status);
return FALSE;
}
// 获取系统所有句柄数量以及句柄信息数组
ullSysHandleCount = pSysHandleInfo->NumberOfHandles;
pSysHandleTableEntryInfo = pSysHandleInfo->Handles;
// 开始遍历系统上所有句柄
for (i = 0; i < ullSysHandleCount; i++)
{
// 获取句柄信息并判断是否是解锁文件句柄
bRet = IsUnlockFileHandle(pSysHandleTableEntryInfo[i], ustrUnlockFileName);
if (bRet)
{
// 关闭文件句柄, 解锁文件
CloseFileHandle(pSysHandleTableEntryInfo[i]);
// 显示
DbgPrint("[UnlockFile][%d][%d]\n",
pSysHandleTableEntryInfo[i].UniqueProcessId, pSysHandleTableEntryInfo[i].HandleValue);
}
}
// 释放
ExFreePool(pSysHandleInfo);
}
```
## 获取句柄信息并判断是否是解锁文件句柄
```c++
// 获取句柄信息并判断是否是解锁文件句柄
BOOLEAN IsUnlockFileHandle(SYSTEM_HANDLE_TABLE_ENTRY_INFO sysHandleTableEntryInfo, UNICODE_STRING ustrUnlockFileName)
{
NTSTATUS status = STATUS_SUCCESS;
CLIENT_ID clientId = { 0 };
OBJECT_ATTRIBUTES objectAttributes = { 0 };
HANDLE hSourceProcess = NULL;
HANDLE hDupObj = NULL;
POBJECT_NAME_INFORMATION pObjNameInfo = NULL;
ULONG ulObjNameInfoSize = 0;
BOOLEAN bRet = FALSE;
WCHAR wszSrcFile[FILE_PATH_MAX_NUM] = { 0 };
WCHAR wszDestFile[FILE_PATH_MAX_NUM] = { 0 };
// 根据句柄对应的PID打开进程获取句柄对应的进程句柄
do
{
InitializeObjectAttributes(&objectAttributes, NULL, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
RtlZeroMemory(&clientId, sizeof(clientId));
clientId.UniqueProcess = sysHandleTableEntryInfo.UniqueProcessId;
status = ZwOpenProcess(&hSourceProcess, PROCESS_ALL_ACCESS, &objectAttributes, &clientId);
if (!NT_SUCCESS(status))
{
ShowError("ZwOpenProcess", status);
break;
}
// 将已打开进程中的文件句柄复制到当前进程 NtCurrentProcess 中
status = ZwDuplicateObject(hSourceProcess, sysHandleTableEntryInfo.HandleValue,
NtCurrentProcess(), &hDupObj, PROCESS_ALL_ACCESS, 0, DUPLICATE_SAME_ACCESS);
if (!NT_SUCCESS(status))
{
ShowError("ZwDuplicateObject", status);
break;
}
// 查询句柄的名称信息
// 先获取缓冲区大小
ZwQueryObject(hDupObj, 1, NULL, 0, &ulObjNameInfoSize);
if (0 >= ulObjNameInfoSize)
{
ShowError("ZwQueryObject", 0);
break;
}
// 申请缓冲区内存
pObjNameInfo = ExAllocatePool(NonPagedPool, ulObjNameInfoSize);
if (NULL == pObjNameInfo)
{
ShowError("ExAllocatePool", 0);
break;
}
RtlZeroMemory(pObjNameInfo, ulObjNameInfoSize);
// 获取句柄名称类型信息
status = ZwQueryObject(hDupObj, 1, pObjNameInfo, ulObjNameInfoSize, &ulObjNameInfoSize);
if (!NT_SUCCESS(status))
{
ShowError("ZwQueryObject", 0);
break;
}
// 判断是否要解锁的文件
DbgPrint("[File]%wZ\n", &pObjNameInfo->Name);
RtlZeroMemory(wszSrcFile, FILE_PATH_MAX_NUM*sizeof(WCHAR));
RtlZeroMemory(wszDestFile, FILE_PATH_MAX_NUM*sizeof(WCHAR));
RtlCopyMemory(wszSrcFile, pObjNameInfo->Name.Buffer, pObjNameInfo->Name.Length);
RtlCopyMemory(wszDestFile, ustrUnlockFileName.Buffer, ustrUnlockFileName.Length);
if (NULL != wcsstr(wszSrcFile, wszDestFile))
{
bRet = TRUE;
break;
}
} while (FALSE);
// 释放
if (NULL != pObjNameInfo)
{
ExFreePool(pObjNameInfo);
}
if (NULL != hDupObj)
{
ZwClose(hDupObj);
}
if (NULL != hSourceProcess)
{
ZwClose(hSourceProcess);
}
return bRet;
}
```
## 关闭文件句柄
```c++
// 关闭文件句柄, 解锁文件
BOOLEAN CloseFileHandle(SYSTEM_HANDLE_TABLE_ENTRY_INFO sysHandleTableEntryInfo)
{
NTSTATUS status = STATUS_SUCCESS;
PEPROCESS pEProcess = NULL;
HANDLE hFileHandle = NULL;
KAPC_STATE apcState = { 0 };
OBJECT_HANDLE_FLAG_INFORMATION objectHandleFlagInfo = { 0 };
// 获取文件句柄
hFileHandle = sysHandleTableEntryInfo.HandleValue;
// 获取文件句柄对应的进程结构对象EPROCESS
status = PsLookupProcessByProcessId(sysHandleTableEntryInfo.UniqueProcessId, &pEProcess);
if (!NT_SUCCESS(status))
{
ShowError("PsLookupProcessByProcessId", status);
return FALSE;
}
// 附加到文件句柄对应的进程空间
KeStackAttachProcess(pEProcess, &apcState);
// 将文件句柄的属性设置为“可以关闭”
objectHandleFlagInfo.Inherit = 0;
objectHandleFlagInfo.ProtectFromClose = 0;
ObSetHandleAttributes((HANDLE)hFileHandl