2

Basically I am using ptrace to inject a shell code to a remote process for execution. But I found some weird behavior regarding RIP register.

What I do is I copy my shell code to the start address of where the program is mapped. Then I set the RIP using ptrace to the address where the start address is. And then I resume the target process for executing the code. Once the shell code finishes (by running int3) I will get signal and recover the code that I just modified.

It works fine except when the remote process is blocked inside of a system call like sleep. If the remote process is blocked inside of a system call at the moment I attach the process, after I set the RIP to where I want to execute my shell code and then resume the target process, I will observe that the RIP is actually 2 less than what the address that I put in the ptrace call. For example if I set the RIP to be 0x4000, once I resume it the RIP becomes 0x3ffe. Typically it crashes for my case due to the segment fault, obviously. But if I grab the register right after I set it without resuming the process, the RIP is the value that I just set. Currently I work around it by insert 2 nop instructions ahead of my shell code and always add 2 when I set the RIP. I just want to know is there anything that I miss for setting the RIP or my whole method for injecting code is totally unstable?

My dev box is Ubuntu14.04, kernel is 3.13.0-45-generic.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
dianpeng
  • 55
  • 5

1 Answers1

3

If I recall correctly, if you interrupt the process while it's blocked in a syscall, the program counter value, upon continuing, will be subtracted by sizeof(syscall instruction) by the kernel. So once you do a PTRACE_DETACH, the process will re-do the syscall it was interrupted from.

I overcome the problem the same way you did (always adding a tiny nop-sled and incrementing RIP).

  • Can you explain little bit about why the kernel will have to subtract the sizeof syscall when continuing the call ? Thanks ! – dianpeng Jun 25 '16 at 04:10
  • I assume it's part of the RESTART handling. You stop it by sending it a signal. If the kernel wishes to restart the syscall after the signal handler finishes, it needs to get back into the kernel. It does so by making sure you'll run the "syscall" instruction again. – Shachar Shemesh Jun 27 '16 at 12:10