Memery: Analyzing Memory for Fun and Profit

Nathan Contreras, Mridu Nanda, Noah Singer Harvard CS 263

Abstract— Memory reading vulnerabilities allow at- are limited by the necessity of the source binary tackers to siphon sensitive data from a remote victim for the process to analyze. process, but time constraints and throughput limitations Given the prevalence of high-value information may necessitate a method to efficiently identify memory stored in memory, other previous work have fo- of interest. Toward this end, we design MEMERY, a black- box heap analysis algorithm that extracts information cused on creating exploits focused on siphoning about the high-level program constructs of an executable that data from the victim. For instance, Mem- with a memory read vulnerability. MEMERY follows ory Cartography attacks [1] work by building chains of pointers to reliably detect both singly- and an ASLR-resistant relative map of variables in doubly-linked data (and any looping within the memory of a browser renderer process. First, them) and offers insights about the types of information the attacker analyzes the data section of their stored in the data structures (including pointers to functions and character strings). Besides singly- and own browser process which has loaded a web- doubly-linked data structures, MEMERY’s analysis can site that the victim will visit in the future. The be used as a foundation for detecting many other chained attacker classifies pointer-sized region of memory data structures. as pointer/non-pointer by checking each region to see if it points to memory allocated by the renderer. I.INTRODUCTION The attacker then creates a refined map by repeat- edly rebooting the machine, building a new map, Analyzing the memory of a program is an area and removing pointers that do not appear in every of interest to the security community because un- map. This reduces false positives (i.e. randomly derstanding the data structures it uses can yield in- valued pointer sized regions that happened to point formation about its vulnerabilities. Function point- to allocated memory during an insignificant num- ers, for example, are high-value targets to attackers ber of iterations of map-building). After a victim given that their intentional corruption allows the visits an attacker webpage, an attacker-created attacker to gain control of the execution of a Javascript program chases pointers to a C++ vtable victim process. Moreover, reverse-engineering the in the map created offline, revealing the absolute data structures of a program might aid an attacker address of one item in the relative map and thus in quickly determining the location of sensitive elucidating the absolute addresses of everything information amongst vast amounts of memory in in the relative map. With this information, the the victim process’s address space. attacker Javascript can find a desired location in the Prior work in the reverse engineering memory victim browser’s memory, such as cookies or other access/allocation has focused on analyzing pro- sensitive data. Memory Cartography’s mapping gram binaries to understand the data structures in approach inspires some of the core ideas behind use. For instance, REWARDS [2] monitors the pro- MEMERY. gram’s calls to well-known functions (with known signatures of parameter variable types) to deter- II.MEMERY mine the types of different variables used by the Our goal is the black-box analysis of heap program. Furthermore, HOWARD [3] uses dynamic memory; that is, we seek to extract information analysis of a program’s execution and memory about high-level program constructs (in particular, access patterns to determine the data structures linked data structures and function pointers) from used by a program. Unfortunately, such approaches heap memory without analyzing the behavior of assembly instructions in the code segment. We also III.DESIGN avoid using standard debugging instrumentation, A. Definitions such as GDB, since there are established anti- We review some definitions about data represen- reverse-engineering measures to detect debugging tations in memory: instrumentation and modify program behavior ac- • Linked data cordingly [4]. To this end, we introduce MEMERY, : A localized pattern a tool for online heap analysis, with the goal of of memory usage (i.e. a C struct), where contributing meaningfully to the reverse engineer- specific instances (“nodes”) of the pattern ing of arbitrary black-box programs. Section III are linked to each other by pointers. Linked describes the design of MEMERY. data structures store relational information; for instance, they include lists, which store MEMERY’s threat model makes several assump- sequential information, and trees, which store tions: hierarchical information. • A read vulnerability that allows for access to • Singly vs. multiply linked data structures: arbitrary regions of memory, without causing A is singly linked if each segmentation faults. node contains one pointer to another node; • A way to leak a single absolute address of an it is doubly linked if each node contains two object on the heap. pointers to other nodes. A prototypical singly linked data structure is the linear linked list1, Additionally, MEMERY relies on various tunable where each node only stores a pointer to the parameters that govern, for instance, the size of the next node; a prototypical doubly linked data heap. SectionIV details how we simulated these structure is the binary , where each parent assumptions on toy victim programs; one major stores pointers to two of its children. Data area of future work is relaxing all of these these structures can also be triply linked, etc. assumptions (SectionV). M EMERY’s threat model • Linear vs. circular data structures:A linear allows for non-execute (NX) bits and address space data structure is one that has a defined “end” layout randomization (ASLR). node, in which following pointers always ter- The primary focus of MEMERY is to identify minates. In the circular data structure, every structured information in (or referenced in) heap node is linked to another node. memory. In particular, MEMERY is able to discover • Function pointer: A pointer to an in-program and distinguish various structures (such as various function (used extensively in representing subtypes of linked lists and binary trees). It also object-oriented constructs). identifies values that appear to be character strings We will also define lower-level memory pat- or function pointers. terns. Let M[i] denote the value of memory at One recurring theme of this paper will be that an address i. Then we can identify the following each of these identification problems is inherently objects: poorly-defined, since the goal is to discover ab- • Chain: A series of pointers p1, p2, p3,... such stract and “semantic” patterns in memory usage that M[p1 + k] = p2, M[p2 + k] = p3, etc., with access only to raw memory values, and such where k is a small, positive value called the patterns (such as “list” or “tree”) can be mapped chain offset. onto actual memory schemes in a massive number • Loop: A chain whose first and last elements of ways. A consequence of this is that it is in gen- are equal. eral simple to construct programmatic constructs See Figure1 for an illustration of pointers that exhibit desired behavior (such as “”) and chains for several common linked data struc- EMERY that are not flagged by M ’s heuristics, or tures. In these terms, primary goal of MEMERY to conversely build programs that do not exhibit is to bridge the gap between low-level con- a desired behavior but are indeed indicated by structs (chains and loops) and high-level constructs MEMERY. This will be further discussed in Section V. 1Linked lists can also be doubly linked (“bidirectional”). configuration.2 We also assume that all linked data 1 2 3 4 structures are allocated on the heap, and that the heap is a contiguous, accessible region of memory. (a) A singly-linked, circular list with four nodes. The red arrows are pointers from one node to another. There is a single C. Singly-Linked Data Structure Algorithm chain of length four, and it is a loop. 1 2 3 4 Address Value Heap Pointer? 0 100 (b) A doubly-linked, linear list with four nodes. The red arrows 1 “Hello” point from one node to the next node, and the blue arrows point 2 3  from one node to the previous node. There are two chains of 3 101 length four (formed by the red pointers and the blue pointers), 4 “World” and neither is a loop. 5 6  6 102 1 7 “!” 8 9  2 3 TABLE I: A toy memory representation of the 4 5 6 7 beginning of a singly-linked linear list. Each node (c) A binary tree. The red arrows point from a node to its left in the list stores a number and a string (e.g. (101, child, and the blue arrows point from a node to its right child. “World”), as well as a pointer to the start of There are four chains; two corresponding to each color (e.g. the next node. When attempting to find a chain (1,2,4) and (3,6) for red). in this region, guessing an offset of k = 1 for Fig. 1: Three linked data structures, with descrip- the entry p1 = M[2] = 3 at address 2 will tions of the corresponding chains. fail, since M[p1 + k] = “World” is not a heap pointer. Conversely, guessing an offset of k = 2 will succeed, since p2 = M[p1 + k] = 6 and p = M[p + k] = 9 are indeed heap pointers. (linked lists, trees, etc.) It can thus roughly be 3 2 divided into two steps: The first step in MEMERY is to heuristically 1) Discover chains. detect singly-linked data structures, which are in- 2) Deduce information about the presence of terpreted as collections of overlapping chains (i.e. structures. chains that contain common nodes). For instance, Note, of course, that chains can form arbitrary a reverse binary tree (i.e. every node contains graphs on the space of memory locations. Thus, a pointer to its parent) is a singly-linked data we will consider the following two above problems structure that consists of many individual chains. “separately”, in the sense that we will attempt to Before singly-linked data structures can be de- find chains in arbitrarily complex node networks, tected, MEMERY must heuristically guess the start- and then we will make heuristic guesses about ing and ending addresses of the heap. In our what the chains might represent. These guesses implementation, MEMERY has access to the ab- will involve invariants, which are properties of solute address v of some object on the heap, as collections of chains that “should be expressed” by well as an estimate H for the size of the heap. a particular type of data structure (such as various MEMERY heuristically treats all values in the range 3 forms of circularity). [v − H, v + H] as heap pointers and seeks to

2This assumption can be relaxed — MEMERY’s algorithm would have to change to track potential data structures aligned at single- B. Specification byte boundaries. Extra care would have to be taken to ensure that detected pointers wouldn’t overlap. We assume the victim process is running on a 3Assuming that H is large enough that the true heap falls into 64-bit machine, with addresses and data structure this range, there are no false negatives for detecting heap pointers; false positives are possible (either if H is too large, or if there fields aligned on eight-byte boundaries, although is an integer that looks like a heap pointer but doesn’t point to MEMERY is not limited to this specific machine meaningful data). construct chains out of such pointers. In particular, classifying pointers as function pointers with MEMERY iterates through all the potential pointers high certainty. To do so, we leverage the func- labeled above. On a given heap pointer p1, it tion calling-conventions of the victim’s operat- guesses possible values for a chain offset k (from ing system/micro-architecture. Given a value p 1 up to some fixed maximum offset). For each which we wish to classify as function-pointer/non- combination of p1 and k: function-pointer, and our previously established 1) Traverse the heap by setting pi+1 := M[pi + ability to query for 8 bytes of data stored at any 8- k] until one of the following conditions is byte-aligned address in the victim process’s virtual satisfied: memory, we proceed as follows: • A loop is detected: We encounter a 1) Given the victim’s suspected operating pointer pi that was previously encoun- systems/micro-architecture, we define the tered in this same heap traversal. function preamble as P re. For example, on • We arrive at a pre-existing chain: We Ubuntu Linux 5.0.0-37 x86-64, the first three encounter a pointer pi that has been assembly operations in a function preamble marked as belonging to an existing chain are PUSH MOV SUB .... In this case, Cold. P re = PUSH MOV SUB. We also define • The chain ends: We encounter a value SzP re as the size of this function preamble pi that is not within the range of poten- pre in bytes. tial heap pointers. (Typically, pi will be 2) We query for szpre bytes starting at p in the null.) victim’s machine. Assume we store these in 2) If the traversal continues past some fixed an array CandidateP trBytes. minimum depth, we conclude that we have 3) We use a disassembler to output the se- found a chain. We assign the traversed nodes quence of assembly operations interpreted to a chain; if we encountered a pre-existing from CandidateP trBytes, storing this se- chain Cold, we assign the nodes to that chain, quence as CandidateP re. or otherwise allocate a new chain and assign 4) If P re is a prefix of CandidateP re, we the nodes to that. know with high certainty that p points to a See TableI for a graphical representation of this region of memory whose first bytes decode process. as a valid function preamble in the victim’s machine, and p is a function pointer. If D. Classifying Node Contents not, then the region is highly unlikely to Once we determine the singly-linked data struc- correspond to valid function code, and p is tures inside the heap, we want a way to classify the not a function pointer. non-pointer contents of the data structure’s nodes. 2) Detecting String Pointers: We also wish In particular, if the pointer in a data structure to scope out pointers to character strings in the occurs at offset k, we are interested in the infor- victim’s address space, given that strings often mation at positions [0, k) in the data structure.4 contain sensitive information. Suppose the attacker MEMERY classifies the elements in the data is interested in certain types of strings that include FUNC structures as either (corresponding to func- characters in the alphabet Σ and are at least szstr tion pointers), STR (corresponding to strings), or in length. For example, an attacker hoping to glean INT (the default type of each element). The fol- strings representing phone numbers would be inter- lowing subsections address these classifications. ested in Σ = {000,0 10,0 20,0 30,0 40,0 50,0 60,0 70,0 80,0 90} 1) Detecting Function Pointers: Given our in- with szstr = 10 (given that phone numbers are terest in scoping out function pointers in the usually 10 digits). Given a p which we wish to victim’s address space, we create a method for classify as a string pointer/not string pointer and the previously established method to query for 8 4We might also be interested in information that occurs after the pointer field; unfortunately, it is difficult to distinguish such bytes of data stored at any 8-byte-aligned address information from other objects on the heap. on the victim’s machine, we proceed as follows: • We query for szstr bytes (assuming each data structure with 2n − 1 nodes, which overlap character is 1 byte) starting at p in the victim’s in pairs (see TableII). Although this ambiguity machine. Assume we store these in an array is unavoidable, we implement several additional CandidateStrBytes. heuristics in MEMERY’s chain-finding algorithm • We check if all szstr characters are in Chars. to encourage it to correct identify multiply-linked If so, we know with high certainty that p structures. In particular, when searching from a points to a region of memory whose first bytes given pointer p with purported offset k, we verify decode as a character string of interest on the two additional conditions: victim’s machine, and p is a string pointer. 1) If we identified p as a memeber of a pre- If not, then the region is highly unlikely to existing chain Cold, we check that no pointer correspond to a valid string, and p is not a in [p − k, p) is already assigned to Cold. string pointer. 2) We check that no pointer in [p − k, p) is k E. Multiply-Linked Data Structure Algorithm already assigned to a chain with offset . If either of these conditions occurs, we conclude Address Value Heap Pointer? that we have selected the wrong offset k and so do 0 “Hello” not assign p to that chain. This means that, assum- 1 3  ing that we have already seen a sufficiently large 2 NULL part of the data structure generated by pointers at 3 “There” 4 6  one offset (e.g. we have seen several nodes linked 5 0  by next pointers in a doubly-linked list), we are 6 “World” able to avoid assigning pointers at another offset to 7 9  8 3  the same data structure (e.g. we can avoid previous 9 “!” pointers being identified as part of the same singly- 10 NULL linked data structure as next pointers in an in-order, 11 6  doubly-linked list). See SectionV for a discussion TABLE II: A toy memory representation of a four- of where this technique can fall short. element doubly-linked linear list. Each node in the Once we assemble all the chains, we must begin list stores a string (e.g. “There”) and pointers to the to identify multistructs. We simply consider the start of the next and previous nodes. Guessing an chains as the vertices of a graph, and there is an offset of k = 1 for the entry p1 = M[1] = 3 at edge between two chains iff they intersect at at address 1 will succeed in finding the chain (1, 4, least one element; then the multistructs correspond 7, 10). However, na¨ıvely, an offset of k = 1 for to the connected components of this graph. the entry p1 = M[5] = 0 will also succeed, since M[0 + 1] = 1 is in a pre-existing data structure; F. Invariants and so the algorithm will conclude that address Finally, MEMERY must attempt to identify the 5 (as well as 8) is a pointer in the same singly- “high-level type” of data structure for each mul- linked data structure. To fix this, we must ensure tistruct. We begin computing a number of prop- that singly-linked data structure nodes are non- erties of a given multistruct, such as the num- overlapping. ber of distinct nodes and the number of distinct chain offsets. (The latter quantity tells us whether The fundamental challenge when attempting to the multistruct is “singly-linked”, “doubly-linked”, identify multiply-linked data structures, or multi- etc.) Then we calculate a number of invariants, structs, is that they are difficult to distinguish from which are high-level properties of structures that a singly-linked out-of-order data structure (i.e. can be used to distinguish between classes of data where the in-memory order of the nodes differs structures. In particular, we employ: from that which would be generated by a traver- 1) Overall strong connectedness: Using all the sal). For instance, a doubly-linked linear list with n chains in the multistruct together, is there a nodes might na¨ıvely appear to be a singly-linked path from every node to every other node? 2) Offset-wise strong connectedness: When re- 1) The victim calls malloc(1) and transmits stricting to chains of each offset k, is there the result to MEMERY, simulating the leak- a path from every node to every other node? age of a heap address via heap feng shui or See Table III for an example of how these in- a similar technique. variants are useful in distinguishing doubly-linked 2)M EMERY queries the victim for the value data structures. All of these strong connectedness stored at a certain address p. The victim calculations are performed using a simplified vari- checks that this is indeed an accessible 5 ant of Kosaraju’s algorithm for finding strongly address , and if so, it dereferences it and connected components; in particular, we need only returns the result to the server. pick a given node n and check, via depth-first After using this vulnerability to leak the entire search, that every node is accessible from n via the contents of the “estimated heap” region, MEMERY original chains, and that every node is accesible proceeds with the algorithms described above to from n when the direction of every edge in the identify data structures, and then generates a user- chains are reversed. friendly report on its findings.

Overall  Overall  V. LIMITATIONS AND FUTURE WORK Offset-wise?  Circular list N/A Offset-wise?  Linear list Binary tree One specific limitation of the above heuristics for detecting multiply-linked data structures has TABLE III: Various types of doubly-linked data to do with the detection of out-of-order multiply- structures, classified by overall strong connected- linked data structures, which consist of nodes ness and offset-wise strong connectedness. whose memory addresses are not strictly increas- ing or strictly decreasing when traversed in the linked order. For instance, if the nodes in TableII IV. IMPLEMENTATION had occurred in the order (“World”, “!”, “There”, “Hello”), the next pointer for the “World” node We have developed an open-source imple- with offset 1 would fail to generate a chain of mentation of MEMERY, available on GitHub at sufficient depth (since following the pointer and tothepowerofn/memery. In this section, we adding 1 would lead to a NULL value in “!”); discuss relevant details of our implementation, therefore, the first chain would be created by fol- with particular focus on how we simulated the lowing the previous pointer for “World” with offset vulnerabilities we assumed in SectionII. 1 into “There” and then following the next pointers We developed a variety of test-cases represent- for “There” and “World” to the null value in ing linked lists that were singly and doubly linked, “!”. This problem represents an inherent ambiguity and linear and circular; for robustness, we tested in the detection of doubly-linked data structures where nodes were allocated in random or linear (since the data could in fact form an out-of-order orders, and with random gaps between nodes. We singly-linked data structure); solving this problem also tested trees, in particular “parent trees” where would necessitate implementing a sophisticated each child only stores a parent pointer, as well branching and backtracking analysis, which might as standard binary trees where each parent stores significantly impair MEMERY’s memory usage and pointers to each of its two children. All tests were runtime. successful (up to the limitations in double-link It is also not very difficult to design programs detection described inV). to intentionally confuse or evade the watchful eye The interaction of the actual MEMERY engine of MEMERY. For instance, to implement a singly with the victim program was simulated via a network interaction; the use of a simulated exploit- 5We employ the ingenious method described at this StackOver- via-socket mirrors the real-world possibility of flow post for checking whether an address is readable in Linux: We exploiting a webserver. The victim program listens setup a dummy memory file and then attempt to write a single byte from address p into the file. If the address is readable, the write for incoming connections. Upon a connection with will succeed, while if it is unreadable, the call to write will fail MEMERY, the following steps take place: with error condition EFAULT. linked list, a clever program could store, in each Standard Template Library (STL). We believe that node, several slots for pointers. All these slots it would not be difficult to adapt our algorithm would be null, except for a single slot, the index to, for instance, specifically search for and parse of which would be randomly chosen and stored virtual table pointers (used to back C++ objects). per-node, storing the pointer. Using this index to retrieve the pointer and advance to the next VI.CONCLUSIONS node, the program would be able to implement In conclusion, MEMERY provides useful black- the semantic properties of a linked list (i.e. this box analysis of heap memory and the data struc- is a valid implementation of a “linked list API”); tures contained within it using only a memory however, the probability of chains of detectable read vulnerability in an NX- and ASLR-protected length existing is very low, and so MEMERY would executable. It implements heuristics for the de- not be able to identify the data structure. Similarly, tection of singly-linked and multiply-linked data an adversarial program could populate large mem- structures, and can identify function pointers and ory regions with PUSH MOV SUB;MEMERY is other types of useful data. We have successfully incapable of distinguishing such regions from true run MEMERY in a simulated, socket-based envi- function pointers that would be useful for reverse ronment and demonstrated its ability to detect and engineering or further exploitation. While such distinguish linked data structures. We recognize issues are concerning from a robustness standpoint, the current limitations of MEMERY and hope to it is a fundamental limitation that it is impossi- improve its robustness and expand its repertoire of ble for programs to truly reason about semantic data structure recognition in the future. properties of arbitrary code, and we believe our REFERENCES contribution is still meaningful and useful even in the non-adversarial case. [1] R. Rogowski, M. Morton, F. Li, F. Monrose, K. Z. Snow, and M. Polychronakis, “Revisiting Browser Security in the Mod- One potentially fruitful area for future research ern Era: New Data-Only Attacks and Defenses,” 2017 IEEE is in relaxing the assumption that we can per- European Symposium on Security and Privacy (EuroS&P), 2017. form arbitrary reads without causing segmentation [2] Z. Lin, X. Zhang, and D. Xu, “Automatic reverse engineering faults. Right now, MEMERY proceeds by attempt- of data structures from binary execution,” InProceedings of ing to leak the entire estimated heap region; how- the 11th Annual Information Security Symposium, 2010. [3] A. Slowinska, T. Stancescu, and H. Bos, “Howard: a Dynamic ever, if the heap estimate is too liberal (and it is Excavator for Reverse Engineering Data Structures,” NDSS, in the above design), an immediate segmentation 2011. fault will ensure. (Furthermore, a segmentation [4] M. N. Gagnon, S. Taylor and A. K. Ghosh, “Software Protec- tion through Anti-Debugging,” in IEEE Security & Privacy, fault causes addresses to be re-randomized via vol. 5, no. 3, pp. 82-84, May-June 2007. ASLR, which would force MEMERY to have to fig- ure out how to leverage cross-execution, relative- addressed-based learnings.) A more conservative strategy would progressively expand the leaked region while accessing pointers that look like they could potentially be heap pointers; when it comes to checking string and function pointers, perhaps using probabilistic reasoning about regions that are most often pointed to and would therefore be the most valuable to access would be helpful. Another area is to try and recognize more sophisticated layouts of individual nodes within a structure. Currently, MEMERY is mostly suited towards recognizing linked data structures built from C structs; it cannot recognize C++ objects, including linked structures built using the C++