diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..34c8dee
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,388 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+##
+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
+
+# User-specific files
+*.rsuser
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Mono auto generated files
+mono_crash.*
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+[Ww][Ii][Nn]32/
+[Aa][Rr][Mm]/
+[Aa][Rr][Mm]64/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+[Ll]ogs/
+
+# Visual Studio 2015/2017 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# Visual Studio 2017 auto generated files
+Generated\ Files/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUnit
+*.VisualState.xml
+TestResult.xml
+nunit-*.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# Benchmark Results
+BenchmarkDotNet.Artifacts/
+
+# .NET Core
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+# ASP.NET Scaffolding
+ScaffoldingReadMe.txt
+
+# StyleCop
+StyleCopReport.xml
+
+# Files built by Visual Studio
+*_i.c
+*_p.c
+*_h.h
+*.ilk
+*.meta
+*.obj
+*.iobj
+*.pch
+*.pdb
+*.ipdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*_wpftmp.csproj
+*.log
+*.tlog
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# Visual Studio Trace Files
+*.e2e
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# AxoCover is a Code Coverage Tool
+.axoCover/*
+!.axoCover/settings.json
+
+# Coverlet is a free, cross platform Code Coverage Tool
+coverage*.json
+coverage*.xml
+coverage*.info
+
+# Visual Studio code coverage results
+*.coverage
+*.coveragexml
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# Note: Comment the next line if you want to checkin your web deploy settings,
+# but database connection strings (with potential passwords) will be unencrypted
+*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# NuGet Symbol Packages
+*.snupkg
+# The packages folder can be ignored because of Package Restore
+**/[Pp]ackages/*
+# except build/, which is used as an MSBuild target.
+!**/[Pp]ackages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/[Pp]ackages/repositories.config
+# NuGet v3's project.json files produces more ignorable files
+*.nuget.props
+*.nuget.targets
+
+# Nuget personal access tokens and Credentials
+nuget.config
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+*.appx
+*.appxbundle
+*.appxupload
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!?*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+orleans.codegen.cs
+
+# Including strong name files can present a security risk
+# (https://github.com/github/gitignore/pull/2483#issue-259490424)
+#*.snk
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+ServiceFabricBackup/
+*.rptproj.bak
+
+# SQL Server files
+*.mdf
+*.ldf
+*.ndf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+*.rptproj.rsuser
+*- [Bb]ackup.rdl
+*- [Bb]ackup ([0-9]).rdl
+*- [Bb]ackup ([0-9][0-9]).rdl
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+node_modules/
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
+*.vbw
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# CodeRush personal settings
+.cr/personal
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc
+
+# Cake - Uncomment if you are using it
+# tools/**
+# !tools/packages.config
+
+# Tabs Studio
+*.tss
+
+# Telerik's JustMock configuration file
+*.jmconfig
+
+# BizTalk build output
+*.btp.cs
+*.btm.cs
+*.odx.cs
+*.xsd.cs
+
+# OpenCover UI analysis results
+OpenCover/
+
+# Azure Stream Analytics local run output
+ASALocalRun/
+
+# MSBuild Binary and Structured Log
+*.binlog
+
+# NVidia Nsight GPU debugger configuration file
+*.nvuser
+
+# MFractors (Xamarin productivity tool) working folder
+.mfractor/
+
+# Local History for Visual Studio
+.localhistory/
+
+# BeatPulse healthcheck temp database
+healthchecksdb
+
+# Backup folder for Package Reference Convert tool in Visual Studio 2017
+MigrationBackup/
+
+# Ionide (cross platform F# VS Code tools) working folder
+.ionide/
+
+# Fody - auto-generated XML schema
+FodyWeavers.xsd
+
+# VS Code files for those working on multiple tools
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+# Local History for Visual Studio Code
+.history/
+
+# Windows Installer files from build outputs
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# JetBrains Rider
+.idea/
+*.sln.iml
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..0ddd3e2
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "Dependencies/phnt"]
+ path = Dependencies/phnt
+ url = https://github.com/processhacker/phnt
diff --git a/1.NtCreateUserProcess/NtCreateUserProcess.sln b/1.NtCreateUserProcess/NtCreateUserProcess.sln
new file mode 100644
index 0000000..ffc97b1
--- /dev/null
+++ b/1.NtCreateUserProcess/NtCreateUserProcess.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32804.467
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NtCreateUserProcess", "NtCreateUserProcess.vcxproj", "{23615833-5F20-4239-BF9F-94DCF3A56969}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {23615833-5F20-4239-BF9F-94DCF3A56969}.Debug|x64.ActiveCfg = Debug|x64
+ {23615833-5F20-4239-BF9F-94DCF3A56969}.Debug|x64.Build.0 = Debug|x64
+ {23615833-5F20-4239-BF9F-94DCF3A56969}.Debug|x86.ActiveCfg = Debug|Win32
+ {23615833-5F20-4239-BF9F-94DCF3A56969}.Debug|x86.Build.0 = Debug|Win32
+ {23615833-5F20-4239-BF9F-94DCF3A56969}.Release|x64.ActiveCfg = Release|x64
+ {23615833-5F20-4239-BF9F-94DCF3A56969}.Release|x64.Build.0 = Release|x64
+ {23615833-5F20-4239-BF9F-94DCF3A56969}.Release|x86.ActiveCfg = Release|Win32
+ {23615833-5F20-4239-BF9F-94DCF3A56969}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {569ED2B7-5C95-4C87-9257-210D336AB9C4}
+ EndGlobalSection
+EndGlobal
diff --git a/1.NtCreateUserProcess/NtCreateUserProcess.vcxproj b/1.NtCreateUserProcess/NtCreateUserProcess.vcxproj
new file mode 100644
index 0000000..b233354
--- /dev/null
+++ b/1.NtCreateUserProcess/NtCreateUserProcess.vcxproj
@@ -0,0 +1,177 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {23615833-5f20-4239-bf9f-94dcf3a56969}
+ NtCreateUserProcess
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/1.NtCreateUserProcess/NtCreateUserProcess.vcxproj.filters b/1.NtCreateUserProcess/NtCreateUserProcess.vcxproj.filters
new file mode 100644
index 0000000..decff76
--- /dev/null
+++ b/1.NtCreateUserProcess/NtCreateUserProcess.vcxproj.filters
@@ -0,0 +1,22 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+
\ No newline at end of file
diff --git a/1.NtCreateUserProcess/main.c b/1.NtCreateUserProcess/main.c
new file mode 100644
index 0000000..9e49ddd
--- /dev/null
+++ b/1.NtCreateUserProcess/main.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (c) 2023 Hunt & Hackett.
+ *
+ * This demo project is licensed under the MIT license.
+ *
+ * Authors:
+ * diversenok
+ *
+ */
+
+#include
+#include
+#include
+
+int wmain(int argc, wchar_t* argv[])
+{
+ NTSTATUS status;
+ HANDLE hProcess;
+ HANDLE hThread;
+
+ wprintf_s(L"Demo for Process Cloning via NtCreateUserProcess by Hunt & Hackett.\r\n\r\n");
+ wprintf_s(L"Hello from the parent! My PID is %zd, TID is %zd\r\n",
+ (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueProcess,
+ (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueThread
+ );
+
+ PS_CREATE_INFO createInfo = { 0 };
+ createInfo.Size = sizeof(createInfo);
+
+ status = NtCreateUserProcess(
+ &hProcess,
+ &hThread,
+ PROCESS_ALL_ACCESS,
+ THREAD_ALL_ACCESS,
+ NULL,
+ NULL,
+ PROCESS_CREATE_FLAGS_INHERIT_HANDLES,
+ 0,
+ NULL,
+ &createInfo,
+ NULL
+ );
+
+ if (status == STATUS_PROCESS_CLONED)
+ {
+ // Executing inside the clone...
+
+ // Re-attach to the parent's console to be able to write to it
+ FreeConsole();
+ AttachConsole(ATTACH_PARENT_PROCESS);
+
+ wprintf_s(L"Hello from the clone! My PID is %zd, TID is %zd\r\n",
+ (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueProcess,
+ (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueThread
+ );
+
+ // Terminate without clean-up
+ NtTerminateProcess(NtCurrentProcess(), STATUS_PROCESS_CLONED);
+ }
+ else
+ {
+ // Executing inside the original (parent) process...
+
+ if (!NT_SUCCESS(status))
+ {
+ wprintf_s(L"Failed to clone the current process: 0x%x\r\n", status);
+ return status;
+ }
+
+ status = NtWaitForSingleObject(hProcess, FALSE, NULL);
+
+ NtClose(hProcess);
+ NtClose(hThread);
+
+ if (!NT_SUCCESS(status))
+ {
+ wprintf_s(L"Failed to wait for the clone: 0x%x\r\n", status);
+ return status;
+ }
+
+ wprintf_s(L"The clone exited.\r\n");
+ }
+
+ return STATUS_SUCCESS;
+}
diff --git a/2.RtlCloneUserProcess/RtlCloneUserProcess.sln b/2.RtlCloneUserProcess/RtlCloneUserProcess.sln
new file mode 100644
index 0000000..49fec19
--- /dev/null
+++ b/2.RtlCloneUserProcess/RtlCloneUserProcess.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32804.467
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "RtlCloneUserProcess", "RtlCloneUserProcess.vcxproj", "{4EDE077B-9200-4ED5-B471-F99177E90EC3}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {4EDE077B-9200-4ED5-B471-F99177E90EC3}.Debug|x64.ActiveCfg = Debug|x64
+ {4EDE077B-9200-4ED5-B471-F99177E90EC3}.Debug|x64.Build.0 = Debug|x64
+ {4EDE077B-9200-4ED5-B471-F99177E90EC3}.Debug|x86.ActiveCfg = Debug|Win32
+ {4EDE077B-9200-4ED5-B471-F99177E90EC3}.Debug|x86.Build.0 = Debug|Win32
+ {4EDE077B-9200-4ED5-B471-F99177E90EC3}.Release|x64.ActiveCfg = Release|x64
+ {4EDE077B-9200-4ED5-B471-F99177E90EC3}.Release|x64.Build.0 = Release|x64
+ {4EDE077B-9200-4ED5-B471-F99177E90EC3}.Release|x86.ActiveCfg = Release|Win32
+ {4EDE077B-9200-4ED5-B471-F99177E90EC3}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {7E6977D7-1824-433F-8DAA-E96BB88A146A}
+ EndGlobalSection
+EndGlobal
diff --git a/2.RtlCloneUserProcess/RtlCloneUserProcess.vcxproj b/2.RtlCloneUserProcess/RtlCloneUserProcess.vcxproj
new file mode 100644
index 0000000..2696a26
--- /dev/null
+++ b/2.RtlCloneUserProcess/RtlCloneUserProcess.vcxproj
@@ -0,0 +1,177 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {4ede077b-9200-4ed5-b471-f99177e90ec3}
+ RtlCloneUserProcess
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/2.RtlCloneUserProcess/RtlCloneUserProcess.vcxproj.filters b/2.RtlCloneUserProcess/RtlCloneUserProcess.vcxproj.filters
new file mode 100644
index 0000000..a8a6563
--- /dev/null
+++ b/2.RtlCloneUserProcess/RtlCloneUserProcess.vcxproj.filters
@@ -0,0 +1,17 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
\ No newline at end of file
diff --git a/2.RtlCloneUserProcess/main.c b/2.RtlCloneUserProcess/main.c
new file mode 100644
index 0000000..84cbdd3
--- /dev/null
+++ b/2.RtlCloneUserProcess/main.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2023 Hunt & Hackett.
+ *
+ * This demo project is licensed under the MIT license.
+ *
+ * Authors:
+ * diversenok
+ *
+ */
+
+#include
+#include
+#include
+
+int wmain(int argc, wchar_t* argv[])
+{
+ NTSTATUS status;
+ RTL_USER_PROCESS_INFORMATION processInfo;
+
+ wprintf_s(L"Demo for Process Cloning via RtlCloneUserProcess by Hunt & Hackett.\r\n\r\n");
+ wprintf_s(L"Hello from the parent! My PID is %zd, TID is %zd\r\n",
+ (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueProcess,
+ (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueThread
+ );
+
+ status = RtlCloneUserProcess(
+ RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES,
+ NULL,
+ NULL,
+ NULL,
+ &processInfo
+ );
+
+ if (status == STATUS_PROCESS_CLONED)
+ {
+ // Executing inside the clone...
+
+ // Re-attach to the parent's console to be able to write to it
+ FreeConsole();
+ AttachConsole(ATTACH_PARENT_PROCESS);
+
+ wprintf_s(L"Hello from the clone! My PID is %zd, TID is %zd\r\n",
+ (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueProcess,
+ (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueThread
+ );
+
+ // Terminate without clean-up
+ NtTerminateProcess(NtCurrentProcess(), STATUS_PROCESS_CLONED);
+ }
+ else
+ {
+ // Executing inside the original (parent) process...
+
+ if (!NT_SUCCESS(status))
+ {
+ wprintf_s(L"Failed to clone the current process: 0x%x\r\n", status);
+ return status;
+ }
+
+ status = NtWaitForSingleObject(processInfo.ProcessHandle, FALSE, NULL);
+
+ NtClose(processInfo.ProcessHandle);
+ NtClose(processInfo.ThreadHandle);
+
+ if (!NT_SUCCESS(status))
+ {
+ wprintf_s(L"Failed to wait for the clone: 0x%x\r\n", status);
+ return status;
+ }
+
+ wprintf_s(L"The clone exited.\r\n");
+ }
+
+ return STATUS_SUCCESS;
+}
diff --git a/3.CloneAndMinidump/CloneAndMinidump.rc b/3.CloneAndMinidump/CloneAndMinidump.rc
new file mode 100644
index 0000000..e0c1b21
--- /dev/null
+++ b/3.CloneAndMinidump/CloneAndMinidump.rc
@@ -0,0 +1,111 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (UK) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (UK) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,0,0,0
+ PRODUCTVERSION 1,0,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "080904b0"
+ BEGIN
+ VALUE "CompanyName", "Hunt & Hackett"
+ VALUE "FileDescription", "Clone a process and save its minidump"
+ VALUE "FileVersion", "1.0.0.0"
+ VALUE "InternalName", "CloneAndMinidump"
+ VALUE "LegalCopyright", "Copyright (C) 2023 Hunt & Hackett"
+ VALUE "OriginalFilename", "CloneAndMinidump.exe"
+ VALUE "ProductName", "CloneAndMinidump"
+ VALUE "ProductVersion", "1.0.0.0"
+ VALUE "Comments", "https://github.com/huntandhackett/process-cloning"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x809, 1200
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/3.CloneAndMinidump/CloneAndMinidump.sln b/3.CloneAndMinidump/CloneAndMinidump.sln
new file mode 100644
index 0000000..69ecfdf
--- /dev/null
+++ b/3.CloneAndMinidump/CloneAndMinidump.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32804.467
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CloneAndMinidump", "CloneAndMinidump.vcxproj", "{3A057020-20EA-47BF-B8E1-59A479857D5C}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3A057020-20EA-47BF-B8E1-59A479857D5C}.Debug|x64.ActiveCfg = Debug|x64
+ {3A057020-20EA-47BF-B8E1-59A479857D5C}.Debug|x64.Build.0 = Debug|x64
+ {3A057020-20EA-47BF-B8E1-59A479857D5C}.Debug|x86.ActiveCfg = Debug|Win32
+ {3A057020-20EA-47BF-B8E1-59A479857D5C}.Debug|x86.Build.0 = Debug|Win32
+ {3A057020-20EA-47BF-B8E1-59A479857D5C}.Release|x64.ActiveCfg = Release|x64
+ {3A057020-20EA-47BF-B8E1-59A479857D5C}.Release|x64.Build.0 = Release|x64
+ {3A057020-20EA-47BF-B8E1-59A479857D5C}.Release|x86.ActiveCfg = Release|Win32
+ {3A057020-20EA-47BF-B8E1-59A479857D5C}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {AFB05A9B-06D8-411B-AB84-4C1B1452AF60}
+ EndGlobalSection
+EndGlobal
diff --git a/3.CloneAndMinidump/CloneAndMinidump.vcxproj b/3.CloneAndMinidump/CloneAndMinidump.vcxproj
new file mode 100644
index 0000000..8f12933
--- /dev/null
+++ b/3.CloneAndMinidump/CloneAndMinidump.vcxproj
@@ -0,0 +1,183 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {3a057020-20ea-47bf-b8e1-59a479857d5c}
+ CloneAndMinidump
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;Dbghelp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;Dbghelp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;Dbghelp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;Dbghelp.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/3.CloneAndMinidump/CloneAndMinidump.vcxproj.filters b/3.CloneAndMinidump/CloneAndMinidump.vcxproj.filters
new file mode 100644
index 0000000..a8a6563
--- /dev/null
+++ b/3.CloneAndMinidump/CloneAndMinidump.vcxproj.filters
@@ -0,0 +1,17 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
\ No newline at end of file
diff --git a/3.CloneAndMinidump/main.c b/3.CloneAndMinidump/main.c
new file mode 100644
index 0000000..43bd671
--- /dev/null
+++ b/3.CloneAndMinidump/main.c
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2023 Hunt & Hackett.
+ *
+ * This demo project is licensed under the MIT license.
+ *
+ * Authors:
+ * diversenok
+ *
+ */
+
+#include
+#include
+#include
+#include
+#include
+
+typedef enum _H2_CLONING_MODE {
+ H2CloneViaReflection = 1,
+ H2CloneViaNativeApi
+} H2_CLONING_MODE;
+
+#define H2_ARGV_MODE_INDEX 1
+#define H2_ARGV_PID_INDEX 2
+#define H2_ARGV_FILENAME_INDEX 3
+
+// Report progress on a specific operation to the console
+BOOLEAN H2ReportStatus(
+ _In_ PCWSTR Location,
+ _In_ PCWSTR LastCall,
+ _In_ NTSTATUS Status
+)
+{
+ if (NT_SUCCESS(Status))
+ wprintf_s(L"%s: Success\r\n", Location);
+ else
+ {
+ PVOID dllBase;
+ UNICODE_STRING dllName;
+ UNICODE_STRING description;
+
+ RtlInitUnicodeString(&description, L"Unknown error");
+
+ // Choose the DLL to look up the error description
+ RtlInitUnicodeString(&dllName, NT_NTWIN32(Status) ? L"kernel32.dll" : L"ntdll.dll");
+ NTSTATUS status = LdrGetDllHandle(NULL, NULL, &dllName, &dllBase);
+
+ if (NT_SUCCESS(status))
+ {
+ PMESSAGE_RESOURCE_ENTRY messageEntry;
+
+ // Lookup the error description
+ status = RtlFindMessage(
+ dllBase,
+ (ULONG)(ULONG_PTR)RT_MESSAGETABLE,
+ 0,
+ NT_NTWIN32(Status) ? WIN32_FROM_NTSTATUS(Status) : (ULONG)Status,
+ &messageEntry
+ );
+
+ if (NT_SUCCESS(status) && messageEntry->Flags & MESSAGE_RESOURCE_UNICODE)
+ RtlInitUnicodeString(&description, (PCWSTR)messageEntry->Text);
+ }
+
+ // Trim the trailing new line
+ if (description.Length > 2 * sizeof(WCHAR) &&
+ description.Buffer[description.Length / sizeof(WCHAR) - 1] == L'\n' &&
+ description.Buffer[description.Length / sizeof(WCHAR) - 2] == L'\r')
+ description.Length -= 2 * sizeof(WCHAR);
+
+ wprintf_s(L"%s: 0x%X at %s - %wZ\r\n", Location, Status, LastCall, &description);
+ }
+
+ return NT_SUCCESS(Status);
+}
+
+int wmain(int argc, wchar_t* argv[])
+{
+ NTSTATUS status;
+ H2_CLONING_MODE mode;
+ UNICODE_STRING fileName = { 0 };
+ HANDLE hParentProcess = NULL;
+ HANDLE hCloneProcess = NULL;
+ HANDLE hFile = NULL;
+
+ wprintf_s(L"Demo for dumping process memory via cloning by Hunt & Hackett.\r\n\r\n");
+
+ if (argc < 4)
+ {
+ wprintf_s(L"Usage: CloneAndMinidump.exe [Mode] [PID] [Filename]\r\n\r\n");
+ wprintf_s(L"Supported modes:\r\n");
+ wprintf_s(L" -r - clone via RtlCreateProcessReflection\r\n");
+ wprintf_s(L" -p - clone via NtCreateProcessEx\r\n");
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (wcscmp(argv[H2_ARGV_MODE_INDEX], L"-r") == 0)
+ mode = H2CloneViaReflection;
+ else if (wcscmp(argv[H2_ARGV_MODE_INDEX], L"-p") == 0)
+ mode = H2CloneViaNativeApi;
+ else
+ {
+ wprintf(L"Error: urecognized mode specified.\r\n");
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ // Enable the Debug privilege when possible
+ BOOLEAN wasDebugEnabled;
+ status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &wasDebugEnabled);
+
+ H2ReportStatus(L"1. Enabling the debug privilege", L"RtlAdjustPrivilege", status);
+
+ // Open the target process for cloning
+ OBJECT_ATTRIBUTES objAttr;
+ CLIENT_ID clientId;
+
+ clientId.UniqueProcess = (HANDLE)(ULONG_PTR)wcstoul(argv[H2_ARGV_PID_INDEX], NULL, 0);
+ clientId.UniqueThread = NULL;
+
+ InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
+
+ status = NtOpenProcess(
+ &hParentProcess,
+ mode == H2CloneViaReflection ?
+ PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE :
+ PROCESS_CREATE_PROCESS,
+ &objAttr,
+ &clientId
+ );
+
+ if (!H2ReportStatus(L"2. Opening the target process", L"NtOpenProcess", status))
+ goto CLEANUP;
+
+ // Clone the target process
+ switch (mode)
+ {
+ case H2CloneViaReflection:
+ {
+ RTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION reflectionInfo;
+
+ status = RtlCreateProcessReflection(
+ hParentProcess,
+ RTL_PROCESS_REFLECTION_FLAGS_NO_SYNCHRONIZE,
+ NULL,
+ NULL,
+ NULL,
+ &reflectionInfo
+ );
+
+ if (!H2ReportStatus(L"3. Cloning the target process", L"RtlCreateProcessReflection", status))
+ goto CLEANUP;
+
+ NtClose(reflectionInfo.ReflectionThreadHandle);
+ hCloneProcess = reflectionInfo.ReflectionProcessHandle;
+ }
+ break;
+
+ case H2CloneViaNativeApi:
+ {
+ InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
+
+ status = NtCreateProcessEx(
+ &hCloneProcess,
+ PROCESS_TERMINATE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ,
+ &objAttr,
+ hParentProcess,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ 0
+ );
+
+ if (!H2ReportStatus(L"3. Cloning the target process", L"NtCreateProcessEx", status))
+ goto CLEANUP;
+ }
+ break;
+ }
+
+ NtClose(hParentProcess);
+ hParentProcess = NULL;
+
+ // Convert the filename to NT format
+ status = RtlDosPathNameToNtPathName_U_WithStatus(
+ argv[H2_ARGV_FILENAME_INDEX],
+ &fileName,
+ NULL,
+ NULL
+ );
+
+ if (!H2ReportStatus(L"4. Preparing the filename", L"RtlDosPathNameToNtPathName_U_WithStatus", status))
+ goto CLEANUP;
+
+ // Create the target file
+ OBJECT_ATTRIBUTES objAttrib;
+ IO_STATUS_BLOCK isb;
+
+ InitializeObjectAttributes(&objAttrib, &fileName, OBJ_CASE_INSENSITIVE, NULL, NULL);
+
+ status = NtCreateFile(
+ &hFile,
+ FILE_WRITE_DATA | DELETE | SYNCHRONIZE,
+ &objAttrib,
+ &isb,
+ NULL,
+ FILE_ATTRIBUTE_NORMAL,
+ 0,
+ FILE_CREATE,
+ FILE_SYNCHRONOUS_IO_NONALERT,
+ NULL,
+ 0
+ );
+
+ RtlFreeUnicodeString(&fileName);
+ memset(&fileName, 0, sizeof(fileName));
+
+ if (!H2ReportStatus(L"5. Creating the file for minidump", L"NtCreateFile", status))
+ goto CLEANUP;
+
+ // Start dumping
+ BOOL result = MiniDumpWriteDump(
+ hCloneProcess,
+ 0,
+ hFile,
+ MiniDumpWithFullMemory,
+ NULL,
+ NULL,
+ NULL
+ );
+
+ if (!H2ReportStatus(L"6. Writing the minidump", L"MiniDumpWriteDump", result ? STATUS_SUCCESS : NTSTATUS_FROM_WIN32(RtlGetLastWin32Error())))
+ goto CLEANUP;
+
+ NtClose(hFile);
+ hFile = NULL;
+
+ status = STATUS_SUCCESS;
+
+CLEANUP:
+ if (hParentProcess)
+ NtClose(hParentProcess);
+
+ if (hCloneProcess)
+ {
+ NtTerminateProcess(hCloneProcess, STATUS_PROCESS_CLONED);
+ NtClose(hCloneProcess);
+ }
+
+ if (fileName.Buffer)
+ RtlFreeUnicodeString(&fileName);
+
+ if (hFile)
+ {
+ // Undo file creation on failure
+ FILE_DISPOSITION_INFORMATION fileInfo;
+ fileInfo.DeleteFile = TRUE;
+
+ NtSetInformationFile(
+ hFile,
+ &isb,
+ &fileInfo,
+ sizeof(fileInfo),
+ FileDispositionInformation
+ );
+
+ NtClose(hFile);
+ }
+
+ return status;
+}
diff --git a/3.CloneAndMinidump/resource.h b/3.CloneAndMinidump/resource.h
new file mode 100644
index 0000000..9bc19bf
--- /dev/null
+++ b/3.CloneAndMinidump/resource.h
@@ -0,0 +1,14 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by CloneAndMinidump.rc
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/4.InspectClonedMemory/InspectClonedMemory.rc b/4.InspectClonedMemory/InspectClonedMemory.rc
new file mode 100644
index 0000000..27126f1
--- /dev/null
+++ b/4.InspectClonedMemory/InspectClonedMemory.rc
@@ -0,0 +1,111 @@
+// Microsoft Visual C++ generated resource script.
+//
+#include "resource.h"
+
+#define APSTUDIO_READONLY_SYMBOLS
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 2 resource.
+//
+#include "winres.h"
+
+/////////////////////////////////////////////////////////////////////////////
+#undef APSTUDIO_READONLY_SYMBOLS
+
+/////////////////////////////////////////////////////////////////////////////
+// English (UK) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK
+#pragma code_page(1252)
+
+#ifdef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// TEXTINCLUDE
+//
+
+1 TEXTINCLUDE
+BEGIN
+ "resource.h\0"
+END
+
+2 TEXTINCLUDE
+BEGIN
+ "#include ""winres.h""\r\n"
+ "\0"
+END
+
+3 TEXTINCLUDE
+BEGIN
+ "\r\n"
+ "\0"
+END
+
+#endif // APSTUDIO_INVOKED
+
+#endif // English (UK) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+/////////////////////////////////////////////////////////////////////////////
+// English (United States) resources
+
+#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU)
+LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
+#pragma code_page(1252)
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,0,0,0
+ PRODUCTVERSION 1,0,0,0
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS 0x40004L
+ FILETYPE 0x1L
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "080904b0"
+ BEGIN
+ VALUE "CompanyName", "Hunt & Hackett"
+ VALUE "FileDescription", "Inspect address space layout changes during process cloning"
+ VALUE "FileVersion", "1.0.0.0"
+ VALUE "InternalName", "InspectClonedMemory"
+ VALUE "LegalCopyright", "Copyright (C) 2023 Hunt & Hackett"
+ VALUE "OriginalFilename", "InspectClonedMemory.exe"
+ VALUE "ProductName", "InspectClonedMemory"
+ VALUE "ProductVersion", "1.0.0.0"
+ VALUE "Comments", "https://github.com/huntandhackett/process-cloning"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x809, 1200
+ END
+END
+
+#endif // English (United States) resources
+/////////////////////////////////////////////////////////////////////////////
+
+
+
+#ifndef APSTUDIO_INVOKED
+/////////////////////////////////////////////////////////////////////////////
+//
+// Generated from the TEXTINCLUDE 3 resource.
+//
+
+
+/////////////////////////////////////////////////////////////////////////////
+#endif // not APSTUDIO_INVOKED
+
diff --git a/4.InspectClonedMemory/InspectClonedMemory.sln b/4.InspectClonedMemory/InspectClonedMemory.sln
new file mode 100644
index 0000000..1d8e337
--- /dev/null
+++ b/4.InspectClonedMemory/InspectClonedMemory.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32804.467
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InspectClonedMemory", "InspectClonedMemory.vcxproj", "{38B708BF-E457-49EB-8E6B-98608A18C947}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {38B708BF-E457-49EB-8E6B-98608A18C947}.Debug|x64.ActiveCfg = Debug|x64
+ {38B708BF-E457-49EB-8E6B-98608A18C947}.Debug|x64.Build.0 = Debug|x64
+ {38B708BF-E457-49EB-8E6B-98608A18C947}.Debug|x86.ActiveCfg = Debug|Win32
+ {38B708BF-E457-49EB-8E6B-98608A18C947}.Debug|x86.Build.0 = Debug|Win32
+ {38B708BF-E457-49EB-8E6B-98608A18C947}.Release|x64.ActiveCfg = Release|x64
+ {38B708BF-E457-49EB-8E6B-98608A18C947}.Release|x64.Build.0 = Release|x64
+ {38B708BF-E457-49EB-8E6B-98608A18C947}.Release|x86.ActiveCfg = Release|Win32
+ {38B708BF-E457-49EB-8E6B-98608A18C947}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {6B0FA1E1-D746-4931-89EC-BFF3516DECB6}
+ EndGlobalSection
+EndGlobal
diff --git a/4.InspectClonedMemory/InspectClonedMemory.vcxproj b/4.InspectClonedMemory/InspectClonedMemory.vcxproj
new file mode 100644
index 0000000..3c5221f
--- /dev/null
+++ b/4.InspectClonedMemory/InspectClonedMemory.vcxproj
@@ -0,0 +1,183 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {38b708bf-e457-49eb-8e6b-98608a18c947}
+ InspectClonedMemory
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ ..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/4.InspectClonedMemory/InspectClonedMemory.vcxproj.filters b/4.InspectClonedMemory/InspectClonedMemory.vcxproj.filters
new file mode 100644
index 0000000..a8a6563
--- /dev/null
+++ b/4.InspectClonedMemory/InspectClonedMemory.vcxproj.filters
@@ -0,0 +1,17 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
\ No newline at end of file
diff --git a/4.InspectClonedMemory/main.c b/4.InspectClonedMemory/main.c
new file mode 100644
index 0000000..8c92c07
--- /dev/null
+++ b/4.InspectClonedMemory/main.c
@@ -0,0 +1,957 @@
+/*
+ * Copyright (c) 2023 Hunt & Hackett.
+ *
+ * This demo project is licensed under the MIT license.
+ *
+ * Authors:
+ * diversenok
+ *
+ */
+
+#include
+#include
+#include
+#include
+
+#define H2_ARGV_CLONING_MODE 1
+#define H2_ARGV_PID 2
+#define H2_ARGV_OPTIONS 3
+
+typedef enum _H2_CLONING_MODE {
+ H2CloneViaReflection = 1,
+ H2CloneViaNativeApi
+} H2_CLONING_MODE;
+
+typedef enum _H2_KNOWN_ADDRESS {
+ H2KnownAddressUserSharedData,
+ H2KnownAddressHypervisorSharedUserVa,
+ H2KnownAddressPebBaseAddress,
+ H2KnownAddressApiSetMap,
+ H2KnownAddressReadOnlySharedMemoryBase,
+ H2KnownAddressAnsiCodePageData,
+ H2KnownAddressGdiSharedHandleTable,
+ H2KnownAddressShimData,
+ H2KnownAddressActivationContextData,
+ H2KnownAddressSystemDefaultActivationContextData,
+ H2KnownAddressMaximum // always last
+} H2_KNOWN_ADDRESS;
+
+typedef struct _H2_KNOWN_ADDRESS_TAG {
+ PVOID Address;
+ PCWSTR Name;
+} H2_KNOWN_ADDRESS_TAG;
+
+typedef struct _H2_KNOWN_ADDRESSES {
+ H2_KNOWN_ADDRESS_TAG Tags[H2KnownAddressMaximum];
+} H2_KNOWN_ADDRESSES, *PH2_KNOWN_ADDRESSES;
+
+VOID
+NTAPI
+H2CollectKnownAddresses(
+ _In_ HANDLE ProcessHandle,
+ _Out_ PH2_KNOWN_ADDRESSES KnownAddresses
+);
+
+NTSTATUS
+NTAPI
+H2QueryShortNameMappedFile(
+ _In_ HANDLE Process,
+ _In_ PVOID Address,
+ _Out_ UNICODE_STRING* ShortName
+);
+
+PCWSTR
+NTAPI
+H2ProtectionToString(
+ _In_ ULONG MemoryProtection
+);
+
+__success(return)
+BOOLEAN
+NTAPI
+H2QueryClassHeap(
+ _In_ HANDLE ProcessHandle,
+ _In_ PVOID Address,
+ _In_ BOOLEAN WoW64,
+ _Out_ PULONG HeapClass
+);
+
+PCWSTR
+NTAPI
+H2HeapClassToString(
+ _In_ ULONG HeapClass
+);
+
+BOOLEAN
+NTAPI
+H2ReportStatus(
+ _In_ PCWSTR LastCall,
+ _In_ NTSTATUS Status
+);
+
+int wmain(int argc, wchar_t* argv[])
+{
+ NTSTATUS status;
+ H2_CLONING_MODE cloningMode;
+ BOOLEAN skipPrivateRegions;
+ HANDLE hParentProcess = NULL;
+ HANDLE hCloneProcess = NULL;
+
+ wprintf_s(L"A tool for inspecting memory layout of cloned processes by Hunt & Hackett.\r\n\r\n");
+
+ if (argc < 3)
+ {
+ wprintf_s(L"Usage: InspectClonedMemory.exe [cloning mode] [PID] [[options]]\r\n\r\n");
+
+ wprintf_s(L"Supported cloning modes:\r\n");
+ wprintf_s(L" -r - clone via RtlCreateProcessReflection\r\n");
+ wprintf_s(L" -p - clone via NtCreateProcessEx\r\n\r\n");
+
+ wprintf_s(L"Supported options:\r\n");
+ wprintf_s(L" -np - skip private regions\r\n");
+
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ if (wcscmp(argv[H2_ARGV_CLONING_MODE], L"-r") == 0)
+ cloningMode = H2CloneViaReflection;
+ else if (wcscmp(argv[H2_ARGV_CLONING_MODE], L"-p") == 0)
+ cloningMode = H2CloneViaNativeApi;
+ else
+ {
+ wprintf(L"ERROR: urecognized cloning mode specified.\r\n");
+ return STATUS_INVALID_PARAMETER;
+ }
+
+ skipPrivateRegions = argc > H2_ARGV_OPTIONS && wcscmp(argv[H2_ARGV_OPTIONS], L"-np") == 0;
+
+ // Enable the Debug privilege when possible
+ BOOLEAN wasDebugEnabled;
+ status = RtlAdjustPrivilege(SE_DEBUG_PRIVILEGE, TRUE, FALSE, &wasDebugEnabled);
+
+ if (!NT_SUCCESS(status))
+ wprintf(L"WARNING: Failed to acquire the debug privilege; continuing without it...\r\n");
+
+ // Open the target process for cloning
+ OBJECT_ATTRIBUTES objAttr;
+ CLIENT_ID clientId;
+
+ clientId.UniqueProcess = (HANDLE)(ULONG_PTR)wcstoul(argv[H2_ARGV_PID], NULL, 0);
+ clientId.UniqueThread = NULL;
+
+ InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
+
+ status = NtOpenProcess(
+ &hParentProcess,
+ PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ |
+ (cloningMode == H2CloneViaReflection ? PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE : PROCESS_CREATE_PROCESS),
+ &objAttr,
+ &clientId
+ );
+
+ if (!H2ReportStatus(L"NtOpenProcess", status))
+ goto CLEANUP;
+
+ PPEB32 WoW64Peb;
+
+ status = NtQueryInformationProcess(
+ hParentProcess,
+ ProcessWow64Information,
+ &WoW64Peb,
+ sizeof(WoW64Peb),
+ NULL
+ );
+
+ if (!H2ReportStatus(L"NtQueryInformationProcess[ProcessWow64Information]", status))
+ goto CLEANUP;
+
+ // Clone the target process
+ switch (cloningMode)
+ {
+ case H2CloneViaReflection:
+ {
+ RTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION reflectionInfo;
+
+ status = RtlCreateProcessReflection(
+ hParentProcess,
+ RTL_PROCESS_REFLECTION_FLAGS_NO_SYNCHRONIZE,
+ NULL,
+ NULL,
+ NULL,
+ &reflectionInfo
+ );
+
+ if (!H2ReportStatus(L"RtlCreateProcessReflection", status))
+ goto CLEANUP;
+
+ NtClose(reflectionInfo.ReflectionThreadHandle);
+ hCloneProcess = reflectionInfo.ReflectionProcessHandle;
+ }
+ break;
+
+ case H2CloneViaNativeApi:
+ {
+ InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
+
+ status = NtCreateProcessEx(
+ &hCloneProcess,
+ PROCESS_ALL_ACCESS,
+ &objAttr,
+ hParentProcess,
+ 0,
+ NULL,
+ NULL,
+ NULL,
+ 0
+ );
+
+ if (!H2ReportStatus(L"NtCreateProcessEx", status))
+ goto CLEANUP;
+ }
+ break;
+ }
+
+ // Save the default text colors
+ CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
+ GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo);
+
+ WORD consoleBackground;
+ consoleBackground = consoleInfo.wAttributes & 0xFFF0;
+
+ // Collect notable addresses
+ H2_KNOWN_ADDRESSES knownAddresses;
+ H2CollectKnownAddresses(hParentProcess, &knownAddresses);
+
+ // Iterate the address space of the parent
+ MEMORY_BASIC_INFORMATION parentInfo;
+ MEMORY_BASIC_INFORMATION cloneInfo;
+
+ for (
+ PVOID address = NULL;
+ NT_SUCCESS(NtQueryVirtualMemory(
+ hParentProcess,
+ address,
+ MemoryBasicInformation,
+ &parentInfo,
+ sizeof(parentInfo),
+ NULL
+ ));
+ address = RtlOffsetToPointer(address, parentInfo.RegionSize)
+ )
+ {
+ // Skip unused regions
+ if (parentInfo.State == MEM_FREE)
+ continue;
+
+ // Allow skipping private regions
+ if (skipPrivateRegions && parentInfo.Type == MEM_PRIVATE)
+ continue;
+
+ if (parentInfo.BaseAddress == parentInfo.AllocationBase)
+ {
+ // Report the base of allocation
+ wprintf_s(L"\r\n0x%0.12zX | ", (ULONG_PTR)parentInfo.AllocationBase);
+
+ PCWSTR memoryType;
+ WORD memoryColor;
+
+ switch (parentInfo.Type)
+ {
+ case MEM_PRIVATE:
+ memoryType = L"Private";
+ memoryColor = FOREGROUND_GREEN | FOREGROUND_RED;
+ break;
+ case MEM_MAPPED:
+ memoryType = L"Mapped";
+ memoryColor = FOREGROUND_GREEN;
+ break;
+ case MEM_IMAGE:
+ memoryType = L"Image";
+ memoryColor = FOREGROUND_BLUE | FOREGROUND_RED;
+ break;
+ default:
+ memoryType = L"Unknown";
+ memoryColor = FOREGROUND_RED;
+ }
+
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleBackground | memoryColor);
+ wprintf_s(L"%s", memoryType);
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleInfo.wAttributes);
+
+ // Higighlight potentially writable shared allocations
+ if (parentInfo.Type == MEM_MAPPED && (parentInfo.AllocationProtect & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)))
+ {
+ wprintf_s(L" | ");
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleBackground | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
+ wprintf_s(L"Writable");
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleInfo.wAttributes);
+ }
+
+ // Tag files with their names
+ if (parentInfo.Type == MEM_MAPPED || parentInfo.Type == MEM_IMAGE)
+ {
+ UNICODE_STRING fileName;
+
+ if (NT_SUCCESS(H2QueryShortNameMappedFile(hParentProcess, parentInfo.AllocationBase, &fileName)))
+ {
+ wprintf_s(L" | ");
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleBackground | FOREGROUND_INTENSITY);
+ wprintf_s(L"%wZ", &fileName);
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleInfo.wAttributes);
+ RtlFreeHeap(RtlGetCurrentPeb()->ProcessHeap, 0, fileName.Buffer);
+ }
+ }
+
+ // Tag known locations
+ for (H2_KNOWN_ADDRESS i = 0; i < H2KnownAddressMaximum; i++)
+ {
+ if (knownAddresses.Tags[i].Address && knownAddresses.Tags[i].Address == parentInfo.AllocationBase)
+ {
+ wprintf_s(L" | ");
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleBackground | FOREGROUND_INTENSITY);
+ wprintf_s(L"%s", knownAddresses.Tags[i].Name);
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleInfo.wAttributes);
+ }
+ }
+
+ wprintf_s(L"\r\n");
+ }
+
+ // Skip reserved and inaccessible sub-regions
+ if (parentInfo.State == MEM_RESERVE || (parentInfo.Protect & PAGE_NOACCESS))
+ continue;
+
+ wprintf_s(L" 0x%0.12zX - 0x%0.12zX | %s%s",
+ (ULONG_PTR)parentInfo.BaseAddress,
+ (ULONG_PTR)RtlOffsetToPointer(parentInfo.BaseAddress, parentInfo.RegionSize - 1),
+ H2ProtectionToString(parentInfo.Protect),
+ parentInfo.Protect & PAGE_GUARD ? L"+G" : L""
+ );
+
+ // Tag known locations
+ if (parentInfo.BaseAddress != parentInfo.AllocationBase)
+ {
+ for (H2_KNOWN_ADDRESS i = 0; i < H2KnownAddressMaximum; i++)
+ {
+ if (knownAddresses.Tags[i].Address && knownAddresses.Tags[i].Address == parentInfo.BaseAddress)
+ {
+ wprintf_s(L" | ");
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleBackground | FOREGROUND_INTENSITY);
+ wprintf_s(L"%s", knownAddresses.Tags[i].Name);
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleInfo.wAttributes);
+ }
+ }
+ }
+
+ ULONG heapClass;
+
+ // Tag heaps
+ if (H2QueryClassHeap(hParentProcess, parentInfo.BaseAddress, !!WoW64Peb, &heapClass))
+ {
+ wprintf_s(L" | ");
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleBackground | FOREGROUND_INTENSITY);
+ wprintf_s(L"%s heap", H2HeapClassToString(heapClass));
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleInfo.wAttributes);
+ }
+
+ // Query the same address for the clone
+ status = NtQueryVirtualMemory(
+ hCloneProcess,
+ address,
+ MemoryBasicInformation,
+ &cloneInfo,
+ sizeof(cloneInfo),
+ NULL
+ );
+
+ PCWSTR comparison;
+
+ if (!NT_SUCCESS(status))
+ comparison = L"Unable to query";
+ else if (cloneInfo.State == MEM_FREE)
+ comparison = L"Missing";
+ else if (memcmp(&parentInfo, &cloneInfo, sizeof(MEMORY_BASIC_INFORMATION)) != 0)
+ comparison = L"Different";
+ else
+ comparison = NULL;
+
+ // Report differences
+ if (comparison)
+ {
+ wprintf_s(L" | ");
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleBackground | FOREGROUND_RED | FOREGROUND_INTENSITY);
+ wprintf_s(L"%s", comparison);
+ SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), consoleInfo.wAttributes);
+ }
+
+ wprintf_s(L"\r\n");
+ }
+
+ wprintf_s(L"\r\nMemory enumeration completed.\r\n");
+ status = STATUS_SUCCESS;
+
+CLEANUP:
+ if (hParentProcess)
+ NtClose(hParentProcess);
+
+ if (hCloneProcess)
+ {
+ NtTerminateProcess(hCloneProcess, STATUS_PROCESS_CLONED);
+ NtClose(hCloneProcess);
+ }
+
+ return status;
+}
+
+VOID
+NTAPI
+H2CollectKnownAddresses(
+ _In_ HANDLE ProcessHandle,
+ _Out_ PH2_KNOWN_ADDRESSES KnownAddresses
+)
+{
+ NTSTATUS status;
+ memset(KnownAddresses, 0, sizeof(KnownAddresses));
+
+ // User shared data
+ KnownAddresses->Tags[H2KnownAddressUserSharedData].Name = L"USER_SHARED_DATA";
+ KnownAddresses->Tags[H2KnownAddressUserSharedData].Address = USER_SHARED_DATA;
+
+ // Hypervisor shared data
+ SYSTEM_HYPERVISOR_SHARED_PAGE_INFORMATION hypervisorInfo;
+
+ status = NtQuerySystemInformation(
+ SystemHypervisorSharedPageInformation,
+ &hypervisorInfo,
+ sizeof(hypervisorInfo),
+ NULL
+ );
+
+ if (NT_SUCCESS(status))
+ {
+ KnownAddresses->Tags[H2KnownAddressHypervisorSharedUserVa].Name = L"HYPERVISOR_SHARED_DATA";
+ KnownAddresses->Tags[H2KnownAddressHypervisorSharedUserVa].Address = hypervisorInfo.HypervisorSharedUserVa;
+ }
+
+ // PEB
+ PROCESS_BASIC_INFORMATION processInfo;
+
+ status = NtQueryInformationProcess(
+ ProcessHandle,
+ ProcessBasicInformation,
+ &processInfo,
+ sizeof(processInfo),
+ NULL
+ );
+
+ if (!NT_SUCCESS(status))
+ return;
+
+ KnownAddresses->Tags[H2KnownAddressPebBaseAddress].Name = L"PEB";
+ KnownAddresses->Tags[H2KnownAddressPebBaseAddress].Address = processInfo.PebBaseAddress;
+
+ // ApiSet map
+ PVOID pebField;
+
+ status = NtReadVirtualMemory(
+ ProcessHandle,
+ &processInfo.PebBaseAddress->ApiSetMap,
+ &pebField,
+ sizeof(pebField),
+ NULL
+ );
+
+ if (NT_SUCCESS(status))
+ {
+ KnownAddresses->Tags[H2KnownAddressApiSetMap].Name = L"ApiSetMap";
+ KnownAddresses->Tags[H2KnownAddressApiSetMap].Address = pebField;
+ }
+
+ // CSR shared memory
+ status = NtReadVirtualMemory(
+ ProcessHandle,
+ &processInfo.PebBaseAddress->ReadOnlySharedMemoryBase,
+ &pebField,
+ sizeof(pebField),
+ NULL
+ );
+
+ if (NT_SUCCESS(status))
+ {
+ KnownAddresses->Tags[H2KnownAddressReadOnlySharedMemoryBase].Name = L"CSR shared memory";
+ KnownAddresses->Tags[H2KnownAddressReadOnlySharedMemoryBase].Address = pebField;
+ }
+
+ // CodePage data
+ status = NtReadVirtualMemory(
+ ProcessHandle,
+ &processInfo.PebBaseAddress->AnsiCodePageData,
+ &pebField,
+ sizeof(pebField),
+ NULL
+ );
+
+ if (NT_SUCCESS(status))
+ {
+ KnownAddresses->Tags[H2KnownAddressAnsiCodePageData].Name = L"CodePage data";
+ KnownAddresses->Tags[H2KnownAddressAnsiCodePageData].Address = pebField;
+ }
+
+ // GDI shared handle table
+ status = NtReadVirtualMemory(
+ ProcessHandle,
+ &processInfo.PebBaseAddress->GdiSharedHandleTable,
+ &pebField,
+ sizeof(pebField),
+ NULL
+ );
+
+ if (NT_SUCCESS(status))
+ {
+ KnownAddresses->Tags[H2KnownAddressGdiSharedHandleTable].Name = L"GDI shared handle table";
+ KnownAddresses->Tags[H2KnownAddressGdiSharedHandleTable].Address = pebField;
+ }
+
+ // Shim data
+ status = NtReadVirtualMemory(
+ ProcessHandle,
+ &processInfo.PebBaseAddress->pShimData,
+ &pebField,
+ sizeof(pebField),
+ NULL
+ );
+
+ if (NT_SUCCESS(status))
+ {
+ KnownAddresses->Tags[H2KnownAddressShimData].Name = L"Shim data";
+ KnownAddresses->Tags[H2KnownAddressShimData].Address = pebField;
+ }
+
+ // Activation context data
+ status = NtReadVirtualMemory(
+ ProcessHandle,
+ &processInfo.PebBaseAddress->ActivationContextData,
+ &pebField,
+ sizeof(pebField),
+ NULL
+ );
+
+ if (NT_SUCCESS(status))
+ {
+ KnownAddresses->Tags[H2KnownAddressActivationContextData].Name = L"Activation context data";
+ KnownAddresses->Tags[H2KnownAddressActivationContextData].Address = pebField;
+ }
+
+ // Default activation context data
+ status = NtReadVirtualMemory(
+ ProcessHandle,
+ &processInfo.PebBaseAddress->SystemDefaultActivationContextData,
+ &pebField,
+ sizeof(pebField),
+ NULL
+ );
+
+ if (NT_SUCCESS(status))
+ {
+ KnownAddresses->Tags[H2KnownAddressSystemDefaultActivationContextData].Name = L"Default activation context data";
+ KnownAddresses->Tags[H2KnownAddressSystemDefaultActivationContextData].Address = pebField;
+ }
+}
+
+PCWSTR
+NTAPI
+H2ProtectionToString(
+ _In_ ULONG MemoryProtection
+)
+{
+ switch (MemoryProtection & 0xFF)
+ {
+ case PAGE_NOACCESS:
+ return L"NA";
+ case PAGE_READONLY:
+ return L"R";
+ case PAGE_READWRITE:
+ return L"RW";
+ case PAGE_WRITECOPY:
+ return L"WC";
+ case PAGE_EXECUTE:
+ return L"X";
+ case PAGE_EXECUTE_READ:
+ return L"RX";
+ case PAGE_EXECUTE_READWRITE:
+ return L"RWX";
+ case PAGE_EXECUTE_WRITECOPY:
+ return L"WCX";
+ default:
+ return L"???";
+ }
+}
+
+NTSTATUS
+NTAPI
+H2QueryShortNameMappedFile(
+ _In_ HANDLE Process,
+ _In_ PVOID Address,
+ _Out_ UNICODE_STRING* ShortName
+)
+{
+ NTSTATUS status;
+ PUNICODE_STRING buffer;
+ SIZE_T bufferSize = RtlGetLongestNtPathLength() * sizeof(WCHAR);
+
+ do
+ {
+ buffer = RtlAllocateHeap(RtlGetCurrentPeb()->ProcessHeap, 0, bufferSize);
+
+ if (!buffer)
+ return STATUS_NO_MEMORY;
+
+ status = NtQueryVirtualMemory(
+ Process,
+ Address,
+ MemoryMappedFilenameInformation,
+ buffer,
+ bufferSize,
+ &bufferSize
+ );
+
+ if (!NT_SUCCESS(status))
+ RtlFreeHeap(RtlGetCurrentPeb()->ProcessHeap, 0, buffer);
+ else
+ break;
+
+ } while (status == STATUS_BUFFER_OVERFLOW);
+
+ if (!NT_SUCCESS(status))
+ return status;
+
+ // Extract the filename only
+ USHORT nameStart = 0;
+ USHORT nameLength = buffer->Length;
+
+ if (buffer->Length > sizeof(WCHAR))
+ {
+ for (USHORT i = buffer->Length / sizeof(WCHAR) - 1; i > 0; i--)
+ {
+ if (buffer->Buffer[i] == OBJ_NAME_PATH_SEPARATOR)
+ {
+ nameStart = i + 1;
+ nameLength = buffer->Length - nameStart * sizeof(WCHAR);
+ break;
+ }
+ }
+ }
+
+ // Copy it
+ UNICODE_STRING name;
+ name.Length = nameLength;
+ name.MaximumLength = nameLength;
+ name.Buffer = RtlAllocateHeap(RtlGetCurrentPeb()->ProcessHeap, 0, nameLength);
+
+ if (name.Buffer)
+ {
+ memmove(name.Buffer, &buffer->Buffer[nameStart], name.Length);
+ *ShortName = name;
+ }
+ else
+ {
+ status = STATUS_NO_MEMORY;
+ }
+
+ RtlFreeHeap(RtlGetCurrentPeb()->ProcessHeap, 0, buffer);
+ return status;
+}
+
+// Not the actual structure, but has the same size.
+typedef struct _HEAP_ENTRY
+{
+ PVOID Data1;
+ PVOID Data2;
+} HEAP_ENTRY, *PHEAP_ENTRY;
+
+#define HEAP_SEGMENT_SIGNATURE 0xffeeffee
+
+typedef struct _HEAP_SEGMENT
+{
+ HEAP_ENTRY Entry;
+ ULONG SegmentSignature;
+ ULONG SegmentFlags;
+ LIST_ENTRY SegmentListEntry;
+ union _HEAP *Heap;
+ PVOID BaseAddress;
+ ULONG NumberOfPages;
+ PHEAP_ENTRY FirstEntry;
+ PHEAP_ENTRY LastValidEntry;
+ ULONG NumberOfUnCommittedPages;
+ ULONG NumberOfUnCommittedRanges;
+ USHORT SegmentAllocatorBackTraceIndex;
+ USHORT Reserved;
+ LIST_ENTRY UCRSegmentList;
+} HEAP_SEGMENT, *PHEAP_SEGMENT;
+
+#define HEAP_SIGNATURE 0xeeffeeff
+
+typedef union _HEAP
+{
+ struct
+ {
+ HEAP_SEGMENT Segment;
+ ULONG Flags;
+ ULONG ForceFlags;
+ ULONG CompatibilityFlags;
+ ULONG EncodeFlagMask;
+ HEAP_ENTRY Encoding;
+ ULONG_PTR PointerKey; // Windows 7 only
+ ULONG Interceptor;
+ ULONG VirtualMemoryThreshold;
+ ULONG Signature;
+ // ...
+ } Old; // Windows 7
+
+ struct
+ {
+ HEAP_SEGMENT Segment;
+ ULONG Flags;
+ ULONG ForceFlags;
+ ULONG CompatibilityFlags;
+ ULONG EncodeFlagMask;
+ HEAP_ENTRY Encoding;
+ ULONG Interceptor;
+ ULONG VirtualMemoryThreshold;
+ ULONG Signature;
+ // ...
+ } New; // Windows 8+
+} HEAP, *PHEAP;
+
+#define SEGMENT_HEAP_SIGNATURE 0xddeeddee
+
+typedef struct _SEGMENT_HEAP
+{
+ ULONG_PTR Padding[2];
+ ULONG Signature;
+ ULONG GlobalFlags;
+ // ...
+} SEGMENT_HEAP, PSEGMENT_HEAP;
+
+typedef struct _HEAP_ENTRY32
+{
+ WOW64_POINTER(PVOID) Data1;
+ WOW64_POINTER(PVOID) Data2;
+} HEAP_ENTRY32, *PHEAP_ENTRY32;
+
+typedef struct _HEAP_SEGMENT32
+{
+ HEAP_ENTRY32 HeapEntry;
+ ULONG SegmentSignature;
+ ULONG SegmentFlags;
+ LIST_ENTRY32 SegmentListEntry;
+ WOW64_POINTER(struct _HEAP32 *) Heap;
+ WOW64_POINTER(PVOID) BaseAddress;
+ ULONG NumberOfPages;
+ WOW64_POINTER(PHEAP_ENTRY32) FirstEntry;
+ WOW64_POINTER(PHEAP_ENTRY32) LastValidEntry;
+ ULONG NumberOfUnCommittedPages;
+ ULONG NumberOfUnCommittedRanges;
+ USHORT SegmentAllocatorBackTraceIndex;
+ USHORT Reserved;
+ LIST_ENTRY32 UCRSegmentList;
+} HEAP_SEGMENT32, *PHEAP_SEGMENT32;
+
+typedef union _HEAP32
+{
+ struct
+ {
+ HEAP_SEGMENT32 Segment;
+ ULONG Flags;
+ ULONG ForceFlags;
+ ULONG CompatibilityFlags;
+ ULONG EncodeFlagMask;
+ HEAP_ENTRY32 Encoding;
+ WOW64_POINTER(ULONG_PTR) PointerKey;
+ ULONG Interceptor;
+ ULONG VirtualMemoryThreshold;
+ ULONG Signature;
+ // ...
+ } Old; // Windows 7
+
+ struct
+ {
+ HEAP_SEGMENT32 Segment;
+ ULONG Flags;
+ ULONG ForceFlags;
+ ULONG CompatibilityFlags;
+ ULONG EncodeFlagMask;
+ HEAP_ENTRY32 Encoding;
+ ULONG Interceptor;
+ ULONG VirtualMemoryThreshold;
+ ULONG Signature;
+ // ...
+ } New; // Windows 8+
+} HEAP32, *PHEAP32;
+
+typedef struct _SEGMENT_HEAP32
+{
+ WOW64_POINTER(ULONG_PTR) Padding[2];
+ ULONG Signature;
+ ULONG GlobalFlags;
+ // ...
+} SEGMENT_HEAP32, PSEGMENT_HEAP32;
+
+typedef union _H2_ANY_HEAP
+{
+ HEAP Heap;
+ HEAP32 Heap32;
+ SEGMENT_HEAP SegmentHeap;
+ SEGMENT_HEAP32 SegmentHeap32;
+} H2_ANY_HEAP;
+
+__success(return)
+BOOLEAN
+NTAPI
+H2QueryClassHeap(
+ _In_ HANDLE ProcessHandle,
+ _In_ PVOID Address,
+ _In_ BOOLEAN WoW64,
+ _Out_ PULONG HeapClass
+)
+{
+ NTSTATUS status;
+ H2_ANY_HEAP buffer;
+
+ status = NtReadVirtualMemory(ProcessHandle, Address, &buffer, sizeof(buffer), NULL);
+
+ if (!NT_SUCCESS(status))
+ return FALSE;
+
+ if (RtlGetCurrentPeb()->OSMajorVersion == 6 && RtlGetCurrentPeb()->OSMinorVersion == 1)
+ {
+ // Windows 7
+
+ if (WoW64)
+ {
+ if (buffer.Heap32.Old.Signature == HEAP_SIGNATURE)
+ {
+ *HeapClass = buffer.Heap32.Old.Flags & HEAP_CLASS_MASK;
+ return TRUE;
+ }
+ }
+ else
+ {
+ if (buffer.Heap.Old.Signature == HEAP_SIGNATURE)
+ {
+ *HeapClass = buffer.Heap.Old.Flags & HEAP_CLASS_MASK;
+ return TRUE;
+ }
+ }
+ }
+ else
+ {
+ // Windows 8+
+
+ if (WoW64)
+ {
+ if (buffer.Heap32.New.Signature == HEAP_SIGNATURE)
+ {
+ *HeapClass = buffer.Heap32.New.Flags & HEAP_CLASS_MASK;
+ return TRUE;
+ }
+ else if (buffer.SegmentHeap32.Signature == SEGMENT_HEAP_SIGNATURE)
+ {
+ *HeapClass = buffer.SegmentHeap32.GlobalFlags & HEAP_CLASS_MASK;
+ return TRUE;
+ }
+ }
+ else
+ {
+ if (buffer.Heap.New.Signature == HEAP_SIGNATURE)
+ {
+ *HeapClass = buffer.Heap.New.Flags & HEAP_CLASS_MASK;
+ return TRUE;
+ }
+ else if (buffer.SegmentHeap.Signature == SEGMENT_HEAP_SIGNATURE)
+ {
+ *HeapClass = buffer.SegmentHeap.GlobalFlags & HEAP_CLASS_MASK;
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+PCWSTR
+NTAPI
+H2HeapClassToString(
+ _In_ ULONG HeapClass
+)
+{
+ switch (HeapClass)
+ {
+ case HEAP_CLASS_0:
+ return L"Process";
+ case HEAP_CLASS_1:
+ return L"Private";
+ case HEAP_CLASS_2:
+ return L"Kernel";
+ case HEAP_CLASS_3:
+ return L"GDI";
+ case HEAP_CLASS_4:
+ return L"User";
+ case HEAP_CLASS_5:
+ return L"Console";
+ case HEAP_CLASS_6:
+ return L"User desktop";
+ case HEAP_CLASS_7:
+ return L"CSR shared";
+ case HEAP_CLASS_8:
+ return L"CSR port";
+ default:
+ return L"Unknown";
+ }
+}
+
+BOOLEAN
+NTAPI
+H2ReportStatus(
+ _In_ PCWSTR LastCall,
+ _In_ NTSTATUS Status
+)
+{
+ if (NT_SUCCESS(Status))
+ return TRUE;
+
+ UNICODE_STRING description;
+ RtlInitUnicodeString(&description, L"Unknown error");
+
+ // Find ntdll
+ PLDR_DATA_TABLE_ENTRY ntdllLdrEntry = CONTAINING_RECORD(
+ NtCurrentTeb()->ProcessEnvironmentBlock->Ldr->InInitializationOrderModuleList.Flink,
+ LDR_DATA_TABLE_ENTRY,
+ InInitializationOrderLinks
+ );
+
+ // Lookup the error description
+ PMESSAGE_RESOURCE_ENTRY messageEntry;
+
+ NTSTATUS status = RtlFindMessage(
+ ntdllLdrEntry->DllBase,
+ (ULONG)(ULONG_PTR)RT_MESSAGETABLE,
+ 0,
+ NT_NTWIN32(Status) ? WIN32_FROM_NTSTATUS(Status) : (ULONG)Status,
+ &messageEntry
+ );
+
+ if (NT_SUCCESS(status) && messageEntry->Flags & MESSAGE_RESOURCE_UNICODE)
+ {
+ RtlInitUnicodeString(&description, (PCWSTR)messageEntry->Text);
+
+ // Trim the trailing new lines
+ while (description.Length > 2 * sizeof(WCHAR) &&
+ description.Buffer[description.Length / sizeof(WCHAR) - 1] == L'\n' &&
+ description.Buffer[description.Length / sizeof(WCHAR) - 2] == L'\r')
+ description.Length -= 2 * sizeof(WCHAR);
+ }
+
+ wprintf_s(L"ERROR: %s failed with 0x%X - %wZ\r\n", LastCall, Status, &description);
+ return FALSE;
+}
diff --git a/4.InspectClonedMemory/resource.h b/4.InspectClonedMemory/resource.h
new file mode 100644
index 0000000..524aede
--- /dev/null
+++ b/4.InspectClonedMemory/resource.h
@@ -0,0 +1,14 @@
+//{{NO_DEPENDENCIES}}
+// Microsoft Visual C++ generated include file.
+// Used by InspectClonedMemory.rc
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 101
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1001
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/5.Library/Example.sln b/5.Library/Example.sln
new file mode 100644
index 0000000..33d3fee
--- /dev/null
+++ b/5.Library/Example.sln
@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32804.467
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Example", "Example.vcxproj", "{3513E32A-E05B-4E03-BB2D-869B311BD935}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|x64 = Debug|x64
+ Debug|x86 = Debug|x86
+ Release|x64 = Release|x64
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {3513E32A-E05B-4E03-BB2D-869B311BD935}.Debug|x64.ActiveCfg = Debug|x64
+ {3513E32A-E05B-4E03-BB2D-869B311BD935}.Debug|x64.Build.0 = Debug|x64
+ {3513E32A-E05B-4E03-BB2D-869B311BD935}.Debug|x86.ActiveCfg = Debug|Win32
+ {3513E32A-E05B-4E03-BB2D-869B311BD935}.Debug|x86.Build.0 = Debug|Win32
+ {3513E32A-E05B-4E03-BB2D-869B311BD935}.Release|x64.ActiveCfg = Release|x64
+ {3513E32A-E05B-4E03-BB2D-869B311BD935}.Release|x64.Build.0 = Release|x64
+ {3513E32A-E05B-4E03-BB2D-869B311BD935}.Release|x86.ActiveCfg = Release|Win32
+ {3513E32A-E05B-4E03-BB2D-869B311BD935}.Release|x86.Build.0 = Release|Win32
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {CCBAD7D5-6DF7-49EE-9ABB-9DFB339B91A8}
+ EndGlobalSection
+EndGlobal
diff --git a/5.Library/Example.vcxproj b/5.Library/Example.vcxproj
new file mode 100644
index 0000000..916811a
--- /dev/null
+++ b/5.Library/Example.vcxproj
@@ -0,0 +1,181 @@
+
+
+
+
+ Debug
+ Win32
+
+
+ Release
+ Win32
+
+
+ Debug
+ x64
+
+
+ Release
+ x64
+
+
+
+ 16.0
+ Win32Proj
+ {3513e32a-e05b-4e03-bb2d-869b311bd935}
+ Example
+ 10.0
+
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+ Application
+ true
+ v143
+ Unicode
+
+
+ Application
+ false
+ v143
+ true
+ Unicode
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+ $(ProjectDir)obj\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ true
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+ false
+ $(SolutionDir)bin\$(Configuration)$(PlatformArchitecture)\
+
+
+
+ Level3
+ true
+ WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ Library;..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ Library;..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+ Level3
+ true
+ _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ Library;..\Dependencies\phnt
+
+
+ Console
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ true
+
+
+
+
+ Level3
+ true
+ true
+ true
+ NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
+ true
+ Library;..\Dependencies\phnt
+ MultiThreaded
+
+
+ Console
+ true
+ true
+ true
+ ntdll.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
+ /PDBALTPATH:%_PDB%
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/5.Library/Example.vcxproj.filters b/5.Library/Example.vcxproj.filters
new file mode 100644
index 0000000..2e6bb0c
--- /dev/null
+++ b/5.Library/Example.vcxproj.filters
@@ -0,0 +1,30 @@
+
+
+
+
+ {4FC737F1-C7A5-4376-A066-2A32D752A2FF}
+ cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx
+
+
+ {93995380-89BD-4b04-88EB-625FBE52EBFB}
+ h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd
+
+
+ {67DA6AB6-F800-4c08-8B7A-83BB121AAD01}
+ rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms
+
+
+
+
+ Source Files
+
+
+ Source Files
+
+
+
+
+ Header Files
+
+
+
\ No newline at end of file
diff --git a/5.Library/Library/cloning.c b/5.Library/Library/cloning.c
new file mode 100644
index 0000000..98ec3bc
--- /dev/null
+++ b/5.Library/Library/cloning.c
@@ -0,0 +1,489 @@
+/*
+ * Copyright (c) 2023 Hunt & Hackett.
+ *
+ * This file is licensed under the MIT license.
+ *
+ * Authors:
+ * diversenok
+ *
+ */
+
+#include
+
+/* Handle inheritance */
+
+// Snapshot current handle values and attributes
+NTSTATUS
+NTAPI
+H2CaptureHandleAttributes(
+ _Outptr_ PH2_HANDLE_SNAPSHOT *Snapshot
+)
+{
+ NTSTATUS status;
+ PH2_HANDLE_SNAPSHOT snapshot = NULL;
+ SIZE_T snapshotSize;
+
+ if (RtlGetCurrentPeb()->OSMajorVersion > 6 ||
+ (RtlGetCurrentPeb()->OSMajorVersion == 6 &&
+ RtlGetCurrentPeb()->OSMinorVersion > 6))
+ {
+ // Windows 8+ supports enumerating per-process handles
+
+ PPROCESS_HANDLE_SNAPSHOT_INFORMATION buffer;
+ ULONG bufferSize = 0x800; // 2 KiB to start with
+
+ do
+ {
+ buffer = RtlAllocateHeap(RtlGetCurrentPeb()->ProcessHeap, 0, bufferSize);
+
+ if (!buffer)
+ return STATUS_NO_MEMORY;
+
+ status = NtQueryInformationProcess(
+ NtCurrentProcess(),
+ ProcessHandleInformation,
+ buffer,
+ bufferSize,
+ &bufferSize
+ );
+
+ if (!NT_SUCCESS(status))
+ RtlFreeHeap(RtlGetCurrentPeb()->ProcessHeap, 0, buffer);
+
+ } while (status == STATUS_INFO_LENGTH_MISMATCH || status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ if (!NT_SUCCESS(status))
+ return status;
+
+ // Allocate the snapshot
+ snapshotSize = sizeof(H2_HANDLE_SNAPSHOT) +
+ sizeof(H2_HANDLE_ENTRY) * (buffer->NumberOfHandles - 1);
+
+ snapshot = RtlAllocateHeap(RtlGetCurrentPeb()->ProcessHeap, 0, snapshotSize);
+
+ if (snapshot)
+ {
+ // Save handle attributes
+ snapshot->NumberOfHandles = buffer->NumberOfHandles;
+
+ for (ULONG_PTR i = 0; i < buffer->NumberOfHandles; i++)
+ {
+ snapshot->Handles[i].HandleValue = buffer->Handles[i].HandleValue;
+ snapshot->Handles[i].HandleAttributes = buffer->Handles[i].HandleAttributes;
+ snapshot->Handles[i].GrantedAccess = buffer->Handles[i].GrantedAccess;
+ snapshot->Handles[i].ObjectTypeIndex = buffer->Handles[i].ObjectTypeIndex;
+ }
+ }
+
+ RtlFreeHeap(RtlGetCurrentPeb()->ProcessHeap, 0, buffer);
+ }
+ else
+ {
+ // Windows 7 requires enumerating all system handles
+
+ PSYSTEM_HANDLE_INFORMATION_EX buffer;
+ ULONG bufferSize = 0x400000; // 4 MiB to start with
+
+ do
+ {
+ buffer = RtlAllocateHeap(RtlGetCurrentPeb()->ProcessHeap, 0, bufferSize);
+
+ if (!buffer)
+ return STATUS_NO_MEMORY;
+
+ status = NtQuerySystemInformation(
+ SystemExtendedHandleInformation,
+ buffer,
+ bufferSize,
+ &bufferSize
+ );
+
+ if (!NT_SUCCESS(status))
+ RtlFreeHeap(RtlGetCurrentPeb()->ProcessHeap, 0, buffer);
+
+ } while (status == STATUS_INFO_LENGTH_MISMATCH || status == STATUS_BUFFER_TOO_SMALL || status == STATUS_BUFFER_OVERFLOW);
+
+ if (!NT_SUCCESS(status))
+ return status;
+
+ // Count our handles
+ ULONG_PTR numberOfHandles = 0;
+
+ for (ULONG_PTR i = 0; i < buffer->NumberOfHandles; i++)
+ {
+ if (buffer->Handles[i].UniqueProcessId == (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueProcess)
+ numberOfHandles++;
+ }
+
+ // Allocate the snapshot
+ snapshotSize = sizeof(H2_HANDLE_SNAPSHOT) +
+ sizeof(H2_HANDLE_ENTRY) * (numberOfHandles - 1);
+
+ snapshot = RtlAllocateHeap(RtlGetCurrentPeb()->ProcessHeap, 0, snapshotSize);
+
+ if (snapshot)
+ {
+ // Save handle attributes
+ snapshot->NumberOfHandles = numberOfHandles;
+
+ ULONG_PTR j = 0;
+ for (ULONG_PTR i = 0; i < buffer->NumberOfHandles; i++)
+ {
+ if (buffer->Handles[i].UniqueProcessId == (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueProcess)
+ {
+ snapshot->Handles[j].HandleValue = (HANDLE)buffer->Handles[i].HandleValue;
+ snapshot->Handles[j].HandleAttributes = buffer->Handles[i].HandleAttributes;
+ snapshot->Handles[j].GrantedAccess = buffer->Handles[i].GrantedAccess;
+ snapshot->Handles[j].ObjectTypeIndex = buffer->Handles[i].ObjectTypeIndex;
+ j++;
+ }
+ }
+ }
+
+ RtlFreeHeap(RtlGetCurrentPeb()->ProcessHeap, 0, buffer);
+ }
+
+ if (!snapshot)
+ return STATUS_NO_MEMORY;
+
+ *Snapshot = snapshot;
+ return STATUS_SUCCESS;
+}
+
+// Adjust inheritance for a set of handles
+VOID
+NTAPI
+H2SetInheritanceHandles(
+ _In_reads_(NumberOfHandles) PH2_HANDLE_ENTRY Handles,
+ _In_ ULONG_PTR NumberOfHandles,
+ _In_ H2_INHERITACE_OPERATION Operation
+)
+{
+ OBJECT_HANDLE_FLAG_INFORMATION handleFlags = { 0 };
+
+ switch (Operation)
+ {
+ case H2InheritanceEnable:
+ handleFlags.Inherit = TRUE;
+ break;
+
+ case H2InheritanceDisable:
+ handleFlags.Inherit = FALSE;
+ break;
+
+ case H2InheritanceRestore:
+ break;
+
+ default:
+ return;
+ }
+
+ for (ULONG_PTR i = 0; i < NumberOfHandles; i++)
+ {
+ if (Operation == H2InheritanceRestore)
+ handleFlags.Inherit = Handles[i].HandleAttributes & OBJ_INHERIT;
+
+ handleFlags.ProtectFromClose = Handles[i].HandleAttributes & OBJ_PROTECT_CLOSE;
+
+ NtSetInformationObject(
+ Handles[i].HandleValue,
+ ObjectHandleFlagInformation,
+ &handleFlags,
+ sizeof(handleFlags)
+ );
+ }
+}
+
+VOID
+NTAPI
+H2ReleaseHandleAttributes(
+ _Frees_ptr_ PH2_HANDLE_SNAPSHOT Snapshot
+)
+{
+ RtlFreeHeap(RtlGetCurrentPeb()->ProcessHeap, 0, Snapshot);
+}
+
+/* Shared memory */
+
+// Allocate a shared memory region for communicating with the clone
+NTSTATUS
+NTAPI
+H2MapSharedMamory(
+ _Outptr_ PVOID *BaseAddress,
+ _In_ SIZE_T AllocationSize,
+ _Out_opt_ SIZE_T *ReturnedSize
+)
+{
+ NTSTATUS status;
+ HANDLE hSection;
+ PVOID baseAddress;
+ LARGE_INTEGER maximumSize;
+ SIZE_T viewSize;
+
+ // Prepare a pagefile-backed section object
+ maximumSize.QuadPart = AllocationSize;
+
+ status = NtCreateSection(
+ &hSection,
+ SECTION_ALL_ACCESS,
+ NULL,
+ &maximumSize,
+ PAGE_READWRITE,
+ SEC_COMMIT,
+ NULL
+ );
+
+ if (!NT_SUCCESS(status))
+ return status;
+
+ // Map it for sharing
+ baseAddress = NULL;
+ viewSize = 0;
+
+ status = NtMapViewOfSection(
+ hSection,
+ NtCurrentProcess(),
+ &baseAddress,
+ 0,
+ 0,
+ NULL,
+ &viewSize,
+ ViewShare,
+ 0,
+ PAGE_READWRITE
+ );
+
+ NtClose(hSection);
+
+ *BaseAddress = baseAddress;
+
+ if (ReturnedSize)
+ *ReturnedSize = viewSize;
+
+ return status;
+}
+
+// Free a shared memory region
+NTSTATUS
+NTAPI
+H2UnmapSharedMamory(
+ _In_ PVOID BaseAddress
+)
+{
+ return NtUnmapViewOfSection(NtCurrentProcess(), BaseAddress);
+}
+
+/* Cloning */
+
+// Attach the clone to the console of the parent process
+BOOL
+WINAPI
+H2AttachToParentConsole(
+ VOID
+)
+{
+ return FreeConsole() && AttachConsole(ATTACH_PARENT_PROCESS);
+}
+
+// Execute a callback in a clone of the current process
+NTSTATUS
+NTAPI
+H2ExecuteInClone(
+ _In_ ULONG Flags,
+ _In_opt_ HANDLE TokenHandle,
+ _In_ PUSER_THREAD_START_ROUTINE Callback,
+ _In_opt_ PVOID Argument,
+ _Out_opt_ PNTSTATUS CompletionStatus,
+ _In_opt_ PLARGE_INTEGER Timeout,
+ _In_ BOOLEAN Alertable
+)
+{
+ NTSTATUS status;
+ BOOLEAN timedOut = FALSE;
+ HANDLE hJob = NULL;
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobLimits = { 0 };
+ PH2_HANDLE_SNAPSHOT handleSnapshot = NULL;
+ RTL_USER_PROCESS_INFORMATION processInfo = { 0 };
+
+ // Snapshot all handles so we can make them inheritable
+ if (Flags & H2_CLONE_PROCESS_FLAGS_INHERIT_ALL_HANDLES)
+ {
+ status = H2CaptureHandleAttributes(&handleSnapshot);
+
+ if (!NT_SUCCESS(status))
+ goto CLEANUP;
+ }
+
+ // Create a job to put the cloned process into
+ status = NtCreateJobObject(
+ &hJob,
+ JOB_OBJECT_ALL_ACCESS,
+ NULL
+ );
+
+ if (!NT_SUCCESS(status))
+ goto CLEANUP;
+
+ // Make sure it terminates on unexpected errors or if the parent exits.
+ // Note that we snapshotted handles for inheritance before creating the job, so
+ // it won't prolong clone's lifetime.
+ jobLimits.BasicLimitInformation.LimitFlags =
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE |
+ JOB_OBJECT_LIMIT_DIE_ON_UNHANDLED_EXCEPTION |
+ JOB_OBJECT_LIMIT_BREAKAWAY_OK;
+
+ status = NtSetInformationJobObject(
+ hJob,
+ JobObjectExtendedLimitInformation,
+ &jobLimits,
+ sizeof(jobLimits)
+ );
+
+ if (!NT_SUCCESS(status))
+ goto CLEANUP;
+
+ if (Flags & H2_CLONE_PROCESS_FLAGS_INHERIT_ALL_HANDLES)
+ {
+ // Enable inheritance for all handles
+ H2SetInheritanceHandles(
+ handleSnapshot->Handles,
+ handleSnapshot->NumberOfHandles,
+ H2InheritanceEnable
+ );
+ }
+
+ // NOTE: when debugging, do not single-step over RtlCloneUserProcess
+ // because it inserts a breakpoint (int 3) that will be copied
+ // to the clone, preventing it from executing the callback.
+
+ // Clone the current process
+ status = RtlCloneUserProcess(
+ RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED |
+ (Flags & (H2_CLONE_PROCESS_FLAGS_INHERIT_HANDLES | H2_CLONE_PROCESS_FLAGS_INHERIT_ALL_HANDLES) ?
+ RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES : 0),
+ NULL,
+ NULL,
+ NULL,
+ &processInfo
+ );
+
+ if (status == STATUS_PROCESS_CLONED)
+ {
+ // Execute the callback in the clone...
+
+ status = STATUS_UNHANDLED_EXCEPTION;
+
+ __try
+ {
+ #pragma warning(suppress: 6387) // Argument may be NULL
+ status = Callback(Argument);
+ }
+ __finally
+ {
+ NtTerminateProcess(NtCurrentProcess(), status);
+ }
+ }
+
+ // Executing in the parent...
+
+ if (Flags & H2_CLONE_PROCESS_FLAGS_INHERIT_ALL_HANDLES)
+ {
+ // Restore handle inheritance
+ H2SetInheritanceHandles(
+ handleSnapshot->Handles,
+ handleSnapshot->NumberOfHandles,
+ H2InheritanceRestore
+ );
+ }
+
+ if (!NT_SUCCESS(status))
+ goto CLEANUP;
+
+ // Try to put the clone into the job, but don't fail if we can't
+ // (which might happen on Windows 7, before nested jobs support)
+ NtAssignProcessToJobObject(hJob, processInfo.ProcessHandle);
+
+ // Replace the primary token, if necessary
+ if (TokenHandle)
+ {
+ PROCESS_ACCESS_TOKEN tokenInfo;
+
+ memset(&tokenInfo, 0, sizeof(tokenInfo));
+ tokenInfo.Token = TokenHandle;
+
+ status = NtSetInformationProcess(
+ processInfo.ProcessHandle,
+ ProcessAccessToken,
+ &tokenInfo,
+ sizeof(tokenInfo)
+ );
+
+ if (!NT_SUCCESS(status))
+ goto CLEANUP;
+ }
+
+ // Let the clone run
+ status = NtResumeThread(processInfo.ThreadHandle, NULL);
+
+ if (!NT_SUCCESS(status))
+ goto CLEANUP;
+
+ do
+ {
+ // Wait for completion
+ status = NtWaitForSingleObject(processInfo.ProcessHandle, Alertable, Timeout);
+ } while (status == STATUS_USER_APC || status == STATUS_ALERTED);
+
+ if (!NT_SUCCESS(status))
+ goto CLEANUP;
+
+ // Terminate the clone on timeout
+ if (status == STATUS_TIMEOUT)
+ {
+ timedOut = TRUE;
+ NtTerminateProcess(processInfo.ProcessHandle, STATUS_TIMEOUT);
+ }
+
+ // Forward the result status to the caller
+ if (CompletionStatus)
+ {
+ PROCESS_BASIC_INFORMATION basicInfo;
+
+ status = NtQueryInformationProcess(
+ processInfo.ProcessHandle,
+ ProcessBasicInformation,
+ &basicInfo,
+ sizeof(basicInfo),
+ NULL
+ );
+
+ if (!NT_SUCCESS(status))
+ goto CLEANUP;
+
+ *CompletionStatus = basicInfo.ExitStatus;
+ }
+
+ if (timedOut)
+ status = STATUS_TIMEOUT;
+
+CLEANUP:
+ if (hJob)
+ {
+ NtTerminateJobObject(hJob, STATUS_CANCELLED);
+ NtClose(hJob);
+ }
+
+ if (processInfo.ProcessHandle)
+ {
+ NtTerminateProcess(processInfo.ProcessHandle, STATUS_CANCELLED);
+ NtClose(processInfo.ProcessHandle);
+ }
+
+ if (processInfo.ThreadHandle)
+ NtClose(processInfo.ThreadHandle);
+
+ if (handleSnapshot)
+ H2ReleaseHandleAttributes(handleSnapshot);
+
+ return status;
+}
diff --git a/5.Library/Library/cloning.h b/5.Library/Library/cloning.h
new file mode 100644
index 0000000..5ecc26a
--- /dev/null
+++ b/5.Library/Library/cloning.h
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2023 Hunt & Hackett.
+ *
+ * This file is licensed under the MIT license.
+ *
+ * Authors:
+ * diversenok
+ *
+ */
+
+#ifndef _CLONING_H
+#define _CLONING_H
+
+#include
+#include
+
+/* Handle inheritance */
+
+typedef struct _H2_HANDLE_ENTRY {
+ HANDLE HandleValue;
+ ULONG HandleAttributes;
+ ULONG GrantedAccess;
+ ULONG ObjectTypeIndex;
+} H2_HANDLE_ENTRY, *PH2_HANDLE_ENTRY;
+
+typedef struct _H2_HANDLE_SNAPSHOT {
+ ULONG_PTR NumberOfHandles;
+ _Field_size_(NumberOfHandles) H2_HANDLE_ENTRY Handles[1];
+} H2_HANDLE_SNAPSHOT, *PH2_HANDLE_SNAPSHOT;
+
+typedef enum _H2_INHERITACE_OPERATION {
+ H2InheritanceEnable = 1,
+ H2InheritanceDisable,
+ H2InheritanceRestore,
+} H2_INHERITACE_OPERATION;
+
+// Snapshot current handle values and attributes
+NTSTATUS
+NTAPI
+H2CaptureHandleAttributes(
+ _Outptr_ PH2_HANDLE_SNAPSHOT *Snapshot
+);
+
+// Adjust inheritance for a set of handles
+VOID
+NTAPI
+H2SetInheritanceHandles(
+ _In_reads_(NumberOfHandles) PH2_HANDLE_ENTRY Handles,
+ _In_ ULONG_PTR NumberOfHandles,
+ _In_ H2_INHERITACE_OPERATION Operation
+);
+
+// Free the handle snapshot
+VOID
+NTAPI
+H2ReleaseHandleAttributes(
+ _Frees_ptr_ PH2_HANDLE_SNAPSHOT Snapshot
+);
+
+/* Shared memory */
+
+// Allocate a shared memory region for communicating with the clone
+NTSTATUS
+NTAPI
+H2MapSharedMamory(
+ _Outptr_ PVOID *BaseAddress,
+ _In_ SIZE_T AllocationSize,
+ _Out_opt_ SIZE_T *ReturnedSize
+);
+
+// Free a shared memory region
+NTSTATUS
+NTAPI
+H2UnmapSharedMamory(
+ _In_ PVOID BaseAddress
+);
+
+/* Cloning */
+
+// Attach the clone to the console of the parent process
+BOOL
+WINAPI
+H2AttachToParentConsole(
+ VOID
+);
+
+#define H2_CLONE_PROCESS_FLAGS_INHERIT_HANDLES 0x00000001
+#define H2_CLONE_PROCESS_FLAGS_INHERIT_ALL_HANDLES 0x00000002
+
+// Execute a callback in a clone of the current process
+NTSTATUS
+NTAPI
+H2ExecuteInClone(
+ _In_ ULONG Flags,
+ _In_opt_ HANDLE TokenHandle,
+ _In_ PUSER_THREAD_START_ROUTINE Callback,
+ _In_opt_ PVOID Argument,
+ _Out_opt_ PNTSTATUS CompletionStatus,
+ _In_opt_ PLARGE_INTEGER Timeout,
+ _In_ BOOLEAN Alertable
+);
+
+#endif
diff --git a/5.Library/example.c b/5.Library/example.c
new file mode 100644
index 0000000..dc90cde
--- /dev/null
+++ b/5.Library/example.c
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2023 Hunt & Hackett.
+ *
+ * This file is licensed under the MIT license.
+ *
+ * Authors:
+ * diversenok
+ *
+ */
+
+#include
+#include
+#include
+#include
+
+NTSTATUS
+NTAPI
+Payload(
+ PVOID Parameter
+)
+{
+ if (!H2AttachToParentConsole())
+ return NTSTATUS_FROM_WIN32(GetLastError());
+
+ wprintf_s(L"Hello from clone! My PID is: %zu\r\n", (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueProcess);
+ return STATUS_SUCCESS;
+}
+
+int wmain(int argc, wchar_t* argv[])
+{
+ NTSTATUS status;
+ NTSTATUS completionStatus;
+
+ wprintf_s(L"Simple demo for Process Cloning by Hunt & Hackett.\r\n\r\n");
+ wprintf_s(L"Hello from parent process! My PID is: %zu\r\n", (ULONG_PTR)NtCurrentTeb()->ClientId.UniqueProcess);
+
+ // H2ExecuteInClone is a wrapper that clones the current process,
+ // executes the provided function there, waits for its completion,
+ // and, optionally, forwards the exit status. It also supports
+ // inheriting all handles and using custom primary token for the
+ // new process.
+
+ status = H2ExecuteInClone(
+ H2_CLONE_PROCESS_FLAGS_INHERIT_ALL_HANDLES,
+ NULL,
+ Payload,
+ NULL,
+ &completionStatus,
+ NULL,
+ FALSE
+ );
+
+ if (!NT_SUCCESS(status))
+ {
+ wprintf_s(L"Unable to clone the current process: 0x%x\r\n", status);
+ return status;
+ }
+
+ if (!NT_SUCCESS(completionStatus))
+ {
+ wprintf_s(L"Clone exited with error code: 0x%x\r\n", status);
+ return status;
+ }
+
+ wprintf_s(L"Clone exited with a successful code.\r\n");
+ return STATUS_SUCCESS;
+}
diff --git a/Dependencies/phnt b/Dependencies/phnt
new file mode 160000
index 0000000..7c1adb8
--- /dev/null
+++ b/Dependencies/phnt
@@ -0,0 +1 @@
+Subproject commit 7c1adb8a7391939dfd684f27a37e31f18d303944
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..2f114d2
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 Hunt & Hackett
+
+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.
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..d0ea6b6
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,427 @@
+# The Definitive Guide To Process Cloning on Windows
+
+![Blog cover](pictures/01-intro.jpg)
+
+While not everybody knows it, Windows natively offers functionality similar to the famous Unix `fork()` API. The primary OS mechanism that makes it possible is the support for **cloning the address space** of a given process. Despite its existence, this feature feels odd for an operating system that went for a completely different design path with process creation. And, as we know from experience in the cybersecurity industry, unusual often means untested, which serves as a great starting point for discovering new attack vectors that rely on abusing edge cases. That's why we often hear offers from security researchers to weaponize process cloning for offensive purposes, such as stealthy memory dumping [[1]](https://billdemirkapi.me/abusing-windows-implementation-of-fork-for-stealthy-memory-operations/), [[2]](https://splintercod3.blogspot.com/p/the-hidden-side-of-seclogon-part-2.html) and code injection [[3]](https://i.blackhat.com/EU-22/Thursday-Briefings/EU-22-Nissan-DirtyVanity.pdf). These ideas get exposure at the [top hacking conferences](https://www.blackhat.com/eu-22/briefings/schedule/index.html#dirty-vanity-a-new-approach-to-code-injection--edr-bypass-28417), so there seems to be potential.
+
+This article aims to provide the reader with a **comprehensive guide** to the technical details and the underlying design decisions of process cloning on Windows and how they affect its usability. We will explore why most techniques for code injection via cloning will almost inevitably struggle with evading security products, yet other attack vectors like credential dumping might find their niche. There is a lot to cover, so let's dive right in!
+
+> **Disclaimer:** the system functions (alongside their behavior) described below is officially undocumented and subject to potential (though unlikely) change. We assume that the myriad of pitfalls described below already serves as a good reason why, but let's stress it out: please, don't rely on them in production code!
+
+# Recap: Process Creation
+
+Windows provides a handful of documented ways for starting new processes: [CreateProcess](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw), [ShellExecuteEx](https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecuteexw), [WMI](https://learn.microsoft.com/en-us/windows/win32/wmisdk/about-wmi)'s [Win32_Process.Create](https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/create-method-in-class-win32-process), [CreateProcessWithLogon](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createprocesswithlogonw), [WinExec](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-winexec) — you name it. Despite this seeming diversity, each one eventually calls [CreateProcessAsUser](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessasuserw) (technically, `CreateProcessInternalW` implemented in kernelbase.dll), either directly or indirectly (i.e., via RPC). Going a bit lower, we reach the final stop before jumping into the kernel — **[`NtCreateUserProcess`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L2201-L2216)** from ntdll.dll. Here are the most noticeable traits of this syscall:
+- It **understands filenames** plus opens and **parses the executable** on our behalf.
+- It automatically **writes process parameters** (command line arguments, current directory, environment variables, etc.) into the new process.
+- It **always creates the initial thread**, automatically selecting stack size and entry point address.
+
+Here is a snippet from the function's definition in [phnt](https://github.com/processhacker/phnt):
+
+```c
+typedef struct _PS_ATTRIBUTE
+{
+ ULONG_PTR Attribute; // Any of ~30 pre-defined PS_ATTRIBUTE_* values
+ SIZE_T Size;
+ ULONG_PTR Value;
+ PSIZE_T ReturnLength;
+} PS_ATTRIBUTE, *PPS_ATTRIBUTE;
+
+typedef struct _PS_ATTRIBUTE_LIST
+{
+ SIZE_T TotalLength;
+ PS_ATTRIBUTE Attributes[ANYSIZE_ARRAY];
+} PS_ATTRIBUTE_LIST, *PPS_ATTRIBUTE_LIST;
+
+NTSYSCALLAPI
+NTSTATUS
+NTAPI
+NtCreateUserProcess(
+ _Out_ PHANDLE ProcessHandle,
+ _Out_ PHANDLE ThreadHandle,
+ _In_ ACCESS_MASK ProcessDesiredAccess,
+ _In_ ACCESS_MASK ThreadDesiredAccess,
+ _In_opt_ POBJECT_ATTRIBUTES ProcessObjectAttributes,
+ _In_opt_ POBJECT_ATTRIBUTES ThreadObjectAttributes,
+ _In_ ULONG ProcessFlags, // PROCESS_CREATE_FLAGS_*
+ _In_ ULONG ThreadFlags, // THREAD_CREATE_FLAGS_*
+ _In_opt_ PRTL_USER_PROCESS_PARAMETERS ProcessParameters,
+ _Inout_ PPS_CREATE_INFO CreateInfo,
+ _In_opt_ PPS_ATTRIBUTE_LIST AttributeList
+ );
+```
+
+As you can see, `NtCreateUserProcess` is a high-level API that supports [substantial customization](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L1847-L1881). Conveniently, it assumes reasonable defaults and can automatically infer almost everything it needs. So, while it might look intimidating, calling `NtCreateUserProcess` is not that complex, especially compared to other duties of `CreateProcess`, such as registering the new process with CSRSS.
+
+Before the era of Windows Vista (when `NtCreateUserProcess` didn't exit), `CreateProcess` used to rely on a different syscall — **[`NtCreateProcessEx`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L1252-L1265)**. This older alternative exists until this day, although it remains available primarily for backward compatibility and supporting minimal processes. Here are its most distinct features:
+- Instead of files, `NtCreateProcessEx` **only accepts memory projection objects** internally called image sections.
+- This API is **unaware of** such user-mode concepts as **process parameters** (the previously mentioned command line arguments and others), thus, requiring the caller to allocate and deliver them manually.
+- It **does not create threads** — only the process itself, leaving the rest up to the caller.
+
+The definition of this function looks innocently straightforward:
+
+```c
+NTSYSCALLAPI
+NTSTATUS
+NTAPI
+NtCreateProcessEx(
+ _Out_ PHANDLE ProcessHandle,
+ _In_ ACCESS_MASK DesiredAccess,
+ _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes,
+ _In_ HANDLE ParentProcess,
+ _In_ ULONG Flags, // PROCESS_CREATE_FLAGS_*
+ _In_opt_ HANDLE SectionHandle,
+ _In_opt_ HANDLE DebugPort,
+ _In_opt_ HANDLE TokenHandle,
+ _Reserved_ ULONG JobMemberLevel
+ );
+```
+
+> Technically, there is a pair of functions called `NtCreateProcess` and `NtCreateProcessEx`. But since the first one is merely a wrapper that invokes the second, we will refer to them as one.
+
+Don't let the function prototype deceive you: using `NtCreateProcessEx` in place of `NtCreateUserProcess` requires a **significant amount of work** consisting of many additional steps, such as opening and parsing the PE file, adjusting PEB, and creating the initial thread. Still, in exchange for this extra complexity, `NtCreateProcessEx` offers unique capabilities that we can weaponize against security software.
+You can read more about these techniques (and even find some demos) in the [Process Tampering section](https://www.huntandhackett.com/blog/concealed-code-execution-techniques-and-detection) of one of our previous blog posts.
+
+Now, how does it all relate to cloning? The answer is simple: both syscalls support it, yet, with vastly different limitations. Shortly we will learn about the differences and how they affect various usage scenarios; for now, let's explore the role of inheritance in process creation. After all, what is cloning if not an extreme manifestation of resource inheritance?
+
+# What Does A New Process Inherit
+
+Process creation includes three roles — the *parent*, the *caller*, and the *child*:
+
+1. The **child** is the new process we create.
+2. The **caller** is whoever invokes the process creation routine and, thus, is the one who controls which executable to run, its arguments, environment variables, and other parameters specified upon creation. The caller also determines which process to assign as a parent. Once the system creates the child, the caller gets handles to it and its initial thread.
+3. The **parent** is the process that supplies the defaults for most inheritable properties (like handles) discussed below. Usually, the caller and the parent are one entity, but they don't have to be. In case of a mismatch, we say that the caller uses re-parenting. Of course, specifying another process as a parent requires opening it for `PROCESS_CREATE_PROCESS` (part of `GENERIC_WRITE` access), both when using the modern `NtCreateUserProcess` and the legacy `NtCreateProcessEx` syscalls.
+
+![Re-parenting during normal process creation](pictures/02-reparenting.png)
+
+Looking at the things that the child inherits from the parent, we can highlight a few properties:
+
+- **[Security context](https://learn.microsoft.com/en-us/windows/win32/secauthz/access-tokens)**. Here, the caller has two options: explicitly provide a token, or let the child inherit one from the parent. The rules for token inheritance have a few caveats, but generally, the child receives an identical copy of the parent's token. There are some exceptions (the no-child-process flags, trust levels, mandatory labels, security attributes, etc.), but they are all out of the scope of the discussion. This feature is supported by both process creation methods, despite the information you might find on the internet that comes from an outdated definition for `NtCreateProcess[Ex]`.
+
+- **[Handles](https://learn.microsoft.com/en-us/windows/win32/sysinfo/kernel-objects)**. The system can copy references to kernel objects from the parent's to the child's handle table. Again, the caller chooses whether to enable/disable this feature and, in the case of `NtCreateUserProcess`, can even narrow it down to a subset of entries. Keep in mind that copying only applies to handles already marked as inheritable. Although it is easy for a process to temporarily change the inheritance mode for any of its handles via [`NtSetInformationObject`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntobapi.h#L142-L150), it might not be so trivial to achieve during re-parenting.
+
+- **[Job object](https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects)**, or the lack of one. Depending on the limitations configured on the parent's job, this feature might be either advantageous or unfavorable. Sometimes, the caller can request to break away the child from the job, which requires a special flag or system-level permissions. Alternatively, the caller can also put the child into more jobs — directly during creation with `NtCreateUserProcess` or post-factum via [`NtAssignProcessToJobObject`](https://learn.microsoft.com/en-us/windows/win32/api/jobapi2/nf-jobapi2-assignprocesstojobobject).
+
+- **[Virtual address space](https://learn.microsoft.com/en-us/windows/win32/memory/virtual-address-space)**. That's where things get interesting. Instead of initializing a clean memory for the child (and mapping its image there), the new process (called a clone in this case) receives a replica of the parent's address space. That includes duplicating all private pages plus most of the mapped memory regions. Because this feature is incompatible with selecting another executable, both `NtCreateUserProcess` and `NtCreateProcessEx` use this mode when the caller doesn't provide a filename or section object on input.
+
+But enough theory. We will discuss the exact rules of address space inheritance (that makes the bulk of cloning) in more detail later; for now, we are ready to start experimenting.
+
+# Cloning for Execution
+
+*Cloning offers a peculiar primitive*: it allows executing code in a temporary process identical to ours while skipping its initialization. In other words, if you don't have control over the `main()` function (because your code is merely part of a library, for example) and, therefore, cannot introduce custom startup arguments, yet, have to perform a simple operation that requires a new process, cloning seems like an ideal candidate. Just beware that you might be asking for too much if you want to use it in complex scenarios like for parallel handling of client requests or isolating browser tabs. Process cloning is certainly not free from caveats, which we will address shortly.
+
+## NtCreateUserProcess
+
+The recipe for cloning the current process with `NtCreateUserProcess` is exceedingly simple: don't specify the **image filename** (which usually comes as one of the Ps- attributes) and ignore the `ProcessParameters` argument.
+
+```c
+PS_CREATE_INFO createInfo = { sizeof(createInfo) };
+HANDLE processHandle;
+HANDLE threadHandle;
+
+NTSTATUS status = NtCreateUserProcess(
+ &processHandle,
+ &threadHandle,
+ PROCESS_ALL_ACCESS,
+ THREAD_ALL_ACCESS,
+ NULL, // ProcessObjectAttributes
+ NULL, // ThreadObjectAttributes
+ PROCESS_CREATE_FLAGS_INHERIT_HANDLES, // ProcessFlags
+ 0, // ThreadFlags
+ NULL, // ProcessParameters
+ &createInfo,
+ NULL // AttributeList
+);
+```
+
+Of course, you can make it more complex by specifying non-conflicting Ps- attributes or overriding security descriptors, but you don't have to. As mentioned earlier, this syscall is quite intelligent — in addition to creating the process object (which, in this case, comes with a copy of our address space), it also **clones the current thread**. Naturally, this new thread continues executing (in a world almost indistinguishable from ours) right after exiting from the syscall.
+
+> Technically, the cloned thread starts in `LdrInitializeThunk` (just like any other thread). But it hardly matters because it skips loader initialization and quickly jumps back to the next instruction after the syscall.
+
+The semantic of using process cloning on Windows, therefore, becomes virtually identical to calling `fork()` on Unix-like systems:
+
+```c
+NTSTATUS status = NtCreateUserProcess(...);
+
+if (status == STATUS_PROCESS_CLONED)
+{
+ // Executing in the clone/child
+}
+else
+{
+ // Executing in the parent/caller
+}
+```
+
+To allow threads to tell each other apart, `NtCreateUserProcess` returns a **special status**: the cloned one always gets `STATUS_PROCESS_CLONED` (a successful code of 0x00000129) while the original receives everything else. The system also automatically adjusts the [`ClientID`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpebteb.h#L285) field in the new thread's [TEB](https://learn.microsoft.com/en-us/windows/win32/debug/thread-environment-block--debugging-notes-) (which remains at the same address) so that [`GetCurrentProcessId`](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentprocessid) and [`GetCurrentThreadId`](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadid) continue returning relevant information. It also sets two other flags: [`InheritedAddressSpace`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpebteb.h#L71) in PEB and [`ClonedThread`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpebteb.h#L429) in TEB. The first one is used and cleared internally by `LdrpInitialize`; the second remains set and further allows distinguishing the clone from the parent.
+
+You can find the demo program that clones itself via `NtCreateUserProcess` in the [dedicated section](https://github.com/huntandhackett/process-cloning/blob/master/1.NtCreateUserProcess/main.c) of the repository.
+
+## NtCreateProcessEx?
+
+The first step of cloning the current process with `NtCreateProcessEx` is also simple — merely invoke the routine without specifying the **section handle**:
+
+```c
+OBJECT_ATTRIBUTES objAttr;
+HANDLE hProcess;
+
+InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
+
+NTSTATUS status = NtCreateProcessEx(
+ &hProcess,
+ PROCESS_ALL_ACCESS,
+ &objAttr,
+ NtCurrentProcess(), // ParentProcess
+ PROCESS_CREATE_FLAGS_INHERIT_HANDLES,
+ NULL, // SectionHandle
+ NULL, // DebugPort
+ NULL, // TokenHandle
+ 0 // Reserved
+);
+```
+
+The code above clones the process, but in contrast with `NtCreateUserProcess`, it doesn't clone the calling thread (or any threads, for that matter). Usually, we would continue by doing it ourselves using either of two options:
+1. Using the modern [`NtCreateThreadEx`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L2230-L2245) function that allocates a new TEB and stack and invokes the provided address.
+2. Using the legacy [`NtCreateThread`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L1445-L1457) API that allows repurposing existing stacks and starting with an [arbitrary context](https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreadcontext). It does create a new TEB, nonetheless.
+
+Unfortunately, it doesn't matter which method we prefer because the results will be identically disappointing: `STATUS_PROCESS_IS_TERMINATING`, or, in other words, "*An attempt was made to access an exiting process.*" The system considers the cloned threadless process as waiting for deletion and, thus, refuses to create threads in it – something we inevitably need to execute code. Sorry, but `NtCreateProcessEx`-based cloning is **incompatible with code execution**.
+
+> Note that it wasn't always the case. The kernel allowed creating threads in such processes until Windows 8.1.
+
+## RtlCloneUserProcess
+
+Returning to `NtCreateUserProcess`, we should notice that ntdll offers several wrappers that call this syscall under the hood and simplify its usage: [`RtlCreateUserProcess`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntrtl.h#L2745-L2759), [`RtlCreateUserProcessEx`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntrtl.h#L2763-L2787), and [**`RtlCloneUserProcess`**](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntrtl.h#L2815-L2831). The first two don't support cloning because they always specify the filename; the third one, however, is entirely dedicated to this task:
+
+```c
+typedef struct _RTL_USER_PROCESS_INFORMATION
+{
+ ULONG Length;
+ HANDLE ProcessHandle;
+ HANDLE ThreadHandle;
+ CLIENT_ID ClientId;
+ SECTION_IMAGE_INFORMATION ImageInformation;
+} RTL_USER_PROCESS_INFORMATION, *PRTL_USER_PROCESS_INFORMATION;
+
+#define RTL_CLONE_PROCESS_FLAGS_CREATE_SUSPENDED 0x00000001
+#define RTL_CLONE_PROCESS_FLAGS_INHERIT_HANDLES 0x00000002
+#define RTL_CLONE_PROCESS_FLAGS_NO_SYNCHRONIZE 0x00000004
+
+NTSYSAPI
+NTSTATUS
+NTAPI
+RtlCloneUserProcess(
+ _In_ ULONG ProcessFlags,
+ _In_opt_ PSECURITY_DESCRIPTOR ProcessSecurityDescriptor,
+ _In_opt_ PSECURITY_DESCRIPTOR ThreadSecurityDescriptor,
+ _In_opt_ HANDLE DebugPort,
+ _Out_ PRTL_USER_PROCESS_INFORMATION ProcessInformation
+ );
+```
+
+As usually happens with wrappers, `RtlCloneUserProcess` doesn't offer the full customization potential of `NtCreateUserProcess`. For example, it doesn't support immediately putting the clone into a [job object](https://learn.microsoft.com/en-us/windows/win32/procthread/job-objects) — something that might be useful for improving stability. Luckily, [`NtAssignProcessToJobObject`](https://learn.microsoft.com/en-us/windows/win32/api/jobapi2/nf-jobapi2-assignprocesstojobobject) can achieve that after process creation. Another thing you might notice is that we cannot specify the token for the new process. However, it is not `RtlCloneUserProcess`'s fault — `PS_ATTRIBUTE_TOKEN` is incompatible with cloning even on `NtCreateUserProcess`'s level. Perhaps, that's because tokens also dictate the session to which the process belongs, and cross-session cloning is something the system cannot afford. And again, luckily, there is a workaround: we can use [`NtSetInformationProcess`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L1359-L1367) with the [`ProcessAccessToken`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L126) info class to change the clone's primary token (but not the session!) before it starts.
+
+So, why is `RtlCloneUserProcess` useful when we already have the more flexible `NtCreateUserProcess`? The reason might be surprising: **we cannot re-implement its functionality**, at least not entirely and precisely. To shed some light on this statement, we can consider a quote from Microsoft Research's [paper](https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf) that highlights the conceptual problems of Unix's `fork()`, many of which equally apply to Windows's process cloning:
+
+> Any non-trivial OS facility must document its behaviour across a fork, and **user-mode libraries must be prepared for their state to be forked at any time**.
+
+If you look closer at `RtlCloneUserProcess` under a decompiler, you'll see that unless the caller specifies the no-synchronize flag, the function **prepares the state of ntdll** for cloning. More specifically:
+- It drains the thread pool work queue.
+- It temporarily acquires a handful of synchronization locks: the loader lock, the PEB lock, the TLS and FLS locks, heap manager locks, etc.
+
+Because most of these operations use unexported variables and functions, manually reproducing this behavior is problematic. That's why you should prefer calling `RtlCloneUserProcess` instead of `NtCreateUserProcess`.
+
+You can find the demo program that clones itself via `RtlCloneUserProcess` in the [dedicated section](https://github.com/huntandhackett/process-cloning/blob/master/2.RtlCloneUserProcess/main.c) of the repository.
+
+## The Myriads of Caveats
+
+Does it mean we are safe with `RtlCloneUserProcess`, then? Not even close. The primary factor determining whether the cloned code will execute correctly or crash dramatically is the **variety of OS facilities** it uses. On the one side of the scale, take programs that are single-threaded implementations of pure mathematical algorithms. Such examples are as compatible with cloning as they can be because the complete state of the memory is enough for them to continue operating without drawbacks. On the extreme opposite side, you can imagine a muli-threaded GUI application that uses hardware acceleration. Any of these properties is [problematic](https://www.microsoft.com/en-us/research/uploads/prod/2019/04/fork-hotos19.pdf) even when forking on Unix-like systems; combined, plus used on Windows, they are a recipe for disaster.
+
+So, let's walk through several categories of OS resources and document how they behave with cloning:
+
+- First and foremost, we have **kernel handles**. Because the handle table is a per-process structure shared by all libraries loaded into the application, most entries inside are merely an implementation detail of somebody else's code. As previously mentioned, it's possible to [iterate through all handles](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L168) and [mark them as inheritable](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntobapi.h#LL52C39-L52C69). Once we clone the process, the OS copies the handles, preserving their indices. There is a problem, however: not all kernel types and handle instances support that. Notable examples of such exceptions include [exclusive handles](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/phnt_ntdef.h#L203), ALPC ports, and types protected by custom [Ob- callbacks](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-obregistercallbacks). These entries will leave vacant spots in the cloned handle table. So if we cannot duplicate such objects by other means, it might be a good idea to occupy the corresponding slots with placeholder objects solely to prevent accidental collisions and double-use.
+
+- Secondly, there are **threads**. `NtCreateUserProcess` (and, subsequently, `RtlCloneUserProcess`) clones only the calling thread and not any other that might exist concurrently. As a general rule, the caller doesn't (and cannot) know the details about all threads in their process. Some might belong to Ntdll's thread pool, others to the runtime, shell extensions, 3rd-party libraries, security products, etc. It's usually safer to ignore them than to let them run uncontrolled. Note that cloning does copy stacks and TEBs of all threads, so if you ever want to try, [`NtCreateThread`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L1445-L1457) can help since it supports reusing stacks and starting with arbitrary contexts. Additionally, there is an issue with existing [thread pools](https://learn.microsoft.com/en-us/windows/win32/procthread/thread-pools): they are not meant for sharing and will continue creating threads in the parent process even when used from the clone.
+
+- Closely related to the previous item, there are problems with **synchronization primitives** that can result in deadlocks. First, our attempt to fix compatibility by inheriting as many handles as possible has a downside — it introduces oversharing of kernel synchronization objects. Acquiring shared [mutexes](https://learn.microsoft.com/en-us/windows/win32/sync/mutex-objects), [events](https://learn.microsoft.com/en-us/windows/win32/sync/event-objects), or [semaphores](https://learn.microsoft.com/en-us/windows/win32/sync/semaphore-objects) in the clone also contends with the parent and might even deadlock its threads. User-mode primitives such as [wait-on-address](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-waitonaddress), [condition variables](https://learn.microsoft.com/en-us/windows/win32/sync/condition-variables), [SRW locks](https://learn.microsoft.com/en-us/windows/win32/sync/slim-reader-writer--srw--locks), and [critical sections](https://learn.microsoft.com/en-us/windows/win32/sync/critical-section-objects) are also not trouble-free. On modern versions of Windows, they rely on [`NtWaitForAlertByThreadId`](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L1672-L1678), and the IDs of cloned threads are different.
+
+- Strictly speaking, the new process does not always inherit a precise replica of the parent's **address space**. The catch is with mapped and image-backed memory. [`NtMapViewOfSection`](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-zwmapviewofsection) has an argument called `InheritDispositon` that controls whether the system should share or unmap the memory view during cloning (note that the official documentation uses the term "child processes," which is, technically, incorrect because typical child processes don't inherit the address space). Even though most Win32 API functions ([`LoadLibrary`](https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibraryw), [`MapViewOfFileEx`](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-mapviewoffileex), etc.) set it to `ViewShare`, some OS components can specify `ViewUnmap` instead. Notable examples include the CSR shared memory, CSR port heap, and GDI shared handle table. Luckily, this problem doesn't affect private regions and is otherwise predictable. Later in the text, we showcase a tool that inspects the layout of the address space of a given process and highlights problematic areas.
+
+So, what is safe to call from the clone? At the very least, most **NT syscalls**. Of course, the OS offers many other higher-level abstractions that rely on the previous facilities, which we cannot exhaustively cover here. Here are some examples of what might work and what not:
+- **Loading more DLLs** in the clone gets stuck on Ntdll's locks. Alternatively, if we instruct `RtlCloneUserProcess` to bypass synchronization, it crashes with an access violation on the CSR port heap. That's because resolving DLL dependencies requires generating [SxS activation contexts](https://learn.microsoft.com/en-us/windows/win32/sbscs/activation-contexts) [for redirection](https://learn.microsoft.com/en-us/windows/win32/sbscs/dll-com-redirection-on-windows) and, thus, calling into CSR. Therefore, make sure to load the necessary libraries beforehand. Generally, we should expect most functionality that internally uses activation contexts (like COM) to misbehave.
+- Yet, some **RPC-based functions** might still work, provided the clone inherits enough handles. For instance, [SCM](https://learn.microsoft.com/en-us/windows/win32/services/service-control-manager), [LSA](https://learn.microsoft.com/en-us/windows/win32/secmgmt/lsa-policy), [SAM](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntsam.h), and [WinStation](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/winsta.h) API operate mostly correctly. That means we can control services, look up SIDs, perform logon, etc.
+- **Console I/O** requires a slight notch in the form of re-attaching: [`FreeConsole`](https://learn.microsoft.com/en-us/windows/console/freeconsole) + [`AttachConsole(ATTACH_PARENT_PROCESS)`](https://learn.microsoft.com/en-us/windows/console/attachconsole) does the trick.
+- **Window- and graphics-related APIs** are unlikely to work, at least because the new process skipped win32k initialization and is missing the GDI shared handle table. Sorry, but no easy `MessageBox`'es from clones.
+
+# Cloning for Remote Execution
+
+If the previous discussion hasn't demotivated you from continuing experimenting, we can take the next logical step and apply cloning to other processes. At first glance, it might appear we merely need to specify the parent process handle to make it happen. A keen reader might spot a conceptual problem: `NtCreateUserProcess` is supposed to create a thread (that's one of the rules), yet cloning the caller's thread cannot work because it belongs to the wrong process. Cloning any of the parent's existing (unrelated) threads would also be strange since we don't control them. Finally, the function prototype has no parameters for specifying the start address. It all suggests that `NtCreateUserProcess` **cannot clone other processes**. Indeed, trying to do so causes it to fail with `STATUS_INVALID_PARAMETER`.
+
+## RtlCreateProcessReflection
+
+Here comes the lifehack of system programming: if you cannot do something cross-process, create a remote thread and do it in-process. Welcome, [**`RtlCreateProcessReflection`**](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntrtl.h#L2851-L2878):
+
+```c
+#define RTL_PROCESS_REFLECTION_FLAGS_INHERIT_HANDLES 0x2
+#define RTL_PROCESS_REFLECTION_FLAGS_NO_SUSPEND 0x4
+#define RTL_PROCESS_REFLECTION_FLAGS_NO_SYNCHRONIZE 0x8
+#define RTL_PROCESS_REFLECTION_FLAGS_NO_CLOSE_EVENT 0x10
+
+typedef struct _RTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION
+{
+ HANDLE ReflectionProcessHandle;
+ HANDLE ReflectionThreadHandle;
+ CLIENT_ID ReflectionClientId;
+} RTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION, *PRTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION;
+
+NTSYSAPI
+NTSTATUS
+NTAPI
+RtlCreateProcessReflection(
+ _In_ HANDLE ProcessHandle,
+ _In_ ULONG Flags,
+ _In_opt_ PVOID StartRoutine,
+ _In_opt_ PVOID StartContext,
+ _In_opt_ HANDLE EventHandle,
+ _Out_opt_ PRTLP_PROCESS_REFLECTION_REFLECTION_INFORMATION ReflectionInformation
+ );
+```
+
+Under the hood, this API performs the following sequence of operations involving three processes:
+
+**Inside the caller**:
+ 1. It prepares and maps a shared memory region between the caller and the parent, passing the required parameters via an `RTLP_PROCESS_REFLECTION_CONTEXT` structure.
+ 2. It creates a few events for synchronizing between processes and duplicates them into the parent.
+ 3. It creates a thread in the parent on an unexported `RtlpProcessReflectionStartup` function and waits until this thread either exits or notifies us that the clone has started.
+ 4. It duplicates the process and thread handles of the clone from the parent into the caller and exits.
+
+**Inside the parent**:
+ 1. After a bit of preparation, `RtlpProcessReflectionStartup` calls `RtlCloneUserProcess`.
+ 2. It duplicates event handles to the new process.
+ 3. Once the clone starts, it notifies the caller and exits.
+
+**Inside the clone**:
+ 1. It waits on the user-provided event (if there is one).
+ 2. Depending on the flags, `RtlpProcessReflectionStartup` invokes the provided callback, suspends itself, or does nothing.
+ 3. After that, the clone terminates.
+
+![Process reflection under the hood](pictures/03-reflection.png)
+
+Under normal circumstances, `RtlCreateProcessReflection` (as well as the injected thread in the parent) should exit almost immediately without waiting for the clone to complete. We can summarize the logic this API executes inside the new process with the following code:
+
+```c
+if (Context->ReflectionStartEvent)
+{
+ // Wait on the user-provided event
+ NtWaitForSingleObject(Context->ReflectionStartEvent, FALSE, NULL);
+}
+
+if (Context->ReflectionRoutine)
+{
+ // Invoke the user-provided callback
+ Context->ReflectionRoutine(Context->ReflectionParameter);
+}
+else if ((Context->ReflectionFlags & RTL_PROCESS_REFLECTION_FLAGS_NO_SUSPEND) == 0)
+{
+ // Suspend the clone
+ NtSuspendThread(NtCurrentThread(), NULL);
+}
+
+// Terminate once done
+NtTerminateProcess(NtCurrentProcess(), STATUS_SUCCESS);
+```
+
+As a result of the implementation details we just discussed, this API requires opening the parent for `PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD | PROCESS_DUP_HANDLE` access and has additional stability considerations. First, the parent needs to be ready to accept remote threads and cannot be [frozen by a job](https://github.com/diversenok/Suspending-Techniques#freezing-via-a-job-object) (an [execution power request](https://github.com/winsiderss/systeminformer/blob/0c9970e182f05009272d6f02b616a15c17ce0ddf/phlib/native.c#L14094-L14145) might solve the problem) or [a debugger](https://github.com/diversenok/Suspending-Techniques#suspend-via-a-debug-object). Secondly, because the injected (and subsequently cloned) thread [skips DLL attaching](https://github.com/processhacker/phnt/blob/7c1adb8a7391939dfd684f27a37e31f18d303944/ntpsapi.h#L2221), it [slightly limits](https://m417z.com/A-guest-in-another-process-a-story-of-a-remote-thread-crash/) the number of supported operations that are safe to perform in the clone.
+
+Despite these challenges, [Windows Error Reporting](https://learn.microsoft.com/en-us/windows/win32/wer/about-wer) successfully relies on this function for asynchronously dumping and analyzing applications when they hang or encounter unhandled exceptions.
+
+## Offensive Code Injection? Unlikely.
+
+The recent Black Hat Europe 2022 held a talk about applying process cloning for offensive purposes called ["Dirty Vanity: A New Approach to Code Injection and EDR Bypass."](https://www.blackhat.com/eu-22/briefings/schedule/index.html#dirty-vanity-a-new-approach-to-code-injection--edr-bypass-28417) The speaker presented the research suggesting weaponizing the peculiarities of cloning for stealthy code injection. In this section, we will review this suggestion and discuss whether it's probable that we will see such techniques applied in the future.
+
+To start with, let's summarize our previous findings:
+1. `NtCreateProcessEx` can clone other processes but **doesn't allow creating threads**. The [takeaways slide](https://i.blackhat.com/EU-22/Thursday-Briefings/EU-22-Nissan-DirtyVanity.pdf) of Dirty Vanity suggests exploring `NtCreateProcessEx`-based execution primitives, which are, unfortunately, impossible according to our research.
+2. `NtCreateUserProcess` allows executing code but **only works on the current process** due to conceptual limitations.
+
+As the only workaround, we have `RtlCreateProcessReflection`, which can clone other processes "from the inside" via a remote thread. From the security perspective, it certainly offers a remarkable possibility but, at the same time, suffers from most of the shortcomings [typical for code injection](https://www.huntandhackett.com/blog/concealed-code-execution-techniques-and-detection#detection). **Gaining code execution in the clone requires first gaining execution in the parent**, which mostly defeats the purpose. We can hardly consider using `RtlCreateProcessReflection` a stealthy approach because it shares many detection vectors with popular offensive techniques:
+- It opens a handle to another process (the parent), triggering the [Ob- callback](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-obregistercallbacks). In itself, this fact isn't problematic. However, the access mask that the function requests look suspiciously similar to the one necessary for shellcode or DLL injection and includes heavily monitored rights.
+- It maps a memory region into a different process, triggering [EtwTi](https://undev.ninja/introduction-to-threat-intelligence-etw/). At least this region is not executable, lowering its chances of triggering behavioral alerts.
+- It performs cross-process thread creation, triggering the [corresponding Ps- callback](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreatethreadnotifyroutineex) and implying that we might attempt to compromise the parent process. Remote thread creation is a particularly invasive operation that alone might be enough to raise alerts.
+- Other less suspicious indicators of compromise include anomalous process creation (typically caught via [another Ps- callback](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex)) and remote process/thread handle duplication (again, visible via [Ob- callbacks](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_ob_pre_duplicate_handle_information)).
+
+As a result, EDRs that collect telemetry from sources capable of detecting classical DLL and shellcode injection should have no trouble seeing the underlying shenanigans required to achieve code injection via cloning. Of course, like any other new approach, Dirty Vanity breaks known and easily recognizable patterns and, thus, has a higher chance of success than classical techniques. Yet, we can argue that it doesn't offer severe improvements in counteracting runtime detection, making it merely slightly appealing for red-teaming applications. It's also worth mentioning that the outcome could've been drastically different (much in favor of undetectability) if `NtCreateProcessEx`-based cloning was compatible with execution like it was before Windows 8.1.
+
+# Cloning for Memory Dumping
+
+**Process cloning is quick** (usually taking the order of milliseconds). The reason is the [copy-on-write mechanism](https://devblogs.microsoft.com/oldnewthing/20220314-00/?p=106346) that allows duplicating the address space without immediately copying the underlying memory and storing the data twice. Beware that this feature works at the expense of extensive commit charge (which guarantees that the OS can always duplicate pages on demand), so it is still possible to exhaust system resources.
+
+Effectively, cloning provides **lightweight checkpoints** that capture the state of a process's address space at a specific moment. As a result, this mechanism becomes convenient for debugging purposes. If we want to read and save huge chunks of memory (like when making [minidumps](https://learn.microsoft.com/en-us/windows/win32/debug/minidump-files)), it's usually better to work on suspended or frozen processes. That's because writing several hundred megabytes on the disk can be a lengthy operation, and performing it while the target is running introduces race conditions. Cloning offers a non-intrusive alternative to suspension because it allows operating in parallel with the target.
+
+If we compare the API calls that make remote clone-based memory dumping possible, `NtCreateProcessEx` outmatches `RtlCreateProcessReflection` both in stability and stealthiness:
+- It **doesn't intrude** into the target as opposed to reflection that can theoretically crash it.
+- It **works on frozen** processes.
+- It relies on **less-monitored rights** (child process creation vs. remote thread creation, memory modification, and handle duplication).
+- It **doesn't trigger** [Ps- callbacks](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/nf-ntddk-pssetcreateprocessnotifyroutineex). Since `NtCreateProcessEx` doesn't insert the initial thread, the system doesn't notify drivers about the new (cloned) process.
+
+With these advantages in mind, you can find blog posts from other security researchers [[1]](https://billdemirkapi.me/abusing-windows-implementation-of-fork-for-stealthy-memory-operations/), [[2]](https://splintercod3.blogspot.com/p/the-hidden-side-of-seclogon-part-2.html) that describe using `NtCreateProcessEx` for memory dumping. Interestingly, the required concepts (address space inheritance and the required access for it) have been documented more than two decades ago in [Gary Nebbett's book "Native API Reference"](https://www.google.com/books/edition/Windows_NT_2000_Native_API_Reference/Fp1ct-bKYdcC) from 2000. The book even provides a code sample for cloning/forking via `NtCreateProcess`, which (being the only process creation syscall at the time) permitted creating threads.
+
+Here is a code snippet for dumping the memory of another process via cloning.
+
+```c
+CLIENT_ID clientId = { ... };
+HANDLE hFile = { ... };
+
+OBJECT_ATTRIBUTES objAttr;
+InitializeObjectAttributes(&objAttr, NULL, 0, NULL, NULL);
+
+// Open the target process
+HANDLE hParent;
+NTSTATUS status = NtOpenProcess(&hParent, PROCESS_CREATE_PROCESS, &objAttr, &clientId);
+
+if (!NT_SUCCESS(status))
+ return status;
+
+// Clone it
+HANDLE hClone;
+status = NtCreateProcessEx(
+ &hClone,
+ PROCESS_ALL_ACCESS,
+ &objAttr,
+ hParent, // ParentProcess
+ 0, // Flags
+ NULL, // SectionHandle
+ NULL, // DebugPort
+ NULL, // TokenHandle
+ 0 // Reserved
+);
+
+if (!NT_SUCCESS(status))
+ return status;
+
+// Proceed reading clone's memory
+BOOL success = MiniDumpWriteDump(
+ hClone, // hProcess
+ 0, // ProcessId
+ hFile, // hFile
+ MiniDumpWithFullMemory, // DumpType
+ NULL, // ExceptionParam
+ NULL, // UserStreamParam
+ NULL // CallbackParam
+);
+```
+
+You can find the complete version of [the code](https://github.com/huntandhackett/process-cloning/blob/master/3.CloneAndMinidump/main.c) and [the pre-compiled binary](https://github.com/huntandhackett/process-cloning/releases/tag/v1.0) in the dedicated sections of the repository:
+
+Starting from Windows 8.1, Microsoft also offers a documented [**process snapshotting** API](https://learn.microsoft.com/en-us/windows/win32/api/_proc_snap/) that relies on `NtCreateProcessEx`-based cloning. This API helps retrieve, iterate, and save information about the target process. It's funny to think that anti-virus products that don't filter out or monitor `PROCESS_CREATE_PROCESS` access on LSASS (thus, allowing admins to leverage it for indirectly reading credentials) are, in fact, vulnerable to the officially documented memory dumping API. Internally, these documented Pss* exports (`PssCaptureSnapshot`, and co.) from kernel32/kernelbase are merely wrappers over NtPss* functions (`PssNtCaptureSnapshot`, etc.) from ntdll that do most of the heavy lifting.
+
+# Memory Tampering & Troubleshooting
+
+The previous section established that `PROCESS_CREATE_PROCESS` (aka. child process creation) access effectively includes `PROCESS_VM_READ` (remote memory reading) access. However, we haven't seen anybody mentioning another similar attack vector that allows a limited `PROCESS_VM_WRITE` primitive. The key to this idea is **writable mapped memory**. Remember the copy-on-write mechanism described earlier? It's important to understand that it only applies to private memory, plus otherwise merely presents an implementation detail. If there were no performance penalties, Microsoft could easily replace it with complete copying. Mapped memory, on the other hand, is designed for sharing and therefore works differently. When the clone inherits a mapped region, it uses the same underlying storage, assuming the view or allocation protection doesn't force the copy-on-write semantics. In other words, any inheritable writable view from the parent also allows writing from the clone and presents a potential attack surface. Unfortunately, such regions are not plentiful, but they are also not necessarily rare. For instance, many programs map files into their address space to perform simplified I/O. Cloning offers a potential backdoor for modifying these files without reopening them or directly writing into the target process.
+
+![](pictures/04-inspect.png)
+
+We present a small tool shown in the screenshot above to help identify problematic regions both in terms of potential for exploitation and troubleshooting access violations. It clones a process of your choice and then compares the address space layouts, highlighting the differences. As always, you can find [the source code](https://github.com/huntandhackett/process-cloning/blob/master/4.InspectClonedMemory/main.c) and [the binary](https://github.com/huntandhackett/process-cloning/releases/tag/v1.0) in the dedicated sections of the repository.
+
+# Conclusions
+
+**Cloning** is the ultimate form of inheritance, an abstraction that breaks the principle of least privileges by trying to share as much as possible. In some cases, it serves as a pleasant benefit; in others — it is an undesirable attack vector. In any of them, it is a peculiar primitive worth investigating. We hope the behavior we documented in this (rather lengthy) blog post serves as a strong foundation for all further research on the topic. In addition to the previously mentioned examples and tools, we also share a [small library](https://github.com/huntandhackett/process-cloning/tree/master/5.Library) (that works on top of [PHNT headers](https://github.com/processhacker/phnt)) that might offer a convenient starting point for additional experimentation.
+
+![](pictures/05-taxonomy.png)
diff --git a/pictures/01-intro.jpg b/pictures/01-intro.jpg
new file mode 100644
index 0000000..89395f5
Binary files /dev/null and b/pictures/01-intro.jpg differ
diff --git a/pictures/02-reparenting.png b/pictures/02-reparenting.png
new file mode 100644
index 0000000..d0a276a
Binary files /dev/null and b/pictures/02-reparenting.png differ
diff --git a/pictures/03-reflection.png b/pictures/03-reflection.png
new file mode 100644
index 0000000..396470e
Binary files /dev/null and b/pictures/03-reflection.png differ
diff --git a/pictures/04-inspect.png b/pictures/04-inspect.png
new file mode 100644
index 0000000..4bf40f8
Binary files /dev/null and b/pictures/04-inspect.png differ
diff --git a/pictures/05-taxonomy.png b/pictures/05-taxonomy.png
new file mode 100644
index 0000000..228385a
Binary files /dev/null and b/pictures/05-taxonomy.png differ