linux-stack-overflow/ 40775 764 764 0 6347413533 13577 5 ustar bryan bryan linux-stack-overflow/index.html 100664 764 764 32000 6347412643 15705 0 ustar bryan bryan
Pas de version francaise pour le moment
As a Web server administrator, I'm concerned by security holes. After many
weeks of setup, I was really CERTAIN that our server was clean and secure.
One day, I read in the news that a new Web site treating about security had
born: Jason T.
Murphy 's Linux Security Home Page . I decided to browse it and to
retrieve some exploits to test my system.
I was afraid to discover that any local user could gain root access simply by
using LPR, MOUNT, UMOUNT, and even through the network by using a fake
library which fooled the telnetd !!! I've patched all that was tested and
tried to find other bugs.
I understood that Linux is able to execute code in the stack (!), which made it possible to overflow a buffer of any root program and make it execute a shell or any program. This is the most common method of gaining root with suid-root programs. That's why I'll describe it here, and, of course, some ways to avoid that in your programs.
The problem comes from the fact that many programmers often use parameter parsing code like this:
void parse(char *arg) { char param[1024]; int localdata; strcpy(param,arg); .../... return; } main(int argc, char **argv) { parse(argv[1]); .../... }or even
main(int argc, char **argv) { char param[1024]; int index=0; while ((param[index++]=getchar())!=EOF); .../... }
As you see, no check is made on the string length. Lazy programmers think "bahh, no one gives a file name which is 1024 chars long. And if it does, he will have a Segmentation Fault". Good programmers dont think like that, but sometimes they quickly add a little feature to one of their programs to help them debug, but when it works well, they forget to add all the necessary boundary tests.
If you don't understand very well the problem, here's a little explaination:
Under Linux (and nearly all Intelx86-based systems), the stack goes
up-to-down. That means that when a program calls a function, the return
address is pushed and the stack pointer (ESP) is decremented by 4 (32 bits).
Then, if a function define local variables (like "param" in the examples),
the array is defined below the return address of the function. When
executing strcpy() in the first program, the stack looks approximately like
this:
[Now you should understand that if you fill with *EXACTLY* 1024 bytes containing the necessary code to execute a shell, AND you add a 4-byte pointer to the beginning of you code inside , then when the function returns, it branches directly to your code inside .] [ ] [] [ ] [ ] [ ] <-------> <-----------> <-------> <----------------> <------------> <-----------> N bytes 4 bytes 1024 bytes 4 bytes M bytes 4 bytes ^ |_ Current ESP when calling strcpy()
So I wrote a very little (20 bytes) "assembler-level execlp() call" and tried to overflow many buffers (specially the suid-root programs).
My program is a shell script which consists in four parts:
The garbage filler fills the beginning of the buffer you want to overflow
with nearly any data. In reality, you must know exactly what you put here
because as you will see below, the stack calculation isn't very precise
and the program you are executing can branch inside the garbage. The first
idea is to use NOP encoding (0x90), but you'll notice that this is a 8-bit
code and it is not printable so this is not very easy for the tests. Better
use another opcode which does something harmless: INC EAX (0x41 = 'A'). EAX
is not used by the execlp() code so that's not a problem, and it's sometimes
usefull to select the 'A' chain on the screen with a mouse and paste it
anywhere else !
The garbage filler I use accepts one parameter which is the number of bytes
you want to fill. This is the most important parameter of the tester because
it determines how the code will be aligned relatively to the stack pointer.
The return pointer is the ONLY parameter that MUST go out of the buffer.
The only output is a long 'AAA...AAA' chain which length is the number you
requested.
To detect the maximum of bugs, you must have the smallest execlp() code so
that it can fit in smaller buffers. This one is only 20 bytes long ! To make
it so small, I've used the fact that we are executing in the stack so we
already know where our parameters are (relative to ESP, of course !).
To execlp(), you have to call INT 80h with EAX=0bh, EDX and ECX pointing to
argv, and EBX pointing to the executable you want to run, terminated by a
zero. First, if argv[0]=NULL, that's not a problem because that means the
executable won't have a name. No matter. That means that argv can point to
the 0x00 terminating the executable name if it's 32-bit. The executable name
must be given just after the code. The stack looks like this:
before the RET: [garbage] [program] [progname] [stack pointer] <---N---> <---20--> <---X----> <------4------> ^_ ESP after the RET: [garbage] [program] [progname] [stack pointer] <---N---> <---20--> <---X----> <------4------> ^_ ESP before int 80: [garbage] [program] [progname] [0000] <---N---> <---20--> <---X----> <--4-> ^ ^_ ECX=EDX -> NULL |_ EBX -> prog nameThe problem is that you must know the program name length (7) to make EBX point to it because it is relative to ESP. Another way would be to CALL IP+5 and pop EBX, and then add the code length to ebx to calculate the program address. But at this time, this has not been used yet. The zero is simply pushed into the stack. No need to explicitly write at an indirect address. The code follows:
mov ecx,esp xor eax,eax push eax lea ebx,[esp-7] add esp,12 push eax push ebx mov edx,ecx mov al,11 int 0x80Get the assembler version and/or the binary version.
The executable name is simply passed by an 'echo -n "/tmp/sh"'. The reason for using "/tmp/sh" instead of "/bin/sh" is that you can copy any program in /tmp/sh (I usually use /usr/bin/id) which helps redirecting to a log file, and doesn't present the risk that someone gets a shell on your console when you make it run as a loop. Moreover, sometimes you get a program executed without root privileges. This is a "semi-bug": a buffer is overflowed, but not in suid parts. In this case, /usr/bin/id is more interesting than /bin/sh to discover well what's happening.
To calculate the stack pointer that the program will use, I use the fact that when you run a program immediately after another one, they are called from the same function in the shell, so with the same ESP value. That make it possible to have a program which calculates ESP before executing the "victim program". My program also provides an option for subtracting a value to the actual stack pointer, and return that in a binary form. (it gives the stack value twice because this sometimes helps finding faster). This value isn't very important because you just need to make the program branch into the garbage preceeding the execlp().
The only way to be secure is to be faster than the crackers. You should have
a mailing list or web addresses which are often updated and quickly test all
the new bugs announced.
Get my package and test it on every
suid-root executable you have. To find them, first make a list and print
it:
(echo "SUID-ROOT LIST" ; find / -user root -type f -perm -4000) | lprAlso search for root-owner/world writable files and directories:
(echo "WORLD WRITABLE LIST" ; find / -user root -perm -022) | lpr
For EACH suid-root executable you find, read its man and try my scripts with ALL options and parameters. For example, this test will succeed on not-so-older LPR (as in slackware 3.1).
./tryall.generic lpr -C ./tryall.generic lpr -JYou may get lots of Segmentation Faults. If a program gives you a core or a Segmentation Fault, this means it has a bug anyway and it is risky. Note it on your paper-list, you'll try it later. Once you have noticed some risky programs, modify the script to try to adapt values to begin with a normal behaviour, and get the segmentation fault after. In this case, you could get a shell. Sometimes, you have a shell very late after the Seg Faults.
Each time you use strcpy(), you should replace it by strncpy() or test the length of the string you are about to copy. The easier way to do this is to define MACROs for strcpy(), fread(), read(), memcpy(), bcopy() ... that test the argument size each time it is possible. When it is difficult to make these tests, better malloc() the buffer instead of letting them in your local variables. With a malloc(), there's no risk because the data there will never be executed. If you don't want to malloc(), try defining your buffers globally. They will be in data sections so once more, it will be impossible to execute them. After that, you could try my program on yours to verify if there's no risk. A simpler way for that is to generate a long string with 'rpt' and pass it to your program. If this make it hang, review it.
Intel CPUs provide two things to make this bug harder or impossible to use:
e 2[oe-|A^T1f+N1,' 6M]_=o;( c;\P0vf9n F+|vxhՊ^! /a bj&CW}VcA+àPa:MxK&LDdJ(=zQ0)ŰaŘ5hWS,Y dEh<"tM O/->(EGRj$ctBf$k> E3$M|+bT\%LI>O_:P{N_[e=nguH'EV=_\9d<SҠi0YM0DG)?T `-aq0A[^ ]RQT_jDb8jk2Рi<贠/c予tye"R -Aw6vpߡcF9|!*-*G>LS ^-i08czY'֏o3746s&'Txjl6???