64 bit ELF, with FULL RELRO, NX, stack guard & PIE enabled.
After doing some reversing, we found that it's a Befunge-93 program interpreter. It will first read some Befunge-93 instructions (at most 20000 characters), then interpret & execute those instructions. The program will store those instructions at the
program buffer, and the maximum of the executed instructions is 10000.
Here's the pseudo code of the main function:
After we took some good look at the main function, we found that we're able to trigger the read/write anywhere vulnerability by doing the followings:
- By using the
.instructions, we're able to read the content of any memory address.
&to push an integer on the
Stack(notice the uppercase S, this
Stackvariable is a buffer that the interpreter use to simulate the stack in a Befunge-93 program).
- By doing
g, the interpreter will first pop two values (let's say
Stackand push the content of
program[80 * x + y]to the
Stack. Since we can control the value of
y, we can push the content of any address on Stack, and print it out by using the
- Notice that the
&instruction only make us able to push an integer (32 bit) on the
Stack. If we want to read the content that is far away from the
programbuffer, the value of
ymight have to be an long integer. To solve this problem, we can use the
*instruction. It will pop two values (
y) from the
x * yto
Stack. It uses 64 bit registers during the whole operation, thus we can use this method to place a long integer on the
Stack, then use
.to leak the content of any memory address.
- With the aforementioned methods, we can also use the
pinstruction to overwrite the content of any memory address.
So now we can leak and overwrite the content of whatever the memory address we want. The first thing we do is to leak the libc's base address. Here I leak the address of
__libc_start_main's GOT, and use libc-database to get the libc's version.
Now we'll have to overwrite some address to hijack the control flow. Notice that there's no function pointer in the program, and GOT were all read-only due to the FULL RELRO protection. The only way we can do this is to overwrite the return address.
So now we'll have to get the stack address. But how? There's no format string vulnerability, so it's hard (nearly impossible) for us to leak the saved ebp or argv address on the stack. Also there's no
mmap, so it's also impossible for us to locate the .tls section address and leak the pointer to stack which is placed in the very section.
By the time I was solving this challenge, the only way left I know is to leak the
__libc_stack_end symbol in the
ld-linux.so. To achieve this I'll have to leak the
ld-linux.so's base address from the
DT_DEBUG info, which is placed in the .dynamic section of the binary. As for the version of the
ld-linux.so, I just assume it's the same version with the libc.so, which is Ubuntu 14.04, 64bit. Luckily, the actual binary can be retrieved from my other VM.
It took me a lot of time and work to do the whole thing, and the method's not elegant. Fortunately, it worked, and I was able to overwrite the return address with the typical x64 ROP chain:
pop_rdi --> bin_sh -->system. At last, we are able to spawn a shell and get the flag.
bbb is the Befunge-93 program I used to exploit the service :
After I pass the challenge and ask a girl from HITCON CTF team, she told me that there's a symbol call
environ in libc, which also store a stack address! And that's the moment I realized I totally forgot to search the stack address in libc while debugging the program!
So the (way) more elegant way to solve this challenge is to leak the stack address via the
environ symbol after we leak the libc's base address. Guess I've still got a lot to learn in the pwn area :P