Process title and missing memory space

Sept. 22, 2018, 11:06 a.m.

It’s amazing what happens when you do any kind of research. Many times, you find a few interesting things around one topic you are looking into. First, I published a post about changing a process name which describes how it can be accomplished on different operating systems. This was the main point of my research. Then I described how to install other operating systems using grub2-bhyve on bhyve hypervisors. Today, the last topic is somewhat related to them. When I was looking at the process title it was easy to notice that the kernel needed to read some piece of memory from it. What would happen if the memory region didn’t exist?

We have to find ways to accomplish this. First read the code – which in this case is much simpler because we would need to check which function is used to read the memory region and check if this function does it in safe way. Another method would be to just free the memory region and see what happens. The other method sounds a little bit hack-ish, so let’s try it!

From a previous post we already know that to force the FreeBSD kernel to read a process memory region we need to set the sysctl kern.proc.args.PID to an empty value. Next step would be to get a sysctl which contains the address of the process name. The address is kept in the kern.ps_strings sysctl. The code below does those two things.
#include <sys/types.h>
#include <sys/sysctl.h>

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

int
main(void)
{
    int oid[4];
    intptr_t addr;
    size_t len;

    /* Force kernel to read process memory. */
    oid[0] = CTL_KERN;
    oid[1] = KERN_PROC;
    oid[2] = KERN_PROC_ARGS;
    oid[3] = getpid();
    sysctl(oid, 4, 0, 0, "", 0);

    len = sizeof(addr);
    sysctlbyname("kern.ps_strings", &addr, &len, NULL, 0);

    return (0);
}

Let’s analyze the binary under gdb.
(gdb) b sysctlbyname
Breakpoint 1 at 0x201420
(gdb) r
Starting program: /usr/home/oshogbo/src/stack-bp/a.out

Breakpoint 1, sysctlbyname (name=0x2002b8 "kern.ps_strings", oldp=0x7fffffffe6e8, oldlenp=0x7fffffffe6e0,
    newp=0x0, newlen=0) at /usr/src/lib/libc/gen/sysctlbyname.c:24
24        oidlen = sizeof(real_oid) / sizeof(int);
(gdb) return
Make sysctlbyname return now? (y or n) y
#0  0x0000000000201345 in main () at first.c:22
22        sysctlbyname("kern.ps_strings", &addr, &len, NULL, 0);
(gdb) p/x addr
$1 = 0x7fffffffe760

First we set a breakpoint on sysctlbyname(2) function, and the next command runs our binary. The return command tells gdb to continue until we return from the current function—in our case it’s sysctlbyname. At the end we check the value of addr variable. We got the 0x7fffffffe760 address. To check in which region the address is, let’s use info proc mappings command to display all mapped address spaces. We will find that this address belongs to the 0x7ffffffdf000 – 0x7ffffffff000 region. This mapping is the only one which has a D flag which means ‘growing down’, so we can assume that this is a process stack. In FreeBSD we can also check the kern.usrstack sysctl which returns the address of the process stack address. We were right.
gdb> info proc mappings
    0x7fffdffff000     0x7ffffffdf000 0x1ffe0000        0x0  --- ----
    0x7ffffffdf000     0x7ffffffff000    0x20000        0x0  rw- ---D
    0x7ffffffff000     0x800000000000     0x1000        0x0  r-x ----

$ sysctl kern.usrstack
kern.usrstack: 140737488351232 (0x7ffffffff000)

The kern.ps_strings is a read-only sysctl, so we can’t change the address to which it points. We could change a kernel to allow us to do that, but instead of doing that let’s just move the stack to a new place and let’s free our old stack. In amd64 the registers that use a stack are rsp (stack pointer) and rbp (base pointer) registers, so we just need to change value of them to a new region returned by the mmap(2) function. The code below does that.

// gcc -g -masm=intel
#include <sys/mman.h>

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

int
main(void)
{
    void *addr;

    addr = mmap(0, 0x2000, PROT_WRITE | PROT_READ, MAP_STACK, -1, 0);

    asm volatile(
        "mov rax, %0;"
        "mov rsp, rax;"
        "mov rbp, rax;"
        :
        : "a" (addr)
     );

    exit(0);
}

