So, the binary lets us guess the flag, and we have three tries to get it right.
When reversing the binary, you’ll quickly see, that it uses to seccomp to blacklist some syscalls, which might make it harder to exploit the binary later on.
The for loop looks pretty suspicious in the way it compares the count of tries.
Taking a closer look at len_input and current_try, it occurs that both are 16 bit values, but when read_user_input is called, len_input will be casted to an int (32 bit). This will become handy in a moment.
read_user_input will read size bytes into buffer and then compares its content to the flag, which is mmapped to a memory region while the app is running. Though this mmapped region can completely be ignored, since it will be cleared and unmapped when we leave the for loop. Don’t think it’s possible to read the flag from there at all, but in the end we won’t need to do that anyways.
Only thing worth noting, is that if we don’t give any input at all current_try will be increased, without losing a try.
Let’s check what happens to len_input instead, when we try multiple times to guess the flag.
rdi contains 0x8b, which means, we can input 139 chars, exactly what we would expect from the previous source code.
If we now just send an empty string, current_tries will be increased by one, and we get back into read_user_input.
rdi now contains 0x1008b (16 bits current_tries and 16 bits from len_input), which means we can now read 65675 into buffer (which is only 140 chars big), so we have a nice buffer overflow at hand.
With this, we can now overflow into current_tries and len_input itself, setting them to arbitrary values. But since the binary has pie enabled, we need some leaks first.
read_user_input will read either exactly len_input characters or we have to end the input with a newline, which will get replaced with a null byte. So in order to align our buffer poperly to leak the return address, we first have to overwrite len_input with the exact offset of the return address. Then we can send another string to fillup the buffer, so it will exactly end where the return address starts (0xa0), and then we can easily leak it, since the binary will tell us, that our input was wrong.
With the base address we can now calculate the addresses of all rop gadgets needed to open and read the flag again, after it was removed by the binary.
With these gadgets we can easily write a open/read/write chain to exfiltrate the flag.
But this will fail, since seccomp is active, which blacklisted open, openat, execve and execveat.
After searching for some other syscalls to use to read the flag instead, I remembered a nifty way to get around blacklisted syscalls.
Using syscall 0x40000002 will also result in calling open, but seccomp will fail to catch it, thus rewarding us with the flag :)