An Introduction to Buffer Overflow Exploits Bart Coppens
An Introduction to Buffer Overflow Exploits Bart Coppens https: //www. bartcoppens. be/
General Note This slide deck is based on a set of presentations I gave, so take note that: • I tend to use lots of figures and give lots of additional detail by explaining those figures, rather than adding text. Slides without my (over)enthousiastic explanations lose a lot of that • I also gave some very quick demo’s, you’ll miss those too • There were some hands-on excercises (including a real-life one based on a proftpd vulnerability from 2010), but I haven’t cleaned those up yet • I had some ‘zen’ slides consisting of CC-licensed pictures of cute puppies, kittens, etc. to make the audience feel less brain-dead, but they made this merged presentation’s file size a bit too large
We’ll be abusing vulnerabilities in code Exploits 1: Basic Buffer Overflows & Shellcode Bart Coppens A kind of vulnerability https: //www. bartcoppens. be/ The attack code we’ll be executing
Structure • Basic exploits – Intro to buffer-overflows, exploits & shellcode – No protections, just like the good old days of the mid-‘ 90 s (executable stacks, no ASLR, …) • Advanced exploits – Protections against buffer-overflow-based exploits (non-executable stacks, ASLR, …) – How to circumvent these (ROP, …)
Part 1: Basic Exploits x 86 -64 assembly crash course A look at buffers are implemented in assembly Buffer overflows How to execute the attack code
Normal program behavior callq int function() { return 0; } … int i = function(); … function xor retq movq %rax, … %rax, %rax
Normal program behavior callq function xor retq movq Stack frame of main Higher addresses rsp %rax, … %rax, %rax
Normal program behavior callq function xor retq movq rsp Return address to main Higher addresses Stack frame of main %rax, … %rax, %rax
Normal program behavior callq function xor retq movq Stack frame of main Higher addresses rsp %rax, … %rax, %rax
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } buffer[0] rsp 0 x 64 bytes buffer[99] rsp adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } rsp rdi 0 x 64 bytes adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } rsp rdi Return address etc 0 x 64 bytes adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } rsp Return address etc H e l rdi l … 0 x 64 bytes adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } rsp rdi H e l l … 0 x 64 bytes adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } rsp rdi H e l l … 0 x 64 bytes adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } rdi rsp adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } rdi rsp adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(50, "Hellon"); } rsp H e l rdi l … 0 x 64 bytes buffer[99] buffer[100] buffer[107] adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "Hello…ABCDEFGH"); } rsp H e l rdi l … 0 x 64 bytes buffer[99] buffer[100] buffer[107] adresses Return address to main Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "Hello…ABCDEFGH"); } rsp H e l rdi l … 0 x 64 bytes buffer[99] buffer[100] buffer[107] adresses A B C D E F G H Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "Hello…ABCDEFGH"); } … rdi rsp adresses A B C D E F G H Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "Hello…ABCDEFGH"); } … rdi rsp adresses A B C D E F G H Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "Hello…ABCDEFGH"); } rsp rdi adresses A B C D E F G H Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7…ABCDEFGH"); } rsp rdi movq $0, %rax … syscall adresses A B C D E F G H Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7…ABCDEFGH"); } 0 x 123 rsp rdi movq $0, %rax … syscall adresses A B C D E F G H Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 123 rsp rdi movq $0, %rax … syscall 0 adresses 0 0 1 2 3 Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 123 rdi movq $0, %rax … syscall rsp 0 adresses 0 0 1 2 3 Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Local variables and the stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 123 rdi movq $0, %rax … syscall rsp 0 adresses 0 0 1 2 3 Stack frame of main … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Demo Sorry, here was a live demo that starts from showing how certain inputs crash a program and analysing that in gdb, to writing & executing very simple shell code. Maybe one day I’ll make screenshots + captions
Exploits part 2 Buffer Overflows: Mitigations & Advanced Exploits Bart Coppens https: //www. bartcoppens. be/
Overview • • • Recap Non-executable stack & ROP Function pointers Address Space Layout Randomization Stack reading Hands-on!
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 123 rsp buffer adresses return address Stack of ‘main’ … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 123 rsp movq $0, %rax … syscall 0 3 adresses 0 0 1 2 Stack of ‘main’ … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 123 movq $0, %rax … syscall rsp 0 3 adresses 0 0 1 2 Stack of ‘main’ … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 123 movq $0, %rax … syscall Stack of ‘main’ … adresses rsp sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 123 movq $0, %rax … syscall adresses variable size! Stack of ‘main’ … … environment … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 123 movq $0, %rax … syscall 0 adresses variable size! 0 0 1 2 3 Stack of ‘main’ … … environment … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 12 F rsp movq $0, %rax … syscall 0 adresses variable size! 0 0 1 2 3 Stack of ‘main’ … … environment … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } 0 x 12 F movq $0, %rax … syscall rsp 0 0 1 2 3 Stack of ‘main’ … … environment … adresses variable size! 0 sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } (garbage instructions) 0 x 12 F movq $0, %rax … syscall rsp 0 0 1 2 3 Stack of ‘main’ … … environment … adresses variable size! 0 sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } rsp adresses variable size! nop NOP slide/sled nop movq $0, %rax … 0 0 0 1 2 3 Stack of ‘main’ … … environment … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } nop NOP slide/sled nop movq $0, %rax … variable size! Stack of ‘main’ … … environment … adresses rsp sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } nop NOP slide/sled nop movq $0, %rax … variable size! Stack of ‘main’ … … environment … adresses rsp sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Executable stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } nop NOP slide/sled nop movq $0, %rax … variable size! Stack of ‘main’ … … environment … adresses rsp sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Non-Executable Stack int silly_function(int len, char* src) { char buffer[100]; memcpy(buffer, src, len); function(%rdi, %rsi, %rdx) return 0; } int main() { return silly_function(108, "x 48xc 7… 230100…"); } movq $0, %rax … syscall Stack of ‘main’ … adresses rsp Stack memory mapped as non-executable! sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Non-Executable Stack proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W movq $0, %rax … syscall Stack of ‘main’ … adresses rsp 4 K R, X 4 K 4 K R, W sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Non-Executable Stack proftpd libc stack 0 0 x 7 ff… 4 K R, X rsp 0 4 K R, X 4 K 4 K R, X R, W 3 adresses 0 0 1 2 Stack of ‘main’ … 4 K R, X 4 K 4 K R, W sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … rsp 0 3 adresses 0 0 1 2 Stack of ‘main’ … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … rsp adresses address of exit Stack of ‘main’ … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … rsp adresses address of system Stack of ‘main’ … sub movq callq xor add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> %rax, %rax $0 x 64, %rsp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … esp 32 bit adresses address of system Stack of ‘main’ … … xor add ret %eax, %eax $0 x 64, %esp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X esp 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer address of system garbage (‘return addr. ’) argument 1 argument 2 32 bit adresses argument 3 Stack of ‘main’ … … xor add ret %eax, %eax $0 x 64, %esp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer esp 4 K R, X address of system garbage (‘return addr. ’) argument 1 argument 2 32 bit adresses argument 3 Stack of ‘main’ … … xor add ret %eax, %eax $0 x 64, %esp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer address of system esp garbage (‘return addr. ’) argument 1 argument 2 32 bit adresses argument 3 Stack of ‘main’ … … xor add ret %eax, %eax $0 x 64, %esp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer address of system esp garbage (‘return addr. ’) argument 1 argument 2 32 bit adresses argument 3 Stack of ‘main’ … … xor add ret %eax, %eax $0 x 64, %esp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer address of system esp garbage (‘return addr. ’) ptr. to “/bin/sh” argument 2 32 bit adresses argument 3 Stack of ‘main’ … … xor add ret %eax, %eax $0 x 64, %esp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W address of system ptr. to “/bin/sh” argument 2 adresses 32 bit 4 K 4 K R, W can be used to execute a second function! garbage (‘return addr. ’) argument 3 Stack of ‘main’ … 4 K R, X exit, read, write, system, mprotect, … overflown buffer esp 4 K R, X … xor add ret %eax, %eax $0 x 64, %esp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X overflown buffer address of system rsp 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … Arguments in registers! fun(%rdi, %rsi, %rdx) garbage (‘return addr. ’) ptr. to “/bin/sh” argument 2 64 bit adresses argument 3 Stack of ‘main’ … … xor add ret %rax, %rax $0 x 64, %rsp
Return-to-libc proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W overflown buffer rsp 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … Arguments in registers! fun(%rdi, %rsi, %rdx) 64 bit adresses Stack of ‘main’ … xor add ret %rax, %rax $0 x 64, %rsp
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W overflown buffer rsp 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … Arguments in registers! fun(%rdi, %rsi, %rdx) 64 bit adresses Stack of ‘main’ … … pop ret … xor add ret %rdi gadget %rax, %rax $0 x 64, %rsp
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X overflown buffer rsp 4 K R, X 4 K 4 K R, X R, W &(pop-rdi; ret) 4 K 4 K R, W Arguments in registers! fun(%rdi, %rsi, %rdx) address of system adresses 64 bit 4 K R, X exit, read, write, system, mprotect, … ptr. to “/bin/sh” Stack of ‘main’ … 4 K R, X … pop ret … xor add ret %rdi gadget %rax, %rax $0 x 64, %rsp
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X overflown buffer &(pop-rdi; ret) rsp 4 K R, X 4 K 4 K R, X R, W 4 K 4 K R, W Arguments in registers! fun(%rdi, %rsi, %rdx) address of system adresses 64 bit 4 K R, X exit, read, write, system, mprotect, … ptr. to “/bin/sh” Stack of ‘main’ … 4 K R, X … pop ret … xor add ret %rdi gadget %rax, %rax $0 x 64, %rsp
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W overflown buffer &(pop-rdi; ret) 4 K R, X Arguments in registers! fun(%rdi, %rsi, %rdx) address of system 64 bit “/bin/sh” adresses Stack of ‘main’ … 4 K 4 K R, W exit, read, write, system, mprotect, … ptr. to “/bin/sh” rsp 4 K R, X … pop ret … xor add ret %rdi gadget %rax, %rax $0 x 64, %rsp
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W overflown buffer &(pop-rdi; ret) 4 K R, X Arguments in registers! fun(%rdi, %rsi, %rdx) address of system “/bin/sh” rsp adresses 64 bit 4 K 4 K R, W exit, read, write, system, mprotect, … ptr. to “/bin/sh” Stack of ‘main’ … 4 K R, X … pop ret … xor add ret %rdi gadget %rax, %rax $0 x 64, %rsp
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W overflown buffer &(pop-rdi; ret) 4 K R, X Arguments in registers! fun(%rdi, %rsi, %rdx) address of system “/bin/sh” rsp adresses 64 bit 4 K 4 K R, W exit, read, write, system, mprotect, … ptr. to “/bin/sh” Stack of ‘main’ … 4 K R, X … pop ret … xor add ret %rdi gadget %rax, %rax $0 x 64, %rsp
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W overflown buffer &(pop-rdi; ret) 4 K 4 K R, W Arguments in registers! fun(%rdi, %rsi, %rdx) adresses 64 bit 4 K R, X exit, read, write, system, mprotect, … 0 x 20 … 4 K R, X … pop ret … xor add ret %rdi gadget %rax, %rax $0 x 64, %rsp
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X overflown buffer chaining gadgets 4 K R, X 4 K 4 K R, X R, W &(pop-rdi; ret) 4 K 4 K R, W Arguments in registers! fun(%rdi, %rsi, %rdx) &(pop rsi; pop rdx; ret) 0 x 23 0 x. FF 73 adresses 64 bit 4 K R, X exit, read, write, system, mprotect, … 0 x 20 … 4 K R, X … pop ret … xor add ret %rdi gadget %rax, %rax $0 x 64, %rsp
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer chaining gadgets 4 K R, X &(pop-rdi; ret) 0 x 20 0 x 81 &(pop rsi; pop rdx; ret) 0 x 23 0 x. FF 73 64 bit adresses … 0 xfa 0 x 5 c 0 xc 3 0 x 00
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W chaining gadgets 4 K 4 K R, W cmp $0 xc 35 c, %edx &(pop-rdi; ret) 0 x 20 0 x 81 &(pop rsi; pop rdx; ret) 0 x 23 0 x. FF 73 adresses 64 bit 4 K R, X exit, read, write, system, mprotect, … overflown buffer … 4 K R, X 0 xfa 0 x 5 c 0 xc 3 0 x 00
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer chaining gadgets 4 K R, X cmp $0 xc 35 c, %edx &(pop-rdi; ret) 0 x 20 0 x 81 0 xfa 0 x 5 c 0 xc 3 &(pop rsi; pop rdx; ret) pop %rsp 0 x 23 0 x. FF 73 64 bit adresses … 0 x 00
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer chaining gadgets 4 K R, X cmp $0 xc 35 c, %edx &(pop-rdi; ret) 0 x 20 0 x 81 0 xfa 0 x 5 c 0 xc 3 &(pop rsi; pop rdx; ret) ret 0 x 23 0 x. FF 73 64 bit adresses … 0 x 00
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer chaining gadgets 4 K R, X cmp $0 xc 35 c, %edx &(pop-rdi; ret) 0 x 20 0 x 81 0 xfa 0 x 5 c 0 xc 3 &(pop rsi; pop rdx; ret) pop %rsp; ret 0 x 23 0 x. FF 73 64 bit adresses … 0 x 00
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W chaining gadgets 4 K 4 K R, W • Pop-gadgets • Write-anywhere gadgets (mov %rdi, (%rsi); ret) • Turing-complete set of gadgets for complex shellcode (eventually calling libc/kernel) • Usually it can be a simple stager: • Write shellcode in RW memory (read) &(pop-rdi; ret) 0 x 20 &(pop rsi; pop rdx; ret) 0 x 23 0 x. FF 73 adresses 64 bit 4 K R, X exit, read, write, system, mprotect, … overflown buffer … 4 K R, X
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X 4 K 4 K R, X R, W chaining gadgets &(pop-rdi; ret) 0 x 20 &(pop rsi; pop rdx; ret) 0 x 23 0 x. FF 73 adresses 64 bit 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer … 4 K R, X • Pop-gadgets • Write-anywhere gadgets (mov %rdi, (%rsi); ret) • Turing-complete set of gadgets for complex shellcode (eventually calling libc/kernel) • Usually it can be a simple stager: • Write shellcode in RW memory (read) • Make page executable (mprotect) (not possible with Pa. X)
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X chaining gadgets &(pop-rdi; ret) 0 x 20 &(pop rsi; pop rdx; ret) 0 x 23 0 x. FF 73 adresses 64 bit 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer … 4 K R, X • Pop-gadgets • Write-anywhere gadgets (mov %rdi, (%rsi); ret) • Turing-complete set of gadgets for complex shellcode (eventually calling libc/kernel) • Usually it can be a simple stager: • Write shellcode in RW memory (read) • Make page executable (mprotect) (not possible with Pa. X) • ‘Return’ to the (executable) shellcode
Return-Oriented Programming proftpd libc stack 0 0 x 7 ff… 4 K R, X chaining gadgets &(pop-rdi; ret) 0 x 20 &(pop rsi; pop rdx; ret) 0 x 23 0 x. FF 73 adresses 64 bit 4 K R, X 4 K 4 K R, W exit, read, write, system, mprotect, … overflown buffer … 4 K R, X • Pop-gadgets • Write-anywhere gadgets (mov %rdi, (%rsi); ret) • Turing-complete set of gadgets for complex shellcode (eventually calling libc/kernel) • Usually it can be a simple stager: • Write shellcode in RW memory (read) • Make page executable (mprotect) (not possible with Pa. X) • ‘Return’ to the (executable) shellcode
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; pos Animal* a = …; a->is. Cuddly(); …
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax Dog: : is. Cute is. Cuddly Labrador: : is. Cuddly
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax Dog: : is. Cute is. Cuddly Labrador: : is. Cuddly system
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax Dog: : is. Cute is. Cuddly Labrador: : is. Cuddly system
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … fake vtable movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax Dog: : is. Cute is. Cuddly Labrador: : is. Cuddly system
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … fake vtable movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax rsp local vars ret addr. main … Dog: : is. Cute is. Cuddly Labrador: : is. Cuddly pop … ret
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … fake vtable movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax rsp ret. addr call local vars ret addr. main … Dog: : is. Cute is. Cuddly Labrador: : is. Cuddly pop … ret Not under our control! No ROP possible?
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … fake vtable movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax rsp ret. addr call local vars ret addr. main … Dog: : is. Cute is. Cuddly rdi Some buffer Labrador: : is. Cuddly pop … ret normal ROP chain …
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … fake vtable movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax rsp ret. addr call local vars ret addr. main … Dog: : is. Cute is. Cuddly rdi Some buffer Labrador: : is. Cuddly mov %rdi, %rsp ret normal ROP chain …
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … fake vtable movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax rsp ret. addr call local vars ret addr. main … Dog: : is. Cute is. Cuddly rdi Some buffer rsp Labrador: : is. Cuddly mov %rdi, %rsp ret normal ROP chain …
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … fake vtable movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax ret. addr call local vars ret addr. main … Dog: : is. Cute is. Cuddly rdi Some buffer rsp Labrador: : is. Cuddly mov %rdi, %rsp ret normal ROP chain …
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … fake vtable movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax ret. addr call local vars ret addr. main … Dog: : is. Cute is. Cuddly rdi Some buffer rsp Labrador: : is. Cuddly mov %rdi, %rsp ret normal ROP chain …
Overwriting function pointers struct Animal { virtual bool is. Cute(); virtual bool is. Cuddly(); Position pos; }; vtable Animal* a = …; pos a->is. Cuddly(); … fake vtable movq (…), %rax movq 0 x 8(%rax), %rax callq *%rax ret. addr call local vars ret addr. main … Dog: : is. Cute is. Cuddly rdi Some buffer rsp Labrador: : is. Cuddly xchg %rdi, %rsp ret normal ROP chain …
Function pointers to libraries proftpd libc exit +0 x 78230 stack
Function pointers to libraries proftpd libc stack exit +0 x. A 8230
Function pointers to libraries NVidia? Intel? AMD? Pillars of Eternity + ? ? ? lib. GL libm libc stack
Function pointers to libraries proftpd libc write +? ? ? stack
Function pointers to libraries libc data code call write proftpd stack
Function pointers to libraries libc data PLT code call write@PLT: write proftpd stack
Function pointers to libraries libc PLT code call write@PLT write proftpd stack write@PLT: jmp GOT[_write] data GOT symbol resolver stub
Function pointers to libraries libc PLT code call write@PLT write proftpd stack write@PLT: jmp GOT[_write] data GOT symbol resolver stub
Function pointers to libraries libc PLT code call write@PLT write proftpd stack write@PLT: jmp GOT[_write] data GOT symbol resolver stub all function pointers in the Global Offset Table can be overwritten, next time application calls write, attack code is executed Use full RELRO relocations: GOT completely filled out on program startup and remapped read-only
Address Space Layout Randomization proftpd rsp adresses address of exit Stack of ‘main’ … libc stack
Address Space Layout Randomization proftpd rsp adresses address of exit Stack of ‘main’ … libc stack
Address Space Layout Randomization proftpd libc stack Randomize on each program start: • Load address of libraries • Stack base • Heap base rsp adresses address of exit Stack of ‘main’ …
Address Space Layout Randomization proftpd libc stack 32 bit … page offset >=4 bits <=16 bits Brute-forcable 12 bits 64 bit canonical address 16 bits … page offset >=28 bits Brute forcing harder 12 bits
Address Space Layout Randomization proftpd rsp adresses address of exit Stack of ‘main’ … libc stack
Address Space Layout Randomization proftpd libc write@PLT At fixed address! rsp gadgets in proftpd Stack of ‘main’ … stack
Address Space Layout Randomization proftpd libc stack Only possible with Position Independent Executable (PIE) (binary can act like a library) rsp gadgets in proftpd Stack of ‘main’ …
Stack canary/cookie buffer H e l l … 0 x 64 bytes buffer[99] return address buffer[107] sub movq callq add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> $0 x 64, %rsp
Stack Smash Protector / Cookies buffer H e l l … 0 x 64 bytes stack cookie/canary return address sub movq callq add retq $0 x 64, %rsp %rdi, %rdx %rsp, %rdi <memcpy> $0 x 64, %rsp
Stack Smash Protector / Cookies buffer H e l l … 0 x 64 bytes stack cookie/canary return address sub movq callq add retq $0 x 6 C, %rsp %rdi, %rdx %rsp, %rdi <memcpy> $0 x 6 C, %rsp
Stack Smash Protector / Cookies Per-process value initialized by loader Segment-relative addressing hard to leak value buffer H e l l … 0 x 64 bytes stack cookie/canary return address sub mov movq callq add retq $0 x 6 C, %rsp %fs: 0 x 28, %rax, 0 x 64(%rsp) %rdi, %rdx %rsp, %rdi <memcpy> $0 x 6 C, %rsp
Stack Smash Protector / Cookies Per-process value initialized by loader Segment-relative addressing hard to leak value buffer H e l l … 0 x 64 bytes stack cookie/canary return address sub mov movq callq mov xor jne add retq $0 x 6 C, %rsp %fs: 0 x 28, %rax, 0 x 64(%rsp) %rdi, %rdx %rsp, %rdi <memcpy> 0 x 64(%rsp), %rax %fs: 0 x 28, %rax … stack_chk_fail $0 x 6 C, %rsp
Stack Smash Protector / Cookies Per-process value initialized by loader Segment-relative addressing hard to leak value (argument copies) local variables buffer H e l l … 0 x 64 bytes stack cookie/canary saved rbx, … return address sub mov movq callq mov xor jne add retq $0 x 6 C, %rsp %fs: 0 x 28, %rax, 0 x 64(%rsp) %rdi, %rdx %rsp, %rdi <memcpy> 0 x 64(%rsp), %rax %fs: 0 x 28, %rax … stack_chk_fail $0 x 6 C, %rsp
Information leakage Unknown to attacker: • Stack/code addresses • Cookie Leak information back to attacker first: • Leak pointer data, use tweaked exploit based on data – – – Read outside array bounds Format string vulnerabilities (not that common anymore? ) Serialization Timing side channels … • In scripting languages: create exploit directly in the script • Bruteforcing (if possible) • Generalized stack reading… https: //www. bartcoppens. be/
Generalized Stack Reading proftpd libc stack Waiting for clients… ct e n con Client rsp cookie return address …
Generalized Stack Reading proftpd ct e n con libc stack Waiting for clients… accept() connection rsp fork() cookie return address Client … proftpd libc Clone of the parent process: Same addresses, same cookie! stack rsp cookie return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… cookie rsp return address Client … √ proftpd libc stack O v e r f l buffer = “Overflow” send(buffer) Connection+Server ok? √ cookie return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… cookie rsp return address Client … proftpd libc stack buffer = “Overflow” cookie_guess = [0 x 00] send(buffer + cookie_guess) Connection+Server ok? O v e r f l 0 cookie return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… rsp Client cookie return address … buffer = “Overflow” cookie_guess = [0 x 00] send(buffer + cookie_guess) Connection+Server ok?
Generalized Stack Reading proftpd libc stack Waiting for clients… ct e n cookie rsp return address Client … proftpd libc stack buffer = “Overflow” cookie_guess = [0 x 01] send(buffer + cookie_guess) Connection+Server ok? O v e r f l 1 cookie return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… cookie rsp return address Client … √ proftpd libc stack buffer = “Overflow” cookie_guess = [0 x 02] send(buffer + cookie_guess) Connection+Server ok! O v e r f l √ 2 cookie return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… rsp cookie return address Client … proftpd libc stack buffer = “Overflow” cookie_guess = [0 x 02, 0 x 00] send(buffer + cookie_guess) Connection+Server ok? O v e r f l 2 0 cookie return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… ct e n con rsp cookie return address Client … proftpd libc stack buffer = “Overflow” cookie_guess = [0 x 02, 0 x 01] send(buffer + cookie_guess) Connection+Server ok? O v e r f l 2 1 cookie return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… ct e n con rsp cookie return address Client … √ proftpd libc stack buffer = “Overflow” cookie_guess = [0 x 02, 0 x 01, 0 x 42, …, 0 x 09] send(buffer + cookie_guess) Connection+Server ok? O v e r f l 9 √ 2 1 … return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… ct e n con rsp cookie return address Client … proftpd libc stack buffer = “Overflow” cookie = [0 x 02, 0 x 01, 0 x 42, …, 0 x 09] identified cookie send(buffer + cookie) Saved rbx, etc. Connection+Server ok? O v e r f l 9 2 1 … return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… ct e n con rsp cookie return address Client … proftpd buffer = “Overflow” cookie = [0 x 02, 0 x 01, 0 x 42, …, 0 x 09] local_stack_guess = [0 x 00] send(buffer + cookie + local_stack_guess) Connection+Server ok? libc stack buffer O v e r f l 9 2 1 … return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… ct e n con rsp cookie return address Client … proftpd libc buffer = “Overflow” buffer cookie = [0 x 02, 0 x 01, 0 x 42, …, 0 x 09] local_stack = [0 x 00, …] return_address_guess = [0 x 00] send(buffer + cookie + local_stack + return_address_guess) Connection+Server ok? stack O v e r f l 9 2 1 … 0 return address …
Generalized Stack Reading proftpd libc stack Waiting for clients… ct e n con rsp cookie return address Client Load address known! proftpd buffer = “Overflow” cookie = [0 x 02, 0 x 01, 0 x 42, …, 0 x 09] local_stack = [0 x 00, …] return_address = [0 x 7 f, 0 x 3 c, …] send(buffer + cookie + local_stack + return_address) Connection+Server ok? ROP chains! libc … stack buffer O v e r f l 9 2 1 … 0 return address …
Some other exploitation techniques • JIT spraying • Heap spray, heap grooming/feng shui • Buffers in statically allocated sections (data, BSS, etc) • …
Some other vulnerabilities to exploit • • • Exploiting double free/use after free/etc Heap spray, heap grooming/feng shui Type confusion Races Integer overflows Your regular kind of vulnerability, but in the kernel The sky is the limit… Have a look at googleprojectzero. blogspot. com Have a look at The Art of Software Security Assessment
- Slides: 132