
Windows memory-forensics and threat hunting tool that scans live process memory for malicious patterns, injection techniques, and reflectively loaded .NET assemblies using a multi-layer confidence model to reduce false positives.

version : 0.9 (stable release)
Aether is a Windows memory-forensics and threat hunting tool that scans live process memory for malicious pattern, detect injection techniques, implant signatures, reflectively loaded .NET assemblies. it works with a multi-layer confidence model that dramatically reduce the false positive rate and hunt for malicious behaviour. Aether has good capabilities in detecting Hollowing, APC, thread hijacking techniques. Security analysts can use it to scan,hunt and snapshot suspicious region for offline analysis.
Docs: https://0xsp.com/docs/aether-getting-started/
Research articles:
quick explainations for Aether core features, you can read the full technical blogpost to get more insights:
"msxsl:script" becomes 6D 00 73 00 78 00 ...)rules/
without recompilationMEM_PRIVATE regions — flags reflectively loaded
.NET assemblies (MZ + PE + BSJB metadata)Aether layers five filters on top of the raw working-set signal so a finding requires multiple agreeing indicators before it is reported with FP filtering:
| Layer | Filter | Purpose |
|---|---|---|
| L1 | Structural | Only executable IMAGE sub-regions are considered (eliminates .data / .rdata COW noise) |
| L2 | Quantitative | Grade by private_pages count and private_ratio (low / medium / high) |
| L3 | Corroboration | Promote only if an independent signal agrees on the same allocation base — signature hit, missing_peb_entry, private_rwx, hook prologue, or on-disk diff |
| L4 | CLR-aware | Per-module suppression for ngen / R2R / tiered-JIT targets (*.ni.dll, mscor*, clr*, coreclr, system.private.corelib*) instead of blanket-skipping when the CLR is loaded |
| L5 | On-disk diff | Map the module file with CreateFileMappingW(SEC_IMAGE_NO_EXECUTE); compare the first 16 bytes of each private executable page against the same RVA on disk. Any divergence is a real-modification IOC |
Other structural checks:
MEM_IMAGE allocations that are not in
the PEB module list (DLL hollowing / module stomping)K32QueryWorkingSetEx, batched with one syscall per region instead
of one per 4 KB page (≈ 50-100× faster than the naïve loop)MEM_PRIVATE + PAGE_EXECUTE_* (it produces FP results)
allocations (shellcode, JIT spray, dynamic code stub allocations)E9 ?? ?? ?? ?? — JMP rel32FF 25 ?? ?? ?? ?? — JMP [rip+disp32]68 ?? ?? ?? ?? C3 — PUSH imm32 ; RET48 B8 ?? ?? ?? ?? ?? ?? ?? ?? FF E0 — MOV RAX, imm64 ; JMP RAX49 BB ?? ?? ?? ?? ?? ?? ?? ?? 41 FF E3 — Detours-style
MOV R11, imm64 ; JMP R11Cor_Private_IPCBlock_v4_<PID>
and the v2 Cor_Private_IPCBlock_<PID> (legacy .NET 2/3 / mscorwks),
so noisy app-pools running old runtimes are not misclassifiedAether checks threads with stricter classification and
cross-correlation against the L1–L5 findings. For every created thread in the
target process Aether reads its Win32StartAddress via NtQueryInformationThread
and, when access permits, also the live Rip / Eip via
GetThreadContext / Wow64GetThreadContext.
Each address is then graded as the following table, for more details read the blogpost:
| Verdict | Severity | Condition |
|---|---|---|
TSAV_SHELLCODE_PRIVATE | CRITICAL | Address lives in a MEM_PRIVATE + PAGE_EXECUTE_* region — classic CreateRemoteThread shellcode |
TSAV_SUSPENDED_RIP | CRITICAL | Suspended thread's Rip disagrees with Win32StartAddress and resolves to a suspicious region — catches Win32StartAddress spoofing (EarlyBird / APC tricks) and SetThreadContext hijacks |
TSAV_HOLLOWED_HOST | HIGH | Address inside a MEM_IMAGE allocation that is not in the PEB module list (DLL hollowing / module stomping) |
TSAV_MODIFIED_HOST | HIGH | Address inside a MEM_IMAGE allocation that the L1–L5 pipeline already flagged as MODIFIED_CODE_*, MISSING_PEB, PRIVATE_RWX, DISK_MEM_DIFF, or HOOK_PROLOGUE |
TSAV_STAGED_PRIVATE_RW | HIGH | MEM_PRIVATE + PAGE_READWRITE — pre-VirtualProtect shellcode staging |
TSAV_MAPPED_NONPE | MEDIUM | MEM_MAPPED (pagefile-backed section) without a PE header — sRDI / pagefile reflective loader |
TSAV_SPOOF_TRAMPOLINE | MEDIUM | Address matches a denylisted trampoline (LoadLibraryA/W/ExA/W, WinExec, CreateProcessA/W, VirtualAlloc[Ex], RtlExitUserThread, RtlExitUserProcess, NtTerminateProcess, ShellExecuteA/W) |
What makes this stronger than the basic check "is start_address in any module" check:
VirtualQueryEx cross-check — every address is queried for Type /
Protect / AllocationBase in a single O(1) call instead of a linear
scan over the module listTSAV_MODIFIED_HOSTWin32StartAddress is process-writable via
NtSetInformationThread and is the spoofable field; the live Rip of a
suspended thread is the one a loader can't easily rewrite. We compare
the two and flag any disagreement that resolves to a suspicious regionWow64GetThreadContext and
reads Eip for 32-bit threads inside a 64-bit processQUERY_INFORMATION | GET_CONTEXT → QUERY_INFORMATION → QUERY_LIMITED_INFORMATION per
thread, so partial-access scenarios still produce useful classificationsRuntime API resolution is a technique frequently used by malware. Aether detection mechanism identifies this behavior by scanning the heap for valid module addresses and pointers, and correlating the results with the filtering criteria described below:
Each filter kills a specific FP class observed in real-world telemetry:
| Filter | Rule | FP class it removes |
|---|---|---|
| F1 | count >= 5 | random pointer-shaped data, NULL, HMODULES |
| F2 | reject runs that point only into the host EXE | application-class C++ vtables |
| F3 | distinct_modules >= 2 | single-DLL framework vtables (Qt, MFC, wxWidgets) |
| F4 | capability_modules >= 2 | browser / CRT vtables that touch a single OS DLL (e.g. iertutil + ucrtbase + shlwapi) |
| F5 (L10) | >= 80% of checkable pointers land on exported RVAs | Winsock LSP dispatch tables, plugin callback arrays, vtables pointing at internal (non-exported) methods |