Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Impeding malware analysis using conditional code obfuscation

2008

Impeding Malware Analysis Using Conditional Code Obfuscation Monirul Sharif1 1 Andrea Lanzi2 Wenke Lee1 School of Computer Science, College of Computing, Georgia Institute of Technology, USA {msharif, giffin, wenke}@cc.gatech.edu 2 Dipartimento di Informatica e Comunicazione, Università degli Studi di Milano, Italy andrew@security.dico.unimi.it Abstract Malware programs that incorporate trigger-based behavior initiate malicious activities based on conditions satisfied only by specific inputs. State-of-the-art malware analyzers discover code guarded by triggers via multiple path exploration, symbolic execution, or forced conditional execution, all without knowing the trigger inputs. We present a malware obfuscation technique that automatically conceals specific trigger-based behavior from these malware analyzers. Our technique automatically transforms a program by encrypting code that is conditionally dependent on an input value with a key derived from the input and then removing the key from the program. We have implemented a compiler-level tool that takes a malware source program and automatically generates an obfuscated binary. Experiments on various existing malware samples show that our tool can hide a significant portion of trigger based code. We provide insight into the strengths, weaknesses, and possible ways to strengthen current analysis approaches in order to defeat this malware obfuscation technique. 1 Jonathon Giffin1 Introduction With hundreds of new malware samples appearing every day [3], malware analysis, which attempts to understand and extract the capabilities or behavior from malware samples, is becoming increasingly important. As malware analysis techniques evolve, malware writers continually employ sophisticated anti-reverse engineering techniques in an effort to defeat and evade the state-of-the-art analyzers. Historically, encryption [44], polymorphism [31, 39], and other obfuscation schemes [11,12,25,41,43] have been primarily employed to thwart anti-virus tools and static analysis [9,10,22,23] based approaches. Dynamic analysis based approaches [2, 6, 13, 29, 40] inherently overcome all anti- static analysis obfuscations, but they only observe a single execution path. Malware can exploit this limitation by employing trigger-based behaviors such as time-bombs, logicbombs, bot-command inputs, and testing the presence of analyzers, to hide its intended behavior. Recent analyzers provide a powerful way to discover trigger based malicious behavior in arbitrary malicious programs. Moser et al. proposed a scheme [28] that explores multiple paths during execution of a malware. After exploring a branch, their technique resumes execution from a previously saved state and takes the alternate branch by inverting the condition and solving constraints to modify related memory variables in a consistent manner. Other recently proposed approaches can make informed path selection [4], discover inputs that take a specific path [5, 7, 13] or force execution along different paths [42]. We call all such approaches input-oblivious analyzers because they do not utilize any source of information about inputs other than the program being analyzed. Our goal is to anticipate attacks against the state-of-theart malware analyzers in order to develop more effective analysis techniques. We present a simple, automated and transparent obfuscation against powerful input oblivious analyzers. We show that it is possible to automatically conceal trigger-based malicious behavior of existing malware from any static or dynamic input-oblivious analyzer by an automatically applicable obfuscation scheme based on static analysis. Our scheme, which we call conditional code obfuscation, relies on the principles of secure triggers [18]. First, we identify and transform specific branch conditions that rely on inputs by incorporating one-way hash functions in such a way that it is hard to identify the values of variables for which the conditions are satisfied. Second, the conditional code, which is the code executed when these conditions are satisfied, is identified and encrypted with a key that is derived from the value that satisfies the condition. As a result, input oblivious analyzers can no longer feasi- bly determine the values that satisfy the condition and consequently the key to unveil the conditional code. Our approach utilizes several static analysis techniques, including control dependence analysis, and incorporates both sourcecode and binary analysis to automate the entire process of transforming malware source programs to their obfuscated binary forms. In order to show that conditional code obfuscation is a realistic threat, we have developed a compiler-level tool that applies the obfuscation to malware programs written in C/C++. Our prototype implementation generates obfuscated compiled ELF binaries for Linux. Since the malware authors will be the ones applying this technique, the assumption of having the source code available is realistic. We have tested our system by applying it on several real malware programs and then evaluated its effectiveness in concealing trigger based malicious code. In our experiments on 7 different malware programs containing 92 malicious triggers, our tool successfully obfuscated and concealed the entire code that implemented 87 of them. We analyze the strengths and weaknesses of our obfuscation. Although the keys are effectively removed from the program, the encryption is still susceptible to brute force and dictionary attacks. We provide a method to measure the strength of particular applications of the obfuscation against such attacks. To understand the possible threats our proposed obfuscation scheme poses, we discuss how malware authors may manually modify their code in different ways to take advantage of the obfuscation technique. Finally, we provide insight into possible ways of defeating the obfuscation scheme, including more informed key search attacks and the incorporation of input-domain information in existing analyzers. We summarize our contributions below: • We present the principles of an automated obfuscation scheme that can conceal condition-dependent malicious behavior from existing and future input oblivious malware analyzers. • We have developed a prototype compiler-level obfuscator for Linux and performed experimental evaluation on several existing real-world malware programs, showing that a large fraction of trigger based malicious behavior can be successfully hidden. • We provide insight into the strengths and weaknesses of our obfuscation. We discuss how an attacker equipped with this knowledge can modify programs to increase the strength of the scheme. We also discuss the possibility of brute-force attacks as a weakness of our obfuscation and provide insight into how to develop more capable malware analyzers that incorporate input domain knowledge. Unlike polymorphic code, our approach does not store encryption keys inside the program. However, this does not limit the usage of polymorphism on our obfuscated binaries. It can be added as a separate layer on top of our obfuscation. The goal of our obfuscation is to hide malicious behavior from malware analyzers that extract behavior. For these analyzers, the usual assumption is that the program being analyzed is already suspicious. Nevertheless, malware authors wish to develop code that is not easily detected. Naı̈ve usage of this obfuscation may actually improve malware detection because of the particular way in which hash functions and decryption routines are used. However, since attackers can add existing polymorphic or metamorphic obfuscation techniques on top of our technique, a detector should be able to detect such malware at best with the same efficacy as polymorphic malware detection. 2 Conditional Code Obfuscation Malware programs routinely employ various kinds of trigger based events. The most common examples are bots [14], which wait for commands from the botmaster via a command and control mechanism. Some keyloggers [37] log keys from application windows containing certain keywords. Timebombs [27] are malicious code executed at a specific time. Various anti-debugging [8] or anti-analysis tricks detect side-effects in the executing environment caused by analyzers and divert program execution when present. The problem for the malware writer is that the checks inside the program that are performed on the inputs give away information about what values are expected. For example, the commands a bot supports are usually contained inside the program as strings. More generally, for any trigger based behavior, the conditions recognizing the trigger reveal information about the inputs required to activate the behavior. The basis of our obfuscation scheme is intuitive. By replacing input-checking conditions with equivalent ones that recognize the inputs without revealing information about them, the inputs can become secrets that the input-oblivious analyzer can no longer discover. Such secrets can then be used as keys to encrypt code. Since the modified conditions are satisfied only when the inputs are sent to the program, the code blocks that are conditionally executed can be encrypted. In other words, our scheme encrypts conditional code with a key that is removed from the program, but is evident when the modified condition is satisfied. Automatically carrying out this transformation as a general obfuscation scheme involves several subtle challenges. We provide a high-level overview of our obfuscation with program examples in Section 2.1. The general mechanism is defined in Section 2.2. The program analysis algorithms and transformations required are described in Sec- Figure 1. Two conditional code snippets. tion 2.3. Section 2.4 describes the consequences of our scheme on existing malware analysis approaches. Section 2.5 discusses possible brute-force attacks on our obfuscation technique. 2.1 Overview Figure 1 shows snippets of two programs that have conditionally executed code. The first program snippet calls a function that starts logging keystrokes after receiving the command “startkeylogger”. The second example starts an attack only if the day of month is between the 11th and the 19th. In both the programs, the expected input can be easily discovered by analyzing the code. We use cryptographic hash functions to hide information. For the first example, we can modify the condition to compare the computed the hard-coded hash of the string in cmd with the hash value of the string “startkeylogger” (Figure 2). The command string “startkeylogger” becomes a secret that an input oblivious analyzer cannot know. This secret can be used as the key to encrypt the conditional code block and the entire function log keys(). Notice that when the expected command is received, the execution enters the if block and the encrypted block is correctly decrypted and executed. fied. Third, the condition must contain an operand that has a statically determinable constant value. Given these requirements above, operators that check equality of two data values are suitable candidates. Hence, conditions having ‘==’, strcmp, strncmp, memcmp, and similar operators can be obfuscated with our mechanism. 2.2 General Mechanism We now formally define the general method of our conditional code obfuscation scheme. Without loss of generality, we assume that any candidate condition is equivalent to the simple condition “X == c” where the operand c has a statically determinable constant value and X is a variable. Also, suppose that a code block B is executed when this condition is satisfied. Figure 3 shows the program transformation required for the obfuscation. The cryptographic hash function is denoted by Hash and the symmetric encryption and decryption routines are Encr and Decr, respectively. Figure 3. General obfuscation mechanism. Figure 2. Obfuscated example snippet. In the second example of Figure 1, cryptographic hashes of the operands do not provide a condition equivalent to the original. Moreover, since several values of the variable n satisfy the condition, it is problematic to use them as keys for encryption and decryption. We define candidate conditions as those suitable for our obfuscation. A candidate needs three properties. First, the ordering relation between the pre-images of the hash function must be maintained in the images. Second, there should be a unique key derived from the condition when it is satis- The obfuscated condition is “Hash(X) == Hc ” where Hc = Hash(c). The pre-image resistance property of the function Hash implies that it is infeasible to find c given Hc . This ensures that it is hard to reverse the hash function. In addition, because of the second pre-image resistance property, it is hard to find another c! for which Hash(c! ) = Hc . Although this property does not strengthen the obfuscation, it is required to make the obfuscated condition semantically equivalent to the original, ensuring the correctness of the program. The block B is encrypted with c as the key. Let BE be the encrypted block where BE = Encr(B, c). Code is inserted immediately before BE to decrypt it with the key contained in variable X. Since Hash(X) = Hc implies X = c, when the obfuscated condition is satisfied, the original code block is found, i.e. B = Decr(BE , c) and the program execution is equivalent to the original. However, a malware analyzer can recover the conditional code B only by watching for the attacker to trigger this behavior, by guessing the correct input, or by cracking the cryptographic operations. 2.3 Automation using Static Analysis In order to apply the general mechanism presented in the previous section automatically on a program, we utilized several known algorithms in static analysis. In this section, we describe how these program analysis techniques were used. 2.3.1 Finding Conditional Code In order to identify conditional code suitable for obfuscation, we first identify candidate conditions in a program. Let F be the set of all functions or procedures and B be the set of all basic blocks in the program that we analyze. For each function Fi ∈ F in the program, we construct a control flow graph (CFG) Gi = (Vi , Ei ) in the program where Vi ⊆ B is the set of basic blocks in Fi and Ei is the set of edges in the CFG representing control-flow between basic blocks. We then identify basic blocks having conditional branches, which have two outgoing edges. Since we are not interested in conditions used in loops, we employ loop analysis to identify such conditions and discard them. From the remaining conditional branches, we select candidate conditions as the ones containing equality operators as described in Section 2.1. Let Ci ⊆ Vi be the set of blocks containing candidate conditions for each function Fi . After candidate conditions are identified in a program, the next step is to find corresponding conditional code blocks. As described earlier, conditional code is the code that gets executed when a condition is satisfied. It may include some basic blocks from the same function the condition resides in and some other functions. Since a basic block can contain at most one conditional branch instruction, by a condition, we refer to the basic block that contains it. We use the mapping CCode : B → (B ∪ F)∗ to represent conditional code for any condition. In order to determine conditional code, we first use control dependence analysis [1, 16] at the intra-procedural level. A block Q is control dependent on another block P if the outcome of P determines the reachability of Q. More precisely, if one outcome of P always executes Q, but the other outcome may not necessarily reach Q, then Q is control dependent on P . Using the standard algorithm for identifying control dependence, we build a control dependence graph (CDG) for each function, where each condition block and their outcome has edges to blocks that are Figure 4. Duplicating conditional code. control dependent on it. For each candidate condition, we find the set of blocks that are control dependent on its true outcome. Therefore, if a true outcome of a candidate condition C ∈ Ci of function Fi has an edge to a block B ∈ Vi , then B ∈ CCode(C). Conditional code blocks may call other functions. To take them into account, we determine reachability in the inter-procedural CFG. If there exists a call to a function F from a conditional code block B ∈ CCode(C) of some candidate condition C, we consider F ∈ CCode(C) which means that we consider all the code in the function F as conditional code of C as well. Now, for every block in F ∈ CCode(C), we find calls to other functions and include them in CCode(C). This step is performed repeatedly until all reachable functions are included. We used this approach instead of inter-procedural control-dependence analysis [35] because it allows us to obfuscate functions that are not control-dependent on a candidate condition but can be reached only from that condition. A candidate condition may be contained in a block that is conditional code of another candidate condition. The next step is to eliminate these cases by making a function or block conditional code of only the closest candidate condition that can reach it. For any block B ∈ CCode(C), if B ∈ CCode(C ! ) where C %= C ! and C ∈ CCode(C ! ) then we remove B from CCode(C ! ). We perform the same operation for functions. Blocks and functions can be obfuscated when they are conditional code of candidate conditions only. If they are reachable by non-candidate conditions, then we cannot obfuscate them. When obfuscations are to be applied, they are applied in an iterative manner, starting with the candidate condition that have no other candidate conditions depending on it. The basic blocks and functions that are conditional code of these conditions are obfuscated first. In the next iteration, candidate conditions with no unobfuscated candidate conditions depending on it are obfuscated. The iterative process continues until all candidate conditions are obfuscated. We use a conservative approach to identify conditional code when the CFG is incomplete. If code pointers are used whose targets are not statically resolvable, we do not encrypt code blocks that are potential targets and any other code blocks that are reachable from them. Otherwise, the Figure 5. Compound condition simplification. encryption can crash the program. Fortunately, type information frequently allows us to limit the set of functions or blocks that are probable targets of a code pointer. 2.3.2 Handling Common Conditional Code A single block or a function may be conditional code of more than one candidate condition that are not conditional code of each other. For example, a bot program may contain a common function that is called after receiving multiple different commands. If a block B is conditional code of two candidate conditions P and Q, where P ∈ / CCode(Q) and Q ∈ / CCode(P ) then B can be reached via two different candidate conditions and cannot be encrypted with the same key. As shown in Figure 4, we solve this problem by duplicating the code and encrypting it separately for each candidate condition. 2.3.3 Simplifying Compound Constructs Logical operators such as && or || combine more than one simple condition. To apply our obfuscation, compound conditions must be first broken into semantically equivalent but simplified conditions. However, parts of a compound condition may or may not be candidates for obfuscation, making the compound condition unsuitable for obfuscation. Logical and operators (&&) can be written as nested if statements containing the operand conditions and the conditional block in the innermost block (Figure 5(a)). Since both the simple conditions must be satisfied to execute conditional code, the code can be obfuscated if at least one of them is a candidate condition. Logical or operators (||) can be obfuscated in two ways. Since either of the conditions may execute conditional code, the conditional code may be encrypted with a single key and placed in two blocks that are individually obfuscated with the simple conditions. Another simple way is to duplicate the conditional code and use if...else if constructs (Figure 5(b)). Note that if either one of the two conditions is not a candidate for obfuscation, then the conditional code will remain revealed. Concealing the other copy does not gain protection. Although it is not possible to determine that the concealed code is equivalent to the re- vealed one, the revealed code gives away the behavior that was intended to be hidden from an analyzer. To introduce more candidate conditions in C/C++ programs, we convert switch...case constructs into several if blocks, each containing a condition using an equality operator. Every case except the default becomes a candidate for obfuscation. Complications arise when a code block under a switch case falls through to another. In such cases, code of the block in which control-flow falls through can be duplicated and contained in the earlier switch case code block, and then the standard approach that we described can be applied. 2.4 Consequences to Existing Analyzers Our obfuscation can thwart different classes of techniques used by existing malware analyzers. The analysis refers to the notations presented in Section 2.2. Path exploration and input discovery: Various analysis techniques have been proposed that can explore paths in a program to identify trigger based behavior. Moser et al.’s dynamic analysis based approach [28] explores multiple paths during execution by repeatedly restoring earlier saved program states and solving constructed path constraints in order to find a consistent set of values of in-memory variables that satisfy conditions leading to different paths. They use dynamic taint analysis on inputs from system calls to construct linear constraints representing dependencies among memory variables. After our obfuscation is applied, the constraint added to the system is “Hash(X) == Hc ”, which is a non-linear function. Therefore, our obfuscation makes it hard for such a multi-path exploring approach to feasibly find value assignments to variables in the programs’s memory to proceed towards the obfuscated path. A similar effect can be seen for approaches that discover inputs from a program that executes it along a specific path. EXE [7] uses mixed symbolic and concrete execution to create constraints that relate inputs to variables in memory. It uses its own efficient constraint solver called STP, which supports all arithmetic, logical, bitwise, and relational operators found in C (including non-linear operations). Cryptographic hash functions are designed to be computationally infeasible to reverse. Even with a powerful solver like Figure 6. The architecture of our automated conditional code obfuscation system. STP, it is infeasible to generate and solve the constraints that represent the complete set of operations required to reverse such a function. Forcing execution: A malware analyzer may force execution along a specific path without finding a consistent set of values for all variables in memory [42], hoping to see some malicious behavior before the program crashes. Suppose that the analyzer forces the malware program to follow the obfuscated branch that would originally execute B without finding the key c. Assuming X has a value c! where c! %= c, the decrypted block then becomes B ! = Decr(BE , c! ) %= B. In practical situations, subsequent execution of B ! should cause the program to eventually crash without revealing any behavior of the original block B. Static analysis: The utilization of hash functions alone cannot impede approaches utilizing pure static analysis or hybrid methods [4] because behavior can be extracted by analyzing the code without requiring constraint solving. However, our utilization of encryption conceals the behavior in the encrypted blocks BE that can only be decrypted by the key c, which is no longer present in the program. 2.5 Brute Force and Dictionary Attacks Although existing input-oblivous techniques can be thwarted by our obfuscation, analyzers that are aware of the obfuscation may attempt brute-force attacks on the keys used for encryption. First, the hash function Hash() being used in the malware needs to be extracted by analyzing the code. Then, a malware analyst may try to identify c by computing Hash(X) for all suitable values of X and searching for a value satisfying Hash(X) = Hc . The strength of the obfuscation applied to the condition can therefore be measured by the size of the minimal set of suitable values of X. Let Domain(X) denote the set of all possible values that X may take during execution. If τ is the time taken to test a single value of X or the hash computation time, then the brute force attempt will take |Domain(X)|τ time. Finding the set Domain(X) is not straightforward. In most cases, only the size of X may be known. If X is n bits in length, then the brute force attack requires 2n τ time. The possibility of using a pre-computed hash table to reduce search time can be thwarted by using a nonce with the data before computing the hash. Moreover, different nonce values for different conditions can be used to make the computed hash for one condition not useful for another. Existing program analysis techniques such as data-flow analysis or symbolic execution may provide a smaller Domain(X) in some cases, enabling a dictionary attack. Section 5 discusses more about attacks on our obfuscation, including automated techniques that can be incorporated in current analyzers. 3 Implementation Approach In this section, we present the design choices and implementation of the compiler-level tool that we developed to demonstrate the automated conditional code obfuscation on malware programs. Our primary design challenge was to select the appropriate level of code on which to work on. Performing encryption at a level higher than binary code would cause un-executable code to be generated after decryption at run-time. On the other hand, essential high-level information such as data types require analysis at a higher level. Our system, therefore, works at both the intermediate code and the binary level. We use the LLVM [24] compiler infrastructure and the DynInst [20] binary analysis and instrumentation system. The LLVM framework is an extensible program optimization platform providing an API to analyze and modify its own RISC-like intermediate code representation. It then emits binary code for various processor families. Most of the heavy-duty analysis and code instrumentation is done using the help of LLVM. The DynInst tool is a C/C++ based binary analysis and instrumentation framework, which we use for binary rewriting. We implemented our prototype for the x86 architecture and the Linux OS. We targeted the Linux platform primarily to create a working system to showcase the capability of the proposed obfuscation scheme without making it a widely applicable tool for malware authors. However, the architecture of our obfuscation tool is general enough to be portable to the Windows OS. The architecture of our system is presented in Figure 6. Our system takes as input a malware source program written in C/C++ and generates an obfuscated binary in the Linux ELF format. The transformation is done in four phases. The phases are (1) the Frontend Code Parsing Phase, (2) the Analysis/Transformation Phase, (3) the Code Generation Phase, and (4) the Encryption Phase. In the first phase LLVM’s GCC-based parser converts C/C++ source programs to LLVM’s intermediate representation. Next, in the analysis and transformation phase, the bulk of the obfuscation except for the actual encryption process is carried out on the intermediate code. In this phase, candidate conditions are identified and obfuscated, conditional code blocks are instrumented with decipher and marker routines, and the key required for encryption is stored as part of the code. Section 3.1 describes these steps in details. In the third phase, we use the static compilation and linking back end of LLVM to convert the LLVM intermediate representation (IR) to x86 assembly from which we generate an x86 ELF binary. In the final phase, which is described in Section 3.2, our DynInst based binary modification tool encrypts marked code blocks to complete the obfuscation process. We describe in Section 3.3 how the decryption of code takes place during run-time. 3.1 Analysis and Transformation Phase The analysis and transformation was implemented as an LLVM plugin, which is loaded by the LLVM optimization module. The steps taken are illustrated in Figure 7 and described below. 3.1.1 Candidate Condition Replacement We followed the method described in section 2.3 to identify candidate conditions and their conditional code. As shown in Figure 7, the transformed code calls the hash function with the variable used in the condition as the argument. We use SHA-256 in our implementation as the hash function. The replaced condition compares the result with the hard coded hash value of the constant. In other words, the condition “X == c” is replaced with “Hash(X) == Hc ”, where Hc = Hash(c). Depending on the data type of X, calls are placed to different versions of the hash function. For length constrained string functions, the prefixes of the strings are taken. A special wrapper function is used for strstr, which computes the hash of every sub-string of X and compares with Hc . 3.1.2 Decipher Routine In our implementation, we selected AES with 256-bit keys as the encryption algorithm. Constants in the conditions are not directly used as keys because of the varying type and length. A key generation function Key(X) is used to produce 256-bit keys. We describe the function in more details in the next section. We use the LLVM API to place a call immediately before the original conditional code to a separate decipher function that can be dynamically or statically linked to the malware program. Figure 7 illustrates this for a basic block. For a function, the call to the decipher routine is inserted as the first statement in the function body. The Decipher routine takes two arguments. The first is a dynamically computed key Key(X), which is based on the variable X in the condition. The second is the length of the code to be decrypted by the decipher routine. When calling a function that is to be obfuscated, these two arguments are added to the list of arguments for that function. This allows them to be passed not only to decipher routine called in that function body, but also to other obfuscated functions that the function may call. At this stage in the obfuscation scheme, this length is not known because the final generated code size will vary from the intermediate code. We keep a place holder so that the actual value can be placed during binary analysis. 3.1.3 Decryption Key and Markers The key generation function Key uses a SHA-256 cryptographic hash to generate a fixed length key from varying length data. However, the system would break if we used the same hash function as used in the condition. The reason is that if the key Key(c) = Hash(c) = Hc , then the stored hash Hc in the condition can be used to decrypt the code blocks. Therefore, we use Key(X) = Hash(X|N ), where N is a nonce. This ensures that the encryption key Kc = Key(c) %= Hc , where c is the constant in the condition. At this stage, the code block to be encrypted is not modified. Immediately following this block, we place the encryption key Kc . During intermediate code analysis, it is not possible to foresee the exact location of the corresponding code in the resulting binary file. Therefore, we place markers in the code, which are later identified using a binary analyzer in order to perform encryption. The function call to Decipher works as a beginning marker and we place a dummy function call End marker() after the encryption key. We use function calls as markers because the LLVM optimization removes other unnecessary instructions from the instruction stream. This type of placement of the key and markers have no abnormal consequences on the execution of the program because it is identified during binary analysis and removed at the final stage of obfuscation. Figure 7. Analysis phase (performed on IR). 3.2 Encryption Phase With the help of DynInst, our tool analyzes the ELF binary output by LLC. In order to improve code identification, we ensure that symbol information is intact in the analyzed binary. Figure 8 illustrates the steps carried out in this phase. At this stage, our tool identifies code blocks needing encryption by searching for calls to the marker functions Decipher() and End marker(). When such blocks are found, it extracts the encryption key Kc from the code and then removes the key and the call to the End marker function by replacing them with x86 NOP instructions. It then calculates the size of the encrypted block. Since AES is a block cipher, we make the size a multiple of 32 bytes. This can always be done because the place for the key in the code leaves enough NOPs at the end of the code block needing encryption. We place the size as the argument to the call to Decipher, and then encrypt the block with the key Kc . The nested conditional code blocks must be managed in a different way. We recursively search for the innermost nested block to encrypt, and perform encryption starting from the innermost one to the outermost one. Since our method of encrypting the code block does not require extra space beyond what is already reserved, our tool does not need to perform code movement in the binary. 3.3 Run-time Decryption Process The Decipher function performs run-time decryption of the encrypted blocks. Notice that the location of the block that needs to be decrypted is not sent to this function. When the Decipher function is called the return address pushed onto the stack is the start of the encrypted block immediately following the call-site. Using the return address pushed on the stack, the key and the block size, the function decrypts the encrypted block and overwrites it. Once the block has been decrypted, the call to the Decipher function is removed by overwriting it with NOP instructions. The decryption function uses the ability to modify code. Figure 8. Encryption Phase (performed on binary). Therefore, write protection on the code pages is switched off before the modification and switched back on afterwards. 4 Experimental Evaluation We used our obfuscation tool on several malicious programs in order to evaluate its ability to hide trigger based behavior. Although we had to select from a very limited number of available malicious programs written for Linux, we chose programs that are representative of different classes of malware for which triggers are useful. We evaluated our system by determining how many manually identified trigger-based malicious behaviors were automatically and completely obfuscated as conditional code sections by our system. In order to evaluate the resistance to brute force attacks on each obfuscated trigger, we defined three levels of strength depending on the type of the data used in the condition. An obfuscation was considered strong, medium, or weak if its condition incorporated strings, integers, or boolean flags, respectively. Table 1 shows the results of our evaluation on various programs. Notice that almost all trigger based malicious code was successfully obfuscated using our tool. However, for a few specific instances our tool was either able to provide weak obfuscation or not able to provide any obfuscation at all. We consider both of these cases as a failure for our obfuscation to provide any protection. We investigated the reasons behind such cases and describe how the malware program could be modified to take advantage of our obfuscation. We first tested our tool on the Slapper worm [38]. Although it is a worm, it contains a large set of trigger based behaviors found in bots and backdoor programs. When the worm spreads, it creates a UDP based peer-to-peer network among the infected machines. This entire network of victims can be controlled by sending commands to carry out Distributed Denial of Service attacks. In addition, it installs a backdoor program on the infected machine that provides shell access to the victim machine. The Slapper worm has two components. The first Malware Slapper worm (P2P Engine) Slapper worm (Backdoor) BotNET (An IRC Botnet Server) passwd rookit login rootkit top rootkit chsh rootkit Malicious triggers 28 1 52 2 3 2 4 Strong 1 52 2 2 2 Medium 28 - Weak 2 None 1 2 - Table 1. Evaluation of our obfuscation scheme on automatically concealing malicious triggers. (unlock.c) contains the worm infection vector and the code necessary to maintain the peer-to-peer network and receive commands. We manually identified 28 malicious triggers in the program that performs various malicious actions depending on the received control commands. These triggers were implemented using a large switch construct. Our tool was able to completely obfuscate all malicious actions of these triggers. However, the obfuscations had medium-level strength because the conditions were based on integers received in the UDP packets. The second part of the worm is a backdoor program (update.c) that opens a Linux shell when the correct password is provided to it. The program contained only one malicious trigger, which uses the strcmp function to check whether the provided password was equal to the hard-coded string “aion1981”. Our tool was able to successfully obfuscate the entire code of this trigger (which included a call to execve) and removed the password string from the program. In this case, our tool provided strong obfuscation because the condition was based on a string. We next tested our obfuscation on a generic open source bot program BotNET for Linux, which had minimal command and control support built into it. Since bots typically initiate different malicious activities after receiving commands, we identified the sections in that program that receive commands and considered them as malicious triggers. We manually found 52 triggers in the program, and after our obfuscation was applied, code conditionally executed for all 52 of them were strongly obfuscated. This result represents what we can expect by obfuscating any typical bot program. Usually, all IRC based bot commands send and receive text based commands, making the triggers suitable for strong obfuscation. We found several rootkit programs [30] for Linux that install user level tools similar to trojan horse programs containing specific logic bombs that provide malicious advantages to attackers, including privileged access to the system. First, we tested the passwd rootkit program, which had two manually identifiable malicious triggers inserted into the original. The first trigger enables a user to spawn a privileged shell when a predefined magic string “satori” is inserted as the new password. The second trigger works in a similar manner and activates when the old password is equal to the magic string. Our obfuscation successfully concealed these trigger based code with strong obfuscation. We next tested on a rootkit version of login. The source code login.c contained three manually identified malicious triggers. Our obfuscation was able to conceal two with strong obfuscation. Both of these triggers were strcmp checks on the user name or password entered by the user. Our obfuscator was not able to protect the third trigger. The reason was that both of the strongly obfuscated code sections increase the value of an integer variable elite that was used by the third trigger placed elsewhere in the program. The third trigger used the operator !=, making it unsuitable for our obfuscation. Our next test was on the top rootkit. This program contained two malicious triggers, which hide specific program names from the list that a user can view. Although these triggers are implemented using strcmp, the actual names of the processes that are to be hidden are read from files. Our obfuscator therefore could not conceal any of these malicious triggers. A malware author could take a different approach to overcome situations like this. By having a trigger that checks for the file containing the process names to start with a hard-coded value, all the other triggers can be contained in a strongly obfuscated code section. Finally, we tested on a rootkit that is a modified version of the chsh shell. Two triggers were manually identified. The first, which checked for a specific username, was strongly obfuscated by our tool. The second trigger used a boolean flag in and therefore was only weakly obfuscated. It is easy for one to overcome this difficulty because by manually modifying the flag to be a string or an integer, stronger obfuscation can be obtained using our tool. 5 Discussion In this section, we discuss our obfuscation technique to help defenders better understand the possible threats it poses. We discuss how malware authors (attackers) may utilize this technique, and analyze its weaknesses to provide insight into how such a threat can be defeated. 5.1 Strengths We have discussed earlier in Section 2.4 how our obfuscation impedes state-of-the-art malware analyzers. If the the variable used in the condition has a larger set of possible reasonable values, the obfuscation is stronger against brute force attacks. Since data type information is hard to determine at the binary level, brute-force attacks may have to search larger sets of values than necessary, providing more advantage to the attackers. Equipped with this knowledge, a malware author may modify his programs to take advantage of the strengths rather than naively applying it to the existing programs. First, a malware author can modify different parts of a program to introduce more candidate conditions. Rather than passing names of resources to system calls, he can query for resources and compare with the names. In addition, certain conditions that use other relational operators such as <, > or %= that are unsuitable for obfuscation may be replaced by ==. For example, time based triggers that use ranges of days in a month can be replaced with several equality checks on individual days. As another example, a program that exits if a certain resource is not found by using the != operator, can be modified to continue operation if the resource is found using the == operator. Second, a malware author can increase the size of the concealed code in the malware programs by incorporating more portions of the code under candidate conditions for obfuscation. Bot authors can have an initial handshaking command that encapsulates the bulk of the rest of its activity. Third, the malware authors can increase the length of inputs to make brute force attacks harder. In our implementation, we use AES encryption with 256-bit keys. If any variable used to generate the key is less than 256-bits in size, then the effective key-length is reduced. Attackers may also avoid low entropy inputs for a given length because that may reduce the search space for dictionarybased attacks. For example, a bot command that uses lower case characters only in a buffer of 10 bytes needs the search space of size 2610 , whereas using both upper, lower, and numeric values would increase search space to 6210 . Unlike strings, numeric values usually have a fixed size and may not be increased in length. Checking the hashes of all possible 32-bit integer values may not be a formidable task, but it is time-consuming, especially if a different nonce is used in each condition to make pre-computed hashes not useful. An attacker can utilize some proprietary function F (x) in the code to map a 32-bit integer x to a longer integer value. However, such an approach does not fundamentally increase the search space. It may just increase the difficulty for the defenders in automatically computing the hashes because the equivalent computation of F has to be extracted from the code and applied for each possible 32-bit value before applying the hash function. 5.2 Weaknesses In its current form, one of the main weaknesses of our obfuscation is the limited types of conditions on which it can be applied to. Although triggers found in the programs that we experimented with were mostly equality tests, there can be many trigger conditions in a malware that checks ranges of values. If the range is large, it may not be possible to represent them as several equality checks as we have mentioned earlier, making the obfuscation inapplicable. The encryption strength depends on the variable that is used. The full strength of having 2256 possible keys for AES is not utilized particularly in the case of numeric data, which are 32-bit or 64-bit integers in current systems. Obfuscations involving string inputs are likely to be more resistant to analysis. Therefore, a subclass of malware, especially bots or backdoors, are likely to be the most beneficial from this approach because the inputs required for the malicious triggers can be selected by the malware authors. Another weakness is that trigger-based behavior may not just depend on data that are input using system calls, but also status results returned from the calls. Most system calls have a small set of possible return values, usually indicating a success or some form of error. As a result, the number of values to check by a brute force attack may be reduced even further for such conditions. Possible ways to defeat: If the proposed obfuscation is successfully applied, existing malware analysis techniques may not be able to extract the behavior that is concealed unless the conditions in the triggers are satisfied. Yet, we suggest several techniques to defeat our obfuscation. First, analyzers may be equipped with decryptors that reduce the search space of keys by taking the input domain into account. Once an obfuscated condition is detected, the variable used in it may be traced back to its source using existing methods. If it is the result or an argument receiving data from a system call, the corresponding specification may be used to find the reasonable set of values that it may take. For example, if the source is set by a calling system call that returns the current system date (such as gettimeofday), the set of all possible values representing valid dates may be used, significantly reducing the search space. If, however, the source is input data that cannot be characterized, brute forcing may become infeasible. Another approach can be to move more towards inputaware analysis. Rather than capturing binaries only, collection mechanisms should capture interaction of the binary with its environment if possible. In case of bots, having related network traces can provide information about the inputs required to break the obfuscation. Existing honey- pots already have the capability to capture network activity. Recording system interaction can provide more information about the inputs required by the binary. Malware detection: Although the obfuscation may prove powerful against malware analyzers, its use may have an upside in malware detection. The existence of hash functions and encryption routines together with a noticeable number of conditions utilizing them may indicate that an unknown binary is a malware. However, our proposed obfuscation allows more layers of other general obfuscation schemes to be applied on top of it. For example, the binary resulting from our system may be packed with executable protectors [34], which a large fraction of malware already do today. The use of protector tools alone are not usually an indication that the program is a malware because these tools are usually created for protecting legitimate programs. Removing such obfuscation layers require unpacking techniques [21, 33] that are mostly used prior to analysis of suspicious code because of their run-time cost. The end result is that detecting such obfuscated malware is not any easier than detecting existing ones. 6 Related Work The problem of discovering trigger-based malicious behaviors has been addressed by recent research. Some techniques have used symbolic execution to derive predicates leading to specific execution paths. In order to identify time-bombs in code, [13] varies time in a virtual machine and uses symbolic execution to identify predicates or conditions in a program that depend on time. Another symbolic execution based method of detecting trigger based behavior is presented in [5]. Brumley et al.’s Bitscope [4] uses static analysis and symbolic execution to understand behavior of malware binaries and is capable of identifying trigger-based behavior as well. Our obfuscation makes it hard for symbolic constraints containing cryptographic oneway hash functions to be solved. Approaches have been proposed that identify triggerbased behavior by exploring paths during execution. Moser et al.’s multi-path exploration approach [28] was the first such approach. The technique can comprehensively discover almost all conditional code in a malware with sufficient execution time. The system uses QEMU and dynamic tainting to identify conditions and construct path constraints that depend on inputs coming from interesting system calls. Once a conditional branch is reached, the approach attempts execution on both of the branches after consistently changing memory variables by solving the constraints. Another approach is presented in [42] that forces execution along different paths disregarding consistent memory updates. The approach has been shown to be useful for rootkits written as Windows kernel drivers. Techniques that can impede analyzers capable of identifying trigger-based behavior need to conceal conditions and the code blocks that are used to implement these behaviors. An example of obfuscated conditional branches in malware was seen in the early 90s in the Cheeba [19] virus, which searched for a specific file by comparing the hash of the name of each file with a hard-coded hash of the file name being searched. In the literature, the idea of using environment generated keys for encryption was introduced in [32]. The work on secure triggers [18] considers a whitehat scenario and presents the principles of constructing protocols for software developers to have inputs coming into a program to decrypt parts of the code. In the malware scenario, the research idea of using environment generated keys for encryption was presented as the Bradley virus [17]. However, such techniques have not become a practical threat because identification of such keys and incorporation of the encryption technique requires a malware to be manually designed and implemented around this obfuscation. Our work shows that encrypting parts of the malware code using keys generated from inputs can be automatically applied on existing malware without any human effort, showing its efficacy as a wide-spread threat. The use of polymorphic engines in viruses is one of the earliest [36] obfuscation techniques used in malware to evade detection. Over the years, various methods of polymorphic and metamorphic techniques have appeared in the wild [39]. In order to detect polymorphic malware, antivirus software use emulation or create signatures to detect the polymorphic decryptors. In [9], obfuscation techniques such as garbage insertion, code transposition, register reassignment, and instruction substitution were shown to successfully disrupt detection of several commercial antivirus tools. Besides malware detection approaches [10, 22, 23], recent research has focused on creating techniques that automate malware analysis. These systems automatically provide comprehensive information about the behavior, runtime actions, capabilities, and controlling mechanisms of malware samples with little human effort. A variety of obfuscation techniques [11,12,25,43] have been presented that can impede such analyzers that are based on static analysis approach. Executable protectors and packers [34] are widely used by malware authors to make reverse engineering or analysis of their code very hard. As with polymorphic code, packers obfuscate the code by encrypting or compressing the binary and adding an unpacking routine, which reverses the operation during execution. Tools [21, 26, 33] have been presented that are able to unpack a large fraction of such programs to aid static analysis. However, by utilizing our obfuscation before packing, malware authors are capable of concealing code implementing triggered behavior from static analyzers even after unpacking is performed. Dynamic analysis approach has been more attractive for automated malware behavior analyzers. This is because performing analysis of code that is executing overcomes obfuscations that impede static analysis, including packed code. Since most dynamic analysis of malware involves debuggers [15], safe virtual machine execution environments or emulators, malware programs use various antidebugging [8] and anti-analysis techniques to detect sideeffects in the execution environment and evade analysis. There has been research on stealth analysis frameworks such as Cobra [40], which places stealth hooks in the code to aid analysis while remaining hidden from the executing malware. In order to automate analysis, dynamic tools such as CWSandbox [6], TTAnalyze [2] or the Norman Sandbox [29] automatically record the actions performed by an executing malware. However, since such tools can only view a single execution path, trigger-based behavior may be missed. These tools have been superseded by the recent approaches that can identify and extract trigger-based behavior, which we have presented earlier in this section. 7 Conclusion We have designed an obfuscation scheme that can be automatically applied on malware programs in order to conceal trigger based malicious behavior from state-of-the-art malware analyzers. We have shown that if a malware author uses our approach, various existing malware analysis approaches can be defeated. Furthermore, if properly used, this obfuscation can provide strong concealment of malicious activity from any possible analysis approach that is oblivious of inputs. We have implemented a Linux based compiler level tool that takes a source program and automatically produces an obfuscated binary. Using this tool we have experimentally shown that our obfuscation scheme is capable of concealing a large fraction of malicious triggers that are found in several unmodified malware source programs representing various classes of malware. Finally, we have provided insight into the strengths and weaknesses of our obfuscation technique and possible ways to defeat it. Acknowledgments This material is based upon work supported by the National Science Foundation under Grants CCR-0133629, CNS-0627477, and CNS-0716570, and by the U.S. Army Research Office under Grant W911NF0610042. Any opinions, findings, and conclusions or recommendations expressed in this material are those of the author(s) and do not necessarily reflect the views of the National Science Foundation and the U.S. Army Research Office. References [1] A. Aho, M. Lam, R. Sethi, and J. Ullman. Compilers Principles, Techniques, & Tools. Addison Wesley, 2006. [2] U. Bayer, C. Kruegel, and E. Kirda. TTAnalyze: A tool for analyzing malware. In Proceedings of the 15th Annual Conference of the European Institute for Computer Antivirus Research (EICAR), 2006. [3] R. Benzmller and T. Urbanski. G DATA malware report 2006. G Data Software AG, 2006. [4] D. Brumley, C. Hartwig, M. G. Kang, Z. Liang, J. Newsome, P. Poosankam, S. D, and H. Yin. Bitscope: Automatically dissecting malicious binaries. In CMU-CS-07-133, 2007. [5] D. Brumley, C. Hartwig, Z. Liang, J. Newsome, D. Song, and H. Yin. Towards automatically identifying triggerbased behavior in malware using symbolic execution and binary analysis. Technical Report CMU-CS-07-105, Carnegie Mellon University, 2007. [6] C. Willems. CWSandbox: Automatic Behaviour Analysis of Malware. http://www.cwsandbox.org/, 2006. [7] C. Cadar, V. Ganesh, P. Pawlowski, D. Dill, and D. Engleri. EXE: Automatically generating inputs of death. In Proceedings of the ACM Conference on Computer and Communications Security, 2006. [8] P. Cerven. Crackproof Your Software: Protect Your Software Against Crackers. 2002. [9] M. Christodorescu and S. Jha. Static analysis of executables to detect malicious patterns. In Proceedings of the Usenix Security Symposium, 2003. [10] M. Christodorescu, S. Jha, S. Seshia, D. Song, and R. Bryant. Semantics-aware malware detection. In Proceedings of the IEEE Symposium on Security and Privacy, 2005. [11] C. Collberg, C. Thomborson, and D. Low. A taxonomy of obfuscating transformations. In Technical Report 148, Department of Computer Sciences, The University of Auckland, July 1997. [12] C. Collberg, C. Thomborson, and D. Low. Manufacturing cheap, resilient, and stealthy opaque constructs. In Proceedings of the ACM Symposium on Principles of Programming Languages (POPL98), January 1998. [13] J. Crandall, G. Wassermann, D. Oliveira, Z. Su, F. Wu, and F. Chong. Temporal search: Detecting hidden malware timebombs with virtualmachines. In Proceedings of the Conference on Architectural Support for Programming Languages and OS, 2006. [14] D. Dagon. Botnet detection and response: The network is the infection. In Proceedings of the OARC Workshop, 2005. [15] Data Rescue. IDA Pro Disassembler and Debugger. http://www.datarescue.com/idabase/index.htm. [16] J. Ferrante, K. Ottenstein, and J. D. Warren. The program dependence graph and its use in optimization. In ACM Transactions on Programming Languages and Systems, volume 9, pages 319–349, 1987. [17] E. Filiol. Strong cryptography armoured computer viruses forbidding code analysis. In Proceedings of the 14th Annual Conference of the European Institute for Computer Antivirus Research (EICAR), 2005. [18] A. Futoransky, E. Kargieman, C. Sarraute, and A. Waissbein. Foundations and applications for secure triggers. In ACM Transactions of Information Systems Security, volume 9, 2006. [19] D. Gryaznov. An analysis of cheeba. In Proceedings of the Annual Conference of the European Institute for Computer Antivirus Research (EICAR), 1992. [20] J. K. Hollingsworth, B. P. Miller, and J. Cargille. Dynamic program instrumentation for scalable performance tools. In Proceedings of the Scalable High Performance Computing Conference, 1994. [21] M. G. Kang, P. Poosankam, and H. Yin. Renovo: a hidden code extractor for packed executables. In Proceedings of the 2007 ACM Workshop on Recurring Malcode (WORM 2007), 2007. [22] E. Kirda, C. Kruegel, G. Banks, G. Vigna, and R. Kemmerer. Behavior-based spyware detection. In Proceedings of the Usenix Security Symposium, 2006. [23] C. Kruegel, W. Robertson, and G. Vigna. Detecting kernellevel rootkits through binary analysis. In Proceedings of the Annual Computer Security Application Conference (ACSAC), 2004. [24] C. Lattner and V. Adve. LLVM: A compilation framework for lifelong program analysis & transformation. In Proceedings of the 2004 International Symposium on Code Generation and Optimization (CGO’04), 2004. [25] C. Linn and S. Debray. Obfuscation of executable code to improve resistance to static disassembly. In Proceedings of the ACM Conference on Computer and Communications Security (CCS), 2003. [26] L. Martignoni, M. Christodorescu, and S. Jha. Omniunpack: Fast, generic, and safe unpacking of malware. In Proceedings of the Annual Computer Security Applications Conference (ACSAC), 2007. [27] D. Moore, C. Shannon, , and J. Brown. Code-red: A case study on the spread and victims of an internet worm. In Proceecings of the 2nd ACM Internet Measurement Workshop, 2002. [28] A. Moser, C. Kruegel, and E. Kirda. Exploring multiple execution paths for malware analysis. In Proceedings of the IEEE Symposium of Security and Privacy, 2007. [29] Norman Sandbox Information Center. http://www.norman.com/microsites/nsic/, 2006. [30] Packet Storm Security. http://www.packetstormsecurity.org/. [31] S. Pearce. Viral polymorphism. VX Heavens, 2003. [32] S. Riordan and B. Schneier. Environmental key generation towards clueless agents. In Mobile Agents and Security, 1998. [33] P. Royal, M. Halpin, D. Dagon, R. Edmonds, and W. Lee. Polyunpack: Automating the hidden-code extraction of unpack-executing malware. In Proceedings of the 22nd Annual Computer Security Applications Conference (ACSAC), 2006. [34] Silicon Realms. Armadillo/software passport professional. http://www.siliconrealms.com/index.html. [35] S. Sinha, M. J. Harrold, and G. Rothermel. Interprocedural control dependence. ACM Transactions on Software Engineering and Methodology, 10(2):209–254, 2001. [36] F. Skulason. 1260-The Variable Virus. Virus Bulletin, 1990. [37] Symantec - Virus Database. Keylogger.stawin. http://www.symantec.com/security response /writeup.jsp?docid=2004-012915-2315-99. [38] Symantec - Virus Database. Linux.slapper.worm. http://securityresponse.symantec.com/avcenter/ security/Content/2002.09.13.html. [39] P. Szor. The Art of Computer Virus Research and Defense. Symatec Press, 2005. [40] A. Vasudevan and R. Yerraballi. Cobra: Fine-grained malware analysis using stealth localized-executions. In Proceedings of the IEEE Symposium on Security and Privacy, 2006. [41] I. V.Popov, S. K. Debray, and G. R. Andrews. Binary obfuscation using signals. In Proceedings of the Usenix Security Symposium, 2007. [42] J. Wilhelm and T. cker Chiueh. A forced sampled execution approach to kernel rootkit identification. In Proceedings of the Recent Advances in Intrusion Detection (RAID), 2007. [43] G. Wroblewski. Generalmethod of program code obfuscation. PhD thesis, Wroclaw University of Technology, 2002. [44] A. Young and M. Yung. Cryptovirology: Extortion based security threats and countermeasures. In Proceedings of the IEEE Symposium of Security and Privacy, 1996.