A decompilation method for Dalvik program

The decompilation method constructs a control flow graph, generates intermediate code, and uses SSA for variable allocation and shrinkage to address errors and crashes in Android decompilation, resulting in a high-quality, readable output.

CN114217819BActive Publication Date: 2024-12-27GUANGZHOU LIANAN TECH CO LTD
View PDF 0 Cites 0 Cited by

Patent Information

Application Number
CN202111484362.9
Authority / Receiving Office
CN · China
Patent Type
Patents(China)
Current Assignee / Owner
Filing Date
2021-12-07
Publication Date
2024-12-27
Estimated Expiration
2041-12-07

AI Technical Summary

Technical Problem

Existing decompilation tools for Android programs, such as dex2jar/jd-gui, JEB, JADX, and Ghidra, suffer from poor decompilation results, errors, and crashes due to excessive logic pressure, especially when dealing with complex jumps and statements, lacking a unified encoding for Dalvik bytecodes.

Method used

A decompilation method involving constructing a control flow graph, generating intermediate code, recognizing statements, allocating variables using static single assignment (SSA), and shrinking variables to produce a high-quality decompilation result in multiple rounds, avoiding errors and crashes.

Benefits of technology

The method achieves a high decompilation success rate with improved readability, effectively handling complex jumps and statements, producing a final decompilation result superior to existing tools, suitable for software security analysis.

✦ Generated by Eureka AI based on patent content.

Smart Images

  • Figure CN114217819B_ABST
    Figure CN114217819B_ABST
Patent Text Reader

Abstract

The invention discloses a decompilation method for a Dalvik program, comprising the following steps: 1) constructing a control flow graph; 2) generating an intermediate code; 3) statement recognition: deducing various statements appearing in a source code according to various jumps in a Dalvik control flow; 4) variable allocation: marking a new variable by incrementing a subscript in an SSA form with the aid of a static single assignment; 5) variable contraction: attempting to contract the variable according to the SSA information in the above steps until no further contraction is possible; 6) generating a decompilation result; the method effectively avoids errors caused by excessive logic pressure in a single-round processing, and multiple-round processing makes the final decompilation result high in quality and has good readability.
Need to check novelty before this filing date? Find Prior Art

Description

Technical Field

[0001] The invention relates to a decompiling method for a Dalvik program. Background Art

[0002] Since the advent of Android in 2008, its market share has been steadily increasing, and has directly led to the withdrawal of mobile operating systems such as Symbian and Windows Phone from the stage of history. However, for such a mainstream mobile operating system, there has been no relatively easy-to-use decompilation tool for Android programs for many years.

[0003] In the early days of decompilation, the combination of dex2jar and jd-gui was used. First, the Android program was converted into a jar format based on Java bytecode, and then Java was decompiled. Later, with the popularity of Android, decompilation tools such as JEB, JADX, and Ghidra, which were directly targeted at Android programs, appeared on the market. For dex2jar / jd-gui, due to two rounds of processing and the fact that dex and jar were not completely equivalent, the decompilation results were not only poor, but also had certain differences from the actual semantics. Many tools that came out later also had problems in various aspects. The closed-source commercial software JEB has a high decompilation success rate, but its decompilation results are rough and contain a large number of goto statements, making them difficult to read. JADX has good readability in its decompiled code, provided that it can successfully decompile. However, the reality is that its statement recognition has more serious problems. When there are more complex jumps inside the function and the statements in the source code are not easy to be inferred, it will report errors and go on strike, and even directly cause the program to enter an infinite loop. Although Ghidra from the NSA is well-known, it is not only targeted at Android programs. It is versatile but not sophisticated, and its performance in decompiling Android programs is dismal and unremarkable.

[0004] Therefore, it is necessary to find a decompilation method that outperforms various products on the market and can provide the software security industry with an effective analysis and understanding of Android programs. Summary of the invention

[0005] In view of this, the purpose of the present invention is to provide a decompilation method for Dalvik programs that can effectively avoid errors or even crashes caused by excessive logic pressure in a single round of processing. At the same time, because of the existence of multiple rounds of processing, the final decompilation result is of high quality and has good readability.

[0006] In order to solve the above technical problems, the technical solution of the present invention is:

[0007] A decompilation method for a Dalvik program comprises the following steps:

[0008] 1) Construct a control flow graph;

