0

I'm learning ptrace by the article "playing with ptrace". Now I can set breakpoint by replacing tracee's instruction with "syscall" but can't inject code successfully.

In X86 , the print can use "int 80" then pause process by "int3". How can I inject code that has instruction "syscall " and stop process when the inject code finish in x64 Thanks.

The code I inject is this

section .text

global main

main:
mov rax, 1
mov rdi, 1
mov rsi, message
mov rdx, 13
syscall
int3

message:
db "Hello world", 10

My code is

#include <sys/ptrace.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define LONG_SIZE 8

void getdata(pid_t child, long addr,char *str,int len)
{
    char *laddr = str;
    int i = 0,j = len/LONG_SIZE;
    union u{
        long val;
        char chars[LONG_SIZE];
    } word;
    while(i<j)
    {
        word.val = ptrace(PTRACE_PEEKDATA,child,addr + i*LONG_SIZE,NULL);
        if(word.val == -1)
            perror("trace error");
        memcpy(laddr,word.chars,LONG_SIZE);
        ++i;
        laddr += LONG_SIZE;
    }
    j = len %LONG_SIZE;
    if(j!=0)
    {
        word.val == ptrace(PTRACE_PEEKDATA,child,addr + i*LONG_SIZE,NULL);
        if(word.val == -1)
            perror("trace error");
    }
    str[len] = '\0';
}


void putdata(pid_t child,long addr,char *str,int len)
{
    char *laddr = str;
    int i = 0, j = len/LONG_SIZE;
    union u{
        long val;
        char chars[LONG_SIZE];
    }word;
    while(i<j)
    {
        memcpy(word.chars,laddr,LONG_SIZE);
        if(ptrace(PTRACE_POKEDATA,child,addr+i*LONG_SIZE,word.val) == -1)
            perror("trace error");
        ++i;
        laddr += LONG_SIZE;
    }
    j = len % LONG_SIZE;
    if(j != 0)
    {
        word.val = 0;
        memcpy(word.chars,laddr,j);
        if(ptrace(PTRACE_POKEDATA,child,addr+i*LONG_SIZE,word.val) == -1)
            perror("trace error");
    }
}


void printBytes(const char* tip,char* codes,int len)
{
    int i;
    printf("%s :",tip);
    for(i = 0;i<len;++i)
    {
        printf("%02x ",(unsigned char)codes[i]);
    }
    puts("");
}

#define CODE_SIZE 48

int main(int argc ,char *argv[])
{
    if(argc != 2)
    {
        puts("no pid input");
        exit(1);
    }
    pid_t traced_process;
    struct user_regs_struct regs;
    long ins;
    char code[CODE_SIZE] = {0xb8,0x01,0x00,0x00,0x00,0xbf,0x01,0x00,0x00,0x00,0x48,0xbe,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xba,0x0d,0x00,0x00,0x00,0x0f,0x05,0xcc,0x48,0x65,0x6c,0x6c,0x6f,0x20,0x77,0x6f,0x72,0x6c,0x64,0x0a};
    char backup[CODE_SIZE];
    traced_process = atoi(argv[1]);
    printf("try to attach pid:%u\n",traced_process);
    if(ptrace(PTRACE_ATTACH,traced_process,NULL,NULL) == -1)
    {
        perror("trace attach error");
    }
    wait(NULL);
    if(ptrace(PTRACE_GETREGS,traced_process,NULL,&regs) == -1)
    {
        perror("trace get regs error");
    }
    //copy instructions into backup variable
    getdata(traced_process,regs.rip,backup,CODE_SIZE);
    printBytes("get tracee instuction",backup,CODE_SIZE);
    puts("try to inject code");
    putdata(traced_process,regs.rip,code,CODE_SIZE);
    puts("inject success, tracee continue");
    if(ptrace(PTRACE_CONT,traced_process,NULL,NULL) == -1)
    {
        perror("trace continue error");
    }
    //wait tracee to execute int3 to stop
    wait(NULL);
    puts("inject code finish, Press <Enter> to continue");
    getchar();
    printBytes("place inject instructions with backup instructions",backup,CODE_SIZE);
    putdata(traced_process,regs.rip,backup,CODE_SIZE);
    ptrace(PTRACE_SETREGS,traced_process,NULL,&regs);
    ptrace(PTRACE_DETACH,traced_process,NULL,NULL);
    return 0;
}

It doesn't work, only can make tracee stop and resume. what's wrong with it? run it in ubuntu 16.04 64bit.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
mmmmar
  • 31
  • 6

2 Answers2

0

You said it yourself. Use a command that raises a signal.

Debuggers use int 3, for a very simple reason. While every single software interrupt you raise is a couple of bytes long (one for the int command, and another for the interrupt number), int 3 is a single byte instruction. It is that precisely so it can be easily injected (and then removed) by a debugger.

To summarize, since you are on x86_64, replace the int 80 injection with syscall, but leave the other software interrupt as they were.

Shachar Shemesh
  • 8,193
  • 6
  • 25
  • 57
  • Ok,I understand. The instruction "int3" make tracee stop itself and the tracer can directly use wait() to wait the signal . Thanks. By the way ,the instruction is "int3" instead of "int 3", which may confuse others. – mmmmar Nov 05 '16 at 11:48
  • If you believe this answer is the answer to your question, please accept it (click on the "V" next to it). – Shachar Shemesh Nov 06 '16 at 05:49
  • I know reason. because the instructions made from asm is not PIC, when I inject them to tracee, the string address is not available. – mmmmar Nov 06 '16 at 14:01
0

I know the reason. the asm code that I post is not PIC, when it is injected into tracee's memory, string address is wrong, so it failed. right asm code should be

section .text

global main

main:
    jmp forward
backward:
    pop rsi
    mov rax, 1
    mov rdi, 1
    mov rdx, 13
    syscall
    int3

forward:
    call backward
db "Hello world",0xa
mmmmar
  • 31
  • 6
  • x86-64 has RIP-relative addressing; you don't need this shellcode hack because you don't need to avoid `0` bytes. Just use `lea rsi, [rel msg]` like a compiler would in normal x86-64 PIC/PIE code. – Peter Cordes Mar 19 '21 at 15:03