Let’s back to the sample overflow program from the scene 1. When the function tries to write 128 bytes of data into the 20 byte buffer (array), the extra 108 bytes are overwritten. Then, when the function finishes, the program attempts to jump to the return address, which is now filled with As in this case. If the return address were controlled and overwritten with something such as an address where actual executable code was located, then the program would return to this address and execute that code instead of just dying. And if the data that overflows into the return address is based on user input (ie. username field), the return address and the subsequent program execution flow can be controlled by the user.
Because it is possible to modify the return address to change the flow of the program execution by overflowing buffers, all that’s we need is something useful to execute. This is where bytecode injection comes into play.
Def. of ByteCode: This is a cleverly designed piece of assembly code that can be injected into buffers. It has to be self-contained and it needs to avoid certain special characters in its instructions because it’s supposed to look like data in buffers.
The most common bytecode is known as shellcode. This is a piece of code that executes a shell. If a suid root program is used in this process, then the attacker will have a user shell with root privileges. Because the system believes that the suid program is still doing whatever it was supposed to be doing.
Now, it’s time to give an example. Soi here it is:
vulnerable.c code
int main(int argc, char *argv[])
{
char buffer[500];
strcpy(buffer, argv[1]);
return 0;
}
This is a piece of vulnerable program code that is similar to overflow_function() from scene 1. It inputs a single argument and tries to fill whatever that argument holds into its 500 byte buffer. Here are the results of this program’s execution.
$ gcc -o vulnerable vulnerable.c
$ ./vulnerable test
At this point the program seems to be doing nothing except mismanage the memory. Now to make it trully vulnerable, the ownership must be changed to the root user, and the suid permission bit must be turned on for the compiled binary
$ sudo chown root vulnerable
$ sudo chmod +s vulnerable
$ ls -l vulnerable
-rwsr-sr-x 1 root users 3546 Sep 2 01:40 vulnerable
Now our program is a suid and vulnerable to a bufferoverflow, all that we need is a piece of code to generate a buffer that can be merged to the vulnerable program. This buffer must contain the desired shellcode and overwrite the return address in the stack so that the shellcode will be executed. This means that the actual address of the shellcode must be known at this time. This is not easy because of dynamically changing stack. To make things even harder, 4 bytes where the return address is stored in the stack frame must be overwritten wtih the value of this address. Even if the correct address is known, but the proper location is not overwtitten, the program will just crash and die. There are two techniques which are commonly used to handle this difficulty.
I will explain one of them later at the next scene and finally we’ll have our own exploit.
See you at scene 3