Last post we got some shellcode to execute in the notesearch program from HTAE. I basically followed what Jon Erickson did with some minor modifications to make the exploit work on the Debian-32 machine that I’ve been using. That being said understanding what we actually did and why we did it is a little more in depth than just writing th code.
Lets talk about what actually happened in this example. We wrote a set of assembly instructions that invoked two system calls. We then compiled those instructions into machine instructions. Notice that I didn’t say program, because those instructions are not a linkable or executable program. As stated before the reason for that is we want the execution of the program they are injected into to continue as if nothing has happened.
We then created an environment variable that executes the cat command on hellworld1. We then found where the variable would be in memory with the notesearch program by using a previous program from HTAE. The address of the environment variable is what we want to overwrite the return address with so the machine instructions execute once eip hits them.
Notice that the first time I ran the program directly from the HTAE code. The last time though I had to actually figure out what offset was going to get me where I wanted to go. When I did this I just played around with the offset until I was right on the border of a seg fault. It turned out that 29 was the right offset for this. Though as a side note there are better ways of going about finding how large the offset needs to be than running trial and error. It could in fact ruin your exploit if you crash the program and it has to be manually restarted. Like I said previously though I’m including the naive things I did when going through these examples because I think its important to see where we need to refine our thought process.
With the offset correct we overwrite the eip pointer and direct it to the address of the environment variable SHELLC. SHELLC invokes the cat command and outputs the contents of hellworld1. What kind of data is the output of the cat command? It’s a string, and Linux shell commands use C programming rules when dealing with strings. We come to find out that when passing instructions as strings we have a problem with null bytes.
Whats Up With Null Bytes?
In C when you use a string the string is terminated with a null byte. The null byte tells the program that that’s the end. So when we encountered the first null byte in our helloworld1 instructions it terminated the cat command. The next two attempts, hellworld2 and helloworld3, are stripping those null bytes out of the program. The first thing we did was actually exploit the manner that binary negation is accomplished in the machine. The second thing we did for helloworld3 was to exploit the shorter versions of the registers to hide null bytes from the execution. This is really a string issue though. If we were injecting shellcode as some other kind of data the null bytes might not cause any issues in execution.
We Did What With What Registers?
In x86 32-bit architecture we have registers eax, ebx, ecx, edx. These registers are 32 bits wide, hence 32 bit architecture. But we also have some legacy functionality going all the way back to 8 bit architecture. So instead of using all 32-bits of eax we can use only a portion of the registers bits. This allows us to strip away null bytes from the registers in a fairly easy manner. We can use the shortened 8-bit versions of our register and leave off the left over null bytes. To do that we just need to make sure that we control what is in the left over bits of the full 32-bit register. To do that we just need to make use of the bitwise xor instruction. The xor instruction simply outputs a 0 if source and destination bits match (both zero or both nonzero) and a 1 otherwise. This allowed us to make sure that we controlled the unused bits of the 32-bit registers by making them 0. In fact in helloworld3 we zeroed out the entire register before moving a value into it.
After we stripped out all the null bytes we don’t have the issue of stopping the machine instructions. Here is where I had to make sure that the offset was actually correct. The goal is to overwrite the return address, not go overwriting everything under the sun in our program. In fact if you even miss the offset by overwriting one half word too far you end up with:
$ ./notesearch $(perl -e 'print "\x68\xfe\xff\xbf"x30') -------[ end of note data ]------- Segmentation fault
Or if we don’t write far enough we end up with:
$ ./notesearch $(perl -e 'print "\x68\xfe\xff\xbf"x28') -------[ end of note data ]-------
In this example we didn’t fill our buffer with enough data to actually reach the end. So as was demonstrated last time the final output is:
$ ./notesearch $(perl -e 'print "\x68\xfe\xff\xbf"x29') -------[ end of note data ]------- Hello, world!
Which is pretty cool in my opinion.
This was a pretty simple example with a lot of information packed into it. Like all of the previous exploits we still have the issues of needing root access to accomplish what we needed. Though once the idea was figured out we wouldn’t have needed the root access to implement the exploit, just to turn off the ASLR to allow it to work correctly.
Next installment in our adventures in shellcode will be to work at spawning a command shell.