POLITECNICO DI MILANO              Facolt` di Ingegneria dell’Informazione                    a            Corso di Laurea...
AcknowledgementsThe authors would like to thank Thomas Dullien, Julien Vanegue, Ralf-PhilippWeinmann and Tim Kornau for th...
4
Contents1 Introduction                                                                 92 Static Analysis                 ...
CONTENTS  4.3   Interprocedural analysis . . . . . . . . . . . . . . . . . . . . . . 37  4.4   C++ peculiarities . . . . ....
List of Figures 2.1   Data-flow equations . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.2   Pointer analyses . . ....
LIST OF FIGURES  5.1   Example of callgraph . . . . . . . . . . . . . . . . . . . . . . . . 45  5.2   Callgraph with relev...
Chapter 1IntroductionIn the era of cloud computing and internet-connected devices, attacks to suchdevices are becoming inc...
CHAPTER 1. IntroductionTCmalloc() in Webkit and V8 JavaScript engines.   The complexity and heterogeneity of memory manage...
fuzzing has been proved to be an e↵ective method for finding such bugs, itsu↵ers from several intrinsic limitations. For ex...
CHAPTER 1. Introduction                          12
Chapter 2Static Analysis2.1     General knowledgeStatic analysis is the automated process of extracting semantic informati...
CHAPTER 2. Static Analysis     makes it particularly useful in analyzing large applications with complex     path triggeri...
2.1. General knowledge                               outb = transb (inb )                             inb = joinp2predb (o...
CHAPTER 2. Static Analysis2.2      Pointer AnalysisWe will now focus on data-flow analysis of pointer values, precisely nam...
2.2. Pointer Analysis• Context-sensitivity: in context-sensitive analyses the calling context of a  function is considered...
CHAPTER 2. Static Analysis   A lot of reasearch has been done on Pointer Analysis in the last twentyfiveyears. Nowadays mod...
2.3. Conclusions and contributions2.3      Conclusions and contributionsIntraprocedural analysis, in terms of e ciency and...
CHAPTER 2. Static Analysis                             20
Chapter 3Preprocessing stage3.1     The REIL intermediate languageThe Reverse Engineering Intermediate Language (REIL) [6]...
CHAPTER 3. Preprocessing stagecalled " are used where necessary. Each of the 17 di↵erent REIL instructionhas exactly one m...
3.1. The REIL intermediate languageArithmetic instructions      OperationADD x1 , x2 , y              y = x1 + x2SUB x1 , ...
CHAPTER 3. Preprocessing stageor a maximum output operand size relative to the sizes of the input operands.   Note that ce...
3.2. Single Static Assignment (SSA) Form3.2      Single Static Assignment (SSA) Form3.2.1      Graph theory overviewThe al...
CHAPTER 3. Preprocessing stage     dominates N; More intuitively, it is the set of nodes where N’s dominance     stops;   ...
3.2. Single Static Assignment (SSA) Form                             Figure 3.3: Non-local variableat a given merge point....
CHAPTER 3. Preprocessing stage        end if        killed     killed [ {z}    end for  end for   In our implementation th...
3.2. Single Static Assignment (SSA) Form  Replace v with vi in the new graph  Stack[v].push(i)  Counters[v ]       i +1end...
CHAPTER 3. Preprocessing stage               Figure 3.4: SSA Form of a REIL function                                 30
Chapter 4Analysis stage4.1     Pointer analysisIn our work we implemented both intraprocedural and interprocedural pointer...
CHAPTER 4. Analysis stageflow, context and path sensitivity. An algorithm is said to be path-sensitiveif it computes di↵ere...
4.1. Pointer analysisbinary. The PCG allows to discern function parameters and calling locations,that is every edge in the...
CHAPTER 4. Analysis stagegraph of a function that can be walked arbitrary depending on the analysisthat is intended to be ...
4.2. Intraprocedural analysisby propagating alive aliases between functions in the binary call graph.We have adapted the a...
CHAPTER 4. Analysis stage   We create a new alias set for every newly allocated object, we then store inthe appropriate al...
4.3. Interprocedural analysisA sample run of the algorithm can be seen in Figure 4.44.3      Interprocedural analysisAt th...
CHAPTER 4. Analysis stageparameters, as indicated in the edge label. If the node corresponding to theformal parameter is t...
4.4. C++ peculiarities4.4     C++ peculiaritiesIn dealing with C++ we had to take into account a certain numbers of charac...
CHAPTER 4. Analysis stage  Arithmetic instructions         Operation  ADD x1 , x2 , y                 y is added to the al...
4.4. C++ peculiarities                             8                             >                             > {{v }} if...
CHAPTER 4. Analysis stage                Figure 4.5: PCG used in our algorithm             Figure 4.6: Computing f1() to f...
4.4. C++ peculiarities              Figure 4.7: Computing f1() to f3() alias treesFigure 4.8: Computing f2() to f4() alias...
CHAPTER 4. Analysis stage        Figure 4.10: E↵ects of combine() on functions alias trees                                ...
Chapter 5Stale pointers detectionDetecting use after free conditions means to verify whether there are any codepaths in wh...
CHAPTER 5. Stale pointers detection             Figure 5.2: Callgraph with relevant functions in red                      ...
For each function that calls a destructor alias we verify whether the concreteobject itself or one of its aliases are used...
CHAPTER 5. Stale pointers detection                                  48
Chapter 6Results and future workIn this paper we have targeted a widely known cause of security flaws. Wehave shown that it...
CHAPTER 6. Results and future workThe principal source of false negatives in our analysis is the heavy presenceof function...
Bibliography[1] Ron Cytron, Jeanne Ferrante, Barry K. Rosen, Mark N. Wegman, and   F. Kenneth Zadeck: ”E ciently computing...
BIBLIOGRAPHY [6] Thomas Dullien, and Sebastian Porst: ”REIL: A platform-independent    intermediate representation of disa...
Upcoming SlideShare
Loading in...5
×

Stale pointers are the new black - white paper

1,276
-1

Published on

Published in: Technology
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total Views
1,276
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
14
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

Stale pointers are the new black - white paper

  1. 1. POLITECNICO DI MILANO Facolt` di Ingegneria dell’Informazione a Corso di Laurea in Ingegneria Informatica Dipartimento di Elettronica e InformazioneDetecting aliased stale pointers via static analysis: An architecture independent practical application of pointer analysis and graph theory to find bugs in binary codeRelatore: Prof. Stefano ZaneroCorrelatore: Ing. Federico Maggi Tesi di Laurea di: Giovanni Gola, matricola 717847 Vincenzo Iozzo, matricola 713583 Anno Accademico 2009-2010
  2. 2. AcknowledgementsThe authors would like to thank Thomas Dullien, Julien Vanegue, Ralf-PhilippWeinmann and Tim Kornau for their suggestions and help while researchingthe topic.The authors would also like to thank the thesis advisors from Politecnico diMilano Stefano Zanero and Federico Maggi.Finally we want to thank all the people who have reviewed the paper. 3
  3. 3. 4
  4. 4. Contents1 Introduction 92 Static Analysis 13 2.1 General knowledge . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.2 Pointer Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 2.3 Conclusions and contributions . . . . . . . . . . . . . . . . . . . 193 Preprocessing stage 21 3.1 The REIL intermediate language . . . . . . . . . . . . . . . . . 21 3.2 Single Static Assignment (SSA) Form . . . . . . . . . . . . . . . 25 3.2.1 Graph theory overview . . . . . . . . . . . . . . . . . . . 25 3.2.2 Computing SSA Form . . . . . . . . . . . . . . . . . . . 264 Analysis stage 31 4.1 Pointer analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 4.1.1 Analysis features . . . . . . . . . . . . . . . . . . . . . . 31 4.1.2 MonoREIL . . . . . . . . . . . . . . . . . . . . . . . . . 33 4.2 Intraprocedural analysis . . . . . . . . . . . . . . . . . . . . . . 34 5
  5. 5. CONTENTS 4.3 Interprocedural analysis . . . . . . . . . . . . . . . . . . . . . . 37 4.4 C++ peculiarities . . . . . . . . . . . . . . . . . . . . . . . . . . 395 Stale pointers detection 456 Results and future work 49 6
  6. 6. List of Figures 2.1 Data-flow equations . . . . . . . . . . . . . . . . . . . . . . . . . 15 2.2 Pointer analyses . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3.1 List of REIL instructions . . . . . . . . . . . . . . . . . . . . . . 23 3.2 REIL translation of a function . . . . . . . . . . . . . . . . . . . 24 3.3 Non-local variable . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.4 SSA Form of a REIL function . . . . . . . . . . . . . . . . . . . 30 4.1 REIL Instructions transformations . . . . . . . . . . . . . . . . 40 4.2 Transfer functions for common instructions . . . . . . . . . . . . 41 4.3 Transfer functions for -nodes instructions . . . . . . . . . . . . 41 4.4 Intraprocedural analysis example . . . . . . . . . . . . . . . . . 41 4.5 PCG used in our algorithm . . . . . . . . . . . . . . . . . . . . 42 4.6 Computing f1() to f2() alias trees . . . . . . . . . . . . . . . . . 42 4.7 Computing f1() to f3() alias trees . . . . . . . . . . . . . . . . . 43 4.8 Computing f2() to f4() alias trees through the leftmost edge . . 43 4.9 Computing f2() to f4() alias trees through the rightmost edge . . 43 4.10 E↵ects of combine() on functions alias trees . . . . . . . . . . . 44 7
  7. 7. LIST OF FIGURES 5.1 Example of callgraph . . . . . . . . . . . . . . . . . . . . . . . . 45 5.2 Callgraph with relevant functions in red . . . . . . . . . . . . . 46 5.3 Pruned callgraph . . . . . . . . . . . . . . . . . . . . . . . . . . 46 5.4 Pruned callgraph ready for bug detection step . . . . . . . . . . 46 5.5 Alias verification equations . . . . . . . . . . . . . . . . . . . . . 47 8
  8. 8. Chapter 1IntroductionIn the era of cloud computing and internet-connected devices, attacks to suchdevices are becoming increasingly profitable. Nowadays web clients, such asPCs, mobiles, UMPCs, tablets and netbooks, are indeed a precious sourceof information. Although built using diverse architectures, including but notlimited to the x86, x86-64, ARM and PowerPC familes, they necessarily sharea base set of network dedicated software, first of all the web browsers. Ap-plications like Firefox, Safari, Internet Explorer, Google Chrome and othercomplementary software including JavaScript engines, Adobe Flash, AdobeAIR, Adobe Reader, the Java Runtime Environment, are distributed pre-installed in almost every OS. Due to the magnitude of the latter applications,developers are bound to adopt self-made custom complex memory manage-ment systems built on top of native memory allocators. Noticeable examplesare NS Alloc()/NS Free() in the XPCOM library, PR NEW/PR DELETEin NSPR library, JS malloc()/JS free() in SpiderMonkey JavaScript engine, 9
  9. 9. CHAPTER 1. IntroductionTCmalloc() in Webkit and V8 JavaScript engines. The complexity and heterogeneity of memory management in large codebases results in numerous memory corruption vulnerablities, such as unintial-ized memory, double frees and dangling pointers. The latter are an insidioustype of flaws, also known as ”use-after-free” or ”stale pointers”, which occurwhen pointer variables reference to freed memory areas. A quick search on theCommon Vulnerabilities and Exposures (CVE) list yields 162 reports for webbrowsers and more than 470 results among the three most-popular browseradd-ons, i.e., Adobe Flash, Adobe Reader and Java Runtime Environment(between 2006 and 2010). The astonishing number of use-after-free bugs reported in recent years makethem one of the most appetible attack vector on client systems. Althoughuse-after-free bugs are scattered throughout the code base, finding them andother memory corruption bugs is arguably more di cult than overflows in thatthey are temporal memory errors and, as such, an e↵ective detection of themmeans understanding how a custom memory management system internallyworks, in order to identify logical pitfalls. The only e↵ective solution consistsin manual code-review e↵orts, in the attempt to recognize exceptions withrespect to the rules that developers were supposed to follow while writing code.This process is clearly cumbersome. A vast variety of approaches has beenproposed to automatically spot memory flaws. In particular, an analyst canactually face the problem of finding dangling pointers with dynamic analysistechniques, such as fuzzing, or by the means of static analysis. Even though 10
  10. 10. fuzzing has been proved to be an e↵ective method for finding such bugs, itsu↵ers from several intrinsic limitations. For example, relying on input toexercise code paths has the disadvantage of scarce coverage of the applicationcode and limits the depth of exercised code paths, leaving part of the codeunexplored. Moreover, the randomic nature of the generated input makes therunning time virtually infinite, and totally unrelated to the code coverage theanalysis reaches over time. We propose a practical approach to automaticallyfind use-after-free conditions in large binary code bases using static analysisand graph theory. 11
  11. 11. CHAPTER 1. Introduction 12
  12. 12. Chapter 2Static Analysis2.1 General knowledgeStatic analysis is the automated process of extracting semantic informationabout a program without executing it. Not having the need of executing thebinary, static analysis o↵ers a number of potential advantages over its dynamiccounterpart: • Architecture independence: the analysis, even if it might be specific to an instruction set or language, can be implemented on top of any framework and run on any machine; • Running time: time complexity of a static analysis algorithm may range from linear to exponential, but it will process the entire binary in finite time; • Code coverage and depth of analysis: not relying on input to exercise code paths, static analysis can achieve total code coverage. This facts 13
  13. 13. CHAPTER 2. Static Analysis makes it particularly useful in analyzing large applications with complex path triggering conditions.Although the static analysis problem has been proven to be theoretically un-decidable, it can be formally reformulated as an over-approximation of the ori-ginal problem which can be proven to halt in finite time. In fact the term ”staticanalysis” is indeed over-broad. It includes various implementation techniquesthat make static analysis feasible. Model checking was the first technique toappear in chronological order. It arose in an attempt to solve the so calledConcurrent Program Verification problem. A model checker checks for thecorrectness of a formula expressed in Temporal Logic, being able to e↵ectivelyuncover hard-to-find concurrency errors. As stated in Chapter 1, use-after-freebugs are temporal memory errors and, as such, can be expressed in terms oftemporal logic: a program point in which a pointer gets dereferenced is reachedafter a program point in which the same pointer gets freed. Model Checkingturns out to be very precise in unveiling temporal memory errors, but revealsnon-negligible faults. The major defect is the need for source code. The modelchecker simply tries to resolve the system of pre- and post-conditions of eachfunction. In order to do that, the pre- and post-conditions has to be previ-ously specified by annotating the source code of the program to analyze. Theprocess of annotating is also extremely time consuming. Moreover, in the gen-eral case, the complexity of model checking algorithms is exponential in time.Another very important method for static analysis is the so called AbstractInterpretation. When running an algorithm based on Abstract Interpretation, 14
  14. 14. 2.1. General knowledge outb = transb (inb ) inb = joinp2predb (outb ) Figure 2.1: Data-flow equationsthe program semantics is over-approximated as a set of monotonic functions,the algorithm uses to transform ordered sets, which are the results of the ana-lysis. It can be viewed as a partial execution of the program which tracks onlypart of information about its semantics, without performing all the calcula-tions. The constraints of monotonicity and order assure the analysis to haltin finite time. This is a well-known technique, mainly used in compilers, foroptimization tasks, and in debugging. Every analysis, taking advantage of theabstract interpretation pattern for gathering information about the possibleset of values (of registers, variables, memory locations, etc.) calculated at agiven point in a program, belongs to the ”Data-flow” family of analyses. Inparticular, a data-flow analysis algorithm usually walks the control flow graph(CFG) of a program. The algorithm, at each program point (instruction, as-signment, basic block and so forth), applies data-flow equations (see Figure2.1) to the state (ordered set) associated to it. The analysis is repeated onevery node until the sets stabilize. As previously stated, the data-flow equa-tions must be monotonic and the sets must carry an order relation, at leastpartial. If the latter conditions hold, the repetition of the analysis will reachthe so called fixpoint and the algorithm can stop. 15
  15. 15. CHAPTER 2. Static Analysis2.2 Pointer AnalysisWe will now focus on data-flow analysis of pointer values, precisely namedPointer Analysis. As one may immediately notice, Pointer Analysis fits ourneed of tracking values assigned to pointers to later check for use-after-freeconditions. There are several dimensions that a↵ect cost/precision trade-o↵sof pointer analysis. How a pointer analysis addresses each of these dimensionshelps to categorize the analysis. The dimensions we are now going to considerare: • Scope: a static analysis algorithm can either be engineered to perform the analysis within a single function only, or could o↵er the possibility to extend the analysis in order to cover multiple functions. The former goes under the name of intraprocedural pointer analysis, the latter is interprocedural anaysis; • Flow-sensitivity: a flow-sensitive analysis takes into account the order of statements in a program, and therefore it can compute a solution for each program point, whereas a flow-insensitive analysis computes a solution for either the whole program or for each procedure. The immediate consequence is a higher degree of precision for flow-sensitive approaches. On the other hand, flow-insensitivity shows much higher scalability in terms of both time and space, therefore proving to be a better choice for analyzing very large programs; 16
  16. 16. 2.2. Pointer Analysis• Context-sensitivity: in context-sensitive analyses the calling context of a function is considered. This means parameters passed on di↵erent calls of the same function can be distinguished and properly returned to the actual caller. Context-sensitivity o↵ers a higher degree of precision and, if properly implemented, mildly impacts speed;• Heap modeling: an accurate pointer analysis should rely on a representa- tion of the entire heap space. This is a non-trivial issue, and constitutes a static analysis branch by itself, going under the name of Shape Ana- lysis. Even though shape analysis is undergoing heavy research, it shows extremely limited scalability in analyzing real-life programs;• Aggregate modeling: a very important factor a↵ecting the precision of pointer analysis is how elements of aggregates are distinguished. An extremely precise modeling, in which every single object can be dis- tiguished, could be achieved by running a full-blown shape analysis. As just stated shape analysis is not a feasible solution for our purposes. A very fast and imprecise model could collapse the elements of the aggreg- ate into one object. This would introduce excessive noise in the analysis and would lead to a situation in which no heap object is discernible from another;• Alias representation: indicates whether alias pairs or points-to pairs are mantained during the analysis. Alias pairs represent alias relations ex- plicitely, whereas points-to data is a more compact representation. 17
  17. 17. CHAPTER 2. Static Analysis A lot of reasearch has been done on Pointer Analysis in the last twentyfiveyears. Nowadays moderate intraprocedural analyses are commonly implemen-ted in almost every compiler, whareas interprocedural algorithms are still inresearch stage. Figure 2.2 shows a summary of the interprocedural analyses Figure 2.2: Pointer analysesproposed so far. Each one having its own pros and cons, they all share a fewlimitations that make them not suitable for our purposes. Their major faultis the need for source code, which usually is, from an analyst point of view,practically impossible to retrieve. Moreover, they are often built to analyzesource code translated to a sub-language of the original language. The lat-ter limitation also a↵ects the few interprocedural analyses proposed to workat the assembly level, like the one by Naeem et al [3]. The need to reducea real assembly language, with hundreds of instructions, to a really narrowsub-language proves to be actually impossible in real scenarios. 18
  18. 18. 2.3. Conclusions and contributions2.3 Conclusions and contributionsIntraprocedural analysis, in terms of e ciency and scalability, is reliable enoughto be implemented with minor modifications apt to make it able to deal withmore expressive assembly languages. On the other hand, interprocedural ana-lysis at the assembly level is still in an alpha stage of development. Thereforewe propose a new tree-based context-sensitive interprocedural analysis target-ing assembly languages. 19
  19. 19. CHAPTER 2. Static Analysis 20
  20. 20. Chapter 3Preprocessing stage3.1 The REIL intermediate languageThe Reverse Engineering Intermediate Language (REIL) [6] is a platform-independent intermediate language which aims to simplify static code analysisalgorithms such as the gadget finding algorithm for return oriented program-ming presented in this paper. It allows to abstract various specific assemblylanguages to facilitate cross-platform analysis of disassembled binary code. REIL performs a simple one-to-many mapping of native CPU instructionsto sequences of simple atomic instructions. Memory access is explicit. Everyinstruction has exactly one e↵ect on the program state. This contrasts sharplyto native assembly instruction sets where the exact behaviour of instructionsis often influenced by CPU flags or other pre-conditions. All instructions use a three-operand format. For instructions where someof the three operands are not used, place-holder operands of a special type 21
  21. 21. CHAPTER 3. Preprocessing stagecalled " are used where necessary. Each of the 17 di↵erent REIL instructionhas exactly one mnemonic that specifies the e↵ects of an instruction on theprogram state.The REIL VMTo define the runtime semantics of the REIL language it is necessary to definea virtual machine (REIL VM) that defines how REIL instructions behave wheninteracting with memory or registers. The name of REIL registers follows the convention t-number, like t0, t1,t2. The actual size of these registers is specified upon use, and not defined apriori (In practice only register sizes between 1 byte and 16 bytes have beenused). Registers of the original CPU can be used interchangeably with REILregisters. The REIL VM uses a flat memory model without alignment constraints.The endianness of REIL memory accesses equals the endianness of memoryaccesses of the source platform.REIL instructionsREIL instructions can loosely be grouped into five di↵erent categories accord-ing to the type of the instruction (See Table 3.1). Arithmetic and bitwise instructions take two input operands and one outputoperand. Input operands either are integer literals or registers; the outputoperand is a register. None of the operands have any size restrictions. However,arithmetic and bitwise operations can impose a minimum output operand size 22
  22. 22. 3.1. The REIL intermediate languageArithmetic instructions OperationADD x1 , x2 , y y = x1 + x2SUB x1 , x2 , y y = x1 x2MUL x1 , x2 , y y = x1 · x2 j kDIV x1 , x2 , y y = x1x 2MOD x1 , x2 , y y = x1 mod x2 8 > > x · 2x2 < 1 if x2 0BSH x1 , x2 , y y = > j x1 k > : if x2 < 0 2 x2Bitwise instructions OperationAND x1 , x2 , y y = x1 &x2OR x1 , x2 , y y = x1 | x2XOR x1 , x2 , y y = x1 x2Logical instructions Operation 8 > > 1 if x = 0 < 1BISZ x1 , ", y y = > > 0 if x 6= 0 : 1JCC x1 , ", y transfer control flow to y i↵ x1 6= 0Data transfer instructions OperationLDM x1 , ", y y = mem[x1 ]STM x1 , ", y mem[y ] = x1STR x1 , ", y y = x1Other instructions OperationNOP ", ", " no operationUNDEF ", ", y undefined instructionUNKN ", ", " unknown instruction Figure 3.1: List of REIL instructions 23
  23. 23. CHAPTER 3. Preprocessing stageor a maximum output operand size relative to the sizes of the input operands. Note that certain native instructions such as FPU instructions and mul-timedia instruction set extensions cannot be translated to REIL code yet.Another limitation is that some instructions which are close to the underlyinghardware such as privileged instructions can not be translated to REIL; sim-ilarly exceptions are not handled. All of these cases require an explicit andaccurate modelling of the respective hardware features. An example of function, translated from x86 assembly alnguage to REIL isshown in Figure 3.2 Figure 3.2: REIL translation of a function 24
  24. 24. 3.2. Single Static Assignment (SSA) Form3.2 Single Static Assignment (SSA) Form3.2.1 Graph theory overviewThe algorithm for building SSA Form relies on the dominator tree and dom-inance frontiers in order to identify merge points.The following notions are required to understand the algorithms for SSA trans-lation and how bug detection works: • Dominance Relation: In a Control Flow Graph, a node D dominates a node N if every path from the start node to N must through D. Nota- tionally, this is equivalent to D dom N. By defition every node dominates itself; • Strict Dominance Relation: A node D strictly dominates a node N if D dominates N and D does not equal N; • Immediate Dominator : The immediate dominator or idom of a node N is the unique node that strictly dominates N but does not strictly dominate any other node that strictly dominates N. Not all nodes have immediate dominators; • Dominator Tree: The dominator tree of a graph is a tree where each node’s children are those nodes it immediately dominates; • Dominance Frontier : The dominance frontier of a node S is the set of all nodes N such that S dominates a predecessor of N but does not strictly 25
  25. 25. CHAPTER 3. Preprocessing stage dominates N; More intuitively, it is the set of nodes where N’s dominance stops; • Iterated Dominance Frontier : Formally, it is the irreflexive closure of the dominance frontier relation. It is actually calculated as follows: S Let DF (S) = x2s DF (x) be the dominance frontier of a set of nodes. The iterated dominance frontier is:3.2.2 Computing SSA FormSingle Static Assignment (SSA) Form is an intermediate representation of afunction graph that is very frequently used in compiler optimization. SSA formimposes a naming convention on the function variables such that each variablename corresponds to the value produced at a single definition point. Anotheradvantage of SSA Form is the ability to identify merge points inside a functionflow graph and mark them with so called -functions.In our prototype all the functions flow graphs inside a binary are translatedinto SSA Form before proceeding with the analysis. There exists three knowntypes of SSA Form translation based on the e ciency of the algorithm and onthe number of -functions present in the resulting graph. We chose to imple-ment a ”semi-pruned” SSA Form as a good trade-o↵ between precision andperformance. In order to reduce the number of -functions inside a flow graph the prunedSSA Form employs liveness analysis to determine which variables are still alive 26
  26. 26. 3.2. Single Static Assignment (SSA) Form Figure 3.3: Non-local variableat a given merge point. To improve performances instead of liveness analysisthe semi-pruned SSA form introduces the concept of non-locals. A non-localis a variable which has been used inside a basic block but it has been definedelsewhere, that is a variable that first appeared in a di↵erent basic block (seeFigure 3.3). It must be noticed that the concept of non-local is an under-approximation of a full blown liveness analysis, thus the semi-pruned form isstill subject to the presence of not strictly needed -functions. The algorithmproposed by Briggs et al[2] is the following: non-locals ; for each block B do killed ; for each instruction z x op y in B do if x 2 killed then / non-locals non-locals [{x} end if if y 2 killed then / non-locals non-locals [{y } 27
  27. 27. CHAPTER 3. Preprocessing stage end if killed killed [ {z} end for end for In our implementation the algorithm maintains three pre-computed datastructures: a list of addresses where to insert the -functions and two hashmapsto keep track respectively of all the previously created variables and of the nextvariable name to be assigned. The first data structure is created by calculatingthe iterated dominance frontier of every live variable in the flow graph. Therest of the algorithm works by recursively walking the dominator tree renamingvariables in the original graph so that when a new assignment or a -functionis found a variable with a new name is created and the results are propagatedto the children in the tree. The pseudo-code as adapted in our implementationis the following: for each variable v do Let A(v ) be the set of blocks containing assignment to v Place a -function for v in the iterated dominance frontier of A(v ) end for for each variable v do Counters[v ] 0 Stack[v ] ; end for Let start be the root node of the dominator tree, RENAME(start) RENAME(block): for each -function, v (...) in block do i Counters[v ] 28
  28. 28. 3.2. Single Static Assignment (SSA) Form Replace v with vi in the new graph Stack[v].push(i) Counters[v ] i +1end forfor each instruction, v x op y in block do i Stack[x].first(), c Stack[y ].first() Replace x with xi and y with yc in the new graph i Counters[v ] Replace v with vi in the new graph Stack[v].push(i) Counters[v ] i +1end forfor each successor s of block do j block variables position index, corresponding to the position of block in the parents array of s. This is just a convention for each -function p in s do v j th operand of p Replace v with vi where i Stack[x].first() end forend forfor each child c of block in the dominator tree do RENAME(c)end forfor each instruction v x op y ||v (...) in block do Stack[v ].pop()end for An example of REIL code translated in SSA Form is shown in Figure 3.4 29
  29. 29. CHAPTER 3. Preprocessing stage Figure 3.4: SSA Form of a REIL function 30
  30. 30. Chapter 4Analysis stage4.1 Pointer analysisIn our work we implemented both intraprocedural and interprocedural pointeranalysis in order to track objects aliases and thus being able to reason aboutpossible dangling pointer conditions. The intraprocedural analysis is performedon the top of MonoREIL[6], an abstract interpretation framework based onREIL. In the following two subsections we are going to briefly describe themain features of our analysis, and MonoREIL. Later on we will focus on theintraprocedural pointer analysis algorithm. In the third section the interpro-cedural algorithm will be explained.4.1.1 Analysis featuresDataflow analysis and abstract interpretation algorithms have a number ofproperties that characterize them. Among those the three most relevant are 31
  31. 31. CHAPTER 4. Analysis stageflow, context and path sensitivity. An algorithm is said to be path-sensitiveif it computes di↵erent piece of analysis information depending on predicatesat conditional branch instructions. The intraprocedural algorithm used in ourwork merges results of the analysis at the function merge-points, this e↵ect-ively results in a path-insensitive algorithm. In fact we are not able to discerncode-paths that lead to the presence of a given alias.Moreover our algorithm is flow-insensitive, in fact during the analysis we donot track code locations. That is, the analysis will not be able to say afterwhich statement a given variable became an alias of another one.The main problem deriving from the path and flow insensitivity of our al-gorithm is the increased number of false positives that can appear in our ana-lysis. In fact we are not able to gauge whether a specific path yielding to astale pointer condition is feasible. Nonetheless the performance gain obtainedby this implementation of the algorithm are significantly more beneficial thanthe increase in the number of false positives.Moreover a number of empirical studies [9] [10] [11] [12] have shown that theimprovement o↵ered by flow-sensitivity is minimal in terms of precision.Our interprocedural algorithm works by merging trees generated in each func-tion, therefore the flow-insensitivity of the intraprocedural analysis and thenature of the merging we perform make it both flow and path insensitive. Thesame considerations done for the intraprocedural analysis on performance gainand precision loss apply to the interprocedural part of our analysis.The algorithm performs the analysis on the procedural call graph (PCG) of the 32
  32. 32. 4.1. Pointer analysisbinary. The PCG allows to discern function parameters and calling locations,that is every edge in the PCG is marked with the parameters passed to a givenfunction. This property guarantees that our analysis is context-sensitive.Context-sensitivity is crucial, in fact the ability to discern function parametersof each call prevents ambiguity and imprecision in tracking aliases betweenfunctions.Another problem of pointer analysis is dealing with data structures which canmake it di cult to track aliases. In order to deal with this nuance we resortedto two strategies.The first one consists of tracking the size of objects whenever possible, that iswhen there is no need to perform range analysis, this way we are able to recog-nize whether a given heap location belongs to a specific object and thereforewe are able to properly track aliases for it.The second strategy is to model widely used data structures such as linkedlists, vectors and other similar ones in order to be able to track objects storedin them. It must be noticed that not all data structures are covered, thereforesome aliases may be missed by our analysis.The two latter strategies allow us to completely avoid heap modeling thusgreatly simplifying the analysis.4.1.2 MonoREILMonoREIL is an abstract interpretation framework that performs fixed-pointiteration until a final state is reached. MonoREIL operates on the control flow 33
  33. 33. CHAPTER 4. Analysis stagegraph of a function that can be walked arbitrary depending on the analysisthat is intended to be performed. The definitions of a lattice, its elements anda formula that can combine the elements are necessary for the framework towork. Every analysis is supposed to start with an initial state that can bearbitrary. Finally the e↵ects of REIL instructions on the lattice need to bemodelled.To guarantee the termination of the analysis the lattice has to satisfy theascending chain condition, that is the lattice has to be a noetherian lattice. Infact if the condition is violated it is not possible to guarantee that there existstwo states in the analysis, p n 1 and p n , such that p n 1 = pn .In the following section we show that our analysis satisfies the requirement andtherefore is always guaranteed to terminate.4.2 Intraprocedural analysisAlias set analysis is a well-known variation of pointer analysis which grants anhigher degree of precision and at the same time avoids performance bottlenecks.Intuitively an alias set is the set of all local pointer variables that point to agiven object. The strength of the analysis lies in the fact that whenever thereis some degree of uncertainty about whether a given variable x points to aconcrete object, instead of creating a may or must-point-to set, it creates twoalias sets, only one of which contains x.Our analysis computes the alias sets for each function in the binary so that theycan be later combined in order to reason on the existence of dangling pointers 34
  34. 34. 4.2. Intraprocedural analysisby propagating alive aliases between functions in the binary call graph.We have adapted the algorithm proposed in [3] to fit our purposes and scope.It can be proved that our analysis reaches the fixed point because our transferfunctions are distributive. In fact the fixed point computed for the alias setdataflow algorithm corresponds to the merge-over-all-paths dataflow value ofour algorithm [7].In order to analyze the functions we have to further simplify our intermediatelanguage so that it can be expressed by the means of a very simple grammar: s ::= v1 v2 |v h|h v |v null|v newWhere h represents any heap location, null represents a null pointer and newrepresents a newly created object.To simplify REIL code so that it can be expressed with the above grammar wecreated transformation functions for every REIL instruction in our MonoREILalgorithm. Table 4.1 shows the appropriate transformations we apply to REILinstructions. It must be noticed that we consider an object to be newly created onlywhen it is the return value of either a constructor or an allocation functions.Both constructors and allocation functions although partially recognized in anautomated fashion by our software need to be manually indicated by the user.We treat code blocks di↵erently depending on whether we are dealing with asimple assignment or a -function. In the former case we first merge all theinfluencing states, performing an union on the sets, for a given node and thenwe apply the equations shown in Figure 4.2 . 35
  35. 35. CHAPTER 4. Analysis stage We create a new alias set for every newly allocated object, we then store inthe appropriate alias set all the variables that alias one of the objects aliasesand finally whenever an heap location, not previously known, is found we createtwo alias sets one with the location and another one without it.In the latter case instead we can easily assume that -functions are to be foundat merge points in the control flow graph of the function, that is when we needto combine one or more incoming states in our lattice. In our analysis thelattice is the set of all alias sets. Figure 4.3 shows the combine function formerge points. Each state is first pruned, that is we remove all the aliases that do notexist in the set of variables of the node strict dominators. Once the alias sethas been pruned it is then updated by adding all destination variables whosevalues are being assigned from variables already in the alias set.We defined the elements of the lattice so that each element is a set of linkedlists. To each lattice element corresponds an object to which the variables inthe linked list alias to.The reason for choosing set of linked lists over other data structure is theperformance gain. In fact it can be proved that the analysis carried on an SSAform graph allows to perform operations only on the head of the list thus savinglook-up time. Nonetheless for further optimization, when the analysis for agiven function is complete we transform each alias set in a tree-like structurewhich makes it easier to perform the interprocedural analysis we will discussin the following section. 36
  36. 36. 4.3. Interprocedural analysisA sample run of the algorithm can be seen in Figure 4.44.3 Interprocedural analysisAt the end of the intraprocedural alias analysis, the resulting alias lists of eachfunction are used to construct points-to tree structures that make the aliasrelationships between variables explicit. In such a points-to tree, each noderepresents a distinct variable and its children the variables pointing to it, sothat siblings are equivalent aliases.For each function we extract its parameters and its return. Given that in-formation, the interprocedural analysis algorithm performs a walkdown on theprocedure call graph, updating a set of points-to trees for the object that needsto be tracked, until the final state of the analysis is reached. We propose animplementation of our algorithm on the top of BinNavi.The interprocedural analysis, as opposed to the intraprocedural one, is run ona Procedure Call Graph (PCG). A PCG is identical to a call graph, with theexception that it has an edge for each call site, and every edge is labelled withthe variables of the source node that act like parameters in the target node(see Figure 4.5). At each iteration, the algorithm properly connects the points-to trees con-taining the incoming parameters to the points-to trees of the previous iterationon the graph. These are updated by connecting the trees containing the formalparameters of the current function to the nodes corresponding to the incoming 37
  37. 37. CHAPTER 4. Analysis stageparameters, as indicated in the edge label. If the node corresponding to theformal parameter is the root node of a points-to tree, than the tree is appen-ded to the node corresponding to the incoming parameter and the result isadded to the newly generated set of trees. Alternatively, if it is not a rootnode, the points-to tree containing it is added to the new state set, with thenode replaced by the incoming parameter. The points-to tree containing theincoming parameter is also copied into the new set of trees and the sub-treewhose root node is the node containing the incoming parameter is detachedfrom that copy of the tree (see Figures 4.6, Figure 4.7, Figure 4.8 and Figure4.9). When a merge point is met, the resulting sets of trees are computedseparately, one for each incoming edge, and a set union is performed, so thatduplicate trees are removed. Moreover, sub-trees of trees contained in the setare also removed. Additional trees can be removed from the set in order tofurtherly reduce space requirements (see Figure 4.10). Considering that the algorithm will not walk more than once on a node not in-volved in a cycle, it is safe to remove trees not containing aliases to the trackedobject from the set of points-to trees of that node. Similarly, the points-totrees of the previous step are extended with the points-to trees containing thereturned variable. Once the fixed point iteration has been reached, the resulting set of treesis an interprocedural set of points-to trees containing all of the aliases of theobject you want to track. 38
  38. 38. 4.4. C++ peculiarities4.4 C++ peculiaritiesIn dealing with C++ we had to take into account a certain numbers of charac-teristics linked to the language. One of the problems of complex applicationswritten in C++ is often the use of smart pointers interfaces. That is, C++classes used for providing memory safety in terms of objects lifetime. In or-der to deal with smart pointers we require the user to specify which functionsshall be considered the constructor and destructor of the object intended tobe analyzed and whether there are multiple constructors or destructors for anobject. In order to improve the precision of our analysis we used well-knowntechniques explained in [8] to identify constructors and destructors of objectsin the binary whenever it is possible. These requirements are necessary to keepthe analysis as application independent as possible without constraining ourwork to one specific kind of smart pointers or memory management architec-ture.User interaction is also needed to handle custom allocators, that is the useris asked to specify whether or not the allocation and deallocation functionsidentified by our tool are the correct ones. 39
  39. 39. CHAPTER 4. Analysis stage Arithmetic instructions Operation ADD x1 , x2 , y y is added to the alias set of x1 + x2 SUB x1 , x2 , y y is added to the alias set of x1 x2 MUL x1 , x2 , y y is added to the alias set of x1 · x2 j k DIV x1 , x2 , y y is added to the alias set of x1x2 MOD x1 , x2 , y y is added to the alias set of x1 mod x2 8 > > x · 2x2 < 1 if x2 0 BSH x1 , x2 , y y is added to the alias set of > ⌅ x1 ⇧ > : 2x2 if x2 < 0 Bitwise instructions Operation AND x1 , x2 , y y is added to the alias set of x1 &x2 OR x1 , x2 , y y is added to the alias set of x1 | x2 XOR x1 , x2 , y y is added to the alias set of x1 x2 Logical instructions Operation BISZ x1 , ", y y is removed from all alias sets JCC x1 , ", y does not a↵ect alias sets Data transfer instructions Operation LDM x1 , ", y y is added to the alias set of mem[x1 ] STM x1 , ", y mem[y ] is added to the alias set of x1 STR x1 , ", y y is added to the alias set of x1 Other instructions Operation NOP ", ", " does not a↵ect alias sets UNDEF ", ", y y is removed from all alias sets UNKN ", ", " does not a↵ect alias sets Figure 4.1: REIL Instructions transformations 40
  40. 40. 4.4. C++ peculiarities 8 > > {{v }} if s = v < new [[s]]gen , > > ; : otherwise 8 > > {a [ {v }} if s = v > > 1 1 v2 ^ v2 2 a > > < [[s]]a (a) , > {a, a [ {v }} if s = v > h > > > > {a} : otherwise [ [[s]]l (l) , [[s]]gen [ [[s]]a (a) a2l Figure 4.2: Transfer functions for common instructions[[ ]]a (a, pred) , {(a vars(sdom( ))) [ (yi : yi xi 2 livevars( , pred) ^ x1 2 a)} [[[ ]]l (l, pred) , [[s]]a (a, pred) a2l Figure 4.3: Transfer functions for -nodes instructions Figure 4.4: Intraprocedural analysis example 41
  41. 41. CHAPTER 4. Analysis stage Figure 4.5: PCG used in our algorithm Figure 4.6: Computing f1() to f2() alias trees 42
  42. 42. 4.4. C++ peculiarities Figure 4.7: Computing f1() to f3() alias treesFigure 4.8: Computing f2() to f4() alias trees through the leftmost edgeFigure 4.9: Computing f2() to f4() alias trees through the rightmost edge 43
  43. 43. CHAPTER 4. Analysis stage Figure 4.10: E↵ects of combine() on functions alias trees 44
  44. 44. Chapter 5Stale pointers detectionDetecting use after free conditions means to verify whether there are any codepaths in which an object alias is used after the object itself was freed. In orderto reason on this condition we first prune the control flow graph (CFG, seeFigure 5.1) of the binary, so that only functions that use aliases of the objectwe are interested in or are linked to a function using an alias are preserved.This can be trivially done by walking the call flow graph and eliminating allthe functions that are neither successors nor predecessors of the procedureswhen an object alias appears (see Figure 5.2 and Figure 5.3). Figure 5.1: Example of callgraph 45
  45. 45. CHAPTER 5. Stale pointers detection Figure 5.2: Callgraph with relevant functions in red Figure 5.3: Pruned callgraph Finally we simply mark calls to the destructors on the pruned callgraph(see Figure 5.4). The rest of the algorithm walks cross references to the object destructorsbackwards, that is it computes all functions that at some program point inval-idate the concrete object. We call them functions aliases. Figure 5.4: Pruned callgraph ready for bug detection step 46
  46. 46. For each function that calls a destructor alias we verify whether the concreteobject itself or one of its aliases are used. To do so we build the dominatortree of the function flow graph and verify the conditions shown in Figure 5.5.We assume the following notation: B is a generic basic block, F is the basicblock that either calls the destructor or destroys the concrete object, v is anobject alias. dom(B) denotes the basic blocks dominated by node B. v 2 Bis the relation that represents the use of variable v in basic block B. Finallysucc(B) are successors of node B. Type of warning Condition v is a stale pointer if v 2 B ^ B 2 dom(F ) v may be a stale pointer if v 2 B ^ B 2 dom(F ) ^ B 2 succ(F ) / v might be a memory leak if v 2 B ^ F 2 dom(B) ^ F 2 succ(B) / v is a memory leak if v 2 B ^ F 2 dom(B) ^ F 2 succ(B) / / v is neither a stale pointer nor a memory leak otherwise Figure 5.5: Alias verification equations 47
  47. 47. CHAPTER 5. Stale pointers detection 48
  48. 48. Chapter 6Results and future workIn this paper we have targeted a widely known cause of security flaws. Wehave shown that it is feasible to collect enough data in terms of alias sets on aC++ binary to discover stale pointers bugs at interprocedural level.We have implemented our work on the top of BinNavi using REIL as theintermediate language and MonoREIL as the monotone solver framework forour algorithms. Our approach of only verifying one type of object per timeallowed us to drastically reduce the execution time and the number of falsepositives to analyze, nonetheless we do realize that this approach is suboptimalfor scenarios in which a developer has to fix bugs in his software because inthat case it would be necessary to run the analysis multiple times.From our test results, on a set of samples we built, it is clear that the primecause of false positives is the lack of flow-sensitiveness of our analysis. Oneof the primary goal of future work in this direction is to use a SMT solver inorder to verify path feasibility. 49
  49. 49. CHAPTER 6. Results and future workThe principal source of false negatives in our analysis is the heavy presenceof function pointers in C++ code and complex data structures, in those caseswe were not able to obtain enough information either on the alias sets oron the relationships between functions. Some techniques exist to deal withthese problems, we did not implement them because the results are far fromsatisfying and could dramatically increase the number of false positives in ouranalysis.Finally we plan on augmenting our analysis by increasing the number of datastructures handled by our algorithm and by doing range analysis in order totrace an higher number of aliases. 50
  50. 50. Bibliography[1] Ron Cytron, Jeanne Ferrante, Barry K. Rosen, Mark N. Wegman, and F. Kenneth Zadeck: ”E ciently computing static single assignment form and the control dependence graph.” ACM Transactions on Programming Languages and Systems, 13(4):451-490, Oct 1991[2] Preston Briggs, Keith D. Cooper, Timothy J. Harvey, and L. Taylor Simpson: ”Practical improvements to the construction and destruction of static single assignment form.” Software-Practice and Experience, 28(8):859-881, Jul 1998.[3] Nomair A. Naeem, and Ondrej Lhotak: ”E cient Alias Set Analysis Using SSA Form.”International Symposium on Memory Management - ISMM , pp. 79-88, 2009[4] Xiaodong Ma, Ji Wang, and Wei Dong: ”Computing Must and May Alias to Detect Null Pointer Dereference.”Leveraging Applications of Formal Methods - ISOLA , pp. 252-261, 2008[5] Sean Heelan: ”Finding use-after-free bugs with static analysis” 51
  51. 51. BIBLIOGRAPHY [6] Thomas Dullien, and Sebastian Porst: ”REIL: A platform-independent intermediate representation of disassembled code for static code analysis.” CanSecWest 2009 [7] J. B. Kam and J. D. Ullman: ”Monotone data flow analysis frameworks.” Acta Inf., 1977. [8] Paul Vincent Sabanal, and Mark Vincent Yason: ”Reversing C++.” Black Hat DC 2007 [9] Michael Hind: ”Pointer Analysis: Haven’t We Solved The Problem Yet?” ACM Transactions on Programming Languages and Systems, June 2001[10] M. Hind, M. Burke, P. Carini and J.-D. Choi: ”Interprocedural pointer alias analysis” ACM Transactions on Programming Languages and Sys- tems, Apr. 1993[11] M. Hind and A. Pioli: ”Which Pointer Analysis Should I Use?” Interna- tional Symposium on Software Testing and Analysis, Aug. 2000[12] M. Hind and A. Pioli: ”Evaluating The E↵ectiveness of Pointer Alias Analysis”, Science of Computer Programming, Jan. 2001 52

×