Cortex XSIAM
#Scenario
Seasons don't fear the reaper, but your Linux endpoints definitely should when RingReaper weaponizes io_uring to silently sever its system call hooks.
markdown
> Make sure you're comfortable with the results in your own environment before using this more widely.
# Snippet - Seasons don't fear the reaper, but your Linux endpoints definitely should when RingReaper weaponises io_uring to silently sever its system call hooks.
## Scenario
This XQL detection rule hunts for evasive Linux post-exploitation activity by correlating three distinct events within a single causality chain:
### Native API Abuse:
Activity from an `io_uring SQPOLL` kernel thread.
### Anomalous Recon:
Execution of discovery commands (like `whoami` or `id`) operating without a parent shell.
### Sensitive Data Access:
Reads of critical system files (such as `/etc/shadow` or SSH keys).
In short, it flags when a suspicious kernel thread bypasses normal shell execution to quietly map out the system and steal credentials.
xql
// Make sure you're comfortable with the results in your own environment before using this more widely.
/ =============================================================================
// GoCortex | RingReaper / io_uring Abuse (T1106) - generic behavioural hunt
// -----------------------------------------------------------------------------
// Detects the TECHNIQUE
// via co-occurrence under one causality chain on one Linux host:
// A) io_uring SQPOLL kernel thread iou-sqp-<pid> (Tier 1)
// B) recon binaries with NO shell ancestor whoami/id/hostname/uname (Tier 2)
// C) sensitive-file reads passwd/shadow/net/tcp/utmp/ssh keys
// Run as: XQL search / scheduled hunt | Range: last 7-14 days
// ATT&CK: T1106 Native API; T1033/T1082/T1087 discovery; T1003.008/T1005/T1552.004
// =============================================================================
dataset = xdr_data
// --- 1. Scope: Linux only (io_uring is Linux), process + file telemetry --------
| filter agent_os_type = ENUM.AGENT_OS_LINUX
| filter event_type = ENUM.PROCESS or event_type = ENUM.FILE
// --- 2. Normalise names for matching -----------------------------------------
| alter
proc = lowercase(action_process_image_name),
parent = lowercase(actor_process_image_name),
cause = lowercase(causality_actor_process_image_name)
// --- 3. Generic behavioural signals ------------------------------------------
| alter
// A) io_uring SQPOLL kernel thread (privileged ring poller = strong signal)
is_iouring_thread = if(action_process_image_name contains "iou-sqp-"
or actor_process_image_name contains "iou-sqp-", 1, 0),
// recon utilities
is_recon_bin = if(proc in ("whoami", "id", "hostname", "uname"), 1, 0),
// is a shell anywhere in the immediate lineage?
parent_is_shell = if(parent in ("sh","bash","dash","zsh","ash","ksh","fish","busybox")
or cause in ("sh","bash","dash","zsh","ash","ksh","fish","busybox"), 1, 0),
// C) sensitive files RingReaper harvests via the ring
is_sensitive_file = if(action_file_path in ("/etc/passwd","/etc/shadow",
"/proc/net/tcp","/proc/net/tcp6","/run/utmp","/var/run/utmp")
or action_file_path contains "/.ssh/id_", 1, 0)
// B) recon spawned programmatically (fork+execve) with no shell in lineage
| alter is_shellless_recon = if(is_recon_bin = 1 and parent_is_shell = 0, 1, 0)
// --- 4. Keep only events that carry one of the three signals -------------------
| filter is_iouring_thread = 1 or is_shellless_recon = 1 or is_sensitive_file = 1
// --- 5. Correlate per host + causality chain (ties the ring thread, the recon --
// children and the file reads back to the SAME originating process) ------
| comp
sum(is_iouring_thread) as iouring_events,
sum(is_shellless_recon) as shellless_recon_events,
sum(is_sensitive_file) as sensitive_file_events,
count_distinct(if(is_shellless_recon = 1, proc, null)) as distinct_recon_bins,
count_distinct(if(is_sensitive_file = 1, action_file_path, null)) as distinct_sensitive_files,
values(if(is_iouring_thread = 1, action_process_image_name, null)) as iouring_threads,
values(if(is_shellless_recon = 1, action_process_image_name, null)) as recon_procs,
values(if(is_sensitive_file = 1, action_file_path, null)) as sensitive_files,
values(action_process_image_command_line) as action_cmdlines,
values(causality_actor_process_command_line) as causality_cmdlines,
min(_time) as first_seen,
max(_time) as last_seen
by agent_hostname, causality_actor_process_image_name
// --- 6. Require the io_uring OR shell-less-recon anchor (drop file-only noise) --
| filter iouring_events > 0 or shellless_recon_events > 0
// --- 7. Fidelity score: the technique = signals TOGETHER, not in isolation -----
| alter tier_count = add(add(
if(iouring_events > 0, 1, 0),
if(shellless_recon_events > 0, 1, 0)),
if(sensitive_file_events > 0, 1, 0))
| alter risk_score = add(add(add(
if(iouring_events > 0 and shellless_recon_events > 0, 55, 0),
if(iouring_events > 0, 25, 0)),
if(distinct_recon_bins >= 2, 10, 0)),
if(sensitive_file_events > 0, 10, 0))
// --- 8. Triage output ---------------------------------------------------------
| fields
risk_score, tier_count,
agent_hostname, causality_actor_process_image_name,
iouring_events, iouring_threads,
shellless_recon_events, distinct_recon_bins, recon_procs,
sensitive_file_events, distinct_sensitive_files, sensitive_files,
action_cmdlines, causality_cmdlines,
first_seen, last_seen
| sort desc risk_score, desc tier_count, desc iouring_events