From DebuggingInformation Based Binary Level Type Inference to
From Debugging-Information Based Binary. Level Type Inference to CFG Generation Dongrui Zeng Gang Tan Computer Science and Engineering Department The Pennsylvania State University 1
Binary-level CFG Importance • • Binary-level static analysis - Requires binary-level CFG to operate on e. g. tainting, dependency, liveness Control Flow Integrity (CFI) - Require a sound binary-level CFG Soundness: all legitimate control flows must be covered Insert checks at branches to make sure the CFG is respected 2
Binary-level CFG Challenges • • • Cannot use CFG generated for code optimization purposes - At higher levels Discrepancy due to compiler’s back-end optimizations Difficult to determine targets of indirect branches - Indirect call (i. Call): call *%eax Indirect jump (i. Jump): jmp *0 x 804 a 00 c Return (Ret): return address on stack Dilemma of precision and soundness - Introduce over-approximation to ensure soundness e. g. allow indirect calls to target all functions 3
Previous Work • • Binary-Analysis Approach - No source code: analyze binary code directly Binary-level static analysis, e. g. pointer-analysis, liveness To achieve soundness at the cost of losing precision Compiler-Modification Approach - Use source-level and IR-level information Propagate to binary-level by modifying compilers High precision but compiler modification 4
Previous Work: Binary-Analysis • • CCFIR [Zhang, et al. , S&P, 2013]: - Assume ASLR which guarantees the relocation information Over-approximate the targets of indirect calls and indirect jumps from relocation information Type. Armor [Veen, et al. , S&P, 2016]: - First generate intra-procedural CFG Liveness analysis to infer number of parameters 5
Previous Work: Compiler-modification • • Forward-Edge CFI [Tice, et al. , Usenix Security, 2014] - Modify GCC and LLVM to propagate the number of parameters CFG generation policy: Arity matching MCFI [Niu, et al. , PLDI, 2014]: - Modify LLVM 3. 5 to propagate type signatures CFG generation policy: type-signature matching 6
Downsides of Compiler Modification • • CFG-generation code has to be upgraded when compiler is upgraded - API changes: passes added to older versions have to be changed; not easy - e. g. LLVM group has to maintain CFG generation for Forward. Edge CFI CFG generation in one compiler cannot easily be reused by a different compiler - e. g. no GCC implementation of MCFI’s CFG generation 7
Our Work • • An alternative to binary-level CFG generation - Assume source code but no compiler modification High precision with type-signature matching policy Insight: analyze compiler-generated meta-information - Debugging info, relocation info, and symbol tables Enough high-level information for high-precision CFG generation Standard formats that compilers must conform 8
Meta-Information • • • Symbol tables - A complete list of function names and starting addresses Relocation information - A complete set of targets of indirect calls and indirect jumps Debugging information - Source-level Type information Location information Line number information Call frame information … 9
Type Information • • For functions - Type signature: return type and parameter types e. g. “void foo (int); ” For variables - Declared source-level type e. g. “int i; ” 10
Location Information • • • For functions - Function starting addresses in code sections For global variables - Memory addresses in data sections For local variables and function parameters - Registers, e. g. EAX, EBX, … Stack locations 11
Challenges and Solutions • • Inaccurate information harms soundness - Line number info and call frame info May lead to wrong CFG; do not use Incomplete information harms precision - Location information: no correspondence for ‣ ‣ - ~50% indirect-call operands all indirect-jump operands Have to introduce over-approximation for soundness Perform a data-flow analysis based type inference to infer more information 12
Workflow 1 st stage Disassembly and base CFG construction Compilation toolchain 2 nd stage High-precision CFG Type Signature matching 13 Type inference
Example Compilation Binary Code + Symbol + Relocation No type signature is directly given 14 + Debugging Info
Disassembly: Base CFG + Debug-Info 1: r 1=*(ESP+4) 2: r 2=*(ESP+8) 3: *(ESP)=r 1 4: call *r 2 ? ? ? 5: ret 15
Data-Flow Type Annotation Analysis 1: r 1=*(ESP+4) 2: r 2=*(ESP+8) 3: *(ESP)=r 1 4: call *r 2 ESP+8: ESP+8 (int—>void)* r 2_2 ? ? ? r 2_4 5: ret 16
Type Propagation 1: r 1=*(ESP+4) 2: r 2=*(ESP+8) 3: *(ESP)=r 1 4: call *r 2 ESP+8: (int—>void)* r 2_2: (int—>void)* ? ? ? r 2_4: (int—>void)* 5: ret 17
Our CFG 1: r 1=*(ESP+4) 2: r 2=*(ESP+8) 3: *(ESP)=r 1 4: call *r 2 foo? ? ? or bar r 2_4: (int—>void)* 5: ret 18
Evaluation • • SPECint_2006 + Nginx Effectiveness of type inference - Do we really need? how effective? CFG soundness validation - Meaningless if unsound Compare CFG precision 19
Effectiveness • 50. 7% i. Call type signatures and all i. Jump type signatures require inference • 97. 8% missing signatures for i. Call and 99. 9% for i. Jump are precisely inferred 648/1279 = 50. 7% 634/648 = 97. 8% 20 only 1 imprecision
Soundness Validation • We use PIN to validate soundness with SPECInt - Dynamically collect indirect branch traces with reference input data - Coverage test against generated CFGs All benchmarks passed the validation 21
Precision Comparison • Compare with compiler-modification approach (MCFI) - MCFI: one type signature for each indirect call We assign one type signature for 98. 9% indirect calls ‣ - including compiler-generated ones and inferred ones Comparable precision with compiler-modification approach 1265/1279 = 98. 9% 22
Precision Comparison • Compare with binary-analysis approach (CCFIR) - Average Reduction of AIBT measures precision improvement ‣ - The statistics of Average Indirect Branch Target (AIBT) measures the precision ‣ - Averagely 82. 8% for large SPEC benchmarks; 90. 3% for Nginx The smaller the better Much higher precision than binary-analysis approach 23
Related and Future Work • • • Newton [Victor et al. , CCS, 2017] - Briefly mention using debugging information for collecting type signatures Extending to C++ programs - Add support for classes: meta-info based class hierarchy analysis introduced by MCFI Security applications, e. g. CFI 24
Conclusion • Binary-analysis approach and compiler-modification approach • Meta-information approach: an alternative for highprecision binary-level CFG generation - No compiler modification Comparable precision with compiler-modification approach 25
Q&A Thank you! 26
Appendix: Canonical Frame Address • • CFA is defined to be the stack location right above the return address location - Return address is stored at CFA-4 Parameters: CFA+off; local variables: CFA-4 -off - The variable i resides in CFA-8 27
Appendix: Valid Range • Valid ranges 28
Appendix: Type Inference Type information retrieval Stack layout inference Constraint generation Constraint solving 29
Appendix: Stack Layout Inference • • Input: assembly code for a function and its base CFG • Algorithm Output: for every address, a set of registers that point to stack and the offset to CFA - Based on instruction semantics Intersection at control flow merge point Work-list algorithm to compute fix-point for handling loops 30
Appendix: Stack Layout Inference 31
Appendix: Implementation • • • OCaml - Disassembly parses ELF file including symbol tables Type inference, CFG construction C and OCaml-C interface - Retrieve debugging information with libdwarf library implemented in Python and Shell scripts: - Retrieve relocation information during compilation with readelf 32
Appendix: Constraint Generation type of eax after instruction 10 type of ebx before instruction 10 address-flow for handling lea control flow is considered 33
Appendix: Constraint Solving 34
Appendix: A More Complicated Example 35
Binary-level CFG demo: 1: if r 1 >= 0 then goto 4 true false 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 ? ? ? 7: ret 36
Binary-level CFG demo: 1: if r 1 >= 0 then goto 4 true false 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 foo or bar 7: ret 37
Baseline CFG demo: 1: if r 1 >= 0 then goto 4 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 • ? ? ? 7: ret 38 Func Ret P 1 P 2 Reloc foo void int Yes bar void int Yes fun 1 void fun 2 void float No fun 3 int No fun 4 void int Yes Allow all functions int Yes
CCFIR’s CFG demo: 1: if r 1 >= 0 then goto 4 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 Func Ret P 1 P 2 Reloc foo void int Yes bar void int Yes fun 1 void fun 2 void float No fun 3 int No fun 4 void int Yes • All code addresses in relocation information ? ? ? 7: ret 39
Type. Armor’s CFG demo: 1: if r 1 >= 0 then goto 4 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 Func Ret P 1 P 2 foo void int Yes bar void int Yes fun 1 void fun 2 void float No fun 3 int No fun 4 void int Yes int • May have 1 parameter • May not have return value ? ? ? 7: ret 40 Reloc Yes
Type. Armor’s CFG demo: 1: if r 1 >= 0 then goto 4 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 Func Ret P 1 P 2 Reloc foo void int Yes bar void int Yes fun 1 void fun 2 void float No fun 3 int No fun 4 void int Yes • Over-approximation for soundness - 0 or 1 parameters - 0 or 1 return value ? ? ? 7: ret 41
Forward-Edge CFI’s CFG demo: 1: if r 1 >= 0 then goto 4 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 ? ? ? Func Ret P 1 foo void int Yes bar void int Yes fun 1 void fun 2 void float No fun 3 int No fun 4 void int • • 7: ret 42 P 2 Reloc Yes int Exactly 1 parameters Exactly 0 return value Yes
Forward-Edge CFI’s CFG demo: 1: if r 1 >= 0 then goto 4 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 Func Ret P 1 P 2 Reloc foo void int Yes bar void int Yes fun 1 void fun 2 void float No fun 3 int No fun 4 void int Yes • Source-level information is precise • No need to over-approximate ? ? ? 7: ret 43
Modular CFI’s CFG demo: 1: if r 1 >= 0 then goto 4 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 ? ? ? Func Ret P 1 foo void int Yes bar void int Yes fun 1 void fun 2 void float No fun 3 int No fun 4 void int • • 7: ret 44 P 2 Reloc Yes Parameter type: (int) Return type: void int Yes
Meta-Info: Debugging information compilation 45
Meta-Info CFG demo: 1: if r 1 >= 0 then goto 4 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 ? ? ? 7: ret 46 Func Ret P 1 P 2 Reloc foo void int Yes bar void int Yes fun 1 void fun 2 void float No fun 3 int No fun 4 void int Yes
Meta-Info CFG demo: 1: if r 1 >= 0 then goto 4 4: r 2 = foo 2: r 2 = bar 3: goto 5 5: p 1 = r 1 6: call *r 2 ? ? ? 7: ret 47 Func Ret P 1 P 2 Reloc foo void int Yes bar void int Yes fun 1 void fun 2 void float No fun 3 int No fun 4 void int Yes
- Slides: 47