I spent a day reversing syscalls and Kernel entry points to find new ways I could share memory or inject code between threads & processes, during which I decided to dive into Hotpatching on Windows and write a PoC which turned out to be a great exercise in working with the PE format.
Below I talk through Hotpatching, resulting in a PoC & PE32/32+ helper code.
What is hotpatching?
Hotpatching is a method supported throughout Windows for modifying running PE32/32+ objects in memory, including Kernel drivers & processes (even those in the Secure Kernel), usually to permit patching or updating code without requiring restarts. This is documented in a few places, including the PE32/32+ format (see an overview from Microsoft here: https://techcommunity.microsoft.com/t5/windows-os-platform-blog/hotpatching-on-windows/ba-p/2959541).
Enabling Hotpatch Support
Hotpatching is not enabled by default in all versions of the OS, its currently supported in Insider builds and in Azure edition of server builds (Azure editions are downloadable as ISOs or deployable in Azure directly, more information here: https://learn.microsoft.com/en-us/azure/automanage/automanage-hotpatch and here: https://learn.microsoft.com/en-us/windows-server/get-started/enable-hotpatch-azure-edition.
If you’re working with the raw ISO or an Insider build, you may need to set registry keys to turn on Hotpatching (as per the links above). Insider builds only require the registry keys to be set, which are included in the PoC at the end.
Hotpatching also relies on the presence of the Secure Kernel, though you can leverage Hotpatching without the Secure Kernel if you set an additional registry key.
All the information about Hotpatching can be gleamed from public documentation, including PE information here: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/SystemServices/struct.IMAGE_HOT_PATCH_INFO.html, here: https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_load_config_directory64 & through reversing of the
NtManageHotPatch syscall in
Pseudo Overview of the Hotpatching Process
In addition to the documentation above, below is my own general walkthrough of the Hotpatching process based on my PoC development.
Patches are created via the
NtManageHotPatch syscall, this syscall takes multiple parameters which determine the operation to call. When we call it to create a patch it will expect to load a PE32/32+ file describing the patch.
These Hotpatch PE32/32+ files are like regular PE32/32+ executable images however they include Hotpatch entries (Including the
IMAGE_HOT_PATCH_INFO struct here: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/SystemServices/struct.IMAGE_HOT_PATCH_INFO.html, the
IMAGE_HOT_PATCH_BASE here: https://microsoft.github.io/windows-docs-rs/doc/windows/Win32/System/SystemServices/struct.IMAGE_HOT_PATCH_BASE.html & other
The Hotpatch entries start with the
IMAGE_HOT_PATCH_INFO struct which is stored in a section in the PE32/32+ file, pointed to by the
HotPatchTableOffset field in the
DataDirectory entry in the
OptionalHeader of the PE32/32+ file.
The PE file is mapped into a system section by the Kernel, which parses the file to determine the offsets of the Hotpatch structures in the file, it then creates a Hotpatch entry in a list.
The Hotpatch will be valid for images of a certain
timedatestamp, which will apply to any image with the corresponding
checksum in its
OptionalHeader, and its
timedatestamp from its
IMAGE_FILE_HEADER. This is how a Hotpatch file tells the system which image the Hotpatch is valid for, e.g. if we wanted to patch kernelbase.dll, we’d read the
timedatestamp from kernelbase.dll and set the
OriginalTimeDateStamp fields of our
IMAGE_HOT_PATCH_BASE struct to those values.
Additionally, if the patch PE contains the exported function
__PatchMainCallout__, it will be automatically invoked after the patch is loaded in a process.
Once the patch is loaded into the Kernel, depending on the type of patch it may automatically be applied to all running processes as the Kernel enumerates processes and calls a notification callback in
ntdll.dll to handle checking for patches.
Limitations & Notes
While Hotpatching is a powerful feature, permitting code changes to multiple parts of the system, there are two main limitations (for non-Microsoft users)
- Administrator privileges is generally required to enable Hotpatching
- To globally apply a Hotpatched PE, the PE is required to be at least
Microsoftsigned or higher (preventing common injection of unsigned DLLs)
For 2. above, the PE does not need to be signed to be loaded into the Kernel list of Hotpatches, and you can still map the Hotpatch into your process by utilizing
NtManageHotPatch, which provides a way to map the section handle of your Hotpatch regardless of its signature.
There are other behaviors of Hotpatching not mentioned here, such as targeting process by user SID, or the fact that processes may continually attempt to load your Hotpatch regardless of validity (which can cause processes to stop launching, or even act as a targeted DoS against certain processes).
Additionally if you target kernelbase the Hotpatched PE can be loaded outside of the typical notification callback, such as in
LdrpInitializeKernel32Functions instead, which has its own interesting properties not discussed here.
I could also foresee uses of this by EDRs to supply ntdll & kernelbase patches, instead of their current approach of injecting + hooking.
Code samples (not complete) for Hotpatching (+ partial helper code for working with PE32 files) are included here: https://github.com/Signal-Labs/Hotpatching_PoC, the Hotpatch loader will take the compiled
hotpatch_replace_vs file (which is expected to already have a LoadConfig table, which is possible if you compile with /GS for example) and create a new file that’s a clone with a Hotpatch entry (a partially valid entry, just enough to get it loaded as a Hotpatch record in the Kernel). It also includes support for enabling Hotpatching if you run as Admin.