Exploiting Linux systems with ptrace

raj3shp
3 min readSep 10, 2022

--

Linux comes with an interesting system call ptrace which can be abused to elevate privileges and steal sensitive information.

The ptrace() system call provides a means by which one process
(the “tracer”) may observe and control the execution of another
process (the “tracee”), and examine and change the tracee’s
memory and registers. It is primarily used to implement
breakpoint debugging and system call tracing.

If you have used debuggers such as gdb , strace they are based on ptrace .

From the Linux kernel documentation:

One particularly troubling weakness of the Linux process interfaces is that a single user is able to examine the memory and running state of any of their processes. For example, if one application (e.g. Pidgin) was compromised, it would be possible for an attacker to attach to other running processes (e.g. Firefox, SSH sessions, GPG agent, etc) to extract additional credentials and continue to expand the scope of their attack

This article demonstrates following attack vectors introduced by ptrace :

Key-logging

Examining a process’s memory and registers gives an attacker an easy way to intercept any processes such as bash, sudo, ssh to log key strokes to get hold of secret information such as sudo passwords and SSH keys.

Here is my simple proof of concept project termspy to demonstrate this process.

  • First attach to the target process with PTRACE_ATTACH which sends a SIGSTOP signal to the target process making it a tracee of the termspy process
  • Then use PTRACE_SYSCALL to restart the stopped process and continue running it until the next entry or exit from a system call execution.
  • To perform key-logging we focus on write syscall since it will be executed when a user types a keystroke into the bash or terminal process. Snippet below shows the registers used for execution of a syscall in x86–64 bit systems
  • With PTRACE_PEEKUSER we can read the value of these registers and check rax register to compare if the system call being executed is write
orig_eax = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL)if (orig_eax == SYS_write)
  • write syscall takes three parameters which are stored in rdi , rsi and rdx parameter respectively.

We are interested in the second parameter which is a pointer to the char array containing user’s keystrokes. We can read this address with PTRACE_PEEKUSER and use PTRACE_PEEKDATA to read the data at this address.

params[1] = ptrace(PTRACE_PEEKUSER, pid, 8 * RSI, NULL);data.val = ptrace(PTRACE_PEEKDATA, child, addr + i * 8, NULL);
  • This returns lot of non-ASCII text which we don’t care about so we filter out any non-ASCII characters. For the demo, we just print out the ASCII characters to stdout
  • This works pretty well when attached to a bash or terminal process.

Process Injection

With ptrace we can also write data into the process effectively achieving the ability to inject code into an existing process. This can be very handy for defense evasion and potentially achieve privilege escalation.

By default many distributions have sudo caching enabled. When a shell process invokes sudo, system caches the password in the form of a token which is valid for certain amount of time so password is not required for any further sudo commands invoked by the same shell until the timer expires. By injecting our shell-code into such shell process, we can effectively become root.

I wrote a small proof of concept project ptrace_injection which uses ptrace to inject shell code into an existing process with PTRACE_POKEDATA .

The shell code forks a child process and spawns a reverse shell. If the process we inject into recently ran a sudo command with sudo caching enabled, we can spawn root shell with sudo -i on our reverse shell connection.

Mitigation

  • Restrict ptrace to only the root user. You can do this by setting ptrace_scope to admin-only mode.
# sysctl kernel.yama.ptrace_scope=2
  • Disable sudo credential caching by setting timestamp_timeout to 0 . Add following line to the /etc/sudoers file.
Defaults timestamp_timeout=0

--

--

No responses yet