A rootkit hides a process by unlinking it from the list the OS walks — but the thing still has to run, so a lower-level view of the system still accounts for it. Enumerate the same thing two ways, diff the results: what the low view sees but the high view doesn't is hidden.
Any list the OS exposes can be tampered with at the layer that produces it. So a defender never trusts one list — they build a second, independent view and compare. A hidden object is a disagreement between the views, and disagreement is something you can state with evidence.
tasklist, ps. Easiest to query, and the easiest for a rootkit to filter.Two independent enumerations of the same host (sample data). Run the diff: processes in both are accounted for; a PID in the low view with no high-view entry is flagged hidden; a high-only PID is treated as a timing race, not a conviction.
You rarely read kernel memory on a Mac. The same idea becomes cross-source correlation: the process list vs. who actually owns a live socket vs. who Zeek saw send traffic. A process that holds a connection or sends packets but isn't in the baseline process view is the same disagreement.
# three independent views osquery: processes osquery: process_open_sockets # owner of each socket zeek: conn.log # traffic, no process diff → a talker with no owning process = investigate
proc_key — an unattributed connection is a cross-view gap worth a look.Processes start and exit between your two passes, so a single diff is noisy — a high-only PID is almost always one that just exited, not evidence of anything. The signal is a persistent low-only object across several passes, ideally from views a single tamper can't fake at once. One snapshot raises a question; repetition answers it.
Next: Lab 4 — detecting C2 (beaconing regularity and DNS tunneling), built on the detection lab's R3/R4.