Even though well known methods exist to bypass ptrace deactivation on a process when spawning (fake ptrace()
preloading, breakpoint on ptrace()
, etc… ), it is trickier when process is already protected.
Thankfully Linux 3.2+ was generous enough to provide read/write capabilities to another process with 2 new system calls: sys_process_vm_readv
and sys_process_vm_writev
. (see the source code). For our Windows friend, those new syscalls are similar to ReadProcessMemory()
and WriteProcessMemory()
.
The manual says:
These system calls transfer data between the address space of the calling process (“the local process”) and the process identified by pid (“the remote process”). The data moves directly between the address spaces of the two processes, without passing through kernel space.
A running process can be ptrace
d like this:
if (ptrace(PTRACE_TRACEME, 0, 0, 0) < 0) {
perror("[-] is traced\n");
return 1;
}
As such, it would acquire an exclusive lock, preventing any other ptrace instance (say a debugger) to manipulate its memory (that’s like ELF anti-debug 101). But it can hence still have its memory read:
struct iovec local[1], remote[1];
local->iov_base = mybuf;
local->iov_len = size_to_read;
remote->iov_base = (void *) strtoll(argv[2], NULL, 16);
remote->iov_len = to_read;
int nread = process_vm_readv(target_pid, local, 1, remote, 1, 0);
Similar call to process_vm_writev
will tamper remote process memory.
Even though it is not possible to read/write in process memory that don’t have the same level of privilege (unless given CAP_SYS_PTRACE
capability), it is a very reliable way to leak or inject data.
I’ve added the syscall filtering to my toy sandboxing tool, bakassabl
.