Keypoints
- System calls (syscalls) are the kernel interface and are commonly monitored by security tools to observe system activity.
- Multiple syscalls can provide overlapping functionality (e.g., open(), creat(), openat(), openat2()), so watching a single syscall can miss activity.
- Bash builtins run inside the shell process and can perform I/O (e.g., reading files) without executing external binaries, avoiding execve()-based detection.
- Demonstration: the loop “while IFS= read -r line; do echo “$line”; done < testfile” reads a file without triggering execve(), but produces openat() activity when strace filters for it.
- Tools like strace can reveal the difference: filtering on execve() misses builtin-based actions, while filtering on file-access syscalls (openat()) catches them.
- Watching all syscalls system-wide is impractical due to volume; defenders must choose which syscall behaviors to monitor intentionally.
MITRE Techniques
- [T1059.004] Command and Scripting Interpreter: Unix Shell – Using built-in shell commands to perform actions without spawning external processes, e.g., the article shows the exact command used: [‘while IFS= read -r line; do echo “$line”; done < testfile’] which executes file reads entirely within bash.
- [T1106] Native API – Leveraging lower-level system interfaces and syscalls (such as openat()) to perform file operations that bypass higher-level process-execution monitoring: [‘we’re going to cheat and jump directly to looking at openat()’].
- [T1562] Impair Defenses (Defense Evasion) – Avoiding detection by tools that focus on process creation (execve()); the article notes this effect: [‘dodged this entirely and allowed us to read the data from our file while flying completely under the radar of tools that were only looking for process execution.’]
Indicators of Compromise
- [File path] Commands/binaries and system files referenced – /usr/bin/ls, /bin/ls (examples of external binaries), /etc/ld.so.cache (library cache used during startup)
- [File name] Test artifacts and user files used in demos – testfile (file read by builtins), .bashrc (shell startup file)
- [Syscall names] Syscalls used as observable indicators – execve(), openat() (used to demonstrate difference in monitoring)
- [URLs] Source and documentation references – https://sysdig.com/blog/exploring-syscall-evasion/, https://man7.org/linux/man-pages/man2/open.2.html
System calls are the kernel’s API: many security tools monitor them to detect malicious activity, commonly focusing on execve() to identify process execution. However, file-access and execution functionality can occur via multiple syscalls (open(), creat(), openat(), openat2(), etc.), so watching only one syscall or only process-creation events can miss relevant activity.
Interactive shells expose a large set of builtins that execute inside the shell process itself. The article demonstrates this by using bash builtins to replicate cat’s behavior: the pipeline while IFS= read -r line; do echo “$line”; done < testfile reads the file line-by-line without invoking an external cat binary, so a strace filtered for execve() shows no evidence of the file read. When the same shell is monitored with strace filtered for openat(), the file-open syscalls appear (openat(“testfile”, O_RDONLY) = 3), revealing that the activity happens via file-access syscalls rather than process creation.
The practical takeaway is that defenders cannot rely solely on execve()-based monitoring; they must identify and monitor the specific syscall behaviors relevant to the actions they want to detect (e.g., file opens, network connects). At the same time, monitoring every syscall system-wide is infeasible due to volume and performance impact, so selection of targeted syscall patterns (and awareness of living-off-the-land techniques such as shell builtins) is necessary to improve detection without overwhelming resources.
Read more: https://sysdig.com/blog/exploring-syscall-evasion/