A Tough call : Mitigating Advanced Code-Reuse Attacks At The Binary Level Victor van der Veen, Enes Göktaş (joint first author) (VU) Moritz Contag, Andre Pawlowski, Thorsten Holz (RUB) Xi Chen, Sanjay Rawat, Herbert Bos, Elias Athanasopoulos, Cristiano Giuffrida (VU)
Control-Flow Integrity • Promising way to stop code-reuse attacks • Hard to enforce in practice • Existing binary-level CFI cannot prevent function-reuse attacks (COOP) 2
Control-Flow Integrity • Promising way to stop code-reuse attacks • Hard to enforce in practice • Existing binary-level CFI cannot prevent function-reuse attacks (COOP) TypeArmor • A more precise binary-level CFI solution • Acceptable overhead (3% on SPEC) • Stops all published code-reuse attacks 3
Running example: normal execution Func1() { ... processor() { } ... while (condition) { ... Func2() { ... call fptr } ... } ... Func3() { } ... } 4
Running example: advanced code-reuse Func1() { ... processor() { } ... while (condition) { ... Func2() { ... call fptr } ... } ... Func3() { } ... } 5
Running example: advanced code-reuse Func1() { ... processor() { } ... while (condition) { ... Func2() { ... call fptr } Gadget ... } ... Func3() { } ... Attacker controlled ‘loop gadget’ } Gadget Function-oriented programming 6
Running example: binary-level CFI • Unable to resolve indirect call targets Func1() { ... • Indirect calls may go to any function } processor() { Func2() { ... ... } Gadget while (condition) { ... call fptr Func3() { ... ... } } Gadget ... } Loop gadget 7
Running example: binary-level CFI • Unable to resolve indirect call targets Func1() { ... • Indirect calls may go to any function } processor() { Func2() { ... ... } Gadget while (condition) { ... call fptr Func3() { ... ... } Binary -level } Gadget ... solutions } Loop gadget 8
Running example: source-level CFI • Enforce class hierarchy (VTV) Func1() { ... • Match function argument types (IFCC) } processor() { Func2() { ... ... } Gadget while (condition) { ... call fptr Func3() { ... ... } Source -level } Gadget ... solutions } Loop gadget 9
Running example: TypeArmor • Approximate source-level accuracy Func1() { ... } processor() { Func2() { ... ... } Gadget while (condition) { ... call fptr Func3() { ... ... } } Gadget TypeArmor ... } Loop gadget 10
Running example: TypeArmor • Approximate source-level accuracy Func1() { ... } Not as accurate as source processor() { Func2() { ... ... } Gadget while (condition) { ... call fptr Func3() { ... ... } } Gadget TypeArmor ... } Loop gadget 11
Running example: TypeArmor • Approximate source-level accuracy Func1() { ... } Not as accurate as source processor() { Func2() { ... ... } Gadget while (condition) { ... call fptr Func3() { ... … But still breaking exploits } } Gadget TypeArmor ... } Loop gadget 12
Approximate source-level invariants? Function signature matching by argcount • Extract argument count at callsite • Extract argument usage at callee • Allow only targets with matching function types 13
Approximate source-level invariants? Function signature matching by argcount • Extract argument count at callsite • Extract argument usage at callee • Allow only targets with matching function types Callsites preparing two args should never call functions expecting three or more 14
Approximate source-level invariants? Function signature matching by argcount • Extract argument count at callsite • Extract argument usage at callee • Allow only targets with matching function types Callsites preparing two args should never call functions expecting three or more Implemented for the x86-64 architecture: • Calling convention: pass arguments via registers • Search for write instructions at the callsite • Search for read-before-write instructions at the callee 15
Running example: TypeArmor • Match argument count expectations Func1(arg1,arg2){ return arg1+arg2 } processor() { Func2(arg1,arg2){ return arg1*arg2 ... } while (condition){ arg1 = x arg2 = y Func3(arg1,arg2,arg3){ call fptr( arg1,arg2 ) return arg3-arg1+arg2 ... } } ... } Loop gadget 16
Running example: TypeArmor Expects 2 arguments • Match argument count expectations Func1( arg1,arg2 ){ return arg1+arg2 } processor() { Func2(arg1,arg2){ return arg1*arg2 ... } while (condition){ arg1 = x Prepares 2 arguments arg2 = y Func3(arg1,arg2,arg3){ call fptr( arg1,arg2 ) return arg3-arg1+arg2 ... } } ... } Loop gadget 17
Running example: TypeArmor Expects 2 arguments • Match argument count expectations Func1(arg1,arg2){ return arg1+arg2 } Expects 2 arguments processor() { Func2( arg1,arg2 ){ return arg1*arg2 ... } while (condition){ Working Gadget arg1 = x Prepares 2 arguments arg2 = y Func3(arg1,arg2,arg3){ call fptr( arg1,arg2 ) return arg3-arg1+arg2 ... } } ... } Loop gadget 18
Running example: TypeArmor Expects 2 arguments • Match argument count expectations Func1(arg1,arg2){ return arg1+arg2 } Expects 2 arguments processor() { Func2(arg1,arg2){ return arg1*arg2 ... } while (condition){ Working Gadget arg1 = x Prepares 2 arguments Expects 3 arguments arg2 = y Func3( arg1,arg2,arg3 ){ call fptr( arg1,arg2 ) return arg3-arg1+arg2 ... } Broken Gadget } ... } Loop gadget 19
Precision How accurate can we determine the prepared and used argument count? Callsites Functions Server # As in source # As in source Memcached 48 41 (86%) 236 210 (89%) lighttpd 54 47 (87%) 353 311 (88%) Nginx 218 161 (74%) 1,111 869 (78%) MySQL 7,532 5,771 (77%) 9,961 6,977 (70%) 20
Precision How accurate can we determine the prepared and used argument count? Callsites Functions Server # As in source # As in source Memcached 48 41 (86%) 236 210 (89%) lighttpd 54 47 (87%) 353 311 (88%) Nginx 218 161 (74%) 1,111 869 (78%) MySQL 7,532 5,771 (77%) 9,961 6,977 (70%) 21
Precision How accurate can we determine the prepared and used argument count? Callsites Functions Server # As in source # As in source Memcached 48 41 (86%) 236 210 (89%) lighttpd 54 47 (87%) 353 311 (88%) Nginx 218 161 (74%) 1,111 869 (78%) MySQL 7,532 5,771 (77%) 9,961 6,977 (70%) 22
Running example: TypeArmor • Runtime enforcement ID: 2 Func1(arg1,arg2){ return arg1+arg2 } processor() { ID: 2 Func2(arg1,arg2){ ... return arg1*arg2 while (condition){ } Working Gadget arg1 = x arg2 = y CHECK TARGET: ID <= 2 call fptr(arg1,arg2) ID: 3 ... Func3(arg1,arg2,arg3){ return arg3-arg1+arg2 } } Broken Gadget ... } Loop gadget 23
Performance SPEC CPU2006: less than 3% (geometric mean) 24
Performance SPEC CPU2006: less than 3% (geometric mean) Server Overhead Language Memcached 1.4% C lighttpd 11.6% C Nginx 13.2% C MySQL 23.9% C++ 25
Conclusion • Extract new invariants from binaries • Enforce strictest security policy at binary-level to date • Binary-level CFI solutions can mitigate sophisticated code-reuse attacks • Keep an eye on http://www.vusec.net 26
Recommend
More recommend