1

I want to get instruction from running process and change it using ptrace. When variable instr (contains current instruction - PTRACE_PEEKDATA) is unsigned everything works, but when I change it to long int there is an error (memory dump). ptrace(PTRACE_PEEKDATA, ...) returns long int so this shouldn't be a problem. I work on Ubuntu.

Where I made a mistake? I'm new to it so this propably will be something stupid.

My code:

#include <stdio.h>
#include <sys/ptrace.h>
#include <sys/user.h>
#include <sys/types.h>
#include <stdlib.h>
#include <wait.h>

int main()
{
    int status; 
    char *pid_char;
    pid_t PID;
    struct user_regs_struct  reg; /* register */
    long int instr;
    unsigned changedInstr;


    printf("Tracee PID: ");
    scanf("%s", pid_char);
    PID = atoi(pid_char);
    printf("\n");

    /* PTRACE STARTS */
    ptrace(PTRACE_ATTACH, PID, NULL, NULL);
    waitpid(PID, &status, 0); 

    ptrace(PTRACE_GETREGS, PID, NULL, &reg);

    instr = ptrace(PTRACE_PEEKDATA, PID, reg.rip, NULL);
    printf("Current Instruction: %llx\n", instr);

    scanf("%u", &changedInstr);
    ptrace(PTRACE_POKEDATA, PID, reg.rip, &changedInstr);

    ptrace(PTRACE_DETACH, PID, NULL, NULL);

    return 0;
}
daniel098
  • 99
  • 8
  • `On error, all requests return -1, and errno is set appropriately.` Have you checked errno? And you changed `instr` from `unsigned long`? Is this the only change? How can I reproduce it with some program? – KamilCuk Nov 08 '18 at 21:26

1 Answers1

3

On x86_64, PTRACE_PEEKDATA returns 8 bytes and PTRACE_POKEDATA transfers 8 bytes starting at the address pointed to by its addr argument. Using a long or unsigned long should be fine.

If you attach to a process that's in a nanosleep system call, the instruction stream looks like this:

(gdb) disass /r
Dump of assembler code for function __nanosleep_nocancel:
   0x00007ffff7ad92e9 <+0>: b8 23 00 00 00  mov    $0x23,%eax
   0x00007ffff7ad92ee <+5>: 0f 05   syscall 
=> 0x00007ffff7ad92f0 <+7>: 48 3d 01 f0 ff ff   cmp    $0xfffffffffffff001,%rax
   0x00007ffff7ad92f6 <+13>:    73 31   jae    0x7ffff7ad9329 <nanosleep+73>
   0x00007ffff7ad92f8 <+15>:    c3  retq  

After executing instr = ptrace(PTRACE_PEEKDATA, PID, reg.rip, NULL);, instr will be 3173fffff0013d48 if it's unsigned long, or f0013d48 if it's unsigned.

In your program, changedInstr is unsigned, and ptrace(PTRACE_POKEDATA, PID, reg.rip, &changedInstr); is going to transfer the 4 bytes of changedInstr followed by 4 bytes of whatever is adjacent to it on the stack, possibly part of some other local variable. Those 4 bytes may be something innocuous or something that will make the target process get an exception, as you saw.

What should work in this case, if you want to write a 4-byte instruction at reg.rip, is

unsigned changedInstr;
...
instr = ptrace(PTRACE_PEEKDATA, PID, reg.rip, NULL);
scanf("%u", &changedInstr);
instr = (instr & ~0xFFFFFFFFul) | changedInstr
ptrace(PTRACE_POKEDATA, PID, reg.rip, &instr);
Mark Plotnick
  • 9,598
  • 1
  • 24
  • 40