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 ﬁnd 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.
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
8.
Chapter 1IntroductionIn the era of cloud computing and internet-connected devices, attacks to suchdevices are becoming increasingly proﬁtable. 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, ﬁrst 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.
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 ﬂaws, 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, ﬁnding them andother memory corruption bugs is arguably more di cult than overﬂows 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 ﬂaws. In particular, an analyst canactually face the problem of ﬁnding dangling pointers with dynamic analysistechniques, such as fuzzing, or by the means of static analysis. Even though 10
10.
fuzzing has been proved to be an e↵ective method for ﬁnding 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 inﬁnite, and totally unrelated to the code coverage theanalysis reaches over time. We propose a practical approach to automaticallyﬁnd use-after-free conditions in large binary code bases using static analysisand graph theory. 11
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 speciﬁc 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 ﬁnite 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.
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 ﬁnite time. In fact the term ”staticanalysis” is indeed over-broad. It includes various implementation techniquesthat make static analysis feasible. Model checking was the ﬁrst technique toappear in chronological order. It arose in an attempt to solve the so calledConcurrent Program Veriﬁcation problem. A model checker checks for thecorrectness of a formula expressed in Temporal Logic, being able to e↵ectivelyuncover hard-to-ﬁnd 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 speciﬁed 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.
2.1. General knowledge outb = transb (inb ) inb = joinp2predb (outb ) Figure 2.1: Data-ﬂow 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 ﬁnite 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-ﬂow” family of analyses. Inparticular, a data-ﬂow analysis algorithm usually walks the control ﬂow graph(CFG) of a program. The algorithm, at each program point (instruction, as-signment, basic block and so forth), applies data-ﬂow 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-ﬂow 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 ﬁxpoint and the algorithm can stop. 15
15.
CHAPTER 2. Static Analysis2.2 Pointer AnalysisWe will now focus on data-ﬂow analysis of pointer values, precisely namedPointer Analysis. As one may immediately notice, Pointer Analysis ﬁts 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 ﬂow-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 ﬂow-insensitive analysis computes a solution for either the whole program or for each procedure. The immediate consequence is a higher degree of precision for ﬂow-sensitive approaches. On the other hand, ﬂow-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.
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.
CHAPTER 2. Static Analysis A lot of reasearch has been done on Pointer Analysis in the last twentyﬁveyears. 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.
2.3. Conclusions and contributions2.3 Conclusions and contributionsIntraprocedural analysis, in terms of e ciency and scalability, is reliable enoughto be implemented with minor modiﬁcations 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
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 ﬁnding algorithm for return oriented program-ming presented in this paper. It allows to abstract various speciﬁc 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 inﬂuenced by CPU ﬂags 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.
CHAPTER 3. Preprocessing stagecalled " are used where necessary. Each of the 17 di↵erent REIL instructionhas exactly one mnemonic that speciﬁes the e↵ects of an instruction on theprogram state.The REIL VMTo deﬁne the runtime semantics of the REIL language it is necessary to deﬁnea virtual machine (REIL VM) that deﬁnes 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 speciﬁed upon use, and not deﬁned 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 ﬂat 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 ﬁve 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.
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 ﬂow 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 undeﬁned instructionUNKN ", ", " unknown instruction Figure 3.1: List of REIL instructions 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.
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 deﬁtion 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.
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 irreﬂexive 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 deﬁnition point. Anotheradvantage of SSA Form is the ability to identify merge points inside a functionﬂow graph and mark them with so called -functions.In our prototype all the functions ﬂow 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 ﬂow graph the prunedSSA Form employs liveness analysis to determine which variables are still alive 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 deﬁnedelsewhere, that is a variable that ﬁrst 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.
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 ﬁrst data structure is created by calculatingthe iterated dominance frontier of every live variable in the ﬂow 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.
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].ﬁrst(), c Stack[y ].ﬁrst() 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].ﬁrst() 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.
CHAPTER 3. Preprocessing stage Figure 3.4: SSA Form of a REIL function 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 brieﬂy 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 featuresDataﬂow analysis and abstract interpretation algorithms have a number ofproperties that characterize them. Among those the three most relevant are 31
31.
CHAPTER 4. Analysis stageﬂow, 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 ﬂow-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 ﬂow 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 speciﬁc path yielding to astale pointer condition is feasible. Nonetheless the performance gain obtainedby this implementation of the algorithm are signiﬁcantly more beneﬁcial 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 ﬂow-sensitivity is minimal in terms of precision.Our interprocedural algorithm works by merging trees generated in each func-tion, therefore the ﬂow-insensitivity of the intraprocedural analysis and thenature of the merging we perform make it both ﬂow 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.
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 ﬁrst 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 speciﬁc 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 ﬁxed-pointiteration until a ﬁnal state is reached. MonoREIL operates on the control ﬂow 33
33.
CHAPTER 4. Analysis stagegraph of a function that can be walked arbitrary depending on the analysisthat is intended to be performed. The deﬁnitions 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 satisﬁes 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.
4.2. Intraprocedural analysisby propagating alive aliases between functions in the binary call graph.We have adapted the algorithm proposed in [3] to ﬁt our purposes and scope.It can be proved that our analysis reaches the ﬁxed point because our transferfunctions are distributive. In fact the ﬁxed point computed for the alias setdataﬂow algorithm corresponds to the merge-over-all-paths dataﬂow 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 ﬁrst merge all theinﬂuencing states, performing an union on the sets, for a given node and thenwe apply the equations shown in Figure 4.2 . 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 ﬁnally 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 ﬂow 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 ﬁrst 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 deﬁned 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.
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 ﬁnal 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.
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 ﬁxed 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.
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 speciﬁc 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 functionsidentiﬁed by our tool are the correct ones. 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.
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.
CHAPTER 4. Analysis stage Figure 4.5: PCG used in our algorithm Figure 4.6: Computing f1() to f2() alias trees 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.
CHAPTER 4. Analysis stage Figure 4.10: E↵ects of combine() on functions alias trees 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 ﬁrst prune the control ﬂow 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 ﬂow 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.
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.
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 ﬂow 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 veriﬁcation equations 47
48.
Chapter 6Results and future workIn this paper we have targeted a widely known cause of security ﬂaws. 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 ﬁx 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 ﬂow-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.
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.
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.
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 ﬂow 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
Clipping is a handy way to collect and organize the most important slides from a presentation. You can keep your great finds in clipboards organized around topics.
Be the first to comment