ring0解锁文件

DemonGan

发布日期: 2019-04-04 16:33:40 浏览量: 570
评分:
star star star star star star star star star star_border
*转载请注明来自write-bug.com

背景

当我们要删除一个文件的的时候,有时候会出现“文件被占用”或是“无法删除”的提示框,这往往是由于该文件句柄已经在其它进程中打开未关闭的缘故。

本文要介绍的是关闭在其它进程中的文件句柄,对文件进行解锁。现在,我把实现过程和原理,整理成文档,分享给大家。

实现原理

要实现对指定文件进行解锁,我们首先要做的就是获取这个文件所有被打开的文件句柄,然后在关闭这些句柄即可。那么接下来,我就对指定文件句柄的遍历以及结束文件句柄的实现原理分别进行解析。

遍历指定文件句柄

  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 结束附加到目标进程。

编码实现

获取并遍历系统上所有句柄

  1. // 遍历句柄, 解锁文件
  2. BOOLEAN Unlockfile(UNICODE_STRING ustrUnlockFileName)
  3. {
  4. NTSTATUS status = STATUS_SUCCESS;
  5. SYSTEM_HANDLE_INFORMATION tempSysHandleInfo = {0};
  6. PSYSTEM_HANDLE_INFORMATION pSysHandleInfo = NULL;
  7. ULONG ulSysHandleInfoSize = 0;
  8. ULONG ulReturnLength = 0;
  9. ULONGLONG ullSysHandleCount = 0;
  10. PSYSTEM_HANDLE_TABLE_ENTRY_INFO pSysHandleTableEntryInfo = NULL;
  11. ULONGLONG i = 0;
  12. BOOLEAN bRet = FALSE;
  13. // 调用ZwQuerySystemInformation的16号功能来枚举系统里的句柄
  14. // 先获取缓冲区大小
  15. ZwQuerySystemInformation(16, &tempSysHandleInfo, sizeof(tempSysHandleInfo), &ulSysHandleInfoSize);
  16. if (0 >= ulSysHandleInfoSize)
  17. {
  18. ShowError("ZwQuerySystemInformation", 0);
  19. return FALSE;
  20. }
  21. DbgPrint("ulSysHandleInfoSize=%d\n", ulSysHandleInfoSize);
  22. // 申请缓冲区内存
  23. pSysHandleInfo = (PSYSTEM_HANDLE_INFORMATION)ExAllocatePool(NonPagedPool, ulSysHandleInfoSize);
  24. if (NULL == pSysHandleInfo)
  25. {
  26. ShowError("ExAllocatePool", 0);
  27. return FALSE;
  28. }
  29. RtlZeroMemory(pSysHandleInfo, ulSysHandleInfoSize);
  30. // 获取系统中所有句柄的信息
  31. status = ZwQuerySystemInformation(16, pSysHandleInfo, ulSysHandleInfoSize, &ulReturnLength);
  32. if (!NT_SUCCESS(status))
  33. {
  34. ExFreePool(pSysHandleInfo);
  35. ShowError("ZwQuerySystemInformation", status);
  36. return FALSE;
  37. }
  38. // 获取系统所有句柄数量以及句柄信息数组
  39. ullSysHandleCount = pSysHandleInfo->NumberOfHandles;
  40. pSysHandleTableEntryInfo = pSysHandleInfo->Handles;
  41. // 开始遍历系统上所有句柄
  42. for (i = 0; i < ullSysHandleCount; i++)
  43. {
  44. // 获取句柄信息并判断是否是解锁文件句柄
  45. bRet = IsUnlockFileHandle(pSysHandleTableEntryInfo[i], ustrUnlockFileName);
  46. if (bRet)
  47. {
  48. // 关闭文件句柄, 解锁文件
  49. CloseFileHandle(pSysHandleTableEntryInfo[i]);
  50. // 显示
  51. DbgPrint("[UnlockFile][%d][%d]\n",
  52. pSysHandleTableEntryInfo[i].UniqueProcessId, pSysHandleTableEntryInfo[i].HandleValue);
  53. }
  54. }
  55. // 释放
  56. ExFreePool(pSysHandleInfo);
  57. }

