Search code examples
clinuxmemorysystem-callsptrace

Remote mmap syscall using ptrace (Linux, C)


I've been stuck with this problem for some days, and still haven't manage to fix it. Basically, I want to do a remote syscall from an attacker program to the target. But before showing the code, I think it'd be a good idea to present my thought process, as the problem could be anything at this point. I am doing this remote syscall through the following steps:

  1. Parse /proc/<process_id>/maps file to get an executable region.
  2. Store the data at the executable region and write a custom buffer that does the syscall to it.
  3. Store the old registers and setup new ones to make the syscall
  4. Write the new registers and continue the execution
  5. After the syscall, the target program will break, which would allow me to get the output of mmap, set back the old registers and therefore, restore the old execution flow.

I am using my memory lib to parse the mmap files, get process id and process information, etc. As far as I am concerned, it is working properly. In any case, here's the source: https://github.com/rdbo/libmem

And the code I am using to do the call:

mem_voidptr_t allocate_ex(mem_process_t process, mem_size_t size, mem_alloc_t allocation)
{
    mem_voidptr_t alloc_addr = (mem_voidptr_t)MEM_BAD_RETURN;
    if(!mem_process_is_valid(&process)) return alloc_addr;
    int status;
    int mmap_syscall = __NR_mmap;
    struct user_regs_struct old_regs, regs;
    mem_byte_t injection_buf[] = 
    {
        0x0f, 0x05, //syscall
        0xcc        //int3
    };


    //Parse /proc/<process.pid>/maps to get executable region

    char path_buffer[64];
    snprintf(path_buffer, sizeof(path_buffer), "/proc/%i/maps", process.pid);
    int fd = open(path_buffer, O_RDONLY);
    if(fd == -1) return alloc_addr;

    int read_check = 0;
    mem_size_t file_size = 0;
    mem_string_t file_buffer = mem_string_init();

    for(char c; (read_check = read(fd, &c, 1)) != -1 && read_check != 0; file_size++)
    {
        mem_string_resize(&file_buffer, file_size);
        mem_string_c_set(&file_buffer,  file_size, c);
    }

    mem_size_t   injection_address_pos, injection_address_end;
    mem_string_t injection_address_str = mem_string_init();
    mem_voidptr_t injection_address = (mem_voidptr_t)MEM_BAD_RETURN;

    injection_address_pos = mem_string_find(&file_buffer, "r-xp", 0);
    injection_address_pos = mem_string_rfind(&file_buffer, "\n", injection_address_pos);
    if(injection_address_pos == file_buffer.npos) return alloc_addr;

    injection_address_end = mem_string_find(&file_buffer, "-", injection_address_pos);
    injection_address_str = mem_string_substr(&file_buffer, injection_address_pos, injection_address_end);
    injection_address = (mem_voidptr_t)strtoull(mem_string_c_str(&injection_address_str), NULL, 16);
    if(injection_address == (mem_voidptr_t)MEM_BAD_RETURN || injection_address == (mem_voidptr_t)0)
        return alloc_addr;

    printf("Injection address: %p\n", injection_address);

    //Store the old data at 'injection_address' and write the injection buffer to it

    mem_byte_t old_data[sizeof(injection_buf)];
    mem_ex_read(process, injection_address, (mem_voidptr_t)old_data, sizeof(old_data));
    mem_ex_write(process, injection_address, (mem_voidptr_t)injection_buf, sizeof(injection_buf));

    //Attach to process and store current registers

    ptrace(PTRACE_ATTACH, process.pid, NULL, NULL);
    ptrace(PTRACE_GETREGS, process.pid, NULL, &old_regs);
    memcpy(&regs, &old_regs, sizeof(regs));

    //Setup syscall registers

    regs.rax = mmap_syscall;          //syscall number
    regs.rdi = 0;                     //address        (arg0)
    regs.rsi = size;                  //length         (arg1)
    regs.rdx = allocation.protection; //protection     (arg2)
    regs.r10 = allocation.type;       //flags          (arg3)
    regs.r8  = -1;                    //fd             (arg4)
    regs.r9  = 0;                     //offset         (arg5)

    regs.rip = (unsigned long long)injection_address; //next instruction to execute

    //Call mmap on external process

    ptrace(PTRACE_SETREGS, process.pid, NULL, &regs);
    ptrace(PTRACE_CONT, process.pid, NULL, NULL);
    waitpid(process.pid, &status, WSTOPPED);

    //Get the registers after syscall to store the return of mmap

    ptrace(PTRACE_GETREGS, process.pid, NULL, &regs);
    alloc_addr = (mem_voidptr_t)regs.rax; //store the return of mmap

    //Restore the original buffer at 'injection_address'

    mem_ex_write(process, injection_address, (mem_voidptr_t)old_data, sizeof(old_data));

    //Continue the original execution

    ptrace(PTRACE_SETREGS, process.pid, NULL, &old_regs);
    ptrace(PTRACE_CONT, process.pid, NULL, NULL);

    //Return allocation address, if valid
    if((mem_uintptr_t)alloc_addr >= (mem_uintptr_t)-2048)
        alloc_addr = (mem_voidptr_t)MEM_BAD_RETURN;
    return alloc_addr;
}

and the main function of the attacker program:

int main()
{
    mem_pid_t pid = mem_ex_get_pid(mem_string_new("target"));
    mem_process_t process = mem_ex_get_process(pid);

    int buffer = 10;
    mem_alloc_t allocation = mem_alloc_init();
    allocation.protection = PROT_READ | PROT_WRITE;
    allocation.type       = MAP_ANON  | MAP_PRIVATE;
    mem_voidptr_t alloc_addr = allocate_ex(process, sizeof(buffer), allocation);
    printf("Allocation Address: %p\n", alloc_addr);
    if(alloc_addr == (mem_voidptr_t)MEM_BAD_RETURN)
    {
        printf("Invalid allocation\n");
        return -1;
    }

    //Check if worked by reading/writing to that buffer
    int read_buffer = 0;
    mem_ex_write(process, alloc_addr, &buffer, sizeof(buffer));
    mem_ex_read(process, alloc_addr, &read_buffer, sizeof(read_buffer));
    printf("Read buffer: %i\n", read_buffer);
    if(read_buffer == buffer)
        printf("Success!\n");

    return 0;   
}

The target program:

int main()
{
    printf("Waiting for injection\n");
    while(1);
}

The output of the attacker program is:

Injection address: 0x55f6e104a000
Allocation Address: (nil)
Read buffer: 0

and a Segmentation Fault is raised on the target program. The executable region is valid (I manually checked) and the process is valid too. Also, I am having some trouble debugging the target program, apparently GDB wouldn't let ptrace do its job from the attacker program. Running Arch Linux. Both programs are compiled with clang (x64). Any ideas?


Solution

  • Turns out the problem was that I was reading/writing the memory using process_vm_read and process_vm_write. I got it to work by changing the read/write method to ptrace PEEK/POKE data. Fixed code (included on my memory lib):

    mem_voidptr_t injection_address;
        struct user_regs_struct old_regs, regs;
        int status;
    
        const mem_byte_t injection_buffer[] = 
        {
            0x0f, 0x05, //syscall
            0xcc        //int3 (SIGTRAP)
        };
    
        mem_byte_t old_data[sizeof(injection_buffer)];
    
        //Find injection address
    
        char path_buffer[64];
        snprintf(path_buffer, sizeof(path_buffer), "/proc/%i/maps", process.pid);
        int fd = open(path_buffer, O_RDONLY);
        if(fd == -1) return alloc_addr;
    
        int read_check = 0;
        mem_size_t file_size = 0;
        mem_string_t file_buffer = mem_string_init();
    
        for(char c; (read_check = read(fd, &c, 1)) != -1 && read_check != 0; file_size++)
        {
            mem_string_resize(&file_buffer, file_size);
            mem_string_c_set(&file_buffer,  file_size, c);
        }
    
        mem_size_t   injection_address_pos, injection_address_end;
        mem_string_t injection_address_str = mem_string_init();
        injection_address = (mem_voidptr_t)MEM_BAD_RETURN;
    
        injection_address_pos = mem_string_find(&file_buffer, "r-xp", 0);
        injection_address_pos = mem_string_rfind(&file_buffer, "\n", injection_address_pos);
        if(injection_address_pos == file_buffer.npos) return alloc_addr;
    
        injection_address_end = mem_string_find(&file_buffer, "-", injection_address_pos);
        injection_address_str = mem_string_substr(&file_buffer, injection_address_pos, injection_address_end);
        injection_address = (mem_voidptr_t)strtoull(mem_string_c_str(&injection_address_str), NULL, 16);
        if(injection_address == (mem_voidptr_t)MEM_BAD_RETURN || injection_address == (mem_voidptr_t)0) return alloc_addr;
    
        //Inject
        ptrace(PTRACE_ATTACH, process.pid, NULL, NULL);
    
        //Store data at injection_address
        for(mem_size_t i = 0; i < sizeof(injection_buffer); i++)
            ((mem_byte_t*)old_data)[i] = (mem_byte_t)ptrace(PTRACE_PEEKDATA, process.pid, injection_address + i, NULL);
    
        //Write injection buffer to injection address
        for(mem_size_t i = 0; i < sizeof(injection_buffer); i++)
            ptrace(PTRACE_POKEDATA, process.pid, injection_address + i, ((mem_byte_t*)injection_buffer)[i]);
    
        ptrace(PTRACE_GETREGS, process.pid, NULL, &old_regs);
        regs = old_regs;
    
        regs.rax = __NR_mmap;                        //syscall number
        regs.rdi = (mem_uintptr_t)0;                 //arg0 (void* address)
        regs.rsi = (mem_uintptr_t)size;              //arg1 (size_t size)
        regs.rdx = (mem_uintptr_t)protection;        //arg2 (int protection)
        regs.r10 = MAP_PRIVATE | MAP_ANON;           //arg3 (int flags)
        regs.r8  = -1;                               //arg4 (int fd)
        regs.r9  = 0;                                //arg5 (off_t offset)
        regs.rip = (mem_uintptr_t)injection_address; //next instruction
    
        ptrace(PTRACE_SETREGS, process.pid, NULL, &regs);
        ptrace(PTRACE_CONT, process.pid, NULL, NULL);
        waitpid(process.pid, &status, WSTOPPED);
        ptrace(PTRACE_GETREGS, process.pid, NULL, &regs);
        alloc_addr = (mem_voidptr_t)regs.rax;
    
        //Restore old execution
        ptrace(PTRACE_SETREGS, process.pid, NULL, &old_regs);
    
        for(mem_size_t i = 0; i < sizeof(injection_buffer); i++)
            ptrace(PTRACE_POKEDATA, process.pid, injection_address + i, ((mem_byte_t*)old_data)[i]);
    
        //ptrace(PTRACE_CONT, process.pid, NULL, NULL);
        ptrace(PTRACE_DETACH, process.pid, NULL, NULL);
    
        if(alloc_addr == (mem_voidptr_t)__NR_mmap)
            alloc_addr = (mem_voidptr_t)MEM_BAD_RETURN;
    
        return alloc_addr;