Dumping Ram From Running Linux Processes

From Colin Hardy
Jump to: navigation, search

TL;DR


During the course of a recent Capture the Flag contest (which was run through my employer via Symantec, and was lots of fun) one challenge was to find a secret key from within a binary file which is then used to sign session cookies on a Go webserver. Usually this type of flag is pretty straight forward, running strings on the binary usually helps but in this instance whilst the answer could be gleaned from strings there was a lot of noise to go through. Here I show how I solved the flag using a combination of some Linux command line kung fu and the infamous tool gdb to dump memory from a running process.

Binary Overview


Running file on the binary, called ctf-bin, reveals that it is a 64-bit Linux executable.

$ file ctf-bin
ctf-bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped

Running strings of length 10 or more would take rather a long time to parse manually:

$ strings -n 10 ctf-bin | wc | uniq
   71085  106643 2081483

Therefore 71k strings to review :). So let's delve a bit deeper and run the program and attach it to gdb.

Mapping a Running Process


So, let's run the process and find it's PID (Process ID) that Linux assigns. In one terminal window run the binary:

$ ./ctf-bin

And in another terminal window let's find the PID:

$ ps -elf | grep ctf-bin
4 S root       6686   5777  0  80   0 -  8403 -      17:02 pts/0    00:00:00 ./ctf-bin
0 S root       6701   6694  0  80   0 -  3179 -      17:02 pts/2    00:00:00 grep ctf-bin

So in this instance my PID is 6686, yours will be different so take a note. Cool, so we have a running process and the PID, let's cat out the memory map of the process:

$ cat /proc/6686/maps
00400000-007bd000 r-xp 00000000 08:01 1727726                            /root/ctf/q3ctf/ctf-bin
007bd000-00bb9000 r--p 003bd000 08:01 1727726                            /root/ctf/q3ctf/ctf-bin
00bb9000-00bdb000 rw-p 007b9000 08:01 1727726                            /root/ctf/q3ctf/ctf-bin
00bdb000-00bff000 rw-p 00000000 00:00 0 
016b2000-016d3000 rw-p 00000000 00:00 0                                  [heap]
c000000000-c000001000 rw-p 00000000 00:00 0 
c81fff0000-c820200000 rw-p 00000000 00:00 0 
7f510bb9f000-7f510bba0000 ---p 00000000 00:00 0 
7f510bba0000-7f510c3a0000 rw-p 00000000 00:00 0                          [stack:6688]
7f510c3a0000-7f510c3a1000 ---p 00000000 00:00 0 
7f510c3a1000-7f510cba1000 rw-p 00000000 00:00 0                          [stack:6687]
7f510cba1000-7f510cd38000 r-xp 00000000 08:01 427476                     /lib/x86_64-linux-gnu/libc-2.23.so
7f510cd38000-7f510cf38000 ---p 00197000 08:01 427476                     /lib/x86_64-linux-gnu/libc-2.23.so
7f510cf38000-7f510cf3c000 r--p 00197000 08:01 427476                     /lib/x86_64-linux-gnu/libc-2.23.so
7f510cf3c000-7f510cf3e000 rw-p 0019b000 08:01 427476                     /lib/x86_64-linux-gnu/libc-2.23.so
7f510cf3e000-7f510cf42000 rw-p 00000000 00:00 0 
7f510cf42000-7f510cf5a000 r-xp 00000000 08:01 427491                     /lib/x86_64-linux-gnu/libpthread-2.23.so
7f510cf5a000-7f510d159000 ---p 00018000 08:01 427491                     /lib/x86_64-linux-gnu/libpthread-2.23.so
7f510d159000-7f510d15a000 r--p 00017000 08:01 427491                     /lib/x86_64-linux-gnu/libpthread-2.23.so
7f510d15a000-7f510d15b000 rw-p 00018000 08:01 427491                     /lib/x86_64-linux-gnu/libpthread-2.23.so
7f510d15b000-7f510d15f000 rw-p 00000000 00:00 0 
7f510d15f000-7f510d183000 r-xp 00000000 08:01 427472                     /lib/x86_64-linux-gnu/ld-2.23.so
7f510d2de000-7f510d361000 rw-p 00000000 00:00 0 
7f510d370000-7f510d382000 rw-p 00000000 00:00 0 
7f510d382000-7f510d383000 r--p 00023000 08:01 427472                     /lib/x86_64-linux-gnu/ld-2.23.so
7f510d383000-7f510d384000 rw-p 00024000 08:01 427472                     /lib/x86_64-linux-gnu/ld-2.23.so
7f510d384000-7f510d385000 rw-p 00000000 00:00 0 
7ffdbeb75000-7ffdbeb96000 rw-p 00000000 00:00 0                          [stack]
7ffdbeba1000-7ffdbeba3000 r--p 00000000 00:00 0                          [vvar]
7ffdbeba3000-7ffdbeba5000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]

Ok, that looks a little scary, but fear not. This just describes regions of contiguous virtual memory in the process. The columns of data align to (taken from here)

  • address - This is the starting and ending address of the region in the process's address space
  • permissions - This describes how pages in the region can be accessed.
  • offset - If the region was mapped from a file (using mmap), this is the offset in the file where the mapping begins. If the memory was not mapped from a file, it's just 0.
  • device - If the region was mapped from a file, this is the major and minor device number (in hex) where the file lives.
  • inode - If the region was mapped from a file, this is the file number.
  • pathname - If the region was mapped from a file, this is the name of the file. This field is blank for anonymous mapped regions. There are also special regions with names like [heap], [stack], or [vdso]. [vdso] stands for virtual dynamic shared object.

So now it's a case of dumping out the relevant sections and running strings against those to see if there is anything meaningful.

Attaching to gdb


Let's launch gdb and attach it to the running process.

$ gdb attach 6686

gdb will then give you some messages about reading symbols etc and you'll arrive at the famililar gdb prompt.

Let's now dump out the virtual memory ranges. You can choose any valid range you wish, out of laziness I'll choose the range which I know contains the answer to the flag:

(gdb) dump memory /root/memory.dump 0xc81fff0000 0xc820200000

Note the syntax here. "dump memory" is the command, "/root/memory.dump" is the path where I want to save the dumped information and then the two hex addresses are the memory range in question. These are in a slightly different format to /proc/6686/maps, i.e. they are prefixed with 0x and have no hyphen in between.

This outputs the file for us nice and quick, now we're left to analyse that memory file how we see fit.

Strings from Memory


Linux makes this nice and easy, simply run strings against the memory.dump file. I like to use a minimum length of 10 to start with to help filter out some noise. Here's the results:

$ strings -n 10 /root/memory.dump
XDG_VTNR=2
XDG_SESSION_ID=1SHELL=/bin/bash
VTE_VERSION=4201USER=rootSHLVL=1USERNAME=root
LANG=en_US.UTF-8XDG_SEAT=seat0
HOME=/root
LOGNAME=root
...
Thu Feb 4 21:00:57 -0800 2010
aBCdEfJ2cPhLzQ2l111oZMHuADgJQVlkA
Wed Sep 28 17:02:15 EDT 2016
Wed Sep 28 17:02:15 EDT 2016
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
 !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
Starting scrum-cookies-server on port 9000.
Starting scrum-cookies-server on port 9000.
Wed Sep 28 17:02:15 EDT 2016 - INFO - main.go:110 - Starting scrum-cookies-server on port 9000.

And you can see within this data there appears to be a hash. Now, for the purpose of not wanting to give the answer away to those who may be taking part in this CTF again, I've change the hash :but the one here in my example is

aBCdEfJ2cPhLzQ2l111oZMHuADgJQVlkA

Summary

Et Voila! You've identified the PID of a running process, dumped RAM from that process using gdb and ran strings to find some nice meaningful data. Hopefully also you've caught the flag. :)

Enjoy!