We use an inline assembly function to change rsp and rbp. I prefer the intel syntax, so the example needs to be compiled ‘-masm=intel’ flag which changes the syntax from AT&T to Intel one. It’s worth noticing that clang had some issue with this assembly code, but it works just fine with gcc. In our program we simplify things because we just call exit syscall, which will kill our program gracefully. Alternatively, we could copy return pointers from stack. The mmap(2) function uses a MAP_STACK flag which will create a valid stack region. Let’s check the code below which forces the kernel to read a process memory to get its title, and change the stack and free the old stack.

#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/mman.h>

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

int
main(void)
{
    void *addr;
    int oid[4];

    /* Force kernel to read process memory. */
    oid[0] = CTL_KERN;
    oid[1] = KERN_PROC;
    oid[2] = KERN_PROC_ARGS;
    oid[3] = getpid();
    sysctl(oid, 4, 0, 0, "", 0);

    addr = mmap(0, 0x2000, PROT_WRITE | PROT_READ, MAP_STACK, -1, 0);

    asm volatile("mov rax, %0;"
        "mov rsp, rax;"
        "mov rbp, rax;"
        :
        : "a" (addr)
     );

    munmap(0x7ffffffdf000, 0x20000);

    exit(0);
}

Let’s run it under
gdb.
(gdb) b exit
Breakpoint 1 at 0x4004f0
(gdb) r
Starting program: /usr/home/oshogbo/src/stack-bp/a.out

Breakpoint 1, exit (status=0) at /usr/src/lib/libc/stdlib/exit.c:66
66        _thread_autoinit_dummy_decl = 1;
(gdb) info proc mappings
process 4310
Mapped address spaces:

          Start Addr           End Addr       Size     Offset   Flags   File
            0x400000           0x401000     0x1000        0x0  r-x CN-- /usr/home/oshogbo/src/stack-bp/a.out
            0x600000           0x601000     0x1000        0x0  rw- ----
         0x800600000        0x800608000     0x8000        0x0  r-- CN-- /libexec/ld-elf.so.1
         0x800608000        0x800621000    0x19000     0x8000  r-x C--- /libexec/ld-elf.so.1
         0x800621000        0x800622000     0x1000    0x21000  rw- C--- /libexec/ld-elf.so.1
         0x800622000        0x800644000    0x22000        0x0  rw- ----
         0x800644000        0x800645000     0x1000        0x0  r-- ----
         0x800645000        0x8006c1000    0x7c000        0x0  r-- CN-- /lib/libc.so.7
         0x8006c1000        0x800817000   0x156000    0x7c000  r-x C--- /lib/libc.so.7
         0x800817000        0x80081d000     0x6000   0x1d2000  rw- C--- /lib/libc.so.7
         0x80081d000        0x800826000     0x9000   0x1d8000  r-- C--- /lib/libc.so.7
         0x800826000        0x800a59000   0x233000        0x0  rw- ----
         0x800a59000        0x800a5a000     0x1000        0x0  --- ----
         0x800a5a000        0x800a5b000     0x1000        0x0  rw- ---D
         0x800c00000        0x801200000   0x600000   0x3da000  rw- ----
      0x7fffdffff000     0x7ffffffdf000 0x1ffe0000        0x0  --- ----
      0x7ffffffff000     0x800000000000     0x1000        0x0  r-x ----
(gdb) info inferior
  Num  Description       Executable
* 1    process 4310      /usr/home/oshogbo/src/stack-bp/a.out

Again, we set the breakpoint, this time on exit function. When the program breaks we can see that the original stack is freed. You will notice that I hardcoded the stack address—as a home work you can do it more generically. The last gdb command returns us the pid of the process. So, let’s see what ps will return us.

$ ps aux | grep 4310
oshogbo  4310    0.0  0.0    10612    1952  7  TX   11:26        0:00.19 [a.out]

As you can see, when the kernel can’t read the process memory, it simply returns the name of a binary. Nothing crashed. We could get to this point simply by reading the kernel code and checking which function was used to read the kernel memory and what happens later, but this way was a very funny way of doing it.