This pwnable challange from PlaidCTF 2016 consists of a binary, which is an “ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, not stripped”
The main routine is pretty small, and mostly consist of an fgets and a two mprotect calls, allowing you to mark any memory location as readable/writable/executable. While looking for potential vulnerabilities, I thought that the “fgets” call looks intersting. But after noticing that the number of bytes it reads (0x32) is less than the size of the current stack frame, I realized that standard buffer overflow is out of the question.
The next thing I looked for are signs of user-controllable writes. Luckily, “mov byte [ss:rbp], cl” provides us a single-byte write primitive that we can partially control, although somewhat limited, which we’ll go into more detail in the next section.
1. Understanding Byte Mutation
Based on the code snippet above, we can see that a byte can be mutated through an XOR of itself and byte 0x1, shifted by a user controllable amount (although only limited to 0..7). The ruby function to calculate valid mutations is shown below.
As an example, byte 48, which represents the ASCII string ‘0’, can only be changed in the following bytes
Sure, we can only mutate a byte to a limited number of output on a single run. But nothing stops us from mutating the mutation again. For example, to change byte 48 to 24, we can mutate 48 into 56 first. Then mutate 56 into 24.
In order to achieve a write anything to anywhere, we basically need a function that generates a sequence of valid mutations that we can validly send over to the server one at a time until the target byte is changed into our desired byte. All we need now is to write the code for find_correct_mutation_sequence(from_byte, to_byte)
3. Controlling Program Flow
Now that we have a single-byte write anything/anywhere (including code segment), and a convenient mprotect (read/write/execute), we can change the program to behave the way we want as long we don’t accidentally corrupt opcode instructions.
However, we still have another problem. The user controlled write happens only once, after which the program exits. Ideally, we want the main function to run in an endless loop.
Our stack size is around 0x68, and fgets reads in 0x32 bytes. However, if we change the stack cleanup epilogue instruction below such that it moves that stack pointer to somewhere in our fgets buffer (where we can specify our own return address), program flow redirection is achieved.
- add rsp, 0x48
+ add rsp, 0x8
By reducing the 0x48 into 0x8, and specifying the return address to point back to the beginning of the main function, located at 0x400788, we now have a looping program that allows us to mutate program bytes infinite number of times.
All we need to do now is get our shell, which we can achieve by finding the system GOT entry, and have RDI register contain the address to “/bin/sh”
The system GOT can be calculated by leaking the puts@GOT and subtracing it by the difference in their offsets. To do that, we modify 0x400956 to 0x600cd0 (address of puts@GOT)
After that, we can put “/bin/sh” in a fixed location, such as 0x400942, which originally contains “mprotect1”
Then, in order to get 0x400914 into the RDI register, we can modify pop rbp into pop rdi
Finally, we just need to send our payload containing the address of “/bin/sh” which will be poped into RDI, and our desired return address “system@GOT”
4. Final Code
The final code is shown below
And finally, to get the flag
/bin/sh
ls -l
total 20
-rwxr-xr-x 1 root root 8328 Apr 15 18:26 butterfly
-r--r----- 1 root problem 28 Apr 15 18:28 flag
-rwxr-xr-x 1 root root 219 Apr 15 21:49 wrapper
cat flag
PCTF{b1t_fl1ps_4r3_0P_r1t3}