When you type a password into an SSH or sudo prompt, you assume it goes to one place: the program that asked for it. On Linux that assumption is wrong by one word. The password goes to that program's memory, and on a shared kernel, memory is something other processes can be granted the right to read. Matthew Keeley's writeup of Hawk, a credential harvester built for a collegiate red-team competition, is a clean, small demonstration of exactly that gap. It is worth understanding not because the tool is novel, but because the boundary it crosses is one most people draw in the wrong place.
What this is, and isn't
This is a defensive read of a public red-team tool. The technique is post-exploitation: it assumes the attacker is already root on the box. The interesting part is not how to build it, it is what it tells you about where the real trust boundary sits and how to watch the line. Nothing here is a how-to.
What Hawk does
Hawk is a small Go program. It watches a host for anyone in the middle of authenticating, grabs the plaintext credential as it is typed, and ships it to a server. During the Western Regional Collegiate Cyber Defense Competition it fed those credentials straight into a Sliver C2, so a password typed on one machine became a foothold on the next. Three moving parts, and that is the whole loop:
/proc for authentication processes, attaches to one with ptrace, reads the typed credential out of its memory as it crosses a write syscall, and exfiltrates the plaintext. The harvest happens in the window between keystroke and hash.Why reading another process's memory is even possible
The whole thing rests on ptrace, the same kernel facility a debugger uses to step through a program and inspect its registers and memory. A tracer attaches to a target, the kernel stops the target at each syscall boundary, and the tracer can read the target's registers and peek bytes out of its address space. That is not a bug. It is how gdb and strace work.
Hawk uses it the obvious way. When a login process makes a write to its terminal, the syscall arguments are sitting in the CPU registers, and the buffer they point at is sitting in the target's memory. The tracer reads them:
| Register | Holds | What Hawk does with it |
|---|---|---|
| orig_rax | The syscall number | Match 1 = write |
| rdi | Arg 1: file descriptor | Match the terminal fd |
| rsi | Arg 2: buffer pointer | Peek the bytes at this address |
| rdx | Arg 3: byte count | How much to read |
It identifies who is logging in by reading /proc/[pid]/cmdline (an sshd session line carries the username), defaults the rest to root, and filters the captured bytes with crude heuristics on length and printability to throw away the noise and keep the credential. None of this is sophisticated. That is the point. The capability it is using is ordinary and built in.
The boundary you actually have
Most people carry a mental model that looks like the first row below. The real one is the second.
| The boundary you imagine | The boundary you have |
|---|---|
| My password goes to sshd, and nothing else sees it | My password lands in sshd's memory, and anything with the right to trace sshd sees it too |
This is the same shape I wrote about in agent memory is an attack surface: the trust boundary you assume is not the trust boundary that exists, and an attacker works in the gap between them. There the gap was a memory store that treated attacker text as a believed fact. Here it is a terminal write that treats the kernel's debugging facility as if it were private.
What actually has to be true for this to work
This is where the defensive read diverges from the red-team writeup, which never says it. Hawk is not a way in. It is a way to stay in and spread. For it to read another user's sshd or a root sudo, the attacker needs the privilege to attach a tracer to a process they do not own, and on Linux that means CAP_SYS_PTRACE, which in practice means root.
The hawk already has the high ground. The tool does not take the box; it harvests once the box is taken. What it buys an attacker is quiet, durable lateral movement: fresh plaintext credentials, not hashes, that survive a password rotation and unlock the next host.
That reframes the defense completely. If you are arguing "but they would need root," you have conceded the premise, not refuted it. The questions that matter are the next three: can a non-root user do this at all, can you see it when it happens, and is the stolen credential worth anything once it leaves.
How to defend the line
Three layers, in the order of how much they actually buy you against this specific behavior.
| Layer | What it does | What it does not do |
|---|---|---|
| Restrict ptrace | Set kernel.yama.ptrace_scope to 1 (default on many distros), 2, or 3. Stops a non-root process from tracing one it didn't spawn. | Does not stop root. CAP_SYS_PTRACE bypasses Yama. Helps against an unprivileged foothold, not a privileged one. |
| Detect the behavior | Audit the ptrace syscall (auditd or eBPF), and watch for a non-zero TracerPid in /proc/[pid]/status on sshd, sudo, su. Flag any process polling /proc at a tight interval. | Does not prevent the read. It tells you it happened, which against a root attacker is often the most you get. |
| Defang the loot | Make a stolen password worthless: SSH keys or hardware-backed certificates plus MFA, short-lived per-host credentials, just-in-time access. A plaintext password that cannot be replayed is a non-event. | Does not stop the capture. It removes the reason to bother and breaks the lateral-movement loop the harvest feeds. |
The order is deliberate. Restricting ptrace is the cheap hygiene that closes the unprivileged case, and you should do it. Detection is what you actually rely on against a root attacker, because at that level prevention has mostly already failed. And defanging the loot is the only layer that changes the economics: it is the difference between an incident where an attacker walked the network on harvested passwords and one where they captured a string that opened nothing.
The takeaway
Hawk is interesting precisely because it is boring. It chains together a polling loop, a built-in kernel facility, and an HTTP request, and it works because the trust boundary people imagine around a password prompt is one word off from the real one. The defenses follow from naming the real boundary out loud: a typed secret is briefly plaintext in a process's memory, the kernel can hand that memory to a sufficiently privileged tracer, and so the secret is only ever as safe as your control over who reaches root and your ability to make the secret useless once it is taken.
If you defend by hoping nobody gets the privilege, you are defending the boundary you imagined. Defend the one you have.
Based on "Hawk's Prey: Snatching SSH Credentials" by Matthew Keeley (PlatformSecurity), which documents the Hawk tool built for the WRCCDC red team. That writeup describes the technical mechanism; this is a defensive read of it, focused on the boundary it crosses and how to hold the line.