返回
顶部

references:

很多人熟悉ProcExp的关闭任意进程中句柄的能力,那么他是怎么完成这项任务的呢

标准的CloseHandle函数只能关闭当前进程的句柄

有两条路线,第一个就是使用内核驱动,前提是你可以加载内核驱动的话,ProcExp使用的就是这种方法

另一种是在用户模式下实现,本文将着重介绍

第一个问题就是如何定位到目标句柄,必须要提供某些条件来唯一标识一个句柄,比如这个句柄是一个命名对象(named object)

使用Windows Media Player作为例子,WMP在同一台机器中只能有一个实例,那么WMP大概率使用了一个互斥量来实现这种效果

4y5u6kuytreiuytrew

通过ProcExp可以看到,他确实是有这么一个互斥量

image-20240321142435037

如果我们关掉这个句柄的话,就可以再打开一个WMP

image-20240321142659547

如果我们想通过编程实现,那么就需要先定位到这个句柄,但是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;

4y5u6kuytreiuytrew45t6yu

完整代码:

#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;
}