Question

I'm trying to use ptrace to change another program variable at run time, in my case a dummy shell.

int main(void)
{
    char    buf[MAXLINE];
    fgets(buf, MAXLINE, stdin);
    if (buf[strlen(buf) - 1] == '\n')
        buf[strlen(buf) - 1] = 0;

    execlp("sh", "sh", "-c", buf, (char *)0);
    printf("couldn't execute: %s", buf);
    exit(127);
}

In my injector app I use PTRACE_ATTACH and listen for sys_read syscalls; then PTRACE_GETREGS to try to obtain the registers that should contain the address of the "buf" array.

    ptrace(PTRACE_SYSCALL, pid, 0, 0);
    wait(&status);
    ptrace(PTRACE_GETREGS, pid, 0, &regs);

    syscall_n = regs.orig_rax;
    if (syscall_n == SYS_read){
        switch(syscall_status){
        case STARTED:
            printf("Waiting user input... ");
            syscall_status = WAITING;
            break;

        case WAITING:
            printf("\t[  OK  ]\n");
            syscall_status = RETURNED;
            break;

        default:
            break;
        }
    }

While I can catch the correct open syscall when fgets is called, I have no idea in which of the registers I should use to obtain the address of the string array. Or if there is a better way to obtain it.

So I can later use PEEKDATA and POKEDATA to inject the modified string.

Btw, I have read the "Playing with ptrace Part I" article, but I don't understand how adapt it to my purpose.

Was it helpful?

Solution

First, fgets() is not a system call, so you're not going to catch it with ptrace. What you are going to catch is sys_read() or similar system call. Even more so, the actual sys_read() may actually be invoked with different buffer and length arguments than those passed to original fgets() because of I/O caching in stdio.

Second, to establish reliably, which registers to check you need to know the exact ABI you're using. For example, on ARM/EABI the sys_read() call will expect the buffer pointer in register "r1" (with register "r0" holding the first argument, e.g. file descriptor and register "r2" holding the length). On x86 it will be ebx/ecx/edx combo; on amd64 - rdi/rsi/rdx.

If you actually want to trace the effects of higher level functions (as opposed to system calls), you will need to dig deep into the debugging information produced by the compiler, because it's more or less the only way to do such things (you can try using the libelf library from elfutils, just like ltrace utility does). You will actually have to programmatically set breakpoints in proper places and then rely on debugging info to deduce the variable layout on stack (to get to the stack allocated buffer).

Consider using the source of an ltrace utility for a working example:

http://anonscm.debian.org/gitweb/?p=collab-maint/ltrace.git;a=tree;h=refs/heads/master;hb=refs/heads/master

By the way, if you're using a sufficiently new kernel, you may benefit from using the 2 new system calls: process_vm_readv and process_vm_writev which can manipulate remote process memory "in bulk", given only the target PID and address (this is much more convenient approach than ptrace PEEK and POKE).

http://man7.org/linux/man-pages/man2/process_vm_readv.2.html

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top