Android Software Reverse Engineering & Decompilation

Xposed + Smali: Injecting Custom Code and Modifying App Behavior at Runtime

Google AdSense Native Placement - Horizontal Top-Post banner

Introduction to Runtime Code Injection with Xposed and Smali

Android application security and functionality often rely on client-side logic. For developers, researchers, and penetration testers, the ability to inspect, modify, and even inject custom code into Android applications at runtime is an invaluable skill. This article delves into combining the power of the Xposed Framework for dynamic runtime hooking with Smali bytecode manipulation for static code injection, offering a comprehensive guide to advanced app modification.

The Xposed Framework allows you to hook methods of an application or system service without modifying the original APK. Instead, it operates by patching methods in memory during the app’s loading process. Smali, on the other hand, is a human-readable assembly-like language for Dalvik bytecode, enabling static modifications directly into an APK’s compiled code. Together, they provide unparalleled control over app behavior.

Prerequisites and Environment Setup

Before diving in, ensure you have the following tools and basic understanding:

  • Rooted Android Device or Emulator: With Xposed Framework installed and active.
  • Android SDK: Including ADB.
  • Java Development Kit (JDK): For compiling Java/Kotlin code.
  • Android Studio: For Xposed module development.
  • Apktool: For decompiling and recompiling APKs to/from Smali.
  • Jadx or Ghidra: For static analysis and understanding app structure.
  • Basic knowledge: Java/Kotlin, Android application structure, and command-line usage.

Setting up an Xposed Development Project

Create a new Android Studio project. Add the Xposed API as a provided dependency in your build.gradle file and configure the Xposed module details in AndroidManifest.xml.

// build.gradle (app-level)
dependencies {
    implementation 'de.robv.android.xposed:api:82'
    provided 'de.robv.android.xposed:api:82:sources'
}

// AndroidManifest.xml
<application ...>
    <meta-data
        android:name="xposedmodule"
        android:value="true" />
    <meta-data
        android:name="xposeddescription"
        android:value="A powerful Xposed module for runtime modification." />
    <meta-data
        android:name="xposedminversion"
        android:value="82" />
    ...
</application>

Identifying Target Methods and Classes

The first step in any modification is to understand the target application. Use tools like Jadx or Ghidra to decompile the APK and analyze its source code. Look for methods that control key functionalities, security checks, or data processing. For this tutorial, let’s assume we want to modify a hypothetical app’s UserAuthManager.checkPassword(String password) method.

Developing Your First Xposed Module

An Xposed module typically implements the IXposedHookLoadPackage interface. This interface provides the handleLoadPackage method, which is the entry point for your module.

package com.example.myxposedmodule;

import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam;

public class MainHook implements IXposedHookLoadPackage {
    private static final String TARGET_PACKAGE = "com.targetapp.example";

    @Override
    public void handleLoadPackage(LoadPackageParam lpparam) throws Throwable {
        if (!lpparam.packageName.equals(TARGET_PACKAGE)) {
            return;
        }

        XposedBridge.log("Loaded app: " + lpparam.packageName);

        // Hooking the checkPassword method
        XposedHelpers.findAndHookMethod(
            TARGET_PACKAGE + ".UserAuthManager",
            lpparam.classLoader,
            "checkPassword",
            String.class,
            new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    XposedBridge.log("Attempting to log in with password: " + param.args[0]);
                    // Bypass logic: always return true
                    param.setResult(true);
                }
                
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    XposedBridge.log("Password check result (after hook): " + param.getResult());
                }
            }
        );
    }
}

In this example, we’re targeting com.targetapp.example. The findAndHookMethod call identifies the checkPassword method within UserAuthManager. The beforeHookedMethod callback allows us to inspect arguments (param.args[0] for the password) and modify the method’s return value (param.setResult(true)), effectively bypassing the password check.

Introduction to Smali for Static Code Injection

While Xposed is excellent for dynamic modification, sometimes you need to inject entirely new functionality or modify core logic that’s hard to hook cleanly. This is where Smali comes in. We can decompile an APK, modify its Smali code, and recompile it.

Decompiling and Modifying with Apktool

First, decompile the target APK:

apktool d target_app.apk -o target_app_smali

This creates a directory target_app_smali containing Smali files (.smali) and other resources. Navigate through the Smali files to find the class you want to modify or add code to. Let’s say we want to add a new static method logCustomMessage(String message) to UserAuthManager that can be called by our Xposed module or other parts of the app.

Locate target_app_smali/smali/com/targetapp/example/UserAuthManager.smali and add the following Smali code:

.method public static logCustomMessage(Ljava/lang/String;)V
    .locals 1

    .param p0, "message"    # Ljava/lang/String;

    sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;

    invoke-virtual {v0, p0}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V

    return-void
.end method

This Smali code defines a new public static method that takes a String and prints it to System.out (which typically goes to logcat in Android).

Recompiling and Signing

After making your Smali modifications, recompile the APK:

apktool b target_app_smali -o target_app_modified.apk

Finally, sign the modified APK. You can use apksigner or jarsigner:

keytool -genkeypair -v -keystore my-release-key.keystore -alias alias_name -keyalg RSA -keysize 2048 -validity 10000
apksigner sign --ks my-release-key.keystore target_app_modified.apk

Install this modified APK on your device, disabling the original application first.

Combining Xposed and Smali: Dynamic Interaction with Static Injection

Now, let’s enhance our Xposed module to interact with the new Smali method we injected. We can call UserAuthManager.logCustomMessage() from our Xposed hook.

// Inside handleLoadPackage after your previous hook
        XposedHelpers.findAndHookMethod(
            TARGET_PACKAGE + ".UserAuthManager",
            lpparam.classLoader,
            "checkPassword",
            String.class,
            new XC_MethodHook() {
                @Override
                protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                    XposedBridge.log("Attempting to log in with password: " + param.args[0]);
                    
                    // Call our custom Smali method through reflection
                    Class<?> userAuthManagerClass = XposedHelpers.findClass(TARGET_PACKAGE + ".UserAuthManager", lpparam.classLoader);
                    XposedHelpers.callStaticMethod(userAuthManagerClass, "logCustomMessage", "Xposed called custom Smali method: " + param.args[0]);

                    param.setResult(true); // Bypass
                }
                
                @Override
                protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                    XposedBridge.log("Password check result (after hook): " + param.getResult());
                }
            }
        );

By using XposedHelpers.callStaticMethod, our Xposed module dynamically invokes the logCustomMessage method that was statically injected into the app via Smali. This demonstrates a powerful synergy: Smali for permanent, complex code additions, and Xposed for dynamic, conditional interaction with that added code or existing app logic.

Advanced Considerations and Best Practices

  • Obfuscation: Heavily obfuscated apps make finding target methods and classes challenging. Tools like Frida or dynamic debugging can help map runtime names to obfuscated ones.
  • Error Handling: Always wrap Xposed hooks in try-catch blocks and use XposedBridge.log() for debugging, as errors in your module can crash the target app.
  • Method Signatures: Be precise with method signatures (parameter types) when using findAndHookMethod. Refer to decompilers for exact types.
  • System Stability: Xposed modules modify core processes; poorly written modules can lead to system instability. Test thoroughly.
  • Permission Bypass: Smali can be used to bypass permission checks or modify permission-related logic before runtime, while Xposed can intercept and alter permission API calls dynamically.

Conclusion

The combination of Xposed and Smali provides an incredibly versatile toolkit for Android app modification. Xposed offers dynamic runtime control, ideal for bypassing checks or altering behavior on the fly, while Smali allows for static injection of entirely new code or deep structural modifications. Mastering both empowers you to reverse engineer, patch, and extend Android applications beyond their original design, opening doors for security research, custom enhancements, and more.

Android Mobile Specs & Compare Directory

Are you researching mobile hardware properties, processor SoCs, GPU chipsets, or RAM configurations? Access our complete specs catalog to compare up to 5 devices side-by-side!

Compare Devices Specs →
Google AdSense Inline Placement - Content Footer banner