Three new docs for contributors and blog authors:
- docs/architecture.md: dispatch flow, thread model, CET compliance
- docs/com-structures.md: MIDL tables, stubs, format strings, fake objects
- docs/pump-lifecycle.md: step-by-step pump cycle walkthrough
All with mermaid diagrams for visual clarity.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Reflects the refactor that eliminated the dedicated STA thread.
Caller is now the STA, pumps directly via fiber, MTA thread exits.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add MsgWaitForMultipleObjectsEx as a sleep primitive dispatched
through NdrStubCall2. This uses the win32u syscall path
(NtUserMsgWaitForMultipleObjectsEx) instead of ntdll
(NtDelayExecution), evading detectors that only monitor ntdll
sleep APIs.
New tests:
- Test 3: Single MsgWait dispatch (queue + pump)
- Test 4: Full encrypt-MsgWait-decrypt chain
New flags:
- --msgwait-only: skip NtDelay tests, run only MsgWait tests
MsgWaitForMultipleObjectsEx(0, NULL, sleep_ms, 0, 0) is a
self-timed wait: no handles, no wake mask, timeout-only return.
Functionally equivalent to Sleep() but through win32k message
subsystem. All frames on sleeping stack are system DLLs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Replace arbitrary CLSID with intentionally obvious {C0E60000-C0E6-...}
for development. Document stealth CLSID candidate ranges in code comments
(shell32, propsys, explorerframe, msctf, actxprxy) with selection criteria.
Key findings from CLSID masquerade research:
- Registered CLSIDs break marshal (COM loads real DLL, not our factory)
- OLE range {000003xx} has special combase handling even when unregistered
- Unregistered CLSIDs adjacent to real system entries work and blend in
- Custom IMarshal is incompatible (would lose NdrStubCall2 dispatch)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The 'Zig optimizer bug' was actually the STA fiber init race condition
(fixed in 257474a). ReleaseSafe and ReleaseFast now pass 30/30 each.
All 4 optimization modes verified: Debug, ReleaseSmall, ReleaseSafe,
ReleaseFast.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause: init() returned before the STA thread completed
ConvertThreadToFiber(), leaving g_ctx.sta_main_fiber as NULL.
When pump() ran immediately after init(), it queued SwitchToFiber(NULL),
which crashed inside kernelbase!SwitchToFiber+0x13 reading [0+0x10].
The crash was swallowed by COM's internal SEH in non-DEBUG builds,
manifesting as a hang instead of a crash (timeout at 15s vs normal 5s).
Fix: STA thread signals pump_ready_event after ConvertThreadToFiber(),
and init() waits on it before returning. This ensures sta_main_fiber
is valid before any caller can use it.
Also includes:
- Dynamic signature scanning (Layer 1) replacing hardcoded RVAs
- VEH crash handler with correct CONTEXT_AMD64 struct (DEBUG builds)
- debugSwitchToFiber wrapper for dispatch parameter validation (DEBUG)
- NtWaitForSingleObject in STA command loop (prevents premature pump)
Validated: 150/150 stability test passes (was ~60% before fix).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Detailed technical writeup of the sporadic ~25% crash in NdrStubCall2
during sleep mode pump dispatch:
- Root cause: var_90 stack local corruption in rpcrt4!NdrStubCall2
- Fix: set MIDL_STUB_DESC.aXmitQuintuple = dispatch_table (one line)
- Full rpcrt4 disassembly with RVAs for future RE reference
- Failed approaches documented with explanations
- Verification: 150/150 across all optimization modes
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Root cause: NdrStubCall2 reads pStubDesc->aXmitQuintuple (+0x38) as a
dispatch table at RVA 0x17455. When NULL, it falls through to an OLE
extension result (var_90) which was unreliable — sporadically producing
wrong function pointers (e.g. 0x27F) depending on stack layout and
optimization level. Setting aXmitQuintuple to our dispatch_table gives
NdrStubCall2 a deterministic, correct dispatch path on every call.
Changes:
- Set stub_desc.aXmitQuintuple = dispatch_table (matches omega1)
- Use CStdStubBuffer_Invoke directly in vtable (no shim/trampoline)
- Remove invokeTrampoline naked function and invokeShim
- Remove csb_invoke_global export (no longer needed)
- Gate VEH crash handler behind DEBUG flag
Verified: 50/50 across ReleaseSafe, ReleaseSmall, and Debug (150/150).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SendReceive returns S_OK now that pDispatchTable=-1 fix is in place.
Both modes pass:
pump() — 2x NtDelay(2s) = 4000ms via PostCall batch dispatch
invoke() — 1x NtDelay(1s) = 1016ms via SendReceive, returns STATUS_SUCCESS
Fixed executePumpCycle to use DeleteFiber on suspended pump fiber
(matching the sleep mode handler — no SetEvent+SwitchToFiber cleanup).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Better reflects the COM message pump mechanism. The public API is
now init(), queue(), pump(), invoke(), deinit().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Status section now reflects actual state (slot scaling works,
proxy mode blocked on RPC_E_FULLSIC_REQUIRED)
- Added Public API section with usage examples for both modes
- Added table clarifying execute() vs invoke() (sleep vs proxy)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
pumpFiberProc wrapper put user .text on the pump fiber stack,
violating the 'no user code on any callstack during sleep' rule.
Fix: ModalLoop is now the direct CreateFiber entry point (zero user
frames). After dispatch completes via SwitchToFiber(main), the
suspended pump fiber is destroyed with DeleteFiber — no need to
resume ModalLoop for cleanup.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CStdStubBuffer_Invoke checks CInterfaceStubHeader.pDispatchTable:
- When 0: skips NdrStubCall2, messages consumed but not dispatched
- When -1: uses MIDL_SERVER_INFO → NdrStubCall2 → our dispatch functions
Also fixed pump fiber to use pumpFiberProc wrapper (calls ModalLoop
then SwitchToFiber back to STA main) instead of raw ModalLoop as
direct fiber proc (which kills the thread on return).
Sleep mode fully working: 2x NtDelay(2s) dispatches in 4015ms.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>