0

Recently I have been working on CTF challenges that require the attacker to stage shellcode in the environment. With ASLR disabled, one can rely on only slight differences between the environment of the shell, for example, and that of the exploitable process (e.g. differences due only to binary name differences). However, GDB (and R2) will make slight changes to the environment that make this very hard to do due to the environment variables shifting around slightly when not being debugged.

For example, GDB seems to at least add the environment variables LINES and COLUMNS. However, these can be removed by invoking GDB as follows:

gdb -ex 'set exec-wrapper env -u LINES -u COLUMNS' -ex 'r < exploit.input' challenge.bin

Note that GDB will implicitly use the fully qualified path when debugging a binary, so the user can further decrease any differences by invoking the binary in a similar manner.

`pwd`/challenge.bin < exploit.input

However, there still appear to be some differences. I have many times been able to get an exploit working while in GDB, but only to have it crash when run without the debugger. I've read mention of some script (sometimes referred to as setenv.sh) that can (allegedly) be used to setup an environment exactly like GDB, but I have not been able to find it.

Looking at the env of the shell:

LANG=en_US.UTF-8
PROFILEHOME=
DISPLAY=:0
OLDPWD=/home/user
SHELL_SESSION_ID=e7a0e681012e480fb044a034a775bb83
INVOCATION_ID=8ef3be94d09f4e47a0322ddf0d6ed787
COLORTERM=truecolor
MOZ_PLUGIN_PATH=/usr/lib/mozilla/plugins
XDG_VTNR=1
XDG_SESSION_ID=c1
USER=user
PWD=/test
HOME=/home/user
JOURNAL_STREAM=9:15350
KONSOLE_DBUS_SESSION=/Sessions/1
KONSOLE_DBUS_WINDOW=/Windows/1
GTK_MODULES=canberra-gtk-module
MAIL=/var/spool/mail/user
WINDOWPATH=1
TERM=xterm-256color
SHELL=/bin/bash
KONSOLE_DBUS_SERVICE=:1.7
KONSOLE_PROFILE_NAME=Profile 1
SHELLCODE=����
XDG_SEAT=seat0
SHLVL=4
COLORFGBG=15;0
LANGUAGE=
WINDOWID=16777222
LOGNAME=user
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
XDG_RUNTIME_DIR=/run/user/1000
XAUTHORITY=/home/user/.Xauthority
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
_=/usr/bin/env

And comparing it to that of GDG (LINES and COLUMNS removed):

/test/challenge.bin
_=/usr/bin/gdb
LANG=en_US.UTF-8
DISPLAY=:0
PROFILEHOME=
OLDPWD=/home/user
SHELL_SESSION_ID=e7a0e681012e480fb044a034a775bb83
INVOCATION_ID=8ef3be94d09f4e47a0322ddf0d6ed787
COLORTERM=truecolor
MOZ_PLUGIN_PATH=/usr/lib/mozilla/plugins
XDG_VTNR=1
XDG_SESSION_ID=c1
USER=user
PWD=/test
HOME=/home/user
JOURNAL_STREAM=9:15350
KONSOLE_DBUS_SESSION=/Sessions/1
KONSOLE_DBUS_WINDOW=/Windows/1
GTK_MODULES=canberra-gtk-module
MAIL=/var/spool/mail/user
WINDOWPATH=1
SHELL=/bin/bash
TERM=xterm-256color
KONSOLE_DBUS_SERVICE=:1.7
KONSOLE_PROFILE_NAME=Profile 1
SHELLCODE=����
COLORFGBG=15;0
SHLVL=4
XDG_SEAT=seat0
LANGUAGE=
WINDOWID=16777222
LOGNAME=user
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
XDG_RUNTIME_DIR=/run/user/1000
XAUTHORITY=/home/user/.Xauthority
PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/bin/site_perl:/usr/bin/vendor_perl:/usr/bin/core_perl
/test/challenge.bin

One can see the environments are not very different on inspection. Notably, the GDB env seems to have a second instance of the debugged binary's name (e.g. challenge.bin, in this case), as well as the fact that it sets _ to GDB rather than the debugged binary. The offsets seem to be way off, even when taking these small changes into account.

TL;DR

How can the GDB environment differences be nulled out for the case when it is necessary to know a priori the locations of things in the environment with and without the debugger running?

In an effort of lazyness, has anyone fully characterized the with/without GDB environment, or the changes GDB makes?

And for those interested, R2 appears to made changes to PATH. There may also be other differences.

sherrellbc
  • 4,650
  • 9
  • 48
  • 77

1 Answers1

1

How can the GDB environment differences be nulled out

One way is to run the binary outside of GDB (have the binary wait for GDB to attach, as described here), and attach GDB to it from "outside".

Update:

the binary in question is part of a challenge and source is not provided

You can patch _start with a jmp _start (so the binary will never progress past the first instruction). Once attached, replace the jmp with the original instruction, and start debugging.

Update 2:

Are you familiar with a better process?

In order to find offset of a given function in the ELF file, you need two values: offset of the function within section, and offset of section within the file.

For example:

$ readelf -Ws a.out | grep  ' _start'
58: 00000000004003b0    43 FUNC    GLOBAL DEFAULT   11 _start

This tells you that _start is linked at 0x4003b0 in section 11.

What is that section, what is its starting address, and where in the file does it start?

$ readelf -WS a.out | grep '\[11\]'
  [11] .text             PROGBITS        00000000004003b0 0003b0 000151 00  AX  0   0 16

We now see that _start is at the very start of .text (this is usually the case), and that .text starts at offset 0x3b0 in the file. QED.

An even better process is to use GDB to perfom the patching (GDB will perform all the finding of offsets). Suppose I want to overwrite the first instruction of _start with 0xCC instruction:

$ gdb --write -q ./a.out
Reading symbols from ./a.out...done.

Let's look at the original instructions first:

(gdb) x/4i _start
   0x4003b0 <_start>:   xor    %ebp,%ebp
   0x4003b2 <_start+2>: mov    %rdx,%r9
   0x4003b5 <_start+5>: pop    %rsi
   0x4003b6 <_start+6>: mov    %rsp,%rdx

Now let's patch the first one:

(gdb) set *(char*)0x4003b0 = 0xCC
(gdb) x/4i _start
   0x4003b0 <_start>:   int3   
   0x4003b1 <_start+1>: in     (%dx),%eax
   0x4003b2 <_start+2>: mov    %rdx,%r9
   0x4003b5 <_start+5>: pop    %rsi
(gdb) quit
Segmentation fault (core dumped)   <<-- this is a GDB bug. I should fix it some day.

$ objdump -d a.out
...
Disassembly of section .text:

00000000004003b0 <_start>:
  4003b0:       cc                      int3   <<-- success!
  4003b1:       ed                      in     (%dx),%eax
  4003b2:       49 89 d1                mov    %rdx,%r9
  ...

Voila!

Employed Russian
  • 199,314
  • 34
  • 295
  • 362
  • Thanks for the suggestion, though the binary in question is part of a challenge and source is not provided. I'll keep this idea in mind if I come across a scenario where I do have the source. – sherrellbc Jun 03 '18 at 03:57
  • This actually worked quite well. I ended up using `objdump` to get the opcodes and just searched for them in the binary using a hex editor. Are you familiar with a better process? You need to get the offset into the file in order to patch the entry, which is not very simple since disassembly tools show you the VA rather than the file offset. – sherrellbc Jun 03 '18 at 06:26