Tuesday, April 16, 2013

Can GDB's List Source Code Be Used for Evil Purposes?

By Alejandro Hernández @nitr0usmx

One day while debugging an ELF executable with the GNU Debugger (GDB), I asked myself, "How does GDB know which file to read when you use the list command?" (For the uninformed, the list command prints a specified number of lines from a source code file -— ten lines is the default.)
Source code filenames are contained in the metadata of an ELF executable (in the .debug_line section, to be exact). When you use the list command, GDB will open(), read(), and display the file contents if and only if GDB has the permissions needed to read the source file. 
The following is a simple trick where you can use GDB as a trampoline to read a file which originally you don’t have enough permission to read. This trick could also be helpful in a binary capture-the-flag (CTF) or reverse engineering challenge.
Here are the steps:

1. Compile 'foo.c' with the GNU Compiler (GCC) using the -ggdb flag.

2. Open the resulting ELF executable with GDB and the list command to read its source code as shown in the following screen shot:

3. Make a copy of ‘foo.c’ and call it ‘_etc_shadow.c’, so that this name is hardcoded within the internal metadata structures of the compiled ELF executable as in the following screen shot.

4. Open the executable with your preferred hex editor (I used HT Editor because it supports the ELF file format) and replace ‘_etc_shadow.c’ with ‘/etc/shadow’ (don't forget the NULL character at the end of the string) the first two times it appears.

5. Evidently, it won't work unless you have sufficient user privileges, otherwise GDB won’t be able to read /etc/shadow.

6. If you trace the open() syscall calls executed by GBD:
 ($strace -e open gdb ./_etc_shadow) 
you can see that it returns -1 (EACCES) because of insufficient permissions.

7. Now imagine that for some reason GDB is a privileged command (the SUID (Set User ID) bit in the permissions is enabled). Opening our modified ELF file with GDB, it would be possible to read the contents of ‘/etc/shadow’ because the gdb command would be executed with root privileges.

8. Imagine another hypothetical scenario: a hardened development (or CTF) server that has been configured with granular privileges using a tool such as Sudo to allow certain commands to be executed. (To be honest I have never seen a scenario like this before, but it’s an example worth considering to illustrate how this attack might evolve).

9. You cannot display the contents of ‘/etc/shadow’ by using the cat command because /bin/cat is an unauthorized command in our configuration. However, the gdb command has been authorized and therefore has the rights needed to display the source file (/etc/shadow):


Taking advantage of this GDB feature and mixing it with other techniques could make a more sophisticated attack possible. Use your imagination.

Do you have other ideas how this could be used as an attack vector, either by itself or if combined with other techniques? Let me know.


  1. I was curious whether running gdb as a setuid root process would cause the child process to have euid root. Interestingly it doesn't, so the trick is really useful in that case.

    If gdb is run through sudo, however, the child process(es) created by gdb spawn as root. In this case you could just do this:

    $ sudo gdb
    (gdb) file /bin/cat
    Reading symbols from /bin/cat...(no debugging symbols found)...done.
    (gdb) run /etc/shadow
    Starting program: /bin/cat /etc/shadow

  2. Reid,

    Great, I haven't thought on that before (sudo, file, run).

    Also, Lucas Apa told me: 'shell cat /etc/shadow' which I tried with sudo gdb and works perfectly as well.


  3. Another way is to call python functions from within GDB, with the python keyword:

    (gdb) python print(open('/etc/shadow').read().strip())

    Or if your program loads libc, I guess that using the keyword `call' to execute C functions will also work.

    Although I prefer the `shell cat' one :)

  4. Eduardo, in the case when gdb is setuid root 'shell cat' would not work as the forked child process will have user-level privs, not root privs. It would work if gdb was run as sudo gdb though. Also, in my own testing, I set gdb to be setuid root and the only techniques that work to display /etc/shadow is "list" and the python open line. I tried call open("/etc/shadow") with a setuid gdb, and it doesnt work. It seems call uses permissions from the spawned debugging shell which has user-level privs. Call jumps into the debugged processes' function right? So if my debugged process is the setuid gdb I am running in the first place, why would call open("/etc/shadow") fail? I guess gdb doesnt honor the setuid permissions for target binaries when the "call" instruction is made in the case that privileged operations are done by the target binary. It succeeds only when I run gdb under sudo, as then any forked process will also be under root.

  5. The python line worked fine with gdb suid, however, 'shell cat /etc/shadow' didn't because for some reason, gdb drops the euid after the fork() in my Linux box:

    nitr0us@chatsubo:~$ ls -l `which gdb`
    -rwsr-sr-x 1 root root 4936108 May 7 2012 /usr/bin/gdb
    nitr0us@chatsubo:~$ gdb -q
    (gdb) shell ./uid
    UID: 1000 EUID: 1000
    GID: 1000 EGID: 1000
    (gdb) shell ps aux | grep gdb | head -1
    root 2165 0.2 2.6 16508 6688 pts/0 S+ 17:46 0:00 gdb -q
    (gdb) shell cat /etc/shadow
    cat: /etc/shadow: Permission denied
    (gdb) file /home/nitr0us/_etc_shadow
    Reading symbols from /home/nitr0us/_etc_shadow...done.
    (gdb) list
    1 root:$6$XYxYE1LYYESq...:15533:0:99999:7:::
    2 daemon:*:15533:0:99999:7:::
    3 bin:*:15533:0:99999:7:::
    4 sys:*:15533:0:99999:7:::
    5 sync:*:15533:0:99999:7:::
    6 games:*:15533:0:99999:7:::
    7 man:*:15533:0:99999:7:::
    8 lp:*:15533:0:99999:7:::
    9 mail:*:15533:0:99999:7:::
    10 news:*:15533:0:99999:7:::

    In OpenBSD, the euid of the fork() process is 0, so, shell cat /etc/master.passwd works fine. There's no necessity of modifying an ELF file...

    Cheers !

  6. @nitr0us - BASH will drop privileges if euid!=uid. GDB uses the shell when it runs the child. Maybe you can change the shell that it uses via some set command...

  7. Also, I think that will only work in case the gdb has +s flag 'setuid' otherwise the technique wont work.