[0009] After obtaining the function information of the program, construct a control flow chart for the control flow in the function. The control flow chart is a tree with directions. Each node in the book represents a section of control flow. Once the program runs to the entrance of a node, then if no exception is thrown, every line of instruction of the entire node will be executed without omission. There are two main situations in which the execution process in the function changes. One is encountering a jump, and the other is catching an exception. The former is mainly reflected in the if and switch statements in the source code. The destination of the jump instruction is the node entrance. If exception handling is registered in the function, then the entrance of these exception handling processes is the node entrance. After obtaining all the node entrances, start from these entrances and perform simulation execution. Once the execution reaches the next node entrance, it means that the current node ends. Repeat this step until all the instructions in the function are sorted and divided into their respective nodes. After sorting and dividing the function into various nodes, these nodes should be connected according to the direction of the execution process, and the pointing relationship between nodes should be combined together. Then the control flow chart is complete.

[0010] 2) Generate intermediate code;

[0011] After building the control flow graph, the Dalvik control flow in the function needs to be translated into intermediate code. The Dalvik control flow is very different from the source code. If each instruction is processed independently, subsequent steps such as variable shrinkage will be difficult to perform due to the lack of a unified encoding. The purpose of introducing intermediate code is, on the one hand, to integrate similar Dalvik bytecodes to reduce complexity, and on the other hand, to provide a unified interface for later processing. The types of intermediate code mainly include assignment, call (invoke series instructions), arithmetic (such as addition, subtraction, multiplication and division), bit operations, etc.

[0012] 3) Sentence recognition;

[0013] Various conditional statements in the source code will bring jump instructions. To achieve decompilation, it is necessary to reverse the various statements in the source code based on the various jumps in the Dalvik control flow. According to the different expressions, the Dalvik control flow actually provides enough information to make it possible to infer the statements in the source code. According to the control flow graph, starting from the entry node of the function, all nodes will be traversed, and specific statement expressions will be perceived during the traversal process. Several nodes will be integrated into one statement. Each node is no longer flat in the function, but a hierarchical relationship appears according to the nesting of statements, and the code indentation effect in the source code can be reproduced accordingly;

[0014] 4) variable allocation;

[0015] In order to correctly represent variables in the decompilation result, it is necessary to use the form of static single assignment. In static single assignment, the source code variable and the SSA variable will be a one-to-many mapping relationship, and this relationship will be represented by the variable subscript. For Dalvik, the carrier of the variable is the register. Therefore, there are as many variables in the SSA form as the number of registers used in a function. Whenever a register is reassigned, the subscript is incremented in the SSA form to mark the new variable. The variables used in the decompilation result can directly use the variables in the SSA form.

[0016] 5) Variable shrinkage

[0017] To express an expression in the source code, multiple Dalvik instructions are often required to complete it. Based on the SSA information in the above steps, traverse the variables of the function and try to shrink the variables until no more shrinkage is possible;

[0018] 6) Generate decompilation results

[0019] The intermediate code is formally translated into a programming language to achieve complete decompilation. For Dalvik, the target language for decompilation is generally Java, and the corresponding expression needs to be constructed according to Java syntax. For example, the intermediate code of "const-stringv0,'Hello,world!'" is "v0='Hello,world!'". If it is translated into Java language, if v0 is not contracted, the result will be "String str='Hello,world'", where str is a variable corresponding to v0 here created based on SSA. When these language-specific translations are completed, the final decompilation result can be obtained.

[0020] The technical effects of the present invention are mainly reflected in the following aspects: the method has taken the decompilation success rate and decompilation effect into consideration at the beginning of its design, introduced multiple rounds of processing in the decompilation process, and gradually corrected and improved the decompilation results, so that each round of processing is clear and not too complicated, effectively avoiding errors or even crashes caused by excessive logic pressure in a single round of processing. At the same time, because of the existence of multiple rounds of processing, the quality of the final decompilation result is guaranteed and has good readability. Its performance is better than that of various product processing on the market, and can provide the software security industry with an effective tool for analyzing and understanding Android programs. BRIEF DESCRIPTION OF THE DRAWINGS

[0021] Figure 1 The present invention is a flowchart of a decompiling method for Dalvik program. DETAILED DESCRIPTION

[0022] The following is combined with Figure 1 , the specific implementation methods of the present invention are further described in detail to make the technical solution of the present invention easier to understand and grasp.

[0023] Example

