references:
很多人熟悉ProcExp的关闭任意进程中句柄的能力,那么他是怎么完成这项任务的呢
标准的CloseHandle
函数只能关闭当前进程的句柄
有两条路线,第一个就是使用内核驱动,前提是你可以加载内核驱动的话,ProcExp使用的就是这种方法
另一种是在用户模式下实现,本文将着重介绍
第一个问题就是如何定位到目标句柄,必须要提供某些条件来唯一标识一个句柄,比如这个句柄是一个命名对象(named object)
使用Windows Media Player作为例子,WMP在同一台机器中只能有一个实例,那么WMP大概率使用了一个互斥量来实现这种效果
通过ProcExp可以看到,他确实是有这么一个互斥量
如果我们关掉这个句柄的话,就可以再打开一个WMP
如果我们想通过编程实现,那么就需要先定位到这个句柄,但是Windows文档中并未提供枚举句柄的函数,即使是在当前进程中
不过我们可以使用NativeAPI
- 使用NtQuerySystemInformation枚举系统中所有的句柄,然后在里面搜索属于WMP进程的句柄
- 只枚举WMP进程的句柄
- 注入代码到WMP进程,在里面遍历WMP进程的所有句柄
第二个选项是最合适的
首先定位WMP进程位置,使用TlHelp32来枚举进程
#include <windows.h>
#include <TlHelp32.h>
#include <stdio.h>
DWORD FindMediaPlayer() {
HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return 0;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
// skip the idle process
::Process32First(hSnapshot, &pe);
DWORD pid = 0;
while (::Process32Next(hSnapshot, &pe)) {
if (::_wcsicmp(pe.szExeFile, L"wmplayer.exe") == 0) {
// found it!
pid = pe.th32ProcessID;
break;
}
}
::CloseHandle(hSnapshot);
return pid;
}
int main() {
DWORD pid = FindMediaPlayer();
if (pid == 0) {
printf("Failed to locate media player\n");
return 1;
}
printf("Located media player: PID=%u\n", pid);
return 0;
}
现在我们已经定位到WMP了,可以枚举这个进程中的所有句柄了
HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE,
FALSE, pid);
if (!hProcess) {
printf("Failed to open WMP process handle (error=%u)\n",
::GetLastError());
return 1;
}
#include <memory>
#pragma comment(lib, "ntdll")
#define NT_SUCCESS(status) (status >= 0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
enum PROCESSINFOCLASS {
ProcessHandleInformation = 51
};
typedef struct _PROCESS_HANDLE_TABLE_ENTRY_INFO {
HANDLE HandleValue;
ULONG_PTR HandleCount;
ULONG_PTR PointerCount;
ULONG GrantedAccess;
ULONG ObjectTypeIndex;
ULONG HandleAttributes;
ULONG Reserved;
} PROCESS_HANDLE_TABLE_ENTRY_INFO, * PPROCESS_HANDLE_TABLE_ENTRY_INFO;
// private
typedef struct _PROCESS_HANDLE_SNAPSHOT_INFORMATION {
ULONG_PTR NumberOfHandles;
ULONG_PTR Reserved;
PROCESS_HANDLE_TABLE_ENTRY_INFO Handles[1];
} PROCESS_HANDLE_SNAPSHOT_INFORMATION, * PPROCESS_HANDLE_SNAPSHOT_INFORMATION;
extern "C" NTSTATUS NTAPI NtQueryInformationProcess(
_In_ HANDLE ProcessHandle,
_In_ PROCESSINFOCLASS ProcessInformationClass,
_Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength);
使用ProcessHandleInformation
获取所有的句柄信息
ULONG size = 1 << 10;
std::unique_ptr<BYTE[]> buffer;
for (;;) {
buffer = std::make_unique<BYTE[]>(size);
auto status = ::NtQueryInformationProcess(hProcess, ProcessHandleInformation,
buffer.get(), size, &size);
if (NT_SUCCESS(status))
break;
if (status == STATUS_INFO_LENGTH_MISMATCH) {
size += 1 << 10;
continue;
}
printf("Error enumerating handles\n");
return 1;
}
上面使用了智能指针在内存不够的情况下自动增大
我们还需要调用另一个NativeAPI——NtQueryObject来查询指定句柄的信息
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectNameInformation = 1
} OBJECT_INFORMATION_CLASS;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef struct _OBJECT_NAME_INFORMATION {
UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;
extern "C" NTSTATUS NTAPI NtQueryObject(
_In_opt_ HANDLE Handle,
_In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,
_Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation,
_In_ ULONG ObjectInformationLength,
_Out_opt_ PULONG ReturnLength);
我们现在是外部进程,我们不能直接往NtQueryObject传递从WMP进程中获取的句柄,而是要先将句柄复制出来再传进去,这也是为什么一开始打开WMP进程的时候需要加入PROCESS_DUP_HANDLE
权限
auto info = reinterpret_cast<PROCESS_HANDLE_SNAPSHOT_INFORMATION*>(buffer.get());
for (ULONG i = 0; i < info->NumberOfHandles; i++) {
HANDLE h = info->Handles[i].HandleValue;
HANDLE hTarget;
if (!::DuplicateHandle(hProcess, h, ::GetCurrentProcess(), &hTarget,
0, FALSE, DUPLICATE_SAME_ACCESS))
continue; // move to next handle
}
BYTE nameBuffer[1 << 10];
auto status = ::NtQueryObject(hTarget, ObjectNameInformation,
nameBuffer, sizeof(nameBuffer), nullptr);
::CloseHandle(hTarget);
if (!NT_SUCCESS(status))
continue;
调用完NtQueryObject之后,就可以把复制出来的句柄关掉了,我们需要将获取到的字符串和WMP互斥量的名称进行对比以得到正确的句柄
WCHAR targetName[256];
DWORD sessionId;
::ProcessIdToSessionId(pid, &sessionId);
::swprintf_s(targetName,
L"\\Sessions\\%u\\BaseNamedObjects\\Microsoft_WMP_70_CheckForOtherInstanceMutex",
sessionId);
auto len = ::wcslen(targetName);
auto name = reinterpret_cast<UNICODE_STRING*>(nameBuffer);
if (name->Buffer &&
::_wcsnicmp(name->Buffer, targetName, len) == 0) {
// found it!
}
假设我们现在找到了目标句柄,那么下一步要怎么做呢?
我们可以再次调用DuplicateHandle
,但是传入DUPLICATE_CLOSE_SOURCE
标志变相达到关闭源句柄的目的
// found it!
::DuplicateHandle(hProcess, h, ::GetCurrentProcess(), &hTarget,
0, FALSE, DUPLICATE_CLOSE_SOURCE);
::CloseHandle(hTarget);
printf("Found it! and closed it!\n");
return 0;
完整代码:
#include <windows.h>
#include <TlHelp32.h>
#include <stdio.h>
#include <memory>
#pragma comment(lib, "ntdll")
#define NT_SUCCESS(status) (status >= 0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
enum PROCESSINFOCLASS {
ProcessHandleInformation = 51
};
typedef struct _PROCESS_HANDLE_TABLE_ENTRY_INFO {
HANDLE HandleValue;
ULONG_PTR HandleCount;
ULONG_PTR PointerCount;
ULONG GrantedAccess;
ULONG ObjectTypeIndex;
ULONG HandleAttributes;
ULONG Reserved;
} PROCESS_HANDLE_TABLE_ENTRY_INFO, * PPROCESS_HANDLE_TABLE_ENTRY_INFO;
// private
typedef struct _PROCESS_HANDLE_SNAPSHOT_INFORMATION {
ULONG_PTR NumberOfHandles;
ULONG_PTR Reserved;
PROCESS_HANDLE_TABLE_ENTRY_INFO Handles[1];
} PROCESS_HANDLE_SNAPSHOT_INFORMATION, * PPROCESS_HANDLE_SNAPSHOT_INFORMATION;
extern "C" NTSTATUS NTAPI NtQueryInformationProcess(
_In_ HANDLE ProcessHandle,
_In_ PROCESSINFOCLASS ProcessInformationClass,
_Out_writes_bytes_(ProcessInformationLength) PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength);
DWORD FindMediaPlayer() {
HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return 0;
PROCESSENTRY32 pe;
pe.dwSize = sizeof(pe);
// skip the idle process
::Process32First(hSnapshot, &pe);
DWORD pid = 0;
while (::Process32Next(hSnapshot, &pe)) {
if (::_wcsicmp(pe.szExeFile, L"wmplayer.exe") == 0) {
// found it!
pid = pe.th32ProcessID;
break;
}
}
::CloseHandle(hSnapshot);
return pid;
}
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectNameInformation = 1
} OBJECT_INFORMATION_CLASS;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef struct _OBJECT_NAME_INFORMATION {
UNICODE_STRING Name;
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;
extern "C" NTSTATUS NTAPI NtQueryObject(
_In_opt_ HANDLE Handle,
_In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,
_Out_writes_bytes_opt_(ObjectInformationLength) PVOID ObjectInformation,
_In_ ULONG ObjectInformationLength,
_Out_opt_ PULONG ReturnLength);
int main() {
DWORD pid = FindMediaPlayer();
if (pid == 0) {
printf("Failed to locate media player\n");
return 1;
}
printf("Located media player: PID=%u\n", pid);
HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_DUP_HANDLE,
FALSE, pid);
if (!hProcess) {
printf("Failed to open WMP process handle (error=%u)\n",
::GetLastError());
return 1;
}
ULONG size = 1 << 10;
std::unique_ptr<BYTE[]> buffer;
for (;;) {
buffer = std::make_unique<BYTE[]>(size);
auto status = ::NtQueryInformationProcess(hProcess, ProcessHandleInformation,
buffer.get(), size, &size);
if (NT_SUCCESS(status))
break;
if (status == STATUS_INFO_LENGTH_MISMATCH) {
size += 1 << 10;
continue;
}
printf("Error enumerating handles\n");
return 1;
}
WCHAR targetName[256];
DWORD sessionId;
::ProcessIdToSessionId(pid, &sessionId);
::swprintf_s(targetName,
L"\\Sessions\\%u\\BaseNamedObjects\\Microsoft_WMP_70_CheckForOtherInstanceMutex",
sessionId);
auto len = ::wcslen(targetName);
auto info = reinterpret_cast<PROCESS_HANDLE_SNAPSHOT_INFORMATION*>(buffer.get());
for (ULONG i = 0; i < info->NumberOfHandles; i++) {
HANDLE h = info->Handles[i].HandleValue;
HANDLE hTarget;
if (!::DuplicateHandle(hProcess, h, ::GetCurrentProcess(), &hTarget,
0, FALSE, DUPLICATE_SAME_ACCESS))
continue; // move to next handle
BYTE nameBuffer[1 << 10];
auto status = ::NtQueryObject(hTarget, ObjectNameInformation,
nameBuffer, sizeof(nameBuffer), nullptr);
::CloseHandle(hTarget);
if (!NT_SUCCESS(status))
continue;
auto name = reinterpret_cast<UNICODE_STRING*>(nameBuffer);
if (name->Buffer &&
::_wcsnicmp(name->Buffer, targetName, len) == 0) {
// found it!
::DuplicateHandle(hProcess, h, ::GetCurrentProcess(), &hTarget,
0, FALSE, DUPLICATE_CLOSE_SOURCE);
::CloseHandle(hTarget);
printf("Found it! and closed it!\n");
return 0;
}
}
return 0;
}