Commands run inside a root shell that sudo cannot log.
What gives it awayExecution recorded with uid 0 but an auid identifying a real user, after a su or sudo shell.
ATT&CK T1059.004Nothing matches that yet. Tell us what you were looking for and it goes on the list.
Linux Audit Framework · Event EXECVE
The Linux audit framework recorded a program being run, with every argument. Unlike sudo logging it works at the kernel level, so it keeps recording after someone obtains a root shell — which is precisely where sudo's audit trail ends.
Also written as type=EXECVEauditd execveaudit.log execvelinux command execution logging
Not something a desktop Linux machine records by default. It is a server auditing feature.
An EXECVE record never stands alone — it is one line of a multi-line event joined by a shared event ID, with the SYSCALL record carrying the user identity and the CWD and PATH records carrying context. Tools like ausearch reassemble them; reading raw audit.log line by line will mislead you. Note that auid, the original login user, survives su and sudo, which is what makes attribution possible at all.
Sanitised. Addresses come from the RFC 5737 documentation ranges.
type=SYSCALL msg=audit(1756374000.123:45678): arch=c000003e syscall=59 success=yes exit=0 a0=55d1f2a3b4c0 a1=55d1f2a3b500 a2=55d1f2a3b560 items=2 ppid=24901 pid=25012 auid=1000 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts1 ses=42 comm="curl" exe="/usr/bin/curl" key="user_commands"
type=EXECVE msg=audit(1756374000.123:45678): argc=4 a0="curl" a1="-s" a2="-o" a3="/tmp/x"
type=CWD msg=audit(1756374000.123:45678): cwd="/home/jbrooks"
type=PATH msg=audit(1756374000.123:45678): item=0 name="/usr/bin/curl" inode=1180 dev=fd:00 mode=0100755 ouid=0 ogid=0 Everything else in the log line is context.
4294967295No login user — the process came from a system service rather than a person. Most of the time it is one of these.
What gives it awayExecution recorded with uid 0 but an auid identifying a real user, after a su or sudo shell.
ATT&CK T1059.004What gives it awayA rapid sequence of enumeration commands from one session, faster than a person types.
ATT&CK T1082What gives it awaycurl, wget, or similar with an argument pointing at an unexpected host, particularly writing to a temporary directory.
ATT&CK T1105What gives it awayCommands truncating history files or removing log files.
ATT&CK T1070.003What gives it awayArguments passed through base64 or built from concatenated strings, which is why hex-encoded arguments deserve decoding rather than skipping.
ATT&CK T1027ausearch -m EXECVE -ts today -i | less ausearch -m EXECVE -ts recent -i | grep -E 'curl|wget|nc |base64|chmod \+x' auditctl -a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=4294967295 -k user_commands ausearch -ua 1000 -ts today -i | grep -A2 EXECVE event.module:"auditd" and auditd.data.syscall:"execve" | stats count by process.args, user.name Add an auditd rule on the execve syscall: `-a always,exit -F arch=b64 -S execve -F auid>=1000 -F auid!=4294967295 -k user_commands`, placed in a file under /etc/audit/rules.d so it persists. That captures execution at the kernel level, which is the only approach that survives a user obtaining a root shell. Shell history is not a substitute — it is user-writable and easily cleared.
Because a single audit event is split across several record types, and the user identity lives in the accompanying SYSCALL record rather than in EXECVE. They share an event ID in the msg=audit() field. Use `ausearch` to reassemble them; reading audit.log line by line loses the attribution entirely.
The audit user ID — the account that originally logged in. It is set at login and does not change when the user runs su or sudo, so it still identifies the person even when uid has become 0. It is the only field that reliably answers 'who did this' after privilege escalation. A value of 4294967295 means there was no login user, indicating a system process.
Because auditd hex-encodes any argument containing spaces, quotes, or non-printable characters, to keep the log format unambiguous. `ausearch -i` decodes them automatically. Grepping raw audit.log for a string will silently miss every encoded occurrence, which is a common reason searches come back empty when they should not.
It can, and how much depends entirely on your rules. A rule capturing every execve on a busy machine generates very large volumes and measurable overhead. Scope rules with auid filters to exclude system accounts, avoid auditing high-frequency syscalls you do not need, and test on something representative before deploying widely.
Last reviewed 28 August 2026