Long time no see! I just spent two days exploiting a CTF challenge and people want me to do a writeup so here we go. Full sploit can be found here: http://pastebin.com/0px8FEJ7
The binary we will be looking at is fruits from Boston Key Party CTF 2014, thanks guys! It can be found here: pastebin.com/n4Z9Vw4A
fruits: ELF 64-bit, uses shared libs, for GNU/Linux 2.6.24, stripped Canary found NX enabled PIE enabled
Ok, about every protection we can think of is activated but also we don’t just want to read the key file. No, we want a shell. Why? Because of some guy named palkeo who challenged me and will now buy me a beer some day.
About fruits
fruits is a server listening on 37717, it forks a process for each client who can then order apples and pears and leave some notes. Here is the menu we get when connecting to the server:
Welcome to our store's shopping cart!
========================================
Cart is empty.
Main Menu:
[0]: Submit Order
[1]: List Notes
[2]: Add a Note
[3]: Change a Note
[4]: Read note from file
[5]: Delete a Note
[6]: Add an Item to your Cart
[7]: Change Item Quantity
[8]: Delete Item from cart
[9]: Set favorite item
[10]: Change fav item
[11]: Print favorite item
Choose an option:
First lets do a quick overview of all the features:
Submit Order
Tells us our order has been placed, doesn’t seem to actually do anything at all.
List Notes, Add a Note, Change a Note, Read note from file, Delete a Note
Those are pretty self explanatory, we have the ability to add, change and delete notes. We can see the notes we added with option 2. Option 3 tells us we can’t read a note from a file because we’re not admin. Since this is a CTF exercise, this kind of option hints to the fact that there probably is a key in some file cleverly named key or flag. We shouldn’t focus on getting code execution too much because bypassing this admin check is probably enough, especially since this is 100pts.
Add an Item to your Cart, Change Item Quantity, Delete Item from cart
This second set of features allows us to do the shopping. There are two kind of objects we can buy, apples and pears. Each time we add an item to our cart it adds an entry to the cart. When we change the quantity of an item it changes the quantity for that entry, not the total amount of apples or pears we have in our cart. We can have: 1 apple + 2 pears + 3 apples + 4 pears, this would be four items.
Set favorite item, Change fav item, Print favorite item
This is not self explanatory! First, remember an item is an entry in the cart, not apples or pears. But the most counter-intuitive (to me at least) is the fact that option 10, Change fav item, doesn’t change which entry is your favorite item but instead it changes the item itself to become apples or pears according to what you select. The last option prints the type of our favorite item.
Solving the CTF Task
After some testing we quickly find a vulnerability. If the favorite item is deleted a use after free seems to be triggered when we try to print the favorite item because the server crashes.
Crashing is definitively not what we want so lets not print our favorite item just yet. Instead lets see if we can play with that memory somehow before it crashes.
There seem to be two kind of objects in this program, items and notes. If we add a new item it will probably end up in the spot of the old one. The problem is from the server’s point of view everything will be normal again because the favorite item is indeed an item. Lets play with the notes instead. In order to use the freed memory it should be about the size of an item, this can be reversed engineered or simply guessed using trial and error.
Lets assume our new note took the place of the deleted item, now what? We just filled that memory with “a”s and printing the favorite items still crashes (why wouldn’t it?).
Defeating PIE
Since we have full ASLR & PIE, our priority should be leaking some stuff to get an idea of what is going on. We have this note filled with “a”s that’s at the same time our favorite item. How could we convince the server to write some address to it? The only actions that change items are changing its quantity or changing its type (which is possible since its our favorite.)
Lets try the later, those are the actions we take:
[6]: Add an Item to your Cart
[9]: Set favorite item
[8]: Delete Item from cart // Now the favorite item is freed.
[2]: Add a Note // The note uses the freed memory.
[10]: Change fav item // This writes to our note :-)
[1]: List Notes
Or 6 0 9 0 8 0 2 aaaaaaaaaaaaaaaa 10 0 1
for short, this is what I get:
Notes (1):
#0: Pé"^
Looks like a leak to me. We just need to figure out what exactly is leaked. For this we have two options. a) We can disassemble the binary and check what exactly is touched when we change an item’s type. b) We can take the address and check what it points to at runtime in gdb.
Under gdb the leaked address I get is 0x0000555555757d90.
0000| 0x555555757d90 --> 0x555555556400 (lea rax,[rip+0x133])
Apparently that’s the address of a function pointer. This address points somewhere in the program which means we just defeated PIE. On linux ASLR randomizes mmap’s base address, but doesn’t re-randomize between each subsequent call to it. That means in theory leaking the address of the binary is enough to compute the addresses for the heap & libc. But I didn’t know that three days ago and it doesn’t work very well on this binary anyway, so lets not assume this.
Reading a file
Changing the favorite item’s type restored a pointer. Lets check if that fixed the crash when we print it.
Choose an option:11
Your favorite item is a Pear
It does! That means this pointer is used, if we can set the pointer to the address of something useful then we’re done. We still haven’t done a lot of reversing or studied how the memory is handled, but we need to be able to set that pointer to what we want. The first thing that comes to mind is simply changing the note to something else and checking if printing the favorite item starts crashing again. If it does then we are able to write back to the pointer after it has leaked by modifying the note. (We could also start all over again because this is a forking server and the addresses should always be the same, but that’s no fun!) Remember the goal is probably to read a file, so lets take a look at the code for Read note from file. The main loop of the binary looks like this:
If we use option 4 a check is performed before calling the function from an array of function pointers. Function pointers? Cool that’s exactly what we need: The known address of the address of a piece of code. We need the offset between the leaked pointer and the address of the function pointer for option 4.
According to IDA, the array of function pointers (rbx in the above diagram), is
at off_203CA0
, which means the fourth pointer is at off_203CC0
. Under gdb
the leaked address was 0x0000555555757d90
and the program is mapped at
0x0000555555554000
.
0x0000555555554000 + 0x203CC0 - 0x0000555555757d90 = -0xd0
Anyway, long story short: That works.
Now how do we pop a shell?
Recon
Now we have a basic understanding of the vulnerability, that’s enough to ret2text and trigger the read file. But if we are going to pop a shell we’ll need more than that. Lets start by examining where exactly the pointer we control is used. So far we know its a pointer to a function that is called, but that’s all we know.
Since the crashes where caused when triggering the print favorite item option,
that’s a good place to start looking. We can see a pointer to the favorite item
was stored and is now loaded into rdi. The first 8 bytes of an item seem to
contain the address of a function that returns its name, “apple” or “pear”.
That’s consistent with what we experienced when blindly setting a function
pointer and hopping it would be called during the CTF. print_fav
doesn’t touch
any registers except for rdi
and rax
, maybe rsi
or others contain
something useful. The following is a dump of what we get when setting
"BBBBBBBB"
as the first bytes of our note/favorite item. We get a crash when
we attempt to call [rax]
.
As expected, rdi
points to the favorite item and rax
contains the address
loaded from the beginning of the item during the previous instruction. The other
registers couldn’t be less helpful. rsi
, rdx
and rcx
which are the other
arguments to the call contain garbage that we can’t control, and the other
registers have some pointers to code. What about the stack?
Wow that’s empty. 0x555555556375
is the return address into the main loop we
saw earlier, and then we already have main’s stack frame. Nothing we control at
all. Bad luck.
OK, what do we have?
0x555555555960: mov rax,QWORD PTR [rdi]
0x555555555963: call QWORD PTR [rax]
The only thing we control is the memory rdi
points to, we control that on
63 bytes given we don’t use null-bytes or new-lines otherwise it will be cut.
We do not have any other registers or the stack to work with. On the plus
side: we did leak an address and bypass PIE.
What can we do with this? Not much, the only thing we can do is call code pointed to by an address stored at a know location in memory, that is to say in the binary itself. Think function pointers, vtables, GOT, etc… Can we have a list? Of course. Here you go:
0x555555758010 --> 0x7ffff7df04e0 (sub rsp,0x38)
0x555555758018 --> 0x7ffff7a97c30 (<free>)
0x555555758020 --> 0x7ffff7b104e0 (<setsockopt>)
0x555555758028 --> 0x5555555551d6 (<inet_ntoa@plt+6>)
0x555555758030 --> 0x5555555551e6 (<fclose@plt+6>)
0x555555758038 --> 0x5555555551f6 (<__stack_chk_fail@plt+6>)
0x555555758040 --> 0x7ffff7a9bc90 (movd xmm1,esi)
0x555555758048 --> 0x7ffff7a86940 (<getc>)
0x555555758050 --> 0x7ffff7b00de0 (<close>)
0x555555758058 --> 0x7ffff7a86350 (<fputc>)
0x555555758060 --> 0x7ffff7a35dd0 (<__libc_start_main>)
0x555555758068 --> 0x555555555256 (<fgets@plt+6>)
0x555555758070 --> 0x555555555266 (<feof@plt+6>)
0x555555758078 --> 0x555555555276 (<__gmon_start__@plt+6>)
0x555555758080 --> 0x7ffff7a97590 (<malloc>)
0x555555758088 --> 0x7ffff7a82d20 (<fflush>)
0x555555758090 --> 0x7ffff7b101d0 (<listen>)
0x555555758098 --> 0x7ffff7a71a80 (<sscanf>)
0x5555557580a0 --> 0x7ffff7a97d30 (<realloc>)
0x5555557580a8 --> 0x7ffff7a82a70 (<fdopen>)
0x5555557580b0 --> 0x5555555552e6 (<__printf_chk@plt+6>)
0x5555557580b8 --> 0x7ffff7b100b0 (<bind>)
0x5555557580c0 --> 0x555555555306 (<memmove@plt+6>)
0x5555557580c8 --> 0x555555555316 (<fopen@plt+6>)
0x5555557580d0 --> 0x7ffff7b10050 (<accept>)
0x5555557580d8 --> 0x555555555336 (<exit@plt+6>)
0x5555557580e0 --> 0x7ffff7a83bc0 (<fwrite>)
0x5555557580e8 --> 0x7ffff7b1e300 (<__fprintf_chk>)
0x5555557580f0 --> 0x7ffff7a9d660 (<strdup>)
0x5555557580f8 --> 0x555555555376 (<__cxa_finalize@plt+6>)
0x555555758100 --> 0x7ffff7ad5db0 (<fork>)
0x555555758108 --> 0x7ffff7b10540 (<socket>)
There are also a bunch of pointers to code contained in the binary (other than the PLT included in the list above) but the binary doesn’t contain anything obviously useful to us so I didn’t include those.
That’s everything we have. Now we need to start thinking about what we can do with this.
Expanding
None of the functions above will spawn us a shell, especially since we only
control the first argument, and in a very limited way: The first argument points
to memory starting with the address of the code being called, that will very
likely mess things up. Even if we had a known pointer to system
the null bytes
in the address would cut the command before anything useful. As none of the
above functions work out of the box we’ll need to find a way to call something
else. To do this we’ll have to put a pointer to what we want at a known location
in memory. The only place where we can reasonably write is on the heap, using
notes. For our note on the heap to be a known address we’dd have to leak it
first. That’s our first goal.
Leaking a heap address
Lets look at our list of callable functions again. Given the first argument is a
pointer to our note, how can we leak the heap? We’re lucky this time. The first
function gives us a solution: free
. If we call free
libc’s malloc will
reclaim the space used by the note and write some pointers to it for
bookkeeping. Because the program doesn’t know we freed the note behind its back
we’ll still be able to list the notes and leak the pointer. You might want to
add a second note after this, then you’ll have note0 and note1 using the same
memory, the memory that’s also used by the favorite item. If you don’t do this
the next allocation from libc or whatever will mess with you.
Getting libc
OK, we’re now able to call any address we want using a trampoline because we can
compute the heap addresses of our notes. There isn’t a silly call to random
anywhere in dlmalloc’s implementation. But that still limits us to calling code
contained in the binary and their aren’t a lot of useful gadgets to ROP with.
There is a nice trick here, remember the way to solve the challenge by simply
reading the flag.txt file? Well we can do the same with /proc/self/maps
, a
note is limited to 63 characters, but luckily libc will always be the first
mapping, except under gdb. We leaked addresses for the heap and libc, we’re
making progress: ASLR is defeated. (Actually not quite, because we still
don’t know the stack, but whatever!) We can call any piece of code we want
now. But still, we don’t control the registers as much as we’d like.
Control
Now that we have knowledge of where the heap and libc are located in memory it is time to start thinking about an actual exploit. We can call any piece of code we want and we control parts of the memory pointed to by rdi. What can we call? Well that’s a good question, I spent hours on this trying to find a good gadget. After a while I stumbled upon some code from setcontext. I you don’t know what setcontext is then go read the man page, now. This gadget is pretty amazing:
<setcontext+53>: mov rsp, QWORD PTR [rdi+0xa0]
<setcontext+60>: mov rbx, QWORD PTR [rdi+0x80]
<setcontext+67>: mov rbp, QWORD PTR [rdi+0x78]
<setcontext+71>: mov r12, QWORD PTR [rdi+0x48]
<setcontext+75>: mov r13, QWORD PTR [rdi+0x50]
<setcontext+79>: mov r14, QWORD PTR [rdi+0x58]
<setcontext+83>: mov r15, QWORD PTR [rdi+0x60]
<setcontext+87>: mov rcx, QWORD PTR [rdi+0xa8]
<setcontext+94>: push rcx
<setcontext+95>; mov rsi, QWORD PTR [rdi+0x70]
<setcontext+99>; mov rdx, QWORD PTR [rdi+0x88]
<setcontext+106>: mov rcx, QWORD PTR [rdi+0x98]
<setcontext+113>: mov r8, QWORD PTR [rdi+0x28]
<setcontext+117>: mov r9, QWORD PTR [rdi+0x30]
<setcontext+121>: mov rdi, QWORD PTR [rdi+0x68]
<setcontext+125>: xor eax, eax
<setcontext+127>: ret
All registers, including rsp
and rbp
, are loaded from the memory pointed to
by rdi
and we control where we ret to.
The problem is the offsets, they are pretty large, and we only control 63 bytes through our notes. We’ll have to setup the heap in such a way that we have the values we want where we want them. There might be a correct way to do this, I don’t know. The way I did it is simple: I just played around with different allocation patterns until I found something that seemed to work OK.
I started by doing a simple loop. Adding some notes so that I could see what a heap looked like. This is what I got, dumping from rdi, our favorite note:
0000| 0x555555759490 --> 0x555555757cc0 --> 0x555555555a90 (push rbx)
0008| 0x555555759498 ('o' <repeats 15 times>)
0016| 0x5555557594a0 --> 0x6f6f6f6f6f6f6f ('ooooooo')
0024| 0x5555557594a8 --> 0x21 ('!')
0032| 0x5555557594b0 ("11111111")
0040| 0x5555557594b8 --> 0x555555759400 --> 0x0
0048| 0x5555557594c0 --> 0x555555759520 ("00000000")
0056| 0x5555557594c8 --> 0x51 ('Q')
0064| 0x5555557594d0 --> 0x0
0072| 0x5555557594d8 --> 0x555555759490 --> 0x555555757cc0 --> 0x555555555a90 (push rbx)
0080| 0x5555557594e0 --> 0x555555759520 ("00000000")
0088| 0x5555557594e8 --> 0x5555557594b0 ("11111111")
0096| 0x5555557594f0 --> 0x555555759570 ("22222222")
0104| 0x5555557594f8 --> 0x5555557595d0 ("33333333")
0112| 0x555555759500 --> 0x5555557595f0 ("44444444")
0120| 0x555555759508 --> 0x555555759610 ("55555555")
0128| 0x555555759510 --> 0x555555759630 ("66666666")
0136| 0x555555759518 --> 0x21 ('!')
0144| 0x555555759520 ("00000000")
0152| 0x555555759528 --> 0x0
0160| 0x555555759530 --> 0x0
0168| 0x555555759538 --> 0x31 ('1')
0176| 0x555555759540 --> 0x0
0184| 0x555555759548 --> 0x555555759490 --> 0x555555757cc0 --> 0x555555555a90 (push rbx)
0192| 0x555555759550 --> 0x555555759520 ("00000000")
0200| 0x555555759558 --> 0x5555557594b0 ("11111111")
0208| 0x555555759560 --> 0x555555759570 ("22222222")
0216| 0x555555759568 --> 0x21 ('!')
0224| 0x555555759570 ("22222222")
0232| 0x555555759578 --> 0x555555759500 --> 0x5555557595f0 ("44444444")
0240| 0x555555759580 --> 0xffffffffffffffff
0248| 0x555555759588 --> 0x41 ('A')
0256| 0x555555759590 --> 0x0
0264| 0x555555759598 --> 0x555555759490 --> 0x555555757cc0 --> 0x555555555a90 (push rbx)
0272| 0x5555557595a0 --> 0x555555759520 ("00000000")
0280| 0x5555557595a8 --> 0x5555557594b0 ("11111111")
0288| 0x5555557595b0 --> 0x555555759570 ("22222222")
0296| 0x5555557595b8 --> 0x5555557595d0 ("33333333")
0304| 0x5555557595c0 --> 0x5555557595f0 ("44444444")
0312| 0x5555557595c8 --> 0x21 ('!')
0320| 0x5555557595d0 ("33333333")
That’s a lot of copy/pasta, but its necessary to show you the patterns. For
this test I added 8 notes: "00000000"
to "77777777"
, so not everything is
included in this dump obviously. There are two things to note in this dump,
first we have the raw notes at offsets 0, 32, 144, 224 and 320. The other thing
we have are the lists of pointers to our notes at offsets 72, 184 and
- If you look carefully you’ll see it always start with our note0 (the one that we use to trigger use after frees) and then continues sequentially with the other notes we add. However they don’t all stop with the same note. This “list” is actually an array that the program uses to keep track of all the notes. It is reallocated every time a note is added or delete, that’s why its at several places.
Now, what we need before jumping into setcontext is a little bit different. If
you look back at the way the registers are loaded we notice we can jump after
the moment rsp
is loaded and avoid changing our stack if we want to. The one
location we MUST control is rdi + 0xa8
because that contains the address we will
ret
to.
Basically we have two options. We can control rsp
and ret
’s target and do a
ropchain. But that’s pretty hard to pull off because those two are loaded from
consecutive addresses. Due to the null bytes we can’t simply write both of them
in the same note. The second option is to control rdi
and ret
’s target and
call system with a controlled argument. That sounds way easier to do.
We’re almost there. If we manage to put the address of a payload at rdi + 0x68
and the address of system at rdi + 0xa8
it’s a win. But that took (me) a long
time, I didn’t really know what I was doing and I had no experience with heap
spraying. Finally the following routine worked out OK:
- Add a bunch of notes with the address of system
- Delete those notes in order to empty the array
- Add a bunch of notes with a payload, the array will fill with pointers to it.
The payload notes should have a different size than the system ones, otherwise they will end up overwriting our pointers.
Looks good! Now we just have to setup a trampoline in order to be able to call
setcontext
, because we need a pointer to the code in rax
remember? But
that’s easy, we just add a new note, cross our fingers that it doesn’t mess-up
everything. (If it does we can put it somewhere else, libc stdio buffers, a note
that we allocate before all this setting up, etc…) Once we have our note with
the address of context + 0x57
in it, we need to setup note0 with the address
of the trampoline. We can read the address in gdb, setting up note0 is business
as usual, it’s the same as we did for free
and dump_file
.
Once we trigger this we have a shell. Full exploit can be found here: http://pastebin.com/0px8FEJ7 It works on my machine, but it depends on libc which means it might require some adjustments before it does on yours. gl;hf.
Ps: The reason I use bash and not sh for this exploit is that sh crashed on my laptop when called from this exploit, I have no idea why.
Comments