Aether

Thread Start-Address Validation (TSAV / L8)

Aether 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:

VerdictSeverityCondition
TSAV_SHELLCODE_PRIVATECRITICALAddress lives in a MEM_PRIVATE + PAGE_EXECUTE_* region — classic CreateRemoteThread shellcode
TSAV_SUSPENDED_RIPCRITICALSuspended thread’s Rip disagrees with Win32StartAddress and resolves to a suspicious region — catches Win32StartAddress spoofing (EarlyBird / APC tricks) and SetThreadContext hijacks
TSAV_HOLLOWED_HOSTHIGHAddress inside a MEM_IMAGE allocation that is not in the PEB module list (DLL hollowing / module stomping)
TSAV_MODIFIED_HOSTHIGHAddress inside a MEM_IMAGE allocation that the L1–L5 pipeline already flagged as MODIFIED_CODE_*MISSING_PEBPRIVATE_RWXDISK_MEM_DIFF, or HOOK_PROLOGUE
TSAV_STAGED_PRIVATE_RWHIGHMEM_PRIVATE + PAGE_READWRITE — pre-VirtualProtect shellcode staging
TSAV_MAPPED_NONPEMEDIUMMEM_MAPPED (pagefile-backed section) without a PE header — sRDI / pagefile reflective loader
TSAV_SPOOF_TRAMPOLINEMEDIUMAddress matches a denylisted trampoline (LoadLibraryA/W/ExA/WWinExecCreateProcessA/WVirtualAlloc[Ex]RtlExitUserThreadRtlExitUserProcessNtTerminateProcessShellExecuteA/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 list
  • Cross-correlation with L1–L5 — a thread whose start lands inside an already-flagged allocation is upgraded from “OK” to TSAV_MODIFIED_HOST
  • PEB consistency — hollowed modules are detected even when the start address technically falls inside a “real” range
  • Suspended-RIP probe — Win32StartAddress 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 region
  • WoW64 aware — automatically switches to Wow64GetThreadContext and reads Eip for 32-bit threads inside a 64-bit process
  • Access-rights ladder — falls back from QUERY_INFORMATION | GET_CONTEXT → QUERY_INFORMATION → QUERY_LIMITED_INFORMATION per thread, so partial-access scenarios still produce useful classifications