获取句柄信息并判断是否是解锁文件句柄

  1. // 获取句柄信息并判断是否是解锁文件句柄
  2. BOOLEAN IsUnlockFileHandle(SYSTEM_HANDLE_TABLE_ENTRY_INFO sysHandleTableEntryInfo, UNICODE_STRING ustrUnlockFileName)
  3. {
  4. NTSTATUS status = STATUS_SUCCESS;
  5. CLIENT_ID clientId = { 0 };
  6. OBJECT_ATTRIBUTES objectAttributes = { 0 };
  7. HANDLE hSourceProcess = NULL;
  8. HANDLE hDupObj = NULL;
  9. POBJECT_NAME_INFORMATION pObjNameInfo = NULL;
  10. ULONG ulObjNameInfoSize = 0;
  11. BOOLEAN bRet = FALSE;
  12. WCHAR wszSrcFile[FILE_PATH_MAX_NUM] = { 0 };
  13. WCHAR wszDestFile[FILE_PATH_MAX_NUM] = { 0 };
  14. // 根据句柄对应的PID打开进程获取句柄对应的进程句柄
  15. do
  16. {
  17. InitializeObjectAttributes(&objectAttributes, NULL, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);
  18. RtlZeroMemory(&clientId, sizeof(clientId));
  19. clientId.UniqueProcess = sysHandleTableEntryInfo.UniqueProcessId;
  20. status = ZwOpenProcess(&hSourceProcess, PROCESS_ALL_ACCESS, &objectAttributes, &clientId);
  21. if (!NT_SUCCESS(status))
  22. {
  23. ShowError("ZwOpenProcess", status);
  24. break;
  25. }
  26. // 将已打开进程中的文件句柄复制到当前进程 NtCurrentProcess 中
  27. status = ZwDuplicateObject(hSourceProcess, sysHandleTableEntryInfo.HandleValue,
  28. NtCurrentProcess(), &hDupObj, PROCESS_ALL_ACCESS, 0, DUPLICATE_SAME_ACCESS);
  29. if (!NT_SUCCESS(status))
  30. {
  31. ShowError("ZwDuplicateObject", status);
  32. break;
  33. }
  34. // 查询句柄的名称信息
  35. // 先获取缓冲区大小
  36. ZwQueryObject(hDupObj, 1, NULL, 0, &ulObjNameInfoSize);
  37. if (0 >= ulObjNameInfoSize)
  38. {
  39. ShowError("ZwQueryObject", 0);
  40. break;
  41. }
  42. // 申请缓冲区内存
  43. pObjNameInfo = ExAllocatePool(NonPagedPool, ulObjNameInfoSize);
  44. if (NULL == pObjNameInfo)
  45. {
  46. ShowError("ExAllocatePool", 0);
  47. break;
  48. }
  49. RtlZeroMemory(pObjNameInfo, ulObjNameInfoSize);
  50. // 获取句柄名称类型信息
  51. status = ZwQueryObject(hDupObj, 1, pObjNameInfo, ulObjNameInfoSize, &ulObjNameInfoSize);
  52. if (!NT_SUCCESS(status))
  53. {
  54. ShowError("ZwQueryObject", 0);
  55. break;
  56. }
  57. // 判断是否要解锁的文件
  58. DbgPrint("[File]%wZ\n", &pObjNameInfo->Name);
  59. RtlZeroMemory(wszSrcFile, FILE_PATH_MAX_NUM*sizeof(WCHAR));
  60. RtlZeroMemory(wszDestFile, FILE_PATH_MAX_NUM*sizeof(WCHAR));
  61. RtlCopyMemory(wszSrcFile, pObjNameInfo->Name.Buffer, pObjNameInfo->Name.Length);
  62. RtlCopyMemory(wszDestFile, ustrUnlockFileName.Buffer, ustrUnlockFileName.Length);
  63. if (NULL != wcsstr(wszSrcFile, wszDestFile))
  64. {
  65. bRet = TRUE;
  66. break;
  67. }
  68. } while (FALSE);
  69. // 释放
  70. if (NULL != pObjNameInfo)
  71. {
  72. ExFreePool(pObjNameInfo);
  73. }
  74. if (NULL != hDupObj)
  75. {
  76. ZwClose(hDupObj);
  77. }
  78. if (NULL != hSourceProcess)
  79. {
  80. ZwClose(hSourceProcess);
  81. }
  82. return bRet;
  83. }

关闭文件句柄

  1. // 关闭文件句柄, 解锁文件
  2. BOOLEAN CloseFileHandle(SYSTEM_HANDLE_TABLE_ENTRY_INFO sysHandleTableEntryInfo)
  3. {
  4. NTSTATUS status = STATUS_SUCCESS;
  5. PEPROCESS pEProcess = NULL;
  6. HANDLE hFileHandle = NULL;
  7. KAPC_STATE apcState = { 0 };
  8. OBJECT_HANDLE_FLAG_INFORMATION objectHandleFlagInfo = { 0 };
  9. // 获取文件句柄
  10. hFileHandle = sysHandleTableEntryInfo.HandleValue;
  11. // 获取文件句柄对应的进程结构对象EPROCESS
  12. status = PsLookupProcessByProcessId(sysHandleTableEntryInfo.UniqueProcessId, &pEProcess);
  13. if (!NT_SUCCESS(status))
  14. {
  15. ShowError("PsLookupProcessByProcessId", status);
  16. return FALSE;
  17. }
  18. // 附加到文件句柄对应的进程空间
  19. KeStackAttachProcess(pEProcess, &apcState);
  20. // 将文件句柄的属性设置为“可以关闭”
  21. objectHandleFlagInfo.Inherit = 0;
  22. objectHandleFlagInfo.ProtectFromClose = 0;
  23. ObSetHandleAttributes((HANDLE)hFileHandle, &objectHandleFlagInfo, KernelMode);
  24. // 关闭文件句柄
  25. ZwClose((HANDLE)hFileHandle);
  26. // 结束进程附加
  27. KeUnstackDetachProcess(&apcState);
  28. return TRUE;
  29. }

程序测试

在 Win7 32 位系统下,驱动程序正常执行:

在 Win8.1 32 位系统下,驱动程序正常执行:

在 Win10 32 位系统下,驱动程序正常执行:

在 Win7 64 位系统下,驱动程序正常执行:

在 Win8.1 64 位系统下,驱动程序正常执行:

在 Win10 64 位系统下,驱动程序正常执行:

总结

对于这个程序的实现,关键是要获取系统上所有的句柄,并对句柄进行判断,判断哪些句柄是指定文件的句柄。在获取文件句柄信息之后,我们就可以通过进程附加,结束文件句柄即可。

所以,大家要着重理解句柄遍历的实现过程。

参考

参考自《Windows黑客编程技术详解》一书

上传的附件 cloud_download UnlockFile_Test.7z ( 152.46kb, 4次下载 )

发送私信

这一切都不是我的,但总有一天,会是我的

73
文章数
67
评论数
最近文章
eject