One of the most common vulnerability that could be found in the wild is the buffer overflow. Either found as a stack overflow or heap overflow, it could allow not only reading but overwriting memory addresses which shouldn’t be accessible from the standard program execution flow. By doing a code revision, or maybe some reversing over a binary or executable, it is possible to find this kind of vulnerabilities which impact the integrity, confidentiality and availability of the information when exploited by an attacker.
Off by one in x86
This vulnerability occurs when a buffer operation is executed once more than it should. Let’s look the next vulnerable c code:
This program takes the string entered as argument when executing the program (i.e. ./program argument), and copies it to the buf variable, which size is 128 bytes. Nevertheless, there is a terrible mistake in the for loop definition, but why?
The buf array length is defined as 128, it is a char array, so its size is 128 bytes in memory when reserved. When the vulnerable function is executed, the argument received at the beginning is copied to the 128 bytes buffer by using a for loop, char by char. Remember that array’s indexes begin from 0, so the first element of the array should be buf, and in this case, the last one should be buf. But the for definition in the line 8 is defined from 0 to 128, not to 127. To make it clear:
for (i = 0; i < 128; i++) is executed 128 times.
for (i = 0; i <= 128; i++) is executed 129 times.
On the other hand, the strings are usually ended by using the null byte character \x00, this is for the program to know where the string ends. By the way, a valid string to the program should be 127 A’s, because the null byte is also copied to the buffer.
In this case, after the for loop is ended and all the bytes from the argument are copied to the buffer, the stack frame of the vulnerable function stays as intented. The program can continue using the saved EBP that was pushed when the vulnerable function was called.
But if the argv are 128 A’s and the null byte character at the end, the total size of the string to be copied will be 129 bytes. Accordingly to the vulnerable for definition, the loop execution will run 129 times, then the for loop will try to copy 129 bytes to the reserved 128 bytes buf memory. If working with little-endian format, we will overwrite the least significant byte of the saved EBP with a null byte by entering as payload 128 A’s. This will cause that the saved EBP address will change from 0xbffff542 to 0xbffff500.
Disassembly and exploiting
We are going to use the GNU Debugger (GDB) to disassemble the executable file of the previous vulnerable c code.
Disassembly of main function
Disassembly of vulnerable function
To debug correctly the register values we are interested in, we are going to set breakpoints just before the vulnerable function exits (*vulnerable_function+91), and before the program exits (*main+26).
Let’s run it using only 127 A’s, which will not overwrite the previously saved EBP address:
The EBP popped after the leave instruction of the vulnerable function was not overwritten. By examining the memory at address 0xbffff543, it’s possible to notice that everything is going as the developer of this software planned.
However, things are going to change when running the program with a 128 A’s string as an argument. Let’s see what happens:
At first glance, we can notice the overwriting of the EBP address with the null byte in its least significant byte. But when examining that address, we find that this memory address is part of the buffer we copied.
Because the EBP register before exitting main has the 0xbffff500 value, after the leave and ret instructions of main’s epilogue, the ESP register will be moved to point to the corrupted EBP, and then, the value where ESP is pointing will be popped to EBP (0x41414141) . Afterwards, ESP will be pointing to the address 0xbffff4fc, and after ret instruction is executed, it will set the EIP register with the value of the memory address where ESP is pointing to (0xbffff4fc = 0x41414141 -> EIP).
Thus, to exploit this vulnerability by loading a shellcode to the buffer, we should set the address of the shellcode once, at least, and it must be in the corrupted EBP – 4 address.
We are going to use a simple shellcode to load as a payload. It will print on screen CSLHack! when successfully executed.
The program execution in GDB will be different when deployed in bash, including the reserved memory addresses. So we need to make a payload which should be reliable from the address that could be obtained when debugging the software on GDB. We will use this structure to build our payload.
By using the NOP’s instructions, the exploit becomes more reliable, and filling the space left with repetitions of some address pointing to the NOP’s segment (&shellcode) will work when running the program in bash and loading the payload to it.