4

As an exercise, I've been trying to use Rust's nix::sys::ptrace::ptrace (which is pretty much identical to C's ptrace) to emulate strace's syscall logging functionality by reading the contents of the RAX/RBX/RCX/RDX registers at the time of each syscall. My desired output would be something similar to this:

Found syscall: 4
Arg1: 1
Arg2: Hello World!

From the examples I've found online, it seems that a combination of PTRACE_PEEKUSER and PTRACE_PEEKDATA would be best for accomplishing this (maybe PTRACE_GETREGS too?), but I'm having a hard time understanding how to satisfy the 3rd argument that I need to pass to these functions in order to make it work.

ptrace's man pages list this example demonstrating the proper usage:

ptrace(PTRACE_PEEKTEXT/PEEKDATA/PEEKUSER, pid, addr, 0);

The man page just says, "Read a word at the address addr in the tracee's memory." But what is addr supposed to be? How can I determine the correct address to satisfy this argument?

The examples that I've found online all have something like:

ptrace(PTRACE_PEEKUSER, pid, sizeof(long)*ORIG_EAX, 0);

or

ptrace(PTRACE_PEEKUSER, pid, somenumber*RAX, 0)

How can I find/calculate the offset of these registers during runtime? (in Rust!)

1 Answers1

3

It's been a long time since I poked Linux kernel (Mac user now). Buf if I remember it correctly then ...

Kernel headers

Install kernel headers with something like sudo apt-get install linux-headers-$(uname -r). Let's say that you're on x86_64-linux-gnu (guess based on your rax interest).

Open /usr/include/x86_64-linux-gnu/sys/reg.h header:

...
#ifdef __x86_64__
/* Index into an array of 8 byte longs returned from ptrace for
   location of the users' stored general purpose registers.  */

# define R15    0
# define R14    1
# define R13    2
# define R12    3
# define RBP    4
# define RBX    5
# define R11    6
# define R10    7
# define R9     8
# define R8     9
# define RAX    10
...

And the comment says:

Index into an array of 8 byte longs returned from ptrace for location of the users' stored general purpose registers.

All those macros (RAX, RCX, ...) define indexes for particular registers. And because each one is 8 byte long (x86_64 only), the offset is 8 * $index. In case of rax register, the offset is calculcated as 8 * RAX = 8 * 10 = 80. 80 is what you should use for the addr argument in the ptrace function call. This is how it works. Be aware that it differs for other architectures, etc.

PTRACE_*

PTRACE_PEEKUSER - use for registers and other debug info.

PTRACE_PEEKDATA - use for program data & code.

PTRACE_PEEKTEXT - man ptrace (Linux) says - Copy the word data to the address addr in the tracee's memory. As for PTRACE_PEEKTEXT and PTRACE_PEEKDATA, these two requests are currently equivalent. That's because Linux doesn't have separate address spaces for text and data.

Rust & PTRACE_PEEKUSER

The nix crate offers getregs function to read all of them. It returns libc user_regs_struct. Which is supported for Linux only:

libc crate also contains those indexes as well:

If you're interested in one register only, you can use this index to calculate offset / addr for the ptrace function. Multiply it with 8 (#[cfg(target_arch = "x86_64")]) / 4 (#[cfg(target_arch = "x86")]) and use PTRACE_PEEKUSER to read it (see Request).

Rust & PTRACE_PEEKDATA

Read What are the calling conventions for UNIX & Linux system calls on i386 and x86-64. In other words, you're interested in rdi, rsi, rdx, ... registers. The nix crate provides specialized read function, which internally calls ptrace function with PTRACE_PEEKDATA.

nix crate

ptrace function is deprecated. Documentation note:

Deprecated since 0.10.0: usages of ptrace() should be replaced with the specialized helper functions instead

You should use specialized functions like getregs & read. You can find list of them in the documentation.

zrzka
  • 20,249
  • 5
  • 47
  • 73