
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:
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 RAXAether 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:
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 → → 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:
Aether supports XOR detection pattern at stub level for now and adopt Shannon entropy alogrithm to check randomness of byte values in memory region, it flags everything above a threshold. to address high amount of FP, Aether uses multiple indicators.
GetExtendedTcpTable for a target PIDMODIFIED_CODE_* finding carries
private_pages and region_pages so triage has the actual numbersAether.exe --scan --pid <PID> [OPTIONS]
Aether.exe --scan --lookup "ProcessName.exe"
Aether.exe --hunt <PID> SLEEP_MS PERIOD
Aether.exe --scan-all [OPTIONS]
git clone https://github.com/0xsp-SRD/aether
cd aether
# Debug build (safety checks enabled)
zig build
# Release build (smaller, faster binary)
zig build -Doptimize=ReleaseSafe
# or
zig build -Doptimize=ReleaseFast
The built executable lands in zig-out/bin/Aether.exe.
Copy zig-out/bin/Aether.exe and the rules/ directory to the target
Windows machine if you want to perform additional signature scan. if the target process requires Administrative privileges, you need to run Aether.exe with Administrator privileges.
Win32StartAddress
rewrite on an already-running thread can only be detected if the thread
happens to be parked in a wait when probed (same constraint as Moneta)Aether Community is licensed under the GNU GPL v3.0. Commercial licenses are available only from the author for proprietary or commercial use.
| 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 |
49 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 misclassified| 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) |
QUERY_INFORMATIONQUERY_LIMITED_INFORMATION| 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 |
| Flag | Description |
|---|
--pid, -p <PID> | Target process ID to scan |
--lookup, -l <name> | Find all PIDs matching a process name |
--json, -j | Output results as JSON (for SIEM integration) |
--verbose, -v | Show per-region scan details |
--scan-all, -a | Scan all processes |
--hunt, -b <PID> [ms] [hits] | Monitor connections — poll every ms (default 2000), flag endpoints with ≥ hits occurrences |
--networking | Network-monitoring mode |
--rules, -r <dir> | Rules directory (default: rules/) |
--config, -c <file> | Single rule file (legacy format) |
--dump, `` | Dump specific memory region with custom size |
--read, `` | Read memory region content live on terminal |
--help, -h | Show help |