When the service then asks you, if you want to play a game and you answer with n, it will print out some portion of the stack (from which you can leak addresses from stack and app_init).

He also figured out a way to do an arbitray read/write

When the service asks you, if you want to play a game, answer with y

It will then ask you for a number, which it will then convert to an address

It then prints out 8 bytes of data from this address

And then reads 8 bytes of data, which will get stored at this address

The catch here seemed to be, that the binary added new randomized memory regions on every round. Though the new addresses, which got created, could be guessed (avery3r also provided calculations for that), those additional regions can be ignored.

The stack won’t move, as well as the app_init section stays in place, and that’s all we needed in the end to finalize this challenge.

To start with this, we’ll first leak the values from the stack, we’re given, when we decline to play:

solve_pow()log.info("Leak app stack")r.recvuntil("So, uh, do you want to play a game? (Y/N) ")r.sendline("n");r.recvuntil("WEAK! Take this I guess...\n")r.recvuntil("WEAK! Take this I guess...\n")stack=r.recv(0x100,timeout=0.5)STACK=u64(stack[17:17+8])eAPP.address=u64(stack[25:25+8])-0x605

For writing data to the stack, we can just play the game, pass the address, we want to write to as our guess. We’ll then receive the data, that’s currently stored there and can write 8 bytes of data to it:

# Will write 8 bytes of data to the address passed as guessing numberdefwrite_value(addr,value):log.info("Write to %s : %s"%(hex(addr),hex(value)))r.recvuntil("So, uh, do you want to play a game? (Y/N) ",timeout=1)r.sendline("y")r.recvuntil("COOL! Guess a number: ")r.sendline(hex(addr)[2:])r.recvuntil("CORRECT! OK, HERE WE GO!\n")r.recv(8)r.send(p64(value))

Since we know the stack address, we could use this to overwrite the return address of the guessing function giving us a free call. Fiddled around with the different stumbler app functions to find out, if there would be some kind of win function, which might give us a proper read or even a shell, but didn’t find a way to exploit this one with one single call.

So I opted for doing a ropchain instead. But we only have a limited amount of guesses. After writing 4 addresses, the binary closed, so it would be hard to do a proper ropchain with that.

Thus, I used 3 writes to prepare a stager ropchain on the stack, which would read my final ropchain. And with the 4th write, I put a stack pivot gadget into the return address of the guessing function, so it would pivot to my stager ropchain, waiting on the stack to get executed.

app_init has a function recv_all which will read x bytes from the given socket descriptor:

We can use this function to receive additional data and abuse the fact that rdi will already contain our socket descriptor from the previous reads and rsi will also already point to a buffer on the stack.

Only problem that arises here, is that rdx will still contain 8, since the service always only reads 8 bytes from us. Not enough for a proper ropchain, but with 3 writes, we can create a small ropchain, that will fix that for us.

So, this prepares our ropchain on the stack, which will set rdx to 0x1000 and then stack pivot into it, resulting in another read of 0x1000 bytes onto the stack. Neat, this should make things much easier, not having to fiddle around with the guessing game anymore.

From here, it’s just a matter of open("flag"), read from it and send_all it back to us :)

Since stumbler already has 10 open file descriptors, we know that the flag fd will be 11 after the open. So we just read 100 bytes (more than enough for a flag) from it and use the send_all method from app_init to send it back to us.