660 likes | 779 Vues
This presentation discusses automated techniques for detecting exploitable vulnerabilities in binaries. It covers the definition of vulnerabilities, the architecture used for detection, and the challenges presented by complex code structures. We will explore a set of tests for analysis tools and showcase a proof-of-concept tool released under GPLv3. Key topics include handling Out-Of-Bound (OOB) memory writes, dealing with branches in code, and the importance of inter-function analysis for effective detection. Suggestions for enhancements will also be provided.
E N D
Automated Exploit Detection in Binaries Finding exploitable vulnerabilities in binaries Matt Hargett http://www.clock.org/~matt matt {hizzat} use {dizznot} net Luis Miras http://dwerd.blogspot.com lmiras {hizzat} gmail {dizznot} com
Agenda • Definition • Architecture • Challenges
bugreport • Set of tests for analysis tools • Proof Of Concept tool • Not a product or real-world tool • Released under GPLv3 draft 2 • http://sf.net/projects/bugreport • Enhancements as issues come up
Why C#? • Very similar to Java and C++ • Open ECMA standard • 3 open source implementations • It has specific features we like • high-speed generics • nullable value types • strong typing • high quality and simple open source tools
Target of Detection • Many vendors have their own definitions of exploitable bugs. "depends on what you mean by exploit and by bug" • Our definition is Out-Of-Bound (OOB) memory write using tainted data.
Out-Of-Bound Write Tests • C code • x86 code • Test • C# code
bugreport architecture • Set of tests • x86 emulator • Other processors will be added later. • Analysis engine
Challenges • Branches • Inter-function Analysis • Non-Contiguous functions • Self Modifying code • Loops
Dealing with Branches • Known values • Results in one machine state • Unknown values • Results in two machine states • Constraints are used • x <= value <= y
Dealing with Branches • cmp, test, math instructions set flags based on input • jxx, sbb, etc. instructions act on flags
Dealing with Branches 1: cmp eax, 0 2: jne 4 3: ret 4: cmp eax, 255 5: jle 7 6: ret 7: ...
Choosing Branches • Cheat, take branches (follow jxx, sbb). • Randomly pick branches • Take all branches (drop through and follow jxx, sbb) • Take some branches (drop through and follow jxx, sbb)
Choosing Branches • Many functions have guards at the entry. • Guards generally drop through on failure. • Taking all branches increases code coverage
Dealing with Branches void main(int argc, char** argv) { if(argc < 2){ exit(-1); } printf("23c3\n"); }
Dealing with Branches cmp [ebp+argc], 1 jg short postGuard push 0FFFFFFFFh; status call _exit postGuard: push offset a23c3 ; ”23c3\n" call _printf
Choosing Branches • Prefix is a tool that randomly took branches. • Found many bugs for customers. • Produced different results each run. • Bought by Microsoft and shelved. • Many customers keep old versions around. • Prefast comes with DDK. • Does not do interfunction value tracking
Choosing Branches • Taking all branches results in multiple machine states. • Taking a branch sets constraints on input. • These constraints must not be broken.
Dealing with Branches int getSize(char *ch){ int size = 1; char x = *ch; if(x != 0){ if(x != '\n'){ size++; } else{ size += 2; } } else{ size--; } return size; }
Dealing with Branches What are the potential states? • (x <=-1 || x >= 1) && (x != ‘\n’) && (size == 2) • (x == ‘\n’) && (size == 3) • (x == 0) && (size == 0) Real world code will have many potential states.
Inter-function: Top-down • Start at an export or entry point. • Traverse code through functions
Inter-function: Top-down main(){foo(); x(); bar();} foo(){x(); } bar(){y(); } x(){y(); z(); } y(){z(); } z(){return 0; } // Code omitted for brevity
Inter-function: Top-down Function Count main() 0 foo() 0 bar() 0 x() 0 y() 0 z() 0
Inter-function: Top-down Function Count main() 1 foo() 0 bar() 0 x() 0 y() 0 z() 0
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 0 x() 0 y() 0 z() 0
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 0 x() 1 y() 0 z() 0
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 0 x() 1 y() 1 z() 0
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 0 x() 1 y() 1 z() 1
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 0 x() 1 y() 1 z() 2
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 0 x() 2 y() 1 z() 2
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 0 x() 2 y() 2 z() 2
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 0 x() 2 y() 2 z() 3
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 0 x() 2 y() 2 z() 4
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 1 x() 2 y() 2 z() 4
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 1 x() 2 y() 3 z() 4
Inter-function: Top-down Function Count main() 1 foo() 1 bar() 1 x() 2 y() 3 z() 5
Inter-function: Top-down • Complexity can explode. • Very time consuming. • Hitting the same functions multiple times. • z() visited 5 times. • Larger programs can have very large call chains. • “like playing with a yo-yo in the grand canyon”
Inter-function: Bottom-up • Describe each function in isolation • Taint return value • Store return values for a function based on constraints • Use it when function call is evaluated • Creating a machine state diff.
Inter-function: Bottom-up • With deeply nested calls • Taint return value • Requires multiple sweeps
Inter-function: Bottom-up main(){foo(); x(); bar();} foo(){ x(); } bar(){ y(); } x(){y(); z(); } y(){ z(); } z(){return 0; } // Code omitted for brevity
Inter-function: Bottom-up Pass #1 main() { foo(); x(); bar(); } Done: <None>
Inter-function: Bottom-up Pass #1 foo() { x(); } Done: <None>
Inter-function: Bottom-up Pass #1 bar() { y(); } Done: <None>
Inter-function: Bottom-up Pass #1 y() { z(); } Done: <None>
Inter-function: Bottom-up Pass #1 x() { y(); z(); } Done: <None>
Inter-function: Bottom-up Pass #1 z() { return 0; } Done: z()
Inter-function: Bottom-up • One pass through call graph seems similar to top-down. • What is the difference? • The difference is z() is evaluated as a machine state diff. • z()’s analysis is cached
Inter-function: Bottom-up Pass #2 main() { foo(); x(); bar(); } Done: z()
Inter-function: Bottom-up Pass #2 foo() { x(); } Done: z()
Inter-function: Bottom-up Pass #2 bar() { y(); } Done: z()
Inter-function: Bottom-up Pass #2 x() { y(); z(); } Done: z()