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.