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
andandroid.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 ofMyClass
isOuterClass
.@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 are0x0008
(which means it is static), the name of the class isMyClass
, and the outer class isOuterClass
.
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:
¶ 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 andclinit
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 usev0
andv1
within the method.Line 4:
The instruction
sget-object
loads thePrintStream
object named out into the first register (v0
).Line 6:
The instruction
const-string
defines a string variable that will be loaded into the registerv1
.Line 8:
With
invoke-virtual
we are calling the method println of the Java classPrintStream
to print our string in registerv1
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.