GDB Guide Part 3 - Process Maps
GDB Guides Series:
1. Introduction
Happy Chinese New Year! In my previous post, I did mention that I will be taking a break from posting anything due to the holiday season. However, my workaholic self jolted me out of my bed earlier this morning just so that I can squeeze out part 3 of this guide.
2. Overview
Today’s guide will mainly focus on process mappings as I only have about an hour to quickly get this guide out. However, I think it is also important to cover some background on dynamic and static executables before talking about Process Maps.
Again, the examples used for this walkthrough is the same test.c file from earlier parts of the guide.
3. Dynamic Executables
Firstly, we will compile our test file without any fanciful GCC flags.
$ gcc test.c -o test_d
We can then use the ldd command to inspect the libraries that it depends on.
$ ldd test_d
# Output
linux-vdso.so.1 (0x00007ffe93378000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x000076a082a00000)
/lib64/ld-linux-x86-64.so.2 (0x000076a082d7a000)
Looking at the output above, it seems at first glance, that we require 3 libraries. However, linux-vdso.so.1, is just a virtual shared object that doesn’t have any physical file on the disk. It’s actually part of the kernel that’s exported into every program’s address space when it’s loaded.
Thus, our test binary actually only loads libc.so.6 and ld-linux-x86-64.so.2 upon running.
4. Static Executables
To compile a static executable, we will require additional GCC flags that tell the compiler to cramp all the different library functions into one single binary.
$ gcc -static test.c -o test_s
Again, we can use the ldd command to check that it is now a standalone executable.
$ ldd test_s
# Output
not a dynamic executable
5. Process Maps
Process Maps are basically a mapping of memory regions to certain sections of a binary or library. Understanding Process Maps will play a crucial part in doing dynamic analysis on a stripped binary. A stripped binary means that it does not contain any symbols and thus, we cannot simply set breakpoints on a function name.
To inspect the memory regions, we can either do it via the old-school method by looking at /proc/$PID/maps or using GDB.
5.1 Via /proc/pid/maps
Run test_d in one of the terminals, and run the following command in a new terminal:
$ cat /proc/`pidof test_d`/maps
# Output
615276507000-615276508000 r--p 00000000 fc:01 49020965 /tmp/test_d
615276508000-615276509000 r-xp 00001000 fc:01 49020965 /tmp/test_d
615276509000-61527650a000 r--p 00002000 fc:01 49020965 /tmp/test_d
61527650a000-61527650b000 r--p 00002000 fc:01 49020965 /tmp/test_d
61527650b000-61527650c000 rw-p 00003000 fc:01 49020965 /tmp/test_d
6152a572b000-6152a574c000 rw-p 00000000 00:00 0 [heap]
741be5800000-741be5828000 r--p 00000000 fc:01 7080047 /usr/lib/x86_64-linux-gnu/libc.so.6
741be5828000-741be59bd000 r-xp 00028000 fc:01 7080047 /usr/lib/x86_64-linux-gnu/libc.so.6
741be59bd000-741be5a15000 r--p 001bd000 fc:01 7080047 /usr/lib/x86_64-linux-gnu/libc.so.6
741be5a15000-741be5a16000 ---p 00215000 fc:01 7080047 /usr/lib/x86_64-linux-gnu/libc.so.6
741be5a16000-741be5a1a000 r--p 00215000 fc:01 7080047 /usr/lib/x86_64-linux-gnu/libc.so.6
741be5a1a000-741be5a1c000 rw-p 00219000 fc:01 7080047 /usr/lib/x86_64-linux-gnu/libc.so.6
741be5a1c000-741be5a29000 rw-p 00000000 00:00 0
741be5a8b000-741be5a8e000 rw-p 00000000 00:00 0
741be5aa1000-741be5aa3000 rw-p 00000000 00:00 0
741be5aa3000-741be5aa5000 r--p 00000000 fc:01 7080039 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
741be5aa5000-741be5acf000 r-xp 00002000 fc:01 7080039 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
741be5acf000-741be5ada000 r--p 0002c000 fc:01 7080039 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
741be5adb000-741be5add000 r--p 00037000 fc:01 7080039 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
741be5add000-741be5adf000 rw-p 00039000 fc:01 7080039 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
7ffc8ca55000-7ffc8ca76000 rw-p 00000000 00:00 0 [stack]
7ffc8cb04000-7ffc8cb08000 r--p 00000000 00:00 0 [vvar]
7ffc8cb08000-7ffc8cb0a000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
From the output above, we know the following base addresses:
- test_d –>
0x615276507000 - libc.so.6 –>
0x741be5800000 - ld-linux-x86-64.so.2 –>
0x741be5aa3000
As practice, you can do the same with test_s and get the base address of itself.
5.2 Via GDB
GDB also has a feature to look at the Process Maps via info proc mappings.
For this example, we shall run test_s and attach to it using another terminal.
$ gdb -p `pidof test_s`
Then, run the following command to inspect the Process Maps:
(gdb) info proc mappings
process 24816
Mapped address spaces:
Start Addr End Addr Size Offset Perms objfile
0x400000 0x401000 0x1000 0x0 r--p /tmp/test_s
0x401000 0x4b7000 0xb6000 0x1000 r-xp /tmp/test_s
0x4b7000 0x4e1000 0x2a000 0xb7000 r--p /tmp/test_s
0x4e2000 0x4e6000 0x4000 0xe1000 r--p /tmp/test_s
0x4e6000 0x4e9000 0x3000 0xe5000 rw-p /tmp/test_s
0x4e9000 0x4ee000 0x5000 0x0 rw-p
0x2e5b6000 0x2e5d8000 0x22000 0x0 rw-p [heap]
0x7fffc2860000 0x7fffc2881000 0x21000 0x0 rw-p [stack]
0x7fffc29b1000 0x7fffc29b5000 0x4000 0x0 r--p [vvar]
0x7fffc29b5000 0x7fffc29b7000 0x2000 0x0 r-xp [vdso]
0xffffffffff600000 0xffffffffff601000 0x1000 0x0 --xp [vsyscall]
From the output above, we know that the base address of test_s is 0x400000.
As practice, you can do the same with test_d and get the base address of itself and its libraries.
6. Conclusion
With the memory regions mapped out, we will be able to debug stripped binaries easily. Stay tuned for part 4 where I will be going through on how we can debug our stripped test program. Adios!