FrostLock Injection is a freeze/thaw-based code injection technique that uses Windows Job Objects to temporarily freeze (suspend) a target process, inject shellcode, and then seamlessly resume (thaw) it.
Tested on:
- Windows 11
- Windows 10
This whitepaper introduces a unique technique for code injection that leverages the freezing (suspension) and thawing (resuming) of a target process using Windows Job Objects. The idea came to me while experimenting with System Informer, where I noticed the functionality to freeze and resume processes.
By freezing a target process at the right moment, we can inject shellcode without encountering many of the typical issues associated with real-time injection methods, such as race conditions or timing problems. This approach offers an elegant alternative to traditional remote thread injection.
System Informer (formerly known as Process Hacker) served as a key source of insights during this investigation. While Microsoft does not officially document a Freeze API, an in-depth analysis of code and header files revealed that freezing a process is indeed possible through an internal information class called JobObjectFreezeInformation
.
For those who prefer a more imaginative perspective (perhaps fans of The Eminence in Shadow): Imagine being able to control processes from the shadows, freezing and resuming them at will. But let’s keep it subtle onward to the technical details!
Process Edge:
Process Notepad (Triggering on Event):
Shellcode execution on close:
Shellcode execution new tab:
Notepad:
SecurityHealthSystray:
Since processes like svchost.exe
are commonly exploited in various attack scenarios, I decided to give it a try as well. Unfortunately, there are compatibility issues with some variants. I tested a few configurations and stumbled upon an interesting feature.
If you target certain svchost.exe
processes, the payload gets executed after the user logs back in. This opens up several potential scenarios for use, but I'll leave the rest to your imagination.
- C:\WINDOWS\system32\svchost.exe -k ClipboardSvcGroup -p -s cbdhsvc
- C:\WINDOWS\system32\svchost.exe -k LocalService -p -s NPSMSvc
Demonstration:
svchost.mp4
Before diving into the Freeze/Thaw approach, let’s briefly discuss the typical code injection method common in many penetration testing and attack tools.
- Enumerate Processes
Identify the target process (e.g., by PID or Name). - Open Handle
UseOpenProcess(PROCESS_ALL_ACCESS, ...)
(or native APIs likeNtOpenProcess
). - Allocate Memory
CallVirtualAllocEx
in the target process. - Write Shellcode
Transfer the shellcode viaWriteProcessMemory
. - Change Memory Protection
UseVirtualProtectEx
(e.g., change from RW to RX). - Create Remote Thread or ResumeThread Call a specific function to start execution at the shellcode entry point.
Although these steps are widespread, modern EDR solutions can often detect this classic pattern. Hence the motivation to explore Freeze/Thaw Injection for stealthier or more specialized scenarios.
A job object allows groups of processes to be managed as a unit. Job objects are namable, securable, sharable objects that control attributes of the processes associated with them. Operations performed on a job object affect all processes associated with the job object. Examples include enforcing limits such as working set size and process priority or terminating all processes associated with a job.
Systeminformer revealed that you can actually pause a process using SetInformationJobObject
with an internal class called JobObjectFreezeInformation
. Although there is no official Microsoft API for this, the Systeminformer code references (and some Windows header definitions) show how to configure an internal structure to freeze or thaw a process.
#define JobObjectFreezeInformation 18
typedef struct _JOBOBJECT_WAKE_FILTER
{
ULONG HighEdgeFilter;
ULONG LowEdgeFilter;
} JOBOBJECT_WAKE_FILTER, *PJOBOBJECT_WAKE_FILTER;
typedef struct _JOBOBJECT_FREEZE_INFORMATION
{
union
{
ULONG Flags;
struct
{
ULONG FreezeOperation : 1;
ULONG FilterOperation : 1;
ULONG SwapOperation : 1;
ULONG Reserved : 29;
};
};
BOOLEAN Freeze;
BOOLEAN Swap;
UCHAR Reserved0[2];
JOBOBJECT_WAKE_FILTER WakeFilter;
} JOBOBJECT_FREEZE_INFORMATION, *PJOBOBJECT_FREEZE_INFORMATION;
This structure holds a Freeze
flag that, when set to TRUE
, suspends all threads within the job. Setting it back to FALSE
resumes them.
Our technique reuses many of the same steps as classic code injection, but inserts a freeze phase before and an unfreeze phase after the shellcode injection.
- Enter PID or otherwise obtain the target PID.
- Open a Process Handle via
OpenProcess(PROCESS_ALL_ACCESS, ...)
. - Create a Job Object (
CreateJobObject
) and assign the process (AssignProcessToJobObject
). - Freeze the Process:
- Initialize
JOBOBJECT_FREEZE_INFORMATION
withFreezeOperation = 1
andFreeze = TRUE
. - Call
SetInformationJobObject
⇒ the process is fully paused.
- Initialize
- Allocate Memory (
VirtualAllocEx
, RW). - Write the Shellcode (
WriteProcessMemory
). - Change Memory Protection (
VirtualProtectEx
, e.g., PAGE_EXECUTE_READ). - Adjust the Main Thread’s Context so its RIP/EIP points to the new shellcode.
- Thaw the Process (
Freeze = FALSE
), allowing the main thread to continue execution from the shellcode address.
Browsers like Chrome or Edge will usually resume their main thread instantly upon thaw. The shellcode runs almost immediately.
Some GUI programs such as Notepad or certain system applications wait for user input or a specific event before truly resuming.
- For Notepad, the shellcode might only execute after you open a new tab, close Notepad, or trigger another GUI event.
- These programs often sit in a message loop waiting for input.
If you need to force the target thread to continue right away (rather than waiting for user interaction), you can call PostThreadMessage
on the main thread ID.
- This only works if the thread has an active message queue.
- Sending a dummy message (like
WM_NULL
) can jumpstart the loop, causing the thread to process events and continue execution in the shellcode.
Another intriguing approach involves launching the target process yourself (e.g., using CreateProcess
) and delaying the code injection until the user closes the application. The shellcode could then be triggered by a final event such as WM_CLOSE
. For instance, imagine the user being puzzled as to why Notepad or another process was launched (or is already running). Upon closing the application, the shellcode is executed, like a stealthy infiltration from the shadows.
This functionality is not currently implemented in the code but could be relatively easily added to a custom proof-of-concept (PoC).
This approach could also be useful in anti-analysis or sandbox evasion scenarios. Since the shellcode is triggered only by specific user actions, such as closing Notepad or opening a new tab, it could bypass automated analysis environments that do not simulate such behaviors.
Windows GUI threads typically have a message queue. Events such as mouse clicks, keystrokes, or system notifications are placed in this queue, and the thread processes them via GetMessage
or PeekMessage
.
With PostThreadMessage(threadId, Msg, wParam, lParam)
, you can post a message directly into another thread’s queue.
- Requirement: The thread must already have a message queue. Otherwise,
PostThreadMessage
fails withERROR_INVALID_THREAD_ID
. - When the target thread picks up the posted message, it processes the message and can exit a wait state, resuming the shellcode execution.
This Freeze/Thaw Injection technique shows how Windows Job Objects can serve more exotic purposes than just resource limiting. By freezing a process, we avoid timing and concurrency issues during code injection. Once the memory is allocated, the shellcode is written, and the thread context is set, we simply thaw the process, causing execution to jump to our injected code.
However, whether the injected code executes immediately or is delayed depends on how the target process handles incoming events. Apps like Chrome or Edge generally run the payload right away, whereas Notepad or certain system processes (e.g., SecurityHealthSystray.exe) might wait for user interaction or message processing. The solution is to either rely on the user to trigger an event or actively push a message (PostThreadMessage
) to force execution.
Acknowledgments
- System Informer Team for their open-source code that revealed the freeze mechanism.
- All the Windows reverse engineers who continue to explore and document hidden and undocumented structures.
- To the creators and mentors of the MalDev Academy, who have provided invaluable foundational knowledge as well as advanced topics. I am still learning and fully aware that I have much, much more to master.
Disclaimer
This Whitepaper is for educational and research purposes only. The author and contributors assume no responsibility for misuse or damage caused by these techniques. Always comply with legal guidelines and use these methods only in authorized environments.