Smali Language

Smali is an assembly-like language used to write code for Android apps. It is designed to be human-readable as well as easy to understand, while still being low-level enough to allow for fine-grained control over the app’s behavior.

Smali files come with the extension .smali and each of them correspond to a single Java class and has the same name as the class, but with slashes (/) replacing dots (.) in the package name. For example, the Java class com.example.MyClass would be represented by the Smali file com/example/MyClass.smali. These names are referred to internal names.

The structure of a Smali source code can be broken down into the following sections:

Class Header

The header of a Smali file contains several pieces of information that are used by the Smali assembler to build the final .dex file for an Android app. It starts with the .class directive, which specifies the name of the class being defined in the file. Here’s an example:

.class public Lcom/example/MyClass;

Here, we are defining a class called MyClass in the package com.example with public as its access modifier. The class name is preceded by the letter L and is followed by a semicolon, which is the standard syntax for type descriptors in Smali. For more information about type descriptors, see chapter Type descriptors of the Smali API.

After the .class directive, the header can include several optional directives to provide additional information about the class. For example, you can use the .super directive to specify the parent class of the current:

.super Ljava/lang/Object;

This directive specifies that the our class inherits contents and functionalities from java.lang.Object.

Other optional directives that can appear in the header include:

  • .implements:

    This directive is used to specify that the current class implements one or more interfaces. For instance, the following code specifies that our class implements java.io.Serializable and android.os.Parcelable:

    .implements Ljava/io/Serializable;
    .implements Landroid/os/Parcelable;
    
  • .source:

    This directive is used to specify the name of the original source that Smali code was generated from. This information can be useful for debugging purposes. Note that for lambda-classes the source definition would point to lambda:

    .source "MyClass.java"
    
  • .debug:

    This directive is used to enable debug information for the current class. When it is present, the Smali assembler will include additional metadata in the final .dex file that can be used by debuggers:

    .debug 0
    

Note

The visitor API provided by this repository uses different method call for each of these directives to provide more flexibility while parsing. Therefore, if you want to get the header information on a class, you have to implement the following methods:

  • visit_class, and optionally

  • visit_super,

  • visit_source,

  • visit_implements and

  • visit_debug

In summary, the header of a Smali source file contains important metadata about the class being defined, as well as optional directives that provide additional information.

Class Annotations

In Smali, class annotations are used to provide metadata information about a class. They are defined using the .annotation directive followed by their descriptor and elements. Here is an example of how class annotations are defined in Smali:

 1.class public Lcom/example/MyClass;
 2.super Ljava/lang/Object;
 3
 4.annotation runtime Ljava/lang/Deprecated;
 5.end annotation
 6
 7.annotation system Ldalvik/annotation/EnclosingClass;
 8    value = Lcom/example/OuterClass;
 9.end annotation
10
11.annotation system Ldalvik/annotation/InnerClass;
12    accessFlags = 0x0008
13    name = "MyClass"
14    outer = Lcom/example/OuterClass;
15.end annotation
16
17# class definition and methods go here

In this example, we have defined three different class annotations:

  • @Deprecated:

    This runtime annotation indicates that the class is deprecated and should no longer be used. It is defined using the @java/lang/Deprecated annotation descriptor.

  • @EnclosingClass:

    This system annotation specifies the enclosing class of the current. It is defined using the @dalvik/annotation/EnclosingClass descriptor. In this case, it specifies that the enclosing class of MyClass is OuterClass.

  • @InnerClass:

    This system annotation specifies that the class is an inner class. It is defined using the @dalvik/annotation/InnerClass descriptor. In this case, it specifies that the access flags for the class are 0x0008 (which means it is static), the name of the class is MyClass, and the outer class is OuterClass.

There is also a special annotation used to mark classes to contain generic types, named @Signature. When used on a class, the annotation specifies the generic signature of the class, including its type parameters.

Here is an example of how the signature annotation can be used on a class in Smali:

 1.class public interface abstract Lcom/example/MyClass;
 2.super Ljava/lang/Object;
 3
 4# add the Signature annotation to the class
 5.annotation system Ldalvik/annotation/Signature;
 6    value = {
 7        "<T:",
 8        "Ljava/lang/Object;
 9        ">",
10        "Ljava/lang/Object;"
11    }
12.end annotation

In this example, the MyClass class is defined with a type parameter T using the signature annotation. Converted into Java code, the class would look like the folowing:

1public interface MyClass<T> {
2    // ...
3}

Note

All type descriptors that are defined after geenric type descriptors define the super classes of the declared class. In this case, there is only one super class (Ljava/lang/Object;).

Fields

The .field directive in Smali is used to define a field within a class. The general syntax for the field directive is as follows:

.field <access_flags> <name>:<type_descriptor> [ = <value> ]

Here is a breakdown of the different components of the field directive:

  • access_flags:

    Access flags specifiy the access modifiers for the field being defined. It can be one of the following values:

    Access flags of the .field directive

    Name

    Description

    public

    The field can be accessed from anywhere within the application.

    protected

    The field can be accessed within the same package or by a subclass.

    private

    The field can only be accessed within the same class or by a non-static subclass.

    static

    The field belongs to the class, but not to any instance of the class.

    final

    The field cannot be modified once it has been initialized.

    synthetic

    The field is generated by the compiler and not present in the original source code.

    You can use a combination of these flags to specify the desired access level for the field. They can be retrieved by using the AccessType class:

    1from smali import AccessType
    2
    3# Retrieve integer flags for access modifiers
    4flags: int = AccessType.get_flags(["public", "static", "final"])
    5# Convert the flags back into human readable names
    6flag_names: list[str] = AccessType.get_names(flags)
    7
    8# or reference them directly
    9flags: int = AccessType.PUBLIC + AccessType.STATIC + AccessType.FINAL
    
  • <name>:

    This section specifies the name of the field. It should start with a letter and can contain letters, digits, underscores, and dollar signs. It must not start with a number.

  • <type_descriptor>:

    The type descriptor is a string that represents the type of the field. The structure of type descriptors is described in the Type descriptors chapter of the Smali API.

  • <value>:

    The value definition is used when the field should be assigned directly with a value.

Let’s take a quick look at a small example of the .field directive:

.field private myField:I

Here, we are defining a private integer field named myField. The I type descriptor indicates that this field has an integer as its value. This field can only be accessed within the same class or any non-static sub-class.

Methods

In Smali, a method definition consists of several components, including access modifiers, return type, parameter types, and implementation code. The code block contains the actual assembly-like code that is executed when the method is called. It can contain registers, instructions, labels, and exception handlers.

.method <access_flags> <name>(<parameter_types>)<return_type>
    <code_block>
.end method

Here is a breakdown of the different components of the method directive:

  • <access_flags>:

    Access flags specifiy the access modifiers for the method. It can have the same values as defined before in field definitions.

  • <name>:

    Stores the actual method name used in references. There are two special method names that are pre-defined: <cinit> for constructor methods and clinit for static initializer blocks.

  • <parameter_types>:

    These are the type descriptors of the parameters of the method.

  • <return_type>:

    Defines the return type for this method (type descriptor).

  • <code_block>:

    This is the actual code that performs the functionality of the method. It may contain the following sections:

    • Registers:

      Registers are used to store temporary data and intermediate results during the execution of the method. They are specified using the letter v followed by a number (e.g., v0, v1, v2, etc.).

    • Instructions:

      Instructions are used to perform operations on registers or objects. Each instruction is represented by a mnemonic (e.g., move, add, sub, etc.) followed by its operands. Operands can be registers, constants, or labels.

    • Labels:

      Labels are used to mark specific locations in the code block. They are specified using a colon (:) followed by its name (e.g., :label1, :label2, etc.).

    • Exception handlers:

      Exception handlers are used to handle exceptions that may occur during the execution of the method. They are specified using the .catch directive, followed by the type of the exception that is being caught and the label of the handler.

The following example method written in Smali can be used to print "Hello World" to the console:

 1.method public static main([Ljava/lang/String;)V
 2    .registers 2
 3
 4    sget-object v0, Ljava/lang/System;->out:Ljava/lang/PrintStream;
 5
 6    const-string v1, "Hello World"
 7
 8    invoke-virtual {v0, v1}, Ljava/lang/PrintStream;->println(Ljava/lang/String;)V
 9
10    return-void
11
12.end method

explanation:

  • Line 1:

    In the first line we defined a method with name main that returns nothing and takes a string array (String[]) as an argument.

  • Line 2:

    .registers 2 declares that the method will use 2 registers, so we can use v0 and v1 within the method.

  • Line 4:

    The instruction sget-object loads the PrintStream object named out into the first register (v0).

  • Line 6:

    The instruction const-string defines a string variable that will be loaded into the register v1.

  • Line 8:

    With invoke-virtual we are calling the method println of the Java class PrintStream to print our string in register v1 to the console.

  • Line 10:

    return-void exits the method execution.

Annotations

The .annotation directive in Smali is used to define an annotation. The structure of this directive is as follows:

.[sub]annotation <visibility> <annotation_type>
    [ <properties> ]
.end [sub]annotation

The annotation directive starts with the .annotation or .subannotation keyword followed by the visibility and the type descriptor for the annotation. There are three possible values for the annotation’s visibility:

  • runtime: The annotation is visible at runtime and can be queried using reflection.

  • system: The annotation is a system annotation and is not visible to the user.

  • build: Another system annotation that indicates special treating of the annotated value.

After the visibility and annotation type descriptor, the properties are defined. These are key-value pairs, where the key is the property’s name and the value is the property’s value. The properties are defined using the syntax key = value. It is possible to define multiple properties on separate lines within one annotation directive.

Finally, the annotation directive is closed using the .end annotation keyword. Here is an example of an annotation directive in Smali:

1.annotation runtime Lcom/example/MyAnnotation;
2    name = "John Doe"
3    age = .subannotation runtime Lcom/example/Age;
4            value = 30
5          .end subannotation
6.end annotation

The sample defines an annotation named MyAnnotation with two properties: name and age. The name property is a simple string and has a value of "John Doe", and the age property is a sub-annotation with a value of 30. The runtime keyword specifies that the annotation is visible at runtime of the application.