[0024] A decompilation method for Dalvik programs, such as Figure 1 As shown, the following steps are included:

[0025] 1) Construct a control flow graph;

[0026] After obtaining the function information of the program, a control flow graph can be constructed for the control flow within the function. The control flow graph is a directional tree, and each node in the book represents a section of control flow. Once the program runs to the entrance of a node, then if no exception is thrown, every line of instruction in the entire node will be executed without omission. In other words, except for the last instruction in the node, all instructions in the node are not jump instructions. Because the node is the maximum allowable granularity of the statement, constructing a control flow graph is to prepare for subsequent statement recognition. For example, for an if statement without complex nesting, its conditional judgment exists in one node, and the code executed after the condition is met exists in another node.

[0027] There are two main situations in which the execution flow in a function changes. One is when a jump occurs, and the other is when an exception is caught. The former is mainly reflected in the if and switch statements in the source code, and the destination of the jump instruction is the node entry. If exception handling is registered in the function, the entry of these exception handling processes is the node entry. After obtaining all the node entries, start from these entries and perform simulation execution. Once the execution reaches the next node entry, it means that the current node ends. Repeat this step until all the instructions in the function are sorted and divided into their respective nodes.

[0028] After the function is sorted and divided into nodes, these nodes need to be connected according to the direction of the execution process. For example, if node A jumps to node B, then in the control flow graph, node A will point to node B. Similarly, if node A registers exception handling, and the exception handling code is in node B, then node A will also point to node B. By combining the pointing relationship between nodes, the control flow graph is complete.

[0029] 2) Generate intermediate code;

[0030] After building the control flow graph, the Dalvik control flow in the function needs to be translated into intermediate code. Dalvik control flow is very different from the source code. Directly decompiling Dalvik control flow is not only difficult, but also not conducive to correcting the decompilation results. For example, there are more than ten kinds of move instructions in Dalvik, but these more than ten kinds of instructions ultimately show register assignments. If each instruction is processed independently, subsequent steps such as variable contraction will be difficult to carry out because there is no unified encoding. The purpose of introducing intermediate code (IR) is, on the one hand, to integrate similar Dalvik bytecodes to reduce complexity, and on the other hand, to provide a unified interface for post-processing. The types of intermediate code mainly include assignment, call (invoke series instructions), arithmetic (such as addition, subtraction, multiplication and division), bit operations, etc.

[0031] 3) Sentence recognition;

[0032] Various conditional statements in the source code will bring jump instructions. If you want to achieve decompilation, you must reverse the various statements that appear in the source code based on the various jumps in the Dalvik control flow. This is the most complicated part of the entire decompilation process.

[0033] Each statement has its own representation from the perspective of the diagram. For example, if the if statement does not have an else, it can be represented in the control flow diagram as "A->B, B->N, A->N", where A is the node where the if condition is judged, B is the branch executed when the condition is met, and N is the next statement at the same level as the if statement. If there is an else branch, the diagram representation of the statement will become "A->B, B->N, A->C, C->N". In this case, C is the branch executed when the condition is not met, and the meaning of the other nodes remains unchanged. From this example, it can be seen that although information loss occurs during the compilation process and the flattened Dalvik control flow does not carry the statement information in the source code, the respective representations of various statements from the perspective of the diagram always exist, whether it is the source code or the Dalvik control flow. Depending on the different representations, the Dalvik control flow actually provides enough information to make it possible to infer the statements in the source code.

[0034] According to the control flow graph, starting from the entry node of the function, this solution will traverse all nodes, and sense specific statement expressions during the traversal process, integrating several nodes into one statement. After this process, the decompilation result based on the intermediate code can be generated at this time, and the prototype of decompilation has appeared. Each node is no longer flat in the function, but has a hierarchical relationship based on the nesting of statements, and the code indentation effect in the source code can be reproduced accordingly.

[0035] 4) variable allocation;

[0036] The number of registers is limited, while the number of variables defined in the source code can be infinite. In addition, even the same register can have different data types at different stages during the function execution. Therefore, registers and variables cannot be simply and roughly equated when decompiling. Variables reside in registers during their life cycle, and when their life cycle ends, other variables will enter this register. In order to correctly represent variables in the decompilation result, it is necessary to use the form of static single assignment (SSA).

[0037] Generally speaking, a variable is called a variable because its content can be changed. In other words, a variable can be reassigned. However, in static single assignment, "single assignment" means that each variable is assigned only once. In this way, the source code variables and SSA variables will be a one-to-many mapping relationship, and this relationship will be represented by the variable subscript. In SSA, each variable has its own subscript, and these subscripts are their version numbers. For Dalvik, the carrier of variables is registers, so there are as many variables in the SSA form as there are registers used in a function. Whenever a register is reassigned, the subscript is incremented in the SSA form to mark the new variable. For example, according to the Dalvik calling convention, p1 is the first parameter. If a function has at least one parameter, the initial variable of this parameter in the SSA form is p1[1], that is, the first version of p1. If this function reassigns this parameter and the value is V, then from the perspective of the intermediate code, this assignment statement will be "p1[2]=V". From this line on, subsequent references to p1 will be p1[2] instead of p1[1].

[0038] To construct the SSA form of a Dalvik function, it is necessary to distinguish between write and read cases. For write operations, that is, assignment, it is only necessary to traverse each node in the function at the intermediate code level, create a new register index at each assignment, and assign it to the newly created register variable here. For local registers within a function, since they are initially in an uninitialized state, they will only get the first version number when the instructions within the function begin to assign values ​​to them. On the other hand, for parameter registers passed to the function, since they have been initialized outside the function, they must be assigned an initial version number before traversing the function nodes.

[0039] In contrast, constructing SSA for read operations is much more difficult. If a register is read, and the definition of this register can be traced back from the current line in the node, then the register version number corresponding to this read operation is obvious. When a function has branches, and the same variable is assigned in multiple branches, when the branches merge, the variable version number at the merge point cannot be determined. For example, there is such a piece of code, "if (condition) {v0 = 1;} else {v0 = 2;} return v0;". Try to convert it into SSA form, and you will get "if (condition) {v0 [1] = 1;} else {v0 [2] = 2;} return v0 [?]". At this point, it can be found that the version number of the v0 variable used in the return statement cannot be confirmed.

[0040] In order to represent this situation, SSA introduces a function called Phi. Its form is V[x]=Φ(V[a],V[b],...), where [x,a,b], etc. are all subscripts of the variable V. The Phi function can be defined as a "variable selector". According to the program context, it can select and return the appropriate version of the variable from the variables defined in other nodes (that is, its parameters). Since the return value of the Phi function must also be stored in the variable, there is also an assignment here, so the new version variable V[x] is used. With the Phi function, the variable version number of the confluence node can be represented.

[0041] Once the SSA form of the function is obtained, the variable allocation problem is solved. Compared with source code variables, SSA variables have smaller granularity from the perspective of life cycle, so SSA variables can directly reflect the variables in the source code without affecting the semantics of the function. For example, a variable that is repeatedly assigned in the source code, at the moment it is repeatedly assigned, the previous variable content no longer exists in objective fact, and its life cycle ends. In this case, this variable can actually be split into multiple variables at the source code level without causing side effects on the program's effects, and it can be inferred that the multi-variable representation in the SSA form is also legal. Therefore, the variables used in the decompilation results can directly use the variables in the SSA form.

[0042] 5) variable shrinkage;

[0043] Generally speaking, compared with a single expression in the source code, a single Dalvik instruction has a lower level of operation and implements simpler functions. Therefore, to express an expression in the source code, multiple Dalvik instructions are often required. If these Dalvik instructions are directly translated into the expressions in the source code one by one, even if they can be translated, they will appear verbose. Take a function call with a return value as an example. In the source code, it is in the form of "Objectobj=function(parameter,...);", and after Dalvik, it becomes two instructions, namely invoke and move-result. Translating these instructions into intermediate code will result in "result=function(register,...); vn=result", where result is the register implicitly stored in Dalvik for the return value of the call, and vn is the register that stores the obj variable at this time. From the perspective of decompilation, this intermediate variable named result is redundant.

[0044] Based on the SSA information in the previous step, traverse the variables of the function and try to shrink the variables. The result variable here is defined from the invoke instruction and is used in move-result. Since result is used only once, that is, there is no other reference to result, the definition of the result variable can be shrunk directly to the place of use at the intermediate code level. After the shrinking process, the corresponding intermediate code will become "vn = function (register, ...)" which is closer to the real source code.

