Exploring Syscall Evasion – Linux Shell Built-ins

The article demonstrates how attackers can avoid detection by using shell builtins (like bash’s read and echo) to perform file operations without spawning external processes, thereby bypassing security tools that monitor process-creation syscalls. It shows that monitoring only execve() is insufficient and that defenders must intentionally monitor other syscalls (e.g., openat()) to detect such activity. #Falco #bash

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/