Category: Forensics / Covert Channels
Author: N!L
Flag format: nexus{...}
Flag: nexus{1m_r3a11y_pr0ud_0f_yuu_d3t3ct0r}
This challenge hides data inside seemingly benign system logs by abusing timing anomalies:
scheduler: tick processing delayed (NNNNus) act as the covert channel.audit: comm_marker entries provide framing boundaries (start/end) for multiple transmissions.shadow) is leaked in audit logs (key_material="...").incident.log (given)In the log there are hundreds of entries like:
kernel: ... scheduler: tick processing delayed (23000us) on cpu7
When extracted, all delays satisfy:
delay_us % 1000 == 0delay_us // 1000 lies in a small alphabet (mostly 1..63)That’s a classic “symbol stream” hiding in timing values.
The log includes explicit markers:
audit: comm_marker group=LOTUS state=SEND_START ...
audit: comm_marker group=LOTUS state=SEND_END ...
audit: comm_marker group=SPIRE state=SEND_START ...
audit: comm_marker group=LOTUS state=PAYLOAD_START ...
audit: comm_marker group=LOTUS state=PAYLOAD_END ...
These timestamps divide the delay stream into multiple message segments (two for LOTUS + two for SPIRE + one payload).
audit: comm_marker lines into (timestamp, group, state).(timestamp, delay_us).This lets you select “the delays that occurred between marker X and marker Y”.
Using the markers, the scheduler stream splits into five segments:
The counts in the provided log add up exactly to the full scheduler-delay list, confirming the framing is correct.
Let:
symbol = delay_us // 1000
Then map to Base64 with an off-by-one shift:
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
char = alphabet[symbol - 1]
This produces a Base64 string per segment.
Segments 1–4 decode cleanly to ASCII messages:
[LOTUS] Channel established. Host logging is verbose - perfect cover. Standby for key rotation.[SPIRE] Acknowledged. Monitor for anomaly detection. Key distribution via policy fragments?[LOTUS] Affirmative. Fragmented in audit trail. Extract shadow-class identifier. No cleartext.[SPIRE] Confirmed. Payload handoff when ready. Standard protocol.Segment 5 decodes to a binary blob (not plaintext).
Later in the log, there are AppArmor audit entries containing:
key_material="c2hhZG93"
key_material="ZnJhZ18wXw=="
key_material="ZnJhZ18xXw=="
...
These are Base64 strings. Decoding them yields:
c2hhZG93 → shadowZnJhZ18wXw== → frag_0_ (and similar)The LOTUS instruction explicitly mentions:
“Extract shadow-class identifier.”
So the XOR key is:
key = b"shadow"
The payload bytes are XORed with the repeating key:
plaintext[i] = payload[i] XOR key[i % len(key)]
Decryption reveals:
nexus{1m_r3a11y_pr0ud_0f_yuu_d3t3ct0r}
Save as solve.py and run against incident.log.
import re
import base64
from datetime import datetime
LOG = "incident.log"
alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def parse_dt(line: str):
p = line.split()
return datetime.strptime(p[0] + " " + p[1], "%Y-%m-%d %H:%M:%S")
def b64decode_relaxed(s: str) -> bytes:
s = s + "=" * ((4 - (len(s) % 4)) % 4)
return base64.b64decode(s)
with open(LOG, "r", errors="replace") as f:
lines = f.read().splitlines()
# --- collect comm markers ---
markers = []
for l in lines:
if "audit: comm_marker" in l:
dt = parse_dt(l)
m = re.search(r'group=(\w+)\s+state=(\w+)', l)
if m:
markers.append((dt, m.group(1), m.group(2)))
def get_marker(group, state, occurrence=1):
hits = [dt for dt,g,s in markers if g == group and s == state]
if len(hits) < occurrence:
raise ValueError(f"marker not found: {group} {state} occ={occurrence}")
return hits[occurrence - 1]
# marker windows (per the given log)
lotus1_start = get_marker("LOTUS", "SEND_START", 1)
lotus1_end = get_marker("LOTUS", "SEND_END", 1)
spire1_start = get_marker("SPIRE", "SEND_START", 1)
spire1_end = get_marker("SPIRE", "SEND_END", 1)
lotus2_start = get_marker("LOTUS", "SEND_START", 2)
lotus2_end = get_marker("LOTUS", "SEND_END", 2)
spire2_start = get_marker("SPIRE", "SEND_START", 2)
spire2_end = get_marker("SPIRE", "SEND_END", 2)
pay_start = get_marker("LOTUS", "PAYLOAD_START", 1)
pay_end = get_marker("LOTUS", "PAYLOAD_END", 1)
# --- scheduler delay stream ---
sched = []
for l in lines:
if "scheduler: tick processing delayed" in l:
dt = parse_dt(l)
m = re.search(r"\((\d+)us\)", l)
if m:
sched.append((dt, int(m.group(1))))
def window_to_b64(start, end):
chunk = [us for (dt, us) in sched if start <= dt <= end]
# off-by-one mapping discovered in analysis:
return "".join(alphabet[(us // 1000) - 1] for us in chunk)
def decode_window(start, end):
return b64decode_relaxed(window_to_b64(start, end))
# decode the four plaintext segments
m1 = decode_window(lotus1_start, lotus1_end).decode()
m2 = decode_window(spire1_start, spire1_end).decode()
m3 = decode_window(lotus2_start, lotus2_end).decode()
m4 = decode_window(spire2_start, spire2_end).decode()
print(m1)
print(m2)
print(m3)
print(m4)
# payload
payload_b64 = window_to_b64(pay_start, pay_end)
payload = b64decode_relaxed(payload_b64)
# XOR key from key_material -> "shadow"
key = b"shadow"
flag = bytes(payload[i] ^ key[i % len(key)] for i in range(len(payload)))
print(flag.decode())
Expected final line:
nexus{1m_r3a11y_pr0ud_0f_yuu_d3t3ct0r}
nexus{1m_r3a11y_pr0ud_0f_yuu_d3t3ct0r}