I am learning app development from Android Basics with Compose. I thought I should try learning reverse engineering of applications, and here I am writing this series (hope so) for learning reverse engineering. There are manny ways of doing this with different tools (ghidra, d8 with baksmali, and more) my approach would be to use apktool to compile and decompile and then analyze it. This is just making foundations for next chapter which would be using this knowledge to hack into applications and change few features without having the high level code. Dont worry everything would be ethical.

This series documents my learning process in reverse engineering, with Android as the primary surface. Not as a tutorial promise, not as a security flex, but as a structured record of how understanding actually forms when source code is unavailable.

Prerequisites

  • I would hope that you just know what classes in OOPs are.

Reverse engineering is a methodical process of reading systems you didn't write, understanding constraints you didn't choose, and reasoning about behavior without source-level intent.

This first chapter establishes two things:

  1. What is .apk files
  2. The practical basics of smali, the language you inevitably end up reading

Android APKs: What You Are Actually Reversing

Before touching Smali, it is necessary to understand what an Android APK really is.

An APK (Android Package) is a zip archive with a strict internal structure. Nothing inside it is accidental. Every directory exists because the Android build system and runtime expect it to.

At a high level, an APK contains:

  • Executable bytecode
  • Resources
  • Metadata
  • Optional native binaries
  • Cryptographic signatures

If you want a detail look run the below code (<APP> is the app name and <OUT_DIR> is the directory/folder where you want your output) or simply use a GUI do unzip.

unzip <APP> -d <OUT_DIR>

Executable bytecode

The core logic of an Android app lives in one or more classes.dex files.

  • DEX is Dalvik Executable format
  • It contains all compiled Kotlin/Java bytecode
  • This is what gets translated into Smali

Modern apps often contain:

  • classes.dex
  • classes2.dex, classes3.dex, etc.

These are not special modules. They are a consequence of method count limits and build decisions (there are some restrictions on how the classes[X].dex are divided and how many can there be and other related stuff which I myself have little knowledge of).

Resources

The res/ directory does not contain logic.

It contains structured inputs (much like static/ in web dev) to the runtime:

  • Layout XML
  • Drawables
  • Colors
  • Dimensions
  • Strings
  • Animations

These files are compiled, not interpreted at runtime as plain XML. And anything in res/ is refereed in the source code from the R class. This returns an id which is resolved and mapped at build time using the file resources.arsc.

Metadata

The most important of all is the AndroidManifest.xml. The manifest defines:

  • App identity
  • Permissions
  • Entry points
  • Components exposed to the system
  • SDK constraints

Even when obfuscated, the manifest is readable.

This file tells you:

  • What can start the app
  • What services run in the background
  • What external interaction is allowed

If you want to understand how an app begins execution, you start here — not in Smali.

Signatures and Alignment

APKs are:

  • Signed
  • Zip-aligned

Signatures affect:

  • Installation
  • Updates
  • Runtime trust

Any modification breaks the original signature. Reverse engineering that modifies behavior must account for this explicitly.

What Smali Actually Is

Smali is a human-readable representation of Dalvik bytecode (dalvik is code which android understands). Smali is not decompiled Java. It is not assembly in the CPU sense. It is a textual form of what the Android runtime executes.

Every .smali file corresponds to a single class in the source code. Every method is explicitly declared. Every register is manually visible.

The Structure of a Smali File

A Smali class always begins with structural declarations.

Before we begin lets discuss the data types in smali.

byte - B
short - S
int - I
long - J
float - F
double - D
boolean - Z
char - C
class or interface - L[classname];
void - V

The file starts with class declaration. Here we can see the class is MainActivity (with its complete path starting from com), that it inherits from ComponentActivity and that the file name (where this code lives) is MainActivity.kt.

None

I have pasted the first method ran in application lifecycle. Here we can identify the method name is onCreate, it is a protected method, takes just one object of Bundle class and returns Void (use the data type referece from above).

.method protected onCreate(Landroid/os/Bundle;)V
    .locals 4
    .param p1, "savedInstanceState"    # Landroid/os/Bundle;

    .line 33
    invoke-super {p0, p1}, Landroidx/activity/ComponentActivity;->onCreate(Landroid/os/Bundle;)V

    .line 34
    move-object v0, p0

    check-cast v0, Landroidx/activity/ComponentActivity;

    const/4 v1, 0x3

    const/4 v2, 0x0

    invoke-static {v0, v2, v2, v1, v2}, Landroidx/activity/EdgeToEdge;->enable$default(Landroidx/activity/ComponentActivity;Landroidx/activity/SystemBarStyle;Landroidx/activity/SystemBarStyle;ILjava/lang/Object;)V

    .line 35
    move-object v0, p0

    check-cast v0, Landroidx/activity/ComponentActivity;

    sget-object v1, Lcom/learning/composequad/ComposableSingletons$MainActivityKt;->INSTANCE:Lcom/learning/composequad/ComposableSingletons$MainActivityKt;

    invoke-virtual {v1}, Lcom/learning/composequad/ComposableSingletons$MainActivityKt;->getLambda-3$app_debug()Lkotlin/jvm/functions/Function2;

    move-result-object v1

    const/4 v3, 0x1

    invoke-static {v0, v2, v1, v3, v2}, Landroidx/activity/compose/ComponentActivityKt;->setContent$default(Landroidx/activity/ComponentActivity;Landroidx/compose/runtime/CompositionContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V

    .line 44
    return-void
.end method

The smali code is very explicit so they use the entire path for the class. Every method declare total number of registers. Here there are 4 local registers (v0, v1, .., v3), one parameter (p1), and p0 (the referece to the object of itself). Now lets take an example of how methods are invoked. The fourth line invokes the super class's onCreate method with arguments p0 (the this keyword) and p1.

Conclusion

I did not attempt to fully justify or exhaustively explain Smali in this chapter. For readers who want to go deeper into the language and its execution model, the following resources are useful starting points: