Next Article in Journal
A Generic WebLab Control Tuning Experience Using the Ball and Beam Process and Multiobjective Optimization Approach
Next Article in Special Issue
Attacker Behaviour Forecasting Using Methods of Intelligent Data Analysis: A Comparative Review and Prospects
Previous Article in Journal
Development of Control Experiments for an Online Laboratory System
Previous Article in Special Issue
A Novel Low Processing Time System for Criminal Activities Detection Applied to Command and Control Citizen Security Centers
Article

Smali+: An Operational Semantics for Low-Level Code Generated from Reverse Engineering Android Applications

1
Department of Computer Science and Software Engineering, Laval University, Pavillon Adrien-Pouliot 1065, avenue de la Médecine, Quebec City, QC G1V 0A6, Canada
2
Automatic Control, Computers and Electronics Department. Petroleum-Gas University of Ploiesti, 100680 Ploiesti, Romania
*
Author to whom correspondence should be addressed.
Information 2020, 11(3), 130; https://doi.org/10.3390/info11030130
Received: 31 January 2020 / Revised: 25 February 2020 / Accepted: 26 February 2020 / Published: 27 February 2020
(This article belongs to the Special Issue Advanced Topics in Systems Safety and Security)

Abstract

Today, Android accounts for more than 80% of the global market share. Such a high rate makes Android applications an important topic that raises serious questions about its security, privacy, misbehavior and correctness. Application code analysis is obviously the most appropriate and natural means to address these issues. However, no analysis could be led with confidence in the absence of a solid formal foundation. In this paper, we propose a full-fledged formal approach to build the operational semantics of a given Android application by reverse-engineering its assembler-type code, called Smali. We call the new formal language Smali + . Its semantics consist of two parts. The first one models a single-threaded program, in which a set of main instructions is presented. The second one presents the semantics of a multi-threaded program which is an important feature in Android that has been glossed over in the-state-of-the-art works. All multi-threading essentials such as scheduling, threads communication and synchronization are considered in these semantics. The resulting semantics, forming Smali + , are intended to provide a formal basis for developing security enforcement, analysis and misbehaving detection techniques for Android applications.
Keywords: Android applications; multi-threading; operational semantics; reverse engineering; Smali+ Android applications; multi-threading; operational semantics; reverse engineering; Smali+

1. Introduction

A few years ago, mobile phones were used to make calls or send messages. Today, they surpass computers as the most commonly used digital device. They manage our agenda, emails, credit cards, itineraries and business documents. Android is the most popular operating system for mobiles and embedded devices, having the largest application market and 85% of all smartphones sold in 2019 were equipped with an Android OS [1]. Android is an open nature platform, which means that applications could be downloaded from sources other than the official Google play store. This is an important feature that has contributed to its unquestionable success, given the breadth of the available application that draws people to the platform, making it an ideal target for malicious application downloads.
Indeed, users are increasingly exposed to attacks targeting the Android environment via malicious applications. They thus endanger privacy information, by disclosing sensitive data (FakeNetflix malware [2]) or collecting sensitive banking information, especially with the increasing use of banking applications (Anubis trojan [3]). Furthermore, the installation of apparently legitimate malicious applications can lead to: clandestine eavesdropping on telephone conversations; tracking GPS position; exploiting pay services to cause financial losses to the user for the benefit of the attacker by calling or sending SMS messages to premium-rate numbers without the user’s knowledge (SMS Trojan such as FakePlayer, AsiaHitGroup and GGTracker [4,5,6].
To deal with this, automated tools for analyzing, verifying and enforcing the security of Android applications are highly needed [7,8,9,10]. Nevertheless, they must be based on a formal specification of the target platform to give solid results. In this paper, we propose formal operational semantics for a subset of the low-level Android code, which we consider particularly relevant for modeling Android applications and which we call Smali + . It includes the main bytecode instructions of Dalvik, and a few important API methods related to Java concurrency. Smali + is ultimately written from Smali with some essential native methods that were replaced with macro-instructions for simplification. Smali + is intended to serve as a basis for further analysis of Android applications and security implementation techniques. Android applications are mainly written in Java. The Java source code is first compiled into a Java Virtual Machine (JVM) bytecode using a standard Java compiler called Javac. Following this, the Java source files are converted into class files that store Java bytecode. The Java bytecode is then translated to an optimized bytecode called Dalvik through a tool called dx. At this stage, all the class files are converted and consolidated into a single DEX file called Dalvik EXecutable or simply a DEX to save memory. An Android Package Kit (APK) is essentially a zip of the DEX file accompanied by a Androidmanifest.xml file, a set of resources and potentially shared libraries. Figure 1 illustrates these steps.
In this work, we focus on the DEX format file, which contains the Dalvik binary code used even by the successor of Dalvik (since Android 5.0) called Android Runtime (ART).
Formalizing a low-level code, rather than high-level Java source or intermediate level Java bytecode, is our choice for many reasons. Firstly, Dalvik byte code is always available and it is easily obtainable from any Android application. Secondly, Dalvik bytecode is the common executable format for all Android applications and therefore the code is much closer to the code really executed. Even though decompilation from Dalvik back to Java or to Java bytecode is possible using reverse engineering tools (such as dex2jar and ded), there is no guarantee to recover the original source code since there is not a 100% robust and correct Dalvik-to-Java reverse translation tool [11]. However, even though that it is possible to retrieve source code or Java bytecode from Dalvik, editing or improving code at this level requires the user to reconvert it back to Dalvik and running the application afterward will often fail [9]. Focusing directly on Smali will avoid such problems. Hence, binary code obtained at this level, in DEX file, is illegible and requires conversion into a more understandable format prior to being analyzed, improved or edited. Reverse engineering in software makes it possible to convert a machine-readable binary file into a human-readable file, which is the case with DEX files.
Apktool [12] is a reverse engineering tool that simplifies the entire process of assembling and disassembling Android applications. It includes “Smali” and “bakSmali”, which are equivalent to “assembler” and “disassembler”, respectively allowing the passage from and to the DEX format. Apktool allows the user to disassemble applications to nearly original form. It uses BakSmali to produce, from an APK, a human-readable format akin to assembly languages called Smali (Smali is both the name of a mnemonic language for the Dalvik bytecode and its assembler version.). This code is nothing but a translation of the machine code generated by the DVM. In other words, it is a readable representation of Dalvik bytecode in an assembly-like code, with mnemonic instructions. BakSmali creates a Smali file for each class in the application preserving the original signature. The structure of such a file is presented in Figure 2. In addition to the code contained in the classes.dex file, Apktool generates the application decoded resources, as well as the AndroidManifest.xml file (in a readable version. These reverse engineering analysis techniques are still effective with the newly introduced ART environment [13].
In this paper, we put forward formal semantics for Smali. Smali is an assembly-like language that runs on Android’s DVM. It is obtained by ’bakSmaling’ the Dalvik executable file (.dex). A syntax and semantics have been adopted to specify this low-level code. The resulting formal language is a sub-language of Smali and a simpler language, called Smali + . A set of the most used Dalvik instructions have been generalized into 12 semantically different instructions (see [11] for generalization process), compared to more than 200 Dalvik original instructions in Smali. In addition to this set, our semantics includes instructions related to multi-threading. We plan to use Smali + in the near future to specify security properties for Android applications and this in order to protect the user from security threats that target the Android environment through downloaded applications.
The paper is organized as follows. In Section 2, we present some related work with similar ideas of bytecode formalization and we discuss their advantages as well as their drawbacks and limitations. In Section 3, we give some essential preliminaries related to Smali (registers, some adopted notations, types, etc.). In Section 4, we present the operational syntax and semantics of Smali + for a single-threaded application. In Section 5, we present the operational syntax and semantics of Smali + for a multi-threaded application. In Section 8, we conclude and we introduce the future avenues of our research.

2. Related Work

Mostly, the studies based on formal semantics of Android target a single well-defined goal. This can be an analysis for certification, a detection of potential vulnerabilities or malicious behavior of an application, or a verification of any aspect. It can also be a means to reveal security breaches of Android applications [14]. We will see in the studies we are presenting hereafter that formalization elements substantially differ from one objective to another. This being said, it is practically impossible to evaluate the efficiency of analyzes that are not based on the formal specification of the targeted platform.
In [15], Payet et al. define operational semantics for a subset of Dalvik opcodes that present registers manipulation, arithmetic operations, object creation, access and method calls as well as Android activities. Semantics rules were relatively complex. An Android program was modeled as a graph of blocks where each block has one or more instructions among the selected instructions. Blocks are linked in a way that they express control flow passing from one block to another. They require that invoke and return instructions only occur at the beginning and the end of a block, respectively. Blocks of semantics integrate instruction semantics for those that are different from a call or a return. Call instruction semantics allow passing from the caller method block to the callee method block. Activity semantics depend on the activity state, method callback, activity life cycle and external events. These semantics are defined to be the basis of static analyses that take into account the life-cycle of the activities. Despite the importance of thread-activity connection in Android semantics, threading was detached from activities semantics and concurrency was ignored in this work.
In [16,17], the authors propose a formal operational semantics for the Dalvik bytecode. The formalization was accompanied by a control flow analysis to detect potential malicious actions. Although the results highlight threading as the most often used language features with a (90.18%), this feature was omitted in both analyses and semantics to focus, instead, on reflection, exceptions and dynamic dispatch with 73.00% and 19.53%, respectively, which we find somewhat awkward. This motivates us to pay a special attention to the mutli-threading aspect modeling for Android.
In [11], the authors present SymDroid as a Dalvik bytecode interpreter for eventual security vulnerabilities detection. It is a symbolic execution for a simplified intermediate language of a fraction of Dalvik opcodes, named μ-Dalvik. SymDroid receives the Dalvik bytecode (the .dex file) as input. The opcode is first translated to μ-Dalvik, which one is based on 16 instructions considered as the most relevant ones to perform code analysis. Then, it is processed by a symbolic execution core using the SMT solver to generate traces as an intermediate result. Finally, the post-analyzer inspects the output traces and determines the final result. Entry points and all possible events affecting the application’s behavior were developed according to a client-oriented specification (it is up to the user to model it) to drive the application under test as desired. Although this work’s models, in addition to modeling bytecode instructions, the system libraries including Bundle and Intent, Android components life cycle, services and views; it ignores the system’s concurrent nature, either in the selected bytecode instructions or at the program symbolic execution level, which is considered as being sequential.
In the same vein, Julia presented in [18] is a static analyzer for Java bytecode based on abstract interpretation. It was extended in [19] and adapted to analyze Dalvik bytecode and handle specific features of Android such as event-driven nature, potentially concurrent entry points and dynamic inflation of graphical views. It applies several static analyses for Android applications’ classcast, nullness, dead code and termination analysis, but does not track information flow. Multi-threaded applications were not included in this work and event handlers are executed by a single thread.
Gunadi et al. [20,21] propose an operational semantics of DEX bytecode for certifying non-interference properties through type system. This study includes a translating process from Java bytecode semantics developed in [22] to Dalvik bytecode, concluding that if the first type system guarantees non-interference then its translation into Dalvik bytecode is also typable. Therefore existing bytecode verifiers for Java could certify non-interference properties of Dalvik bytecode.
Multi-threading programming semantics in applications have lately drawn increasing attention. Some combine it with event handling [23,24,25], others consider the main API methods relating to it [26]. In [24], Kanade proposes a semantic of a combined concurrency model of threads and events. All the focus in this work goes to the event-driven nature of Android and its relationship with the application’s threads. As a consequence, all other states that semantics could reach, such as those resulting from basic instruction execution (method call, jump, return instruction, etc.), have not been treated. The semantics proposed in [26] were the closest to ours. They cover the main important Dalvik instructions and handle multi-threading. This paper could be seen an extension of [27], with the obviously major change of the semantics needed for the concurrent setting and exception handling. However, thread scheduling was not discussed and thread spawning is left to the virtual machine to execute in an unpredictable point in time.
In the same stream of thought, in [28], Chaudhuri presents a formal security study on Android using operational semantics and a system of types for specific Android constructs. However, semantics ignore all Java constructs that may appear in Android applications (no class and method modeling), to focus instead on Android components, intents and all Android-specific features related to it (binding a service, sending an intent, etc.). This can be seen as a unified formal understanding of security for users and developers of Android applications to deal with their security concerns.
Some works have focused on other issues of Android such as multi-tasking. For instance, ASM presented in [29] is a formal model that formalizes all Android elements related to multi-tasking, such as activities, back stacks and tasks. An Android application is somewhat seen as a collection of activities with different types that interact with the user through a back stack. ASM has recently been extended in [30] to capture all the core elements of the multi-tasking mechanism used in inter-component communication.
Over time, formalization has included the permissions system as well [31,32,33]. For example, Bagheri et al. propose in [31] a formal specification for Android application’s permission system through an ad-hoc specification language called Alloy. It aims to formally specify the behavior of Android applications, in particular, the mutual interaction between applications based on permissions and security consequences caused by it or what authors call inter-app permission leakage vulnerabilities. Almost all Android elements related to inter-app permissions were taken into account in the formalism. Every application is modeled as a set of components, permissions, intent filters and vulnerable paths. Similarly, in [33], a formal model of the Android’s permissions is specified in the theorem prover Coq syntax.
Acteve++ [34] is an automated testing tool for Android Apps. It is based on Acteve [35] but is improved to support input events and broadcast events in order to achieve higher coverage. Authors use a non-standard operational semantics that describes the concolic execution of the program. Semantics describe program execution in response to a sequence of events generated automatically from an external environment. All other features and instructions that Android handles were neglected to focus instead on the event-driven paradigm, which we found not expressive enough to model an Android application. Our operational semantics consider, besides the concurrent feature, a variety of instructions that models methods invocations, object creation and the whole tree structure of an application (class, method and fields).
In [36], the authors focused on the low-level interactions with the operating system, by recording the system calls (syscalls) invoked. To benefit from two levels, the analysis uses generic low-level syscall traces to reconstruct the high-level semantics. While syscalls analysis offers more security guarantees, it, in our opinion, complicates the task more. Especially, this information is extracted from internal interfaces between the Android libraries and the kernel, which may change in the next versions of Android without notice. In our work, we propose a rich semantics that covers all API calls at a high level and we consider that it is sufficient to enforce security policies later.
Some studies like those conducted by Stowway [8] and Comdroid [37] for flow analysis directly analyze the disassembled DEX file for a given application to identify potential component and/or communication vulnerabilities. Despite the promising results of both tools in analyzing Dalvik bytecode and Android’s API, proving its soundness and evaluating its efficiency or deficiency is practically impossible in the absence of formal specification and proof.
Concurrent programming concepts and techniques are widely used in Android in order to manage different tasks and threads. Our formalism Smali + consider this important feature that was neglected before given its complexity. Overall, none of the aforementioned studies, including those considering multi-threading, offer complete semantics covering all the states that a thread can reach nor representing all multi-threading essentials. Most of the studies formalizing Dalvik byte code and handling multi-threading include only the two Dalvik instructions related to monitor use, monitor-enter and monitor-exit, since Dalvik opcodes encompass only these two instructions with regard to threading. However, a semantic for an Android program should not be limited to these instructions and must also consider instructions related to threads communication, signaling and scheduling. In this paper, we fill this gap by proposing semantics that incorporate, in addition to Dalvik instructions, a wide range of API methods covering multi-threading essentials formulated in macro instructions for the sake of simplicity. In comparison with all test-based approaches, Smali + is based on formal methods with their foundation in mathematical logic, allowing us to achieve rigorous and unambiguous reasoning in the system specification and proofs, ensuring the system proprieties, while test-based approaches can only ensure that systems satisfy the requirements for test cases. In sum, the proposed formal language is expressive enough to enforce security proprieties and to detect security critical APIs (i.e., those related to sensitive data access such as camera, SMS, telephony and contact list). Its syntax includes the class fully qualified name for each invoked method facilitating to localize such APIs.

3. Preliminaries

In this section, we present the most essential information for Smali. First, we present the DVM architecture and how it affects Smali syntax. Then, we present method invocation and how it affects Smali registers. Finally, we present Smali special notations for types.

3.1. Registers

Being optimized to run on devices on which resources and processor speed are scarce and the DVM architecture is register-based. Local variables are assigned to any of the 2 12 available registers. A register is used to hold any data value, except for double andlong values where each one requires two registers (64 bits). The Dalvik opcodes operate on the register’s content instead of operating directly on values and accesses elements on a program stack such as stack-based virtual machines. Hence, registers allow the DVM to keep track of program evolution while it executes bytecode [38]. Each method in Smali has its own set of registers for each method’s arguments, local variables and a special register for its return value. We will see later that most of the instructions include source and destination registers. Smali language denotes each set of registers differently, which allows us to visually distinguish between the method’s local and argument registers.
The alternate .locals directive specifies the number of local registers used by the method (non-parameter registers) which is statically known. Local registers in Smali are denoted with v 0 , v 1 , v 2 , . . . , v n , where v 0 is the first local register, v 1 the second and so on until the last register. This includes a special register for a method return value that allows passing return values from the callee back to the caller, which one is denoted by r e t .
L o c a l R e g i s t e r s = N { r e t }
Parameter registers in Smali are denoted by p 0 , p 1 , p 2 , . . . , p n . The first parameter for non-static methods is always the object that the method is being invoked on, in this case p 0 holds the object reference and p 1 the second parameter register. For a static method invocation p 1 is the first parameter register. For more details, please see the Method invocation subsection.
The .registers directive specifies the total number of registers in the method. This includes the registers needed to hold the method parameters, which are stored in the last registers in the method.
R e g i s t e r s = L o c a l R e g i s t e r s P a r a m e t e r R e g i s t e r s

3.2. Method Invocation

The DVM conforms to the ARM’s calling convention which is used for low-level code where parameters, return values, return addresses and scope links are placed in registers. It dictates how these elements are shared between the caller and the callee. In fact, these two share a part of their register array so that the caller passes arguments to the callee by setting its parameter registers in the right order. As for class methods, a lookup procedure starts by searching in the list of all static methods that belong to the named class, where classes have distinct names and locating the invoked method through its signature (i.e., name, argument types and number, and return type). Then, its parameter registers array is set according to ARM’s calling convention, so that the first argument leads to the first parameter register p 1 and so on until the last argument which identifies the last register for arguments (n arguments lead to n parameter registers).
In the dynamic invocation case, the class of the object whose method is being called (or recipient object’s class) is statically unknown, so it is first retrieved from the heap through its reference (see the semantics section for more details). Then, a lookup procedure searches among the class method list upwards to its super-class chain, for a method matching the given method signature. Registers comprise an additional register for the object reference called p 0 in Smali code. Hence, the actual number of parameter registers is p + 1 .
Local register contents are initially undefined (registers are untyped in Dalvik), however, its number is statically known.

3.3. Types in Smali

Smali code has two major classes of types, primitive types and reference types.
A primitive notation in Smali is particular where a single letter specifies each type, for example V is used for a void type.
Reference types are objects (i.e., class type) and arrays. A class type takes the formLpackagename/ClassName; where the leading L indicates that it is a class type, packagename is the package name path where class ClassName belongs to, whereas ClassName refers to the class name. For example, a thread object in Smali has the following type:LJava/lang/Thread; which is equivalent to Java.lang.Thread in Java. Arrays take the form [ T y p e ( T y p e which could obviously be a primitive or a reference). Arrays with multiple dimensions are presented by corresponding number of "[" characters. For example, a two-dimension arrays of int(s) is presented as follow [ [ I which is equivalent to i n t [ ] [ ] in Java. Table 1 summarizes different types in Smali.

4. Operational Semantics for a Single-Threaded Application

4.1. Notations

Throughout the paper, we use the following notations:
  • A : : B : : C to designate a stack, where A is the top-most value of the stack, B is the underlying element and C is the remaining portion of the stack. An empty stack is presented by ϵ .
  • ⊥ to denote any undefined value.
  • d o m ( f ) is domain of a function f. The notation d o m [ f x ] expresses the domain d o m where the value of a functionf is updated to x.
  • f [ x y ] expresses the function f where value x maps to y so f ( x ) = y .

4.2. Syntax

Table 2 provides basic syntactic categories as well as the selected instructions syntax.
A package of a disassembled DEX bytecode format is specified by a name p c k and sequences of classes. In our formal model, we consider that a package consists only of classes that correspond to .Smali files (Androidmanifest file and the rest of XML files are not considered in our formalization).
A class C l definition includes its access flags A c c - f l g , which is a keyword defining the class visibility, a fully qualified class name C f n that indicates the class package path name followed by the class name c (we assume an unlimited supply of distinct names). This includes also its direct super-class fully qualified name (a single inheritance). ⊤ is applied to classes without super-classes such as the Object class and the Thread class, and finally a set of implemented interfaces I n t f , fields F l d and methods M t d .
An interface is specified by its fully qualified name I n f , access flags A c c - f l g , a set of super-interfaces S i n f , its abstract methods (which consist of their method signatures) and constant fields. A field definition comprised its name f, its access flags and a type τ (which could be a primitive for static fields or a class type for instance fields ). A method definition includes a set of access flags that determines its scope, the method signature, the number of local registers it operates on denoted by l o c and a sequence of labeled instructions I n s t that present the method body. A method signature consists of the method name m, argument(s) type τ and a return type r e t τ which might be a void, primitive or a class type. In Smali + , we consider a subset of Dalvik instructions being selected based on results of a study of 1700 Android applications, carried out to determine what instructions and language features are most often used in typical applications [16,17]. In fact, Dalvik bytecode comprises 218 instructions [39]. We bring some modification to the selected instructions that does not affect the expressive power of Dalvik language. In contrast, it simplifies the representation of our semantics. For example, in Dalvik we find 13 variants of the move instruction that are semantically similar, we model this group of instructions by only one move instruction.
In our formal model, we consider instructions expressing the unconditional and conditional jump with, respectively, goto and if instructions. A move instruction to move values from source S r c to destination D e s . A destination may be a register name v, an instance field v r e f . f or a static field C f n . f , whereas a source S r c may be any of these elements beside constants c s t . We consider also instructions expressing the creation of a new object of a class C f n , a return from a void and non-void method with new-instance, return-void and return instructions, respectively. Method invocation refers to the method name, argument types and number, return type and registers. For methods class that are dynamically dispatched, it includes in addition to that a register holding the recipient object reference.

4.3. Semantics

Table 3 defines the domains used by our operational semantics. In fact, each application has at least one thread that defines the code path of execution and all of the code will be processed along the same code path if there is no other created thread. Hereafter, we suppose a single-threaded execution, a simple programming model with deterministic execution order, which means that an instruction has to wait for all preceding instructions to finish prior to being processed. We model such execution with a local configuration denoted by σ . It models the full state of a single-threaded program. It includes a call stack C s , a heap H and a static heap S. A call stack allows keeping track of all information concerning methods invoked in the program. It is initially empty and presented as a sequence of method frames. A method frame F m is a triplet consisting of a method name m, a program counter i for execution progress, both determine the program point in the invoked method and finally a register array R mapping register names (parameters, locals and return) to values. We adopt the same notations for registers used in Smali, as explained in the Registers subsection. Therefore, we have a set of registers for the method parameters and a set for the method local variables. Local registers content are initially undefined denoted by ⊥. The top of the call stack represents the currently executing method’s frame. Values can be either primitives or heap locations. A heap H map locations (we suppose an arbitrary number of unique locations) to objects O b j or arrays A r r . Objects record their class and a mapping from (class) fields to values, whereas arrays record the array type and its values. Finally, the static heap S is a mapping from static (class) field names to their values. Fields are annotated with their type used for initialization, to determine the default values of each primitive type (see Table 4). This annotation is omitted when it is unneeded.
The relation σ m ( i ) σ models evolution of a starting configuration σ into a new σ as the result of a computation step. m ( i ) represents the program point, which corresponds to the instruction at a position i in a specified method m, always for the top-most method frame of the call stack in σ .
To illustrate the semantics, we present in Table 5 the semantic rules for instructions presented in Table 2.
These rules are as follows. The rule R g o t o updates the program counter to the specified one unconditionally. Rules related to a m o v e instruction from source to destination use an evaluation function ⟦-⟧ that evaluates a destination or a source under the current configuration σ , except for registers. In this case, for the sake of being simple, we use directly R ( v ) always from the top-most method frame of the call stack in σ since ⟦v⟧ is equivalent to R ( v ) . Constants are evaluated to themselves whereas static and instance fields are evaluated based on static S and dynamic H heaps, respectively, obviously under the current configuration σ . The rule R m v - r e g evaluates the source sub-expression and then updates the destination register content in the register array. Rules R m v - i n s f and R m v - S f update instance and static field, respectively, by the content of the source register. Rule R m v - c s t is quite straightforward. That is, after evaluating the source to constant, it updates the destination register content by the constant value.
Rule R n e w - i n s creates a new object in the heap by reserving a memory with a new fresh location l, loading the class that is instantiated from and initialing its static fields, each by its default value according to Table 4. Once created, it returns the newly allocated object by pushing its heap location in a destination register v.
Rules R b - o p and R u - o p compute a binary or unary expression, respectively, and store the results in the destination register. Rules R i f - t r u e and R i f - f a l s e models conditional jump. If the guard is evaluated to true, it branches to the targeted program counter ( R i f - t r u e ), otherwise the program counter is advanced to the next instruction ( R i f - f a l s e ). In rules R i n v - s t and R i n v - d y , a lookup function is called to look up for the appropriate method. In the dynamic case, the method class is retrieved from the heap through object location l which is passed to the register v r e f . In both rules, a new method frame structure is pushed on the top of the call stack. It includes the method name, a count program set to 0 and a register array R set as explained in the subsection Method invocation. Notice that here we increment the program counter of the caller by one to restart from the correct instruction once the callee returns.
A lookup method searches for a method matching the given method signature ( m ( τ 1 , . . . τ n ) l o c τ ) in the given class full name and upwards to its super-class chain. Once located, it returns the method signature with the number of its local registers. We assume that the identified class and method exist in the package and class ancestry, respectively, with an array of local registers. Moreover, we admit that all verification checks are performed by the DVM. For instance it is verified that the method can be legally accessed by the class. Thus, the invoke instructions R i n v - s t and R i n v - d y are safe to execute.
l o o k u p ( M t d S i g n , C f n ) = m ( τ 1 , . . . τ n ) r e t τ l o c i f m C f n l o o k u p ( M t d S i g n , C f n . S c ) e l s e
Rules R r e t - n v and R r e t - v pop the top frame from the call stack and pass on the return value from the callee back to the caller through its return register r e t . Notice that, in the case of a void method, the return value must be moved to r e t by the callee before the return-void instruction.

5. Operational Semantics for a Multi-Threaded Program

Results shown in [17] have highlighted multi-threading as a widely used feature in Android applications with 90.18% including a reference to Java/lang/Thread and 88% using monitors. An important rate that motivates us to take this feature into account in our formalization in order to develop a complete semantic.

5.1. Syntax

Here, we consider multi-threaded programs. Multi-threading semantics include single-threaded semantics for each running thread separately. Threads in the same DVM interact and synchronize using shared objects and monitors associated with these objects. In order to give a full account of Java concurrency, we consider instructions related to this aspect. We define macro-instructions that cover methods of the Java Thread API [40] which are start for thread spawning and join for joining a referenced thread. We also define macro-instructions that cover several methods of the Java Object API [41] related to thread signaling such as notify, notifyAll and to synchronization such as wait. We also give the semantics of Dalvik instructions related to threads synchronization and monitors with the instructions monitor-enter and monitor-exit. All instructions syntax are illustrated in Table 6.

5.2. Semantics

An overall configuration Σ = < C s , S r b l , H , S > models the full state of an Android application in its low-level implementation. It presents a multi-threading program configuration including as first attribute a running thread’s call stack C s , a set of runnable threads S r b l , a heap H and a static heap S.
  • Each thread in the program has a call stack C s for methods being invoked, their arguments and local variables, with the same syntax used in Table 3.
  • S r b l is a set of pending threads. Each thread is presented by its call stack for method invoked information, plus a special register p 0 holding the thread reference. Threads in this set are in a “runnable” state (i.e., waiting to be selected by the scheduler).
  • H and S are dynamic and static heaps which are shared between all threads in the program and have the same semantics domain used for the single-threaded program in Table 3.
A new semantic domain for multi-threaded program is provided in Table 7. Some changes are applied to the object definition. It includes a new fields a c q which indicates if the object’s monitor is acquired by another thread. If this is the case, a c q will contain this thread’s reference, otherwise it will contain an undefined value ⊥ since an object cannot be reserved by more than one thread at once, at a given time. S b l c k is a set of blocked threads waiting for the object’s monitor to be released. S w a i t is a set of threads pending notification (threads that executed the wait instruction). The initial state of a new instance object, in a multi-threading context, will be initialized as seen in the single-threaded environment (with default values). New attributes are initialized as follows:
-
a c q initialized to an undefined value, which means that initially the object is in a free state and could be acquired by a given thread.
-
S b l c k , an empty set of blocked threads, which means that initially there is no thread waiting for the monitor to be released.
-
S w a i t , an empty set of waiting to be notified threads.
A class C l is a Thread class if and only if it is an instance of a Thread class (⊥=Thread), which means that its super class S c is either the Thread top class path ( C f n = LJava/lang/Thread) or another class that it is extended from this class. Each thread object has a Boolean finished field indicating whether the thread has completed its execution or not, a mapping from a group of threads to a set of threads call stacks, it contains a set of threads waiting to join this thread and an attribute called s t a t e indicating the current state of the thread. Each thread has a run method. Thread attributes are initialized as follows:
-
f i n i s h e d f a l s e .
-
S j o i n , an empty set of join threads, which means that initially there is no thread waiting to join the current thread.
-
s t a t e = .
Table 8 provides the semantics of spawning and scheduling threads. Rule R s t a r t starts a new thread, which reference is stored in the register v r e f . It internally calls the referenced thread’s r u n ( ) method that will be executed in this thread separately, once selected. Therefore, a l o o k u p ( ) procedure for its run method is performed and a separate call stack for a new thread is created with one frame comprising all information about the thread’s r u n ( ) method returned by the l o o k u p ( ) function. This thread moves to a "runnable" state in S r b l . When it gets a chance to execute, its target run() method will be executed. The actual execution of the launched thread will be managed with the rule R s e l e c t . Notice that, as expressed by the rule R s t a r t , the reference of the launched thread is always stored in the register p 0 and we assume that it will remain there for all semantics rules and for all method’s frames in the thread’s call stack.
Rules R s e l e c t and R s t o p manage threads scheduling. Rule R s e l e c t selects from S r b l one thread to be executed for a time slice t s . The selected thread’s state will be updated to a “Running( t s )” state. The thread’s call stack will be removed from the runnable set and placed at the first position of configuration Σ to start execution. The s e l e c t ( S r b l ) function will be based on a CFS scheduler’s algorithm for scheduling threads in S r b l . It takes into account the thread’s nice values and returns the selected thread’s local state presented in its current call stack as well as the time slice allocated to it for execution.
Rule R s t o p stops, in a monitoring mode (i.e., a mode that monitors the execution time given to each thread), a thread whose allocated time slice to execute a task has expired. We model the timing aspect in our formalism by the function c l o c k ( ) which represents the scheduler timer to control running threads.
Synchronization in Dalvik is modeled by the use of monitors with instructions monitor-enter and monitor-exit. That actually corresponds to the synchronized keyword in Java. A monitor is attached to an object and could be acquired and released by threads.
The semantics of these two instructions must fulfill two conditions. The first is related to the mutual exclusive access to shared objects in the heap by different threads. The second relates to the cooperation between these threads. Cooperation is modeled by a set of threads waiting for notification when the object is released by another thread. The sole thread running and owning the monitor is in a critical section. Table 9 presents rules related to synchronization. Monitor-enter semantics represent a thread trying to access the critical section by acquiring monitor for the object, whose reference is stored in a register v r e f . It first checks if the object is acquired by any other thread. If this is the case, the current thread will be blocked (mutual exclusive access condition) and added to the object blocking set S b l c k to join other threads (if any) with the same situation (cooperation condition). This case is modeled by the rule R b l o c k ). Otherwise, the current thread can take ownership of the monitor. The a c q attribute is then updated with this thread’s reference. This thread could resume its execution in the critical section. This case is modeled with the rule R a c q - m n t .
Monitor-exit semantics represents a thread that reaches the end of the critical section by releasing the owned monitor for another thread to take ownership, which perfectly fulfills the cooperation condition. Rule R R l s - m n t r provides this semantics, the current thread must first own this object’s monitor, once this condition is satisfied, the a c q attribute is updated to an undefined value (object is free). Then, all waiting threads in S b l c k are removed to the runnable set S r b l . It is up to the scheduler to select which thread to execute (there is no ordering among the blocked threads).
A thread could voluntarily give up ownership of the monitor before reaching the end of the critical section by calling the w a i t ( ) method or by executing the w a i t instruction. This thread releases ownership of this monitor and remains in a waiting state (i.e., suspended or inactive until be notified by another thread). Rule R w a i t provides the semantics of wait instruction. The calling thread must own this object’s monitor (i.e., must executing w a i t from inside a synchronized block) then relinquish it. Once the monitor associated with this object is released, the current thread is placed in the wait set for this object.
Table 10 presents rules R n o t i f y and R n o t i f y A l l expressing the signaling mechanism. Rule R n o t i f y represents the semantics for waking up a single thread that is waiting for this object’s monitor in the waiting set S w a i t . One thread among the set will be chosen randomly by the function r a n d o m ( ) . This thread will be moved from the waiting set to the runnable set to be selected later on by the scheduler and then processed. The rule R n o t i f y A l l is similar to the rule R n o t i f y , with the exception that it wakes all threads in the waiting set, which ones will be moved to the runnable set S r b l . Notice that, rules R n o t i f y and R n o t i f y A l l release in addition to waiting thread(s) set S w a i t all blocked threads in S b l c k . The two sets have the same privileges with regards to acquiring monitor. In other words, waiting threads have no precedence over potentially blocked threads that also want to synchronize on this object.
Table 11 presents semantics of finishing thread and joining instructions. Rules R J o i n - e x e c and R J o i n - w a i t check if the joined thread has finished its execution, if so, the current thread resumes execution ( R J o i n - e x e c ). Otherwise, the rule R J o i n - w a i t is applied. The current running thread is removed into S j o i n for threads waiting for the same thread to complete its execution (no release by the monitor of the object is acquired by the running thread here). The rule R f i n i s h ensures that when a thread completes its execution (i.e., its run() method returns) and releases all waiting threads in S j o i n by moving them to the runnable set S r b l .

6. Practical Aspects

We give, hereafter, some practical aspects of Smali+ through an example. For the sake of simplicity and due to the space limitation, we only present an illustration of a single-threaded program in Smali + that includes various important instructions such as method call, return, static and instance field update, etc. As shown in Table 12, the program is sequential and consists of two classes c 1 and c 2 belonging to the same package called p. Figure 3 shows the initial configuration. We show in detail, through this example, how the rules are applied and how the configuration evolves in every step. Each rule is followed by the resulting configuration.
The first table corresponds to the call stack C s , which is the current method frame. The second table corresponds to an empty heap H and the last two tables correspond to the register arrays for methods m 1 and m 2 , respectively. The first Smali + instruction to execute is the move instruction labeled with 5. It is a constant displacement, so the rule R m v - c s t applies. Since constants are evaluated to themselves, the register v 1 for m 1 locals registers is updated by the constant value and the program counter is incremented.
R m v - c s t m 1 ( 5 ) = move v 1 30 30 = 30 < < m 1 , 5 , R > : : C s , H , S > m 1 ( 5 ) < < m 1 , 6 , R [ v 1 30 ] > : : C s , H , S >
Information 11 00130 i001
The next instruction corresponds to the unconditional jump g o t o . The rule R g o t o so applies to update the program counter by the instruction labeled with 10.
R g o t o   m 1 ( 6 ) = goto 10 < < m 1 , 6 , R > : : C s , H , S > m 1 ( 6 ) < < m 1 , 10 , R > : : C s , H , S >
m 1 ( 10 ) is an invocation of a static method. Rule R i n v - s t so applies. A new frame for the called method is pushed on top of C s and the counter program in the caller method frame is incremented.
Information 11 00130 i002
R i n v - s t m 1 ( 10 ) = invoke - static L p / c 1 m 2 ( i n t , c h a r ) c h a r v 0 , v 1 l o o k u p ( m 2 ( i n t , c h a r ) c h a r , L p / c 1 ) = m 2 ( i n t , c h a r ) c h a r 2 R = { v 0 , v 1 , p 1 R ( v 0 ) , p 2 R ( v 1 ) } < < m 1 , 10 , R 1 > : : C s , H , S > m 1 ( 10 ) < < m 2 , 0 , R > : : < m 1 , 11 , R > : : C s , H , S >
Information 11 00130 i003
After some execution steps, we suppose that the register v 1 in m 2 is updated by a new value "CA" and the current instruction to execute is labeled with 18 in m 2 .
Information 11 00130 i004
The instruction m 2 ( 18 ) is a return from a non-void method m 2 , so the rule R r e t - n v applies. The top frame of C s is popped and the return value is passed from the callee back to the caller through its return register r e t .
R r e t - n v m 2 ( 18 ) = return v 1 < < m 2 , 18 , R > : : < m 1 , 11 , R > : : C s , H , S > m 2 ( 18 ) < < m 1 , 11 , R [ r e t R ( v 1 ) ] > : : C s , H , S >
Information 11 00130 i005
The instruction m 1 ( 11 ) is a static field update. So the rule R m v - s t t f so applies to update the indicated field in the static heap S by the register v 0 content.
R m v - s t t f m 1 ( 11 ) = move L p / c 2 . x v 0 R ( v 0 ) = 5 L p / c 2 . x = S ( L p / c 2 . x ) < < m 1 , 11 , R > : : C s , H , S > m 1 ( 11 ) < < m 1 , 12 , R > : : C s , H , S [ L p / c 2 . x 5 ] >
Information 11 00130 i006
The instruction m 1 ( 12 ) corresponds to an object creation. The rule R n e w - i n s t a n c e so applies to create a new instance from the class c 1 in the heap H and all fields are initialized according to their types.
R n e w - i n s   m 1 ( 12 ) = new - instance v 2 L p / c 1 o = { | L p / c 1 ; ( a n u l l , b \ u 0000 , c \ u 0000 ) } l d o m ( H ) < < m 1 , 12 , R > : : C s , H , S > m 1 ( 12 ) < < m 1 , 13 , R [ v 2 l ] > : : C s , H [ l o ] , S >
The instruction m 1 ( 13 ) is an instance field update. So the rule R m v - i n s t f applies. The register v 2 holds the instance location o in H. The instance field in o is updated with the source register v 1 content.
Information 11 00130 i007
R m v - i n s t f m 1 ( 13 ) = move v 2 . b v 1 R ( v 2 ) = l R ( v 1 ) = 30 H ( l ) = o < < m 1 , 13 , i , R > : : C s , H , S > m 1 ( 13 ) < < m , 14 , R > : : C s , H [ l o [ b 30 ] , S >
Information 11 00130 i008

7. Discussion

So far, we have proposed a formal language for Android programs called Smali + . Presented in a BNF notation, S m a l i + is a simple language that remains faithful to the original Smali notations and the .Smali file structure. It contains 12 generalized instructions from 218 Dalvik instructions [39] and some macros instructions modeling concurrency aspect. These 12 instructions were selected carefully to highlight Dalvik’s characteristics, such as register-based architecture, assembly-like code for Smali, methods invocations, monitors, etc. Macro instructions were used for the sake of simplification as well as to model multi-threading in Android. All the important API methods that affect a thread life-cycle were considered in Smali + semantics. Another important feature that lacks so far in Android application semantics is thread scheduling. This important aspect, in general, consists in picking a thread for execution and allocating an execution time to it, depending on its priority, before selecting a new thread to execute and switching the context. Android applications including their threads adhere to the Linux execution environment. So, threads are scheduled using the standard scheduler of the Linux kernel, known as a completely fair scheduler (CFS). On Linux, the thread priority is called a “nice value”. A low nice value corresponds to a high priority and vice versa. In Android, a Linux thread has niceness values in the range of −20 (most prioritized) to 19 (least prioritized), with a default niceness of 0 [42]. We exhibited in this work two rules related to scheduling feature in Android, R s e l e c t and R s t o p . In the first rule, we presented a function s e l e c t ( ) that plays the same role as the CFS, meaning it selects from runnable threads the most prioritized thread based on nice values comparison and allocates to it an amount of time for execution. The second rule stops a thread when the allocated time expires, prior to picking a new one through R s e l e c t . We mean by “monitoring mode” mentioned in threads scheduling, a monitor that is based on the CFS algorithm that monitors each thread for each task executed, and we suppose that each rule in the concurrent context is executing under a monitoring mode. This mode was presented just for R s t o p and omitted in other rules for simplification reasons. The operational semantics are mainly created to secure Android applications. In fact, we intend to use these semantics in an upcoming work to check a number of security proprieties to protect users from rogue applications. Our ultimate goal is to formally reinforce security policies on Android applications. That is to say, starting from a Smali + program and a formal specification of a security policy, we automatically generate a new equivalent secure version of the original program that respects the security policy. Formally, the approach takes, as input, a Smali + program P and a formal specification of a security policy ϕ and generates, as output, a new version P that respects ϕ . The new version of the program preserves all the behavior of the original version, except in cases where the security policy is on the verge of being violated. This is equivalent to saying that the traces of P are the intersection of traces accepted by ϕ and traces of P. It is formally modeled by (1).
P = P ϕ
Security policies will be enforced through a program-rewriting approach that combines static and dynamic approaches. It rewrites the program statically, according to a given security property, then generates a new executable version that satisfies this property. Security modifications or tests are added at well-calculated points in the program to force the latter to conform to the security property during execution. In other words, the untrusted code will be transformed into a self-monitoring code that will be exploded at specific points in the program. The rewritten version should be equivalent but more restrictive than the original so that it will be able to avoid potentially dangerous operations before they occur.
Reinforced security properties will obviously be specific to malware and attacks threatening Android applications, such as sensitive information leakage, which could be SMS contents, call logs, contact information or geographical location or Android financial malware, which exploit the premium services to incur financial loss to the user for the benefit of the attacker, for example, by calling or texting to premium-rate numbers without the user’s consent and privilege escalation attacks [43]. Therefore, all mediums that could be exploited for this kind of malware, such as Internet access, system services access including SMS, contact, telephony, Bluetooth, Global Positioning System (GPS) as well as APIs resulted from inter-application communication, will be checked through security policies. Such APIs will be easily located in Smali + , since it provides for each invocation the class fully qualified name.

8. Conclusions

In this paper, we have proposed a formal operational semantics for Smali, an assembly-like code generated form reverse engineering Android applications. We called the new formal language Smali + . Smali + covers the semantics of a large subset of the main Dalvik instructions as well as many important aspects related to multi-threading programming which are rarely considered in the state-of-the-art works of Android applications. This formal model is meant to be an environment to run formal verification of applications. Broader work consisting in techniques to reinforce the security of Android applications using this formalism is currently underway. We are deeply convinced that this will be of great help in analyzing the security of Android applications and verifying their hidden functions affecting users’ privacy as well as protecting users from malicious actions.

Author Contributions

Conceptualization, M.Z., J.F. and M.M.; methodology, M.Z., M.M. and J.F; validation, J.F., M.M. and E.P.; formal analysis, M.Z., M.M. and J.F.; investigation, M.Z., J.F., M.M. and E.P.; resources, M.Z., J.F., M.M. and E.P.; writing–original draft preparation, M.Z. and J.F.; writing–review and editing, M.Z. and J.F.; supervision, M.M., J.F. and E.P.; project administration, M.M.; funding acquisition, M.M. All authors have read and agreed to the published version of the manuscript.

Funding

This research was funded by the Natural Sciences and Engineering Research Council of Canada (NSERC).

Conflicts of Interest

The authors declare no conflict of interest.

References

  1. IDC Corporation. Smartphone Market Share. Available online: https://www.idc.com/promo/smartphone-market-share/os (accessed on 19 February 2020).
  2. Zhou, Y.; Jiang, X. Dissecting Android Malware: Characterization and Evolution. In Proceedings of the 2012 IEEE Symposium on Security and Privacy, San Francisco, CA, USA, 20–23 May 2012. [Google Scholar] [CrossRef]
  3. Sergiu, G. Anubis Android Trojan Spotted with Almost Functional Ransomware Module. Available online: https://www.bleepingcomputer.com/news/security/anubis-android-trojan-spotted-with-almost-functional-ransomware-module/ (accessed on 20 February 2020).
  4. Barrett, L. SMS-Sending Trojan Targets Android Smartphones. Available online: https://www.esecurityplanet.com/trends/article.php/3898041/SMSSending-Trojan-Targets-Android-Smartphones.htm/ (accessed on 2 January 2020).
  5. Collier, N. New Android Trojan Malware Discovered in Google Play. Available online: https://blog.malwarebytes.com/cybercrime/2017/11/new-trojan-malware-discovered-google-play// (accessed on 2 January 2020).
  6. F-Secure. Trojan:Android/GGTracker. Available online: https://www.f-secure.com/v-descs/trojan_android_ggtracker.shtml (accessed on 2 January 2020).
  7. Arzt, S.; Rasthofer, S.; Fritz, C.; Bodden, E.; Bartel, A.; Klein, J.; Le Traon, Y.; Octeau, D.; McDaniel, P. FlowDroid: Precise Context, Flow, Field, Object-sensitive and Lifecycle-aware Taint Analysis for Android Apps. SIGPLAN Not. 2014, 49, 259–269. [Google Scholar] [CrossRef]
  8. Felt, A.P.; Chin, E.; Hanna, S.; Song, D.; Wagner, D. Android Permissions Demystified. In Proceedings of the 18th ACM Conference on Computer and Communications Security, New York, NY, USA, October 2011. [Google Scholar] [CrossRef]
  9. Davis, B.; Sanders, B.; Khodaverdian, A.; Chen, H. I-arm-droid: A rewriting framework for in-app reference monitors for android applications. In Proceedings of the Mobile Security Technologies 2012, San Francisco, CA, USA, May 2012; Available online: http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.298.7191&rep=rep1&type=pdf (accessed on 2 January 2020).
  10. Xu, R.; Saïdi, H.; Anderson, R.J. Aurasium: Practical Policy Enforcement for Android Applications. In Proceedings of the 21th USENIX Security Symposium, Bellevue, WA, USA, 8–10 August 2012; pp. 539–552. [Google Scholar]
  11. Jeon, J.; Micinski, K.K. SymDroid: Symbolic Execution for Dalvik; CS-TR-5022; University of Maryland: College Park, MD, USA, July 2012; Available online: http://www.cs.tufts.edu/~jfoster/papers/symdroid.pdf (accessed on 4 January 2020).
  12. Apktool. A Tool for Reverse Engineering Android Apk Files. Available online: https://ibotpeaches.github.io/Apktool/ (accessed on 19 February 2019).
  13. Na, G.; Lim, J.; Kim, K.; Yi, J.H. Comparative Analysis of Mobile App Reverse Engineering Methods on Dalvik and ART. J. Internet Serv. Inf. Secur. 2016, 6, 27–39. [Google Scholar]
  14. El-Zawawy, M.A. An Operational Semantics for Android Applications. In Proceedings of the Computational Science and Its Applications - ICCSA 2016 - 16th International Conference, Beijing, China, 4–7 July 2016; pp. 100–114. [Google Scholar]
  15. Payet, E.; Spoto, F. An Operational Semantics for Android Activities. Available online: https://doi.org/10.1145/2543728.2543738 (accessed on 5 December 2019).
  16. Wognsen, E.; Karlsen, S. Static Analysis of Dalvik Bytecode and Reflection in Android. Master’s Thesis, Department of Computer Science Aalborg University, Aalborg, Denmark, 6 June 2012. Available online: https://projekter.aau.dk/projekter/files/63640573/rapport.pdf (accessed on 10 December 2019).
  17. Wognsen, E.; Søndberg Karlsen, H.; Chr. Olesen, M.; Hansen, R. Formalisation and analysis of Dalvik bytecode. Sci. Comput. Program. 2014, 92, 25–55. [Google Scholar] [CrossRef]
  18. Cousot, P.; Cousot, R. Abstract Interpretation: A Unified Lattice Model for Static Analysis of Programs by Construction or Approximation of Fixpoints. In Proceedings of the 4th ACM SIGACT-SIGPLAN Symposium on Principles of Programming Languages, New York, NY, USA, January 1977; pp. 238–252. [Google Scholar] [CrossRef]
  19. Payet, E.; Spoto, F. Static Analysis of Android Programs. Inf. Softw. Technol. 2012, 54, 1192–1201. [Google Scholar] [CrossRef]
  20. Gunadi, H. Formal Certification of Non-interferent Android Bytecode (DEX Bytecode). In Proceedings of the 2015 20th International Conference on Engineering of Complex Computer Systems ICECCS, Gold Coast, Australia, 9–12 December 2015; pp. 202–205. [Google Scholar]
  21. Gunadi, H.; Tiu, A.; Gore, R. Formal Certification of Android Bytecode. arXiv 2015, arXiv:1504.01842v5. Available online: https://arxiv.org/abs/1504.01842 (accessed on 19 February 2020).
  22. Barthe, G.; Pichardie, D.; Rezk, T. A certified lightweight non-interference Java bytecode verifier. Math. Struct. Comput. Sci. 2013, 23, 1032–1081. [Google Scholar] [CrossRef]
  23. Maiya, P.; Kanade, A.; Majumdar, R. Race detection for Android applications. In Proceedings of the ACM SIGPLAN Conference on Programming Language Design and Implementation, PLDI ’14, Edinburgh, UK, 9–11 June 2014; pp. 316–325. [Google Scholar]
  24. Kanade, A. Chapter Seven - Event-Based Concurrency: Applications, Abstractions, and Analyses. Adv. Comput. 2019, 112, 379–412. [Google Scholar]
  25. Bouajjani, A.; Emmi, M.; Enea, C.; Ozkan, B.K.; Tasiran, S. Verifying Robustness of Event-Driven Asynchronous Programs Against Concurrency. In Proceedings of the Programming Languages and Systems 26th European Symposium on Programming, ESOP 2017, Held as Part of the European Joint Conferences on Theory and Practice of Software, Uppsala, Sweden, 22–29 April 2017; pp. 170–200. [Google Scholar]
  26. Calzavara, S.; Grishchenko, I.; Koutsos, A.; Maffei, M. A Sound Flow-Sensitive Heap Abstraction for the Static Analysis of Android Applications. arXiv 2017, arXiv:1705.10482v2. Available online: https://arxiv.org/pdf/1705.10482.pdf (accessed on 15 December 2019).
  27. Calzavara, S.; Grishchenko, I.; Maffei, M. HornDroid: Practical and Sound Static Analysis of Android Applications by SMT Solving. In Proceedings of the 2016 IEEE European Symposium on Security and Privacy (EuroSP), aarbrucken, Germany, 21–24 March 2016. [Google Scholar] [CrossRef]
  28. Chaudhuri, A. Language-based security on Android. In Proceedings of the 2009 Workshop on Programming Languages and Analysis for Security, Dublin, Ireland, 15–21 June 2009. [Google Scholar] [CrossRef]
  29. Chen, T.; He, J.; Song, F.; Wang, G.; Wu, Z.; Yan, J. Android Stack Machine. Computer Aided Verification. In Proceedings of the 30th International Conference, CAV 2018, Held as Part of the Federated Logic Conference, Oxford, UK, 14–17 July 2018; pp. 487–504. [Google Scholar] [CrossRef]
  30. He, J.; Chen, T.; Wang, P.; Wu, Z.; Yan, J. Android Multitasking Mechanism: Formal Semantics and Static Analysis of Apps. In Proceedings of the Programming Languages and Systems - 17th Asian Symposium, Nusa Dua Bali, Indonesia, 1–4 December 2019; pp. 291–312. [Google Scholar] [CrossRef]
  31. Bagheri, H.; Kang, E.; Malek, S.; Jackson, D. Detection of Design Flaws in the Android Permission Protocol Through Bounded Verification. In Proceedings of the FM 2015: Formal Methods - 20th International Symposium, Oslo, Norway, 24–26 June 2015; pp. 73–89. [Google Scholar] [CrossRef]
  32. Ren, L.; Chang, R.; Yin, Q.; Man, Y. A Formal Android Permission Model Based on the B Method. In Proceedings of the Security, Privacy, and Anonymity in Computation, Communication, and Storage 10th International Conference, Guangzhou, China, 12–15 December 2017; pp. 381–394. [Google Scholar] [CrossRef]
  33. Khan, W.; Kamran, M.; Ahmad, A.; Khan, F.A.; Derhab, A. Formal Analysis of Language-Based Android Security Using Theorem Proving Approach. IEEE Access 2019, 7, 16550–16560. [Google Scholar] [CrossRef]
  34. Qin, J.; Zhang, H.; Wang, S.; Geng, Z.; Chen, T. Acteve++: An Improved Android Application Automatic Tester Based on Acteve. IEEE Access 2019, 7, 31358–31363. [Google Scholar] [CrossRef]
  35. Anand, S.; Naik, M.; Harrold, M.J.; Yang, H. Automated concolic testing of smartphone apps. In Proceedings of the 20th ACM SIGSOFT Symposium on the Foundations of Software Engineering (FSE-20), Cary, NC, USA, 11–16 November 2012; p. 59. [Google Scholar] [CrossRef]
  36. Nisi, D.; Bianchi, A.; Fratantonio, Y. Exploring Syscall-Based Semantics Reconstruction of Android Applications. In Proceedings of the 22nd International Symposium on Research in Attacks, Intrusions and Defenses, Beijing, China, 23–25 September 2019; pp. 517–531. [Google Scholar]
  37. Chin, E.; Felt, A.P.; Greenwood, K.; Wagner, D. Analyzing Inter-application Communication in Android. In Proceedings of the 9th International Conference on Mobile Systems, Applications, and Services, New York, NY, USA, June 2011; pp. 239–252. [Google Scholar] [CrossRef]
  38. Drake, J.J.; Lanier, Z.; Mulliner, C.; Fora, P.O.; Ridley, S.A.; Wicherski, G. Android Hacker’s Handbook; Wiley Publishing: Hoboken, NJ, USA, 2014. [Google Scholar]
  39. Android Open Source Project (AOSP). Dalvik Bytecode. Available online: https://source.android.com/devices/tech/dalvik/dalvik-bytecode (accessed on 30 January 2020).
  40. Oracle Corporation. Java Documentation on Thread. Available online: https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html (accessed on 2 October 2019).
  41. Oracle Corporation. Java Documentation on Object. Available online: https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html (accessed on 2 October 2019).
  42. Göransson, A. Efficient Android Threading: Asynchronous Processing Techniques for Android Applications, 1st ed.; O’Reilly Media: Sebastopol, CA, USA, 2014; ISBN 978-1449364137. [Google Scholar]
  43. Davi, L.; Dmitrienko, A.; Sadeghi, A.; Winandy, M. Privilege Escalation Attacks on Android. In Proceedings of the Information Security 13th International Conference, Boca Raton, FL, USA, 25–28 October 2010; pp. 346–360. [Google Scholar]
Figure 1. Compilation steps of an Android application.
Figure 1. Compilation steps of an Android application.
Information 11 00130 g001
Figure 2. Structure of a Smali file.
Figure 2. Structure of a Smali file.
Information 11 00130 g002
Figure 3. Initial configuration.
Figure 3. Initial configuration.
Information 11 00130 g003
Table 1. Types in Smali.
Table 1. Types in Smali.
Primitive Types
Bbyte
Cchar
Ffloat
Iint
Jlong
Sshort
Vvoid
Zboolean
Reference Types
Lpackagename/Classname;Object
[ . . . Object or PrimitivesArray
Table 2. Smali + : sequential execution.
Table 2. Smali + : sequential execution.
(Package) P c k g : : = . package p c k { C l * }
(Class definition) C l : : = . class ( A c c - f l g * ) C f n . super S c . implements I n t f * { F l d * , M t d * }
(Super class ) S c : : = C f n |
(Interface definition) I n t f : : = . interface ( A c c - f l g * ) I n f . super S i n f * { C s t F l d * , M t d S i g n * }
(Super interface ) S i n f : : = I n f
(Field definition) F l d : : = . field ( A c c - f l g ) * f : τ
(Constant Field definition) C s t F l d : : = . field public final static f : τ
(Method definition) M t d : : = . method ( A c c - f l g ) * M t d S i g n . locals l o c { L a b e l I n s t * }
(Method signature) M t d S i g n : : = m ( τ 1 , . . . τ n ) r e t τ
(Access flags) A c c - f l g : : = public | private | protected | final | . . .
(Labeled Instruction) L a b e I I n s t : : = i I n s t
(Label)i : : = int
(Instructions) I n s t : : = goto i (unconditional jump)
| move D e s S r c (move from source to destination)
| binop v v 1 v 2 (binary operation)
| unop v v 1 (unary operation)
| if v 1 v 2 i (conditional jump)
| new - instance v C f n (object creation)
| invoke - static C f n M t d S i g v * (static method invocation)
| invoke - instance v r e f M t d S i g v * (instance method invocation)
| return v (retrun from non-void method )
| return - void (retrun from a void method )
(Destination register) D e s : : = v (register name)
| v r e f . f (instance field)
| C f n . f (static field)
(Source register) S r c : : = D e s | C s t (des or constant)
(Operators) : : = + | - | . . . (binary operator)
: : = ¬ | + + | . . . (unary operator)
: : = < | > | . . . (comparison operator)
(Program counter)i : : = int
(Num. of loc. registers ) l o c : : = int
(Local registers name)v : : = string
(Parameter registers name) p : : = string
(Constant) C s t : : = S i n g l e (constant)
(Type) τ : : = P r i m | R e f
P r i m : : = S i n g l e | D o u b l e
R e f : : = C f n | A r r a y T y p e
A r r a y T y p e : : = A r r a y T S i n g l e | A r r a y T D o u b l e
A r r a y T S i n g l e : : = array ( S i n g l e | R e f )
A r r a y T D o u b l e : : = array D o u b l e
S i n g l e : : = boolean | char | byte | short | int | float
D o u b l e : : = long | double
(ReturnType) r e t τ : : = τ | V
(Names) C f n : : = L p a c k a g e n a m e / c (class full name)
I n f : : = L p a c k a g e n a m e / i t f (interface full name)
p c k , c , i t f , f , m : : = String (package, class, interface, field and method names)
Table 3. Semantic domains.
Table 3. Semantic domains.
(Local configuration) σ : : = < C s , H , S >
(Call stack) C s : : = ϵ | F m | C s : : C s
(Method frame) F m : : = < m , i , R >
(Registers array)R : : = ( R g V a l ) *
(Registers names) R g : : = v * p * r e t
(Heap)H : : = ϵ | ( l ( O b j | A r r ) ) *
(Object) O b j : : = { | C f n ; ( f τ V a l ) * | }
(Array) A r r : : = A r r a y T y p e [ * V a l
(Static Heap)S : : = ϵ | ( C f n . f τ V a l ) *
(Values) V a l : : = τ | l |
(Local register)v : : = string
(Parameter register)p : : = string
(Return register) r e t : : = string
(location)l : : = heap locations | null
Table 4. Default values of primitive types.
Table 4. Default values of primitive types.
int0
long 0 |
short0
char \ u 0000
byte(byte) 0
float0.0f
double0.0d
objectnull
boolean(int)false (0)
Table 5. Single-threaded semantics.
Table 5. Single-threaded semantics.
R g o t o m ( i ) = goto i < < m , i , R > : : C s , H , S > m ( i ) < < m , i , R > : : C s , H , S > R m v - r e g m ( i ) = move v S r c S r c = V a l < < m , i , R > : : C s , H , S > m ( i ) < < m , i + 1 , R [ v V a l ] > : : C s , H , S >
R m v - s t t f m ( i ) = move C f n . f v R ( v ) = V a l C f n . f = S ( C f n . f ) < < m , i , R > : : C s , H , S > m ( i ) < < m , i + 1 , R > : : C s , H , S [ C f n . f V a l ] > R m v - i n s t f m ( i ) = move v r e f . f v R ( v ) = V a l R ( v r e f ) = l H ( l ) = o < < m , i , R > : : C s , H , S > m ( i ) < < m , i + 1 , R > : : C s , H [ l o [ f V a l ] ] , S >
R m v - c s t m ( i ) = move v C s t C s t = C s t < < m , i , R > : : C s , H , S > m ( i ) < < m , i + 1 , R [ v c s t ] > : : C s , H , S > R n e w - i n s m ( i ) = new - instance v C f n o = { | C f n ; ( f τ 0 τ ) * | } l d o m ( H ) < < m , i , R > : : C s , H , S > m ( i ) < < m , i + 1 , R [ v l ] > : : C s , H [ l o ] , S >
R b - o p m ( i ) = binop v v 1 v 2 ( R ( v 1 ) R ( v 2 ) ) = V a l < < m , i , R > : : C s , H , S > m ( i ) < < m , i + 1 , R [ v V a l ] > : : C s , H , S > R u - o p m ( i ) = unop v v 1 ( R ( v 1 ) ) = V a l < < m , i , R > : : C s , H , S > m ( i ) < < m , i + 1 , R [ v V a l ] > : : C s , H , S >
R i f - t r u e m ( i ) = if v 1 v 2 i R ( v 1 ) R ( v 2 ) = true < < m , i , R > : : C s , H , S > m ( i ) < < m , i , R > : : C s , H , S > R i f - f a l s e m ( i ) = if v 1 v 2 i R ( v 1 ) R ( v 2 ) = false < < m , i , R > : : C s , H , S > m ( i ) < < m , i + 1 , R > : : C s , H , S >
R i n v - s t m ( i ) = invoke - static C f n m ( τ 1 , . . . , τ n ) r e t τ v 1 , . . . , v n l o o k u p ( m ( τ 1 , . . , . τ n ) r e t τ , C f n ) = m ( τ 1 , . . . , τ n ) r e t τ l o c R = { ( v j ) j < l o c , p 1 R ( v 0 ) , . . . , p n R ( v n ) } < < m , i , R > : : C s , H , S > m ( i ) < < m , 0 , R > : : < m , i + 1 , R > : : C s , H , S > R i n v - i n s t m ( i ) = invoke - instance v r e f m ( τ 1 , . . . , τ n ) r e t τ v 1 , . . . , v n R ( v r e f ) = l H ( l ) = { | C f n ; ( f τ V a l ) * | } l o o k u p ( m ( τ 1 , . . . , τ n ) r e t τ , C f n ) = m ( τ 1 , . . . , τ n ) r e t τ l o c R = { ( v j ) j < l o c , p 0 l , p 1 R ( v 0 ) , . . . , p n R ( v n ) } < < m , i , R > : : C s , H , S > m ( i ) < < m , 0 , R > : : < m , i + 1 , R > : : C s , H , S >
R r e t - n v m ( i ) = return v < < m , i , R > : : < m , i , R > : : C s , H , S > m ( i ) < < m , i , R [ r e t R ( v ) ] > : : C s , H , S > R r e t - v m ( i ) = return - void < < m , i , R > : : < m , i , R > : : C s , H , S > m ( i ) < < m , i , R [ r e t R ( r e t ) ] > : : C s , H , S >
Table 6. Smali + : concurrent instructions.
Table 6. Smali + : concurrent instructions.
I n s t : : = start v r e f (start the thread in v r e f )
| monitor - enter v r e f (acquire the monitor for object in v)
| monitor - exit v r e f (release the monitor for object in v)
| join v r e f (join the thread in v r e f )
| wait v r e f (release object’s monitor in v r e f and suspend current thread)
| notify v r e f (notify one thread from those waiting on object’s monitor in v r e f )
| notifyAll v r e f (notify all threads waiting on object’s monitor in v r e f )
Table 7. Semantic domains for a multi-threaded program.
Table 7. Semantic domains for a multi-threaded program.
(Global configuration) Σ : : = < C s , S r b l , H , S >
(Set of runnable threads) S r b l : : = | C s | { S r b l , S r b l }
(Object) O b j : : = { | C f n ; ( f τ V a l ) * ; a c q V a l ; S b l c k S b ; S w a i t S w | }
(A thread Object) t h : : = { | C f n ; ( f τ V a l ) * ; f i n i s h e d b o o e l a n ; S j o i n S j | }
(Set of blocked threads) S b : : = | C s | { S b , S b }
(Set of waiting threads) S w : : = | C s | { S w , S w }
(Set of join threads) S j : : = | C s | { S j , S j }
(Acquiring field) a c q : : f (field name)
(finished field) f i n i s h e d : : f (field name)
(Groups names) S w a i t , S b l c k , S j o i n : : = String
Table 8. Multi-threaded semantics: scheduling.
Table 8. Multi-threaded semantics: scheduling.
R s t a r t m ( i ) = start v r e f R ( v r e f ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; f i n i s h e d f a l s e ; S j o i n S j | } l o o k u p ( r u n ( ) V , C f n ) = r u n ( ) V l o c R = { ( v j ) j < l o c , p 0 l } F m = < @ r u n , 0 , R > < < < m , i , R > : : C s , S r b l , H , S > m ( i ) < < m , i + 1 , R > : : C s , S r b l { F m } , H , S >
R s e l e c t s e l e c t F r o m ( S r b l ) = [ F m : : C s , t s ] F m = < m , i , R > R ( p o ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; f i n i s h e d f a l s e ; s t a t e - S j o i n S j | } t h = { | C f n ; ( f τ V a l ) * ; f i n i s h e d f a l s e ; s t a t e R u n n i n g ( t s ) S j o i n S j | } < ϵ , S r b l , H , S > τ < F m : : C s , S r b l \ { F m : : C s } , H [ l t h ] , S >
R s t o p R ( p o ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; f i n i s h e d f a l s e ; s t a t e R u n n i n g ( t s ) S j o i n S j | } c l o c k ( ) > t s < < m , i , R > : : C s , S r b l , H S > M τ < ϵ , S r b l { C s } , H , S > M
Table 9. Multi-threaded semantics: synchronization.
Table 9. Multi-threaded semantics: synchronization.
R A c q - m n t r m ( i ) = monitor - enter v r e f R ( v r e f ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; a c q ; S b l c k S b ; S w a i t S w | } o = { | C f n ; ( f τ V a l ) * ; a c q R ( p 0 ) ; S b l c k S b ; S w a i t S w | } < < m , i , R > : : C s , S r b l , H , S > m ( i ) < < m , i + 1 , R > : : C s , S r b l , H [ l o ] , S >
R b l o c k m ( i ) = monitor - enter v r e f R ( v r e f ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; a c q l ; S b l c k S b ; S w a i t S w | } o = { | C f n ; ( f τ V a l ) * ; a c q l ; S b l c k S b { < m , i , R > : : C s } ; S w a i t S w | } < < m , i , R > : : C s , S r b l , H , S > m ( i ) < < m , i , R > : : C s , S r b l , H [ l o ] , S >
R R l s - m n t r m ( i ) = monitor - exit v r e f R ( p 0 ) = l R ( v r e f ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; a c q l ; S b l c k S b ; S w a i t S w | } o = { | C f n ; ( f τ V a l ) * ; a c q ; S b l c k ; S w a i t S w | } < < m , i , R > : : C s , S r b l , H , S > m ( i ) < < m , i + 1 , R > : : C s , S r b l S b , H [ l o ] , S >
R w a i t m ( i ) = wait v r e f R ( p 0 ) = l R ( v r e f ) = l H ( l ) = = { | C f n ; ( f τ V a l ) * ; a c q l ; S b l c k S b ; S w a i t S w | } o = { | C f n ; ( f τ V a l ) * ; a c q ; S b l c k ; S w a i t S w { < m , i , R > : : C s } | } < m , i , R > : : C s , S r b l , H , S > m ( i ) < < m , i , R > : : C s , S r b l S b , H [ l o ] , S >
Table 10. Multi-threaded semantics: signaling.
Table 10. Multi-threaded semantics: signaling.
R n o t i f y m ( i ) = notify v r e f R ( p 0 ) = l R ( v r e f ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; a c q l ; S b l c k S b ; S w a i t S w | } r a n d o m ( S w ) = C s o = { | C f n ; ( f τ V a l ) * ; a c q ; S b l c k ; S w a i t S w \ { C s } | } < < m , i , R > : : C s , S r b l , H , S > m ( i ) < < m , i + 1 , R > : : C s , S r b l { C s } S b , H [ l o ] , S >
R n o t i f y A l l m ( i ) = notifyAll v r e f R ( p 0 ) = l R ( v r e f ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; a c q l ; S b l c k S b ; S w a i t S w | } o = { | C f n ; ( f τ V a l ) * ; a c q ; S b l c k ; S w a i t | } } < < m , i , R > : : : C s , S r b l , H , S > m ( i ) < < m , i + 1 , R > : : C s , S r b l S w S b , H [ l o ] , S >
Table 11. Multi-threaded semantics: join.
Table 11. Multi-threaded semantics: join.
R J o i n - e x e c m ( i ) = join v r e f R ( v r e f ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; f i n i s h e d t r u e ; S j o i n S j | } < < m , i , R > : : C s , S r b l , H , S > m ( i ) < < m , i + 1 , R > : : C s , S r b l , H , S >
R j o i n - w a i t m ( i ) = join v r e f R ( v r e f ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; f i n i s h e d f a l s e ; S j o i n S j | } o = { | C f n ; ( f τ V a l ) * ; f i n i s h e d f a l s e ; S j o i n S j { < m , i , R > : : C s } | } < < m , i , R > : : C s , S r b l , H , S > m ( i ) < < m , i , R > : : C s , S r b l , H [ l o ] , S >
R f i n i s h @ r u n ( i ) = return - void R ( p 0 ) = l H ( l ) = { | C f n ; ( f τ V a l ) * ; f i n i s h e d f a l s e ; S j o i n S j | } o = { | C f n ; ( f τ V a l ) * ; f i n i s h e d t r u e ; S j o i n | } < < @ r u n , i , R > : : ϵ , S r b l , H , S > @ r u n ( i ) < ϵ , S r b l S j , H [ l o ] , S >
Table 12. Smali + program.
Table 12. Smali + program.
.class public L p / c 2 .super c1 { .class public L p / c 1 .super ⊥ {
.field publicx: int .field public a: LJava/lang/String
.field publicy: char .field publicb: int
.method public static m 1 ()V .locals 3 { .field private finalc: char
 ... .method public static m 2 (int,char)char .locals 2 {
5 move v 1 30 ......
6 goto 10
... ...
10 invoke - static L p / c 1 m 2 ( i n t , c h a r ) c h a r v 0 , v 1
11 move c2.x v 0
12 new-instance v 2 Lp/c1... ...
13 move v 2 . b v 1 18return v 1
...... }
}
Back to TopTop