0

I've been working on a recode of a mini strace program without using PTRACE_SYSCALL in order to get familiar with the registers.

So in my code after using ptrace(PTRACE_GETREGS, ...) to set the user_reg_struct field, I'm using ptrace(PTRACE_PEEKDATA, ...) to read from it.

Not really knowing what to do with the retur of that function to use it's data (syscalls etc...), I started looking at some code and I came across things like:

int is_a_syscall() {

struct user_reg_struct regs;
unsigned short int ret;

ret = ptrace(PTRACE_PEEKDATA, pid, regs.rip, 0);
if (ret == 0xFFFF) {
   perror("failed")
   exit(1); }
if (ret == 0x80CD || ret == 0x50F)
   return (true);
return (false);
}

Now can someone explain to me what are the numbers in the if() statement, a.k.a:

  • 0xFFFF, I assume it has to do with the architecture of the processor but I couldn't verify it
  • 0x80CD and 0x50F

I wish to know what they are, where can I find them, can I interpret them and how can I use them to get my system calls and their arguments.

Zoubro
  • 39
  • 1
  • 6

2 Answers2

3

The code you included is somewhat unconventional, so I'll explain what it does and then show a different way to do it.

unsigned short int ret;
ret = ptrace(PTRACE_PEEKDATA, pid, regs.rip, 0);

ptrace on Linux returns a long, not an unsigned short. The author of the code is just looking at the low-order 2 bytes of the 4 or 8 bytes that ptrace returns. Since the x86 is little-endian, this gets the job done, but I'll show another way to do this later.

if (ret == 0xFFFF) {
   perror("failed")
   exit(1); }

ptrace returns -1 on failure. Since the code stored only the low-order 2 bytes of the return value, and is treating the value as unsigned, the test for -1 uses 0xFFFF instead.

Since ptrace(PTRACE_PEEKDATA, ...) can return -1 even when it succeeds, it would be better to also look at errno (I'll show this later).

if (ret == 0x80CD || ret == 0x50F)
   return (true);
return (false);

You can find opcode lists at sites like http://ref.x86asm.net/coder64.html . CD is int and CD80 is int 80 and 0F05 is syscall. These are two opcodes used in x86 Linux to do system calls. Further info at What is better “int 0x80” or “syscall”?.

Here's another way to check for system call instructions (untested):

int is_a_syscall(regs)
struct user_reg_struct regs;
{
    long ret;
    unsigned char primary, secondary;
    extern int errno;

    errno = 0;
    ret = ptrace(PTRACE_PEEKTEXT, pid, regs.rip, 0);
    if (ret == -1 && errno != 0) {
        perror("failed")
        exit(1);
    }
    primary = (unsigned)0xFF & ret;
    secondary = ((unsigned)0xFF00 & ret) >> 8;
    if (primary == 0xCD && secondary == 0x80 || primary == 0x0F && secondary == 0x05)
        return true;
    return false;
}

I use the full width of ptrace's return value. If it's -1 and if errno has been set, then that indicates an error. I also use PTRACE_PEEKTEXT instead of PTRACE_PEEKDATA, although on x86 it doesn't matter. Bitmasks are used to get the primary and secondary opcode bytes.

how can I use them to get my system calls and their arguments.

For this, please see this detailed answer: What are the calling conventions for UNIX & Linux system calls on x86-64.

Marco Bonelli
  • 63,369
  • 21
  • 118
  • 128
Mark Plotnick
  • 9,598
  • 1
  • 24
  • 40
0

Do you mean how do these relate to decimal (e.g. 0x80CD = 32973), or what is their semantic meaning in relation to ptrace() (e.g. 0xFFFF probably means the function encountered an error)?

If the former, these are numbers expressed in hexadecimal. That is, the number system where the numeral range consists of: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F.

If the latter, check out the return value section in the man page for ptrace

Toby
  • 9,696
  • 16
  • 68
  • 132