[0045] In addition to this example, variable shrinking is also required in chain calls and other situations. This traversal process needs to be performed multiple times, and a state is maintained to record whether shrinking has been performed in this round. If shrinking has been performed in this round, the same traversal process will continue to be performed until shrinking is no longer possible.

[0046] 6) Generate decompilation results;

[0047] The steps described above, except for the construction of the control flow graph, are all based on the intermediate code. The last step is to formally translate these intermediate codes into a programming language to achieve complete decompilation. For Dalvik, the target language for decompilation is generally Java, and the corresponding expression needs to be constructed according to Java's syntax. For example, the intermediate code of "const-string v0,'Hello, world!'" is "v0 = 'Hello, world!'". If it is translated into Java language, if v0 is not contracted, the result will be "String str = 'Hello, world'", where str is a variable corresponding to v0 here created based on SSA. When these language-specific translations are completed, the final decompilation result can be obtained.

[0048] It will be apparent to those skilled in the art that the present invention is not limited to the details of the exemplary embodiments described above and that the present invention can be implemented in other specific forms without departing from the spirit or essential features of the present invention. Therefore, the embodiments should be considered exemplary and non-restrictive in all respects, and the scope of the present invention is defined by the appended claims rather than the above description, and it is intended that all changes falling within the meaning and scope of the equivalent elements of the claims be included in the present invention.

[0049] In addition, it should be understood that although the present specification is described according to implementation modes, not every implementation mode contains only one independent technical solution. This description of the specification is only for the sake of clarity. Those skilled in the art should regard the specification as a whole. The technical solutions in each embodiment may also be appropriately combined to form other implementation modes that can be understood by those skilled in the art.

Claims

1. A decompilation method for Dalvik program, characterized in that: The steps include: 1) Construct a control flow graph; After obtaining the function information of the program, a control flow chart is constructed for the control flow in the function. The control flow chart is a tree with directions. Each node in the tree represents a section of control flow. Once the program runs to the entrance of a node, then if no exception is thrown, every line of instruction of the entire node will be executed without omission. There are two main situations in which the execution process in the function changes. One is encountering a jump, and the other is catching an exception. The former is mainly reflected in the if and switch statements in the source code. The destination of the jump instruction is the node entrance. If exception handling is registered in the function, then the entrance of these exception handling processes is the node entrance. After obtaining all the node entrances, start from these entrances and perform simulation execution. Once the execution reaches the next node entrance, it means that the current node ends. Repeat this step until all the instructions in the function are sorted and divided into their respective nodes. After sorting and dividing the function into various nodes, these nodes are connected according to the direction of the execution process, and the pointing relationship between nodes is combined together, and the control flow chart is complete. 2) Generate intermediate code; After building the control flow graph, the Dalvik control flow in the function needs to be translated into intermediate code. The types of intermediate code mainly include assignment, invoke series instruction call, addition, subtraction, multiplication and division arithmetic, and bit operation. 3) Sentence recognition; Various conditional statements in the source code will bring jump instructions. To achieve decompilation, it is necessary to reverse the various statements in the source code based on the various jumps in the Dalvik control flow. According to the control flow graph, starting from the entry node of the function, all nodes will be traversed, and the statement expression will be perceived during the traversal process. Several nodes will be integrated into one statement. Each node is no longer flat in the function, but a hierarchical relationship appears according to the nesting of statements, and the code indentation effect in the source code can be reproduced accordingly; 4) variable allocation; In static single assignment, the source code variable and the SSA variable will be a one-to-many mapping relationship, and this relationship will be expressed by the variable subscript. For Dalvik, the carrier of the variable is the register. Therefore, there are as many variables in the SSA form as the number of registers used in a function. Whenever a register is reassigned, the subscript is incremented in the SSA form to mark the new variable. The variables used in the decompiled result directly use the variables in the SSA form. 5) variable shrinkage; To express an expression in the source code, multiple Dalvik instructions are required to complete it. According to the SSA information in the variable allocation step, the variables of the function are traversed and the variables are shrunk. The traversal process needs to be performed for multiple rounds, and a state is maintained to record whether the round has been shrunk. If the round has been shrunk, the same traversal process will continue until it can no longer be shrunk. 6) Generate decompilation results; The intermediate code is formally translated into a programming language to achieve complete decompilation. For Dalvik, the target language for decompilation is Java, and the corresponding expression needs to be constructed according to Java syntax. When the intermediate code is translated, the final decompilation result is obtained.