GDB Guides Series:

  1. GDB Guide Part 1 - Basics
  2. GDB Guide Part 2 - Breakpoints & Linux Calling Conventions
  3. GDB Guide Part 3 - Process Maps
  4. GDB Guide Part 4 - Examining Memory
  5. GDB Guide Part 5 - Stepping
  6. GDB Guide Part 6 - Automation
  7. GDB Guide Part 7 - Custom Commands
  8. GDB Guide Part 8 - Invoking Function Calls

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
Compiling a Dynamically Linked Binary

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)
Showing Shared Library Dependencies

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
Compiling a Statically Linked Binary

Again, we can use the ldd command to check that it is now a standalone executable.

$ ldd test_s

  # Output
  not a dynamic executable
Proving that It is Standalone

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]
Showing Process Maps - /proc/pid/maps

From the output above, we know the following base addresses:

  1. test_d –> 0x615276507000
  2. libc.so.6 –> 0x741be5800000
  3. 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`
Attaching to Static Test Process

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]
Showing Process Maps - info proc mappings

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!

7. Resources

  1. test.c

GDB Guides Series:

  1. GDB Guide Part 1 - Basics
  2. GDB Guide Part 2 - Breakpoints & Linux Calling Conventions
  3. GDB Guide Part 3 - Process Maps
  4. GDB Guide Part 4 - Examining Memory
  5. GDB Guide Part 5 - Stepping
  6. GDB Guide Part 6 - Automation
  7. GDB Guide Part 7 - Custom Commands
  8. GDB Guide Part 8 - Invoking Function Calls