0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %ebp $79 %esp
0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %ebp $79 %esp
0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %ebp $79 $91 %esp
0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %ebp $79 $91 %esp
0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %ebp $79 $91 $103 %esp
0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %ebp $79 $91 $103 %esp
0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %ebp $79 $91 $103 $293 %esp
0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %ebp $79 $91 $103 $293 %esp
0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %esp, %ebp $79 $91 $103 $293
%ebp 0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 %esp $79 $91 $103 $293
%esp, %ebp 0xffffd0d8 $99 $88 $77 0x08049bbc 0xffffd0d8 $79 $91 $103 $293 %eip = 0x08049bbc
Example 1 argv argc #include <stdio.h> #include <string.h> saved ret int main( int argc, char **argv) { saved ebp char nice[] = "is nice."; %ebp char name[8]; nice[4-7] gets(name); printf("%s %s\n",name,nice); nice[0-3] return 0; name[4-7] } name[0-3] %esp
Example 1 argv argc #include <stdio.h> #include <string.h> saved ret int main( int argc, char **argv) { saved ebp char nice[] = "is nice."; %ebp char name[8]; nice[4-7] gets(name); printf("%s %s\n",name,nice); nice[0-3] return 0; name[4-7] } name[0-3] %esp
Example 1 argv argc #include <stdio.h> #include <string.h> saved ret int main( int argc, char **argv) { saved ebp char nice[] = "is nice."; %ebp char name[8]; nice[4-7] gets(name); printf("%s %s\n",name,nice); nice[0-3] return 0; name[4-7] } name[0-3] %esp
Example 1 argv argc #include <stdio.h> #include <string.h> saved ret int main( int argc, char **argv) { saved ebp char nice[] = "is nice."; %ebp char name[8]; nice[4-7] gets(name); printf("%s %s\n",name,nice); nice[0-3] return 0; name[4-7] } name[0-3] %esp
Example 1 argv argc #include <stdio.h> #include <string.h> saved ret int main( int argc, char **argv) { saved ebp char nice[] = "is nice."; %ebp char name[8]; nice[4-7] gets(name); printf("%s %s\n",name,nice); nice[0-3] return 0; name[4-7] } name[0-3] %esp What happens if we read a long name?
Example 1 argv argc #include <stdio.h> #include <string.h> saved ret int main( int argc, char **argv) { saved ebp char nice[] = "is nice."; %ebp char name[8]; nice[4-7] gets(name); printf("%s %s\n",name,nice); nice[0-3] return 0; name[4-7] } name[0-3] %esp What happens if we read a long name?
Example 1 argv argc #include <stdio.h> #include <string.h> saved ret int main( int argc, char **argv) { saved ebp char nice[] = "is nice."; %ebp char name[8]; nice[4-7] gets(name); printf("%s %s\n",name,nice); nice[0-3] return 0; name[4-7] } name[0-3] %esp If not null terminated can read more of the stack
Let's run this program!
Example 2 #include <stdio.h> #include <string.h> void foo() { printf("hello all!!\n"); exit(0); } void func( int a, int b, char *str) { int c = 0xdeadbeef; char buf[4]; strcpy(buf,str); } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Example 2 #include <stdio.h> #include <string.h> void foo() { printf("hello all!!\n"); exit(0); } void func( int a, int b, char *str) { int c = 0xdeadbeef; char buf[4]; strcpy(buf,str); } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Example 2 #include <stdio.h> argv[1] #include <string.h> 0xbbbbbbbb void foo() { printf("hello all!!\n"); 0xaaaaaaaa exit(0); } saved ret void func( int a, int b, char *str) { saved ebp int c = 0xdeadbeef; 0xdeadbeef char buf[4]; strcpy(buf,str); buf[0-3] } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Example 2 #include <stdio.h> argv[1] #include <string.h> 0xbbbbbbbb void foo() { printf("hello all!!\n"); 0xaaaaaaaa exit(0); } saved ret void func( int a, int b, char *str) { saved ebp int c = 0xdeadbeef; %ebp 0xdeadbeef char buf[4]; strcpy(buf,str); buf[0-3] } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Example 2 #include <stdio.h> argv[1] #include <string.h> 0xbbbbbbbb void foo() { printf("hello all!!\n"); 0xaaaaaaaa exit(0); } saved ret void func( int a, int b, char *str) { saved ebp int c = 0xdeadbeef; %ebp 0xdeadbeef char buf[4]; strcpy(buf,str); buf[0-3] } %esp int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Example 2 #include <stdio.h> argv[1] #include <string.h> 0xbbbbbbbb void foo() { printf("hello all!!\n"); 0xaaaaaaaa exit(0); } saved ret void func( int a, int b, char *str) { saved ebp int c = 0xdeadbeef; %ebp 0xdeadbeef char buf[4]; strcpy(buf,str); buf[0-3] } %esp int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Example 2 #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x41414141 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %ebp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } %esp int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; } If first argument to program is “AAAAAAAA…”
Example 2 #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x41414141 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %ebp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } %esp int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Example 2 #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x41414141 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %esp, %ebp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Example 2 #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x41414141 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %esp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; } %ebp = 0x41414141
Example 2 #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x41414141 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %esp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; } %ebp = 0x41414141 %eip = 0x41414141
Example 2 #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x41414141 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %esp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; } %ebp = 0x41414141 %eip = 0x41414141
Stack Buffer Overflow • If source string of strcpy controlled by attacker (and destination is on the stack) ➤ Attacker gets to control where the function returns by overwriting the return address ➤ Attacker gets to transfer control to anywhere! • Where do you jump?
Existing functions #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x08049b95 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %ebp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } %esp int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Existing functions #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x08049b95 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %esp, %ebp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; }
Existing functions #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x08049b95 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %esp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; } %ebp = 0x41414141
Existing functions #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x08049b95 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %esp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; } %ebp = 0x41414141 %eip = 0x08049b95
Existing functions #include <stdio.h> 0x41414141 #include <string.h> 0x41414141 void foo() { printf("hello all!!\n"); 0x41414141 exit(0); } 0x08049b95 void func( int a, int b, char *str) { 0x41414141 int c = 0xdeadbeef; %esp 0x41414141 char buf[4]; strcpy(buf,str); 0x41414141 } int main( int argc, char **argv) { func(0xaaaaaaaa,0xbbbbbbbb,argv[1]); return 0; } %ebp = 0x41414141 %eip = 0x08049b95
Let’s look at this in GDB (w/ GEF)
Better Hijacking Control argv[1] 0xbbbbbbbb • Jump to attacker-supplied code 0xaaaaaaaa • Where? We have control of saved ret saved ebp string! %ebp 0xdeadbeef ➤ Put code in string buf[0-3] %esp ➤ Jump to start of string
Better Hijacking Control shellcode • Jump to attacker-supplied code • Where? We have control of hijacked ret string! %ebp ➤ Put code in string %esp ➤ Jump to start of string
Shellcode • Shellcode: small code fragment that receives initial control in an control flow hijack exploit ➤ Control flow hijack: taking control of instruction ptr • Earliest attacks used shellcode to exec a shell ➤ Target a setuid root program, gives you root shell
Shellcode void main() { char *name[2]; name[0] = "/bin/sh"; name[1] = NULL; execve(name[0], name, NULL); }
Shellcode • Can we just take output from gcc/clang?
Shellcode • There are some restrictions ➤ 1. Shellcode cannot contain null characters ‘\0’ ➤ Why not? ➤ Fix: use different instructions and NOPs to eliminate \0 ➤ 2. If payload is via gets() must also avoid line-breaks
How do we make this robust? shellcode • 3. Exact address of shellcode &shellcode[0] start not always easy to guess %ebp ➤ Miss? SEGFAULT! • Fix: NOP-sled %esp
How do we make this robust? shellcode • 3. Exact address of shellcode ~&shellcode[0] start not always easy to guess %ebp ➤ Miss? SEGFAULT! • Fix: NOP-sled %esp
How do we make this robust? shellcode NOP-sled • 3. Exact address of shellcode ~&shellcode[0] start not always easy to guess %ebp ➤ Miss? SEGFAULT! • Fix: NOP-sled %esp
Metasploit helps!
Buffer Overflow Defenses • Avoid unsafe functions • Stack canary • Separate control stack • Address Space Layout Randomization (ASLR) • Memory writable or executable, not both (W^X) • Control flow integrity (CFI)
Avoiding Unsafe Functions • strcpy, strcat, gets, etc. • Plus: Good idea in general • Minus: Requires manual code rewrite • Minus: Non-library functions may be vulnerable ➤ E.g. user creates their own strcpy • Minus: No guarantee you found everything • Minus: alternatives are also error-prone
Even printf is tricky If buf is under control of attacker is: printf(“%s\n”, buf) safe?
Recommend
More recommend