Smali - Decompiling/[Modifying]/Compiling
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the π¬ Discord group or the telegram group or follow us on Twitter π¦ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
Sometimes it is interesting to modify the application code to access hidden information for you (maybe well obfuscated passwords or flags). Then, it could be interesting to decompile the apk, modify the code and recompile it.
Opcodes reference: http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html
Fast Way
Using Visual Studio Code and the APKLab extension, you can automatically decompile, modify, recompile, sign & install the application without executing any command.
Another script that facilitates this task a lot is https://github.com/ax/apk.sh
Split APKs / App Bundles
Modern targets are commonly delivered as split APKs (base.apk + split_config.*.apk) instead of a single monolithic APK. If you patch only base.apk, resources or native libraries can go out of sync and the installation may fail.
Quick triage from a device:
adb shell pm path com.example.app
adb pull /data/app/.../base.apk
adb pull /data/app/.../split_config.arm64_v8a.apk
adb pull /data/app/.../split_config.en.apk
If the target is a split package, either rebuild the whole set or use tooling that joins the APKs first. apk.sh is handy here because it can combine split APKs into a single patchable APK and fix public resource identifiers.
For Frida/Objection-oriented repacking workflows, also check Android Anti-Instrumentation & SSL Pinning Bypass.
Decompile the APK
Using APKTool you can access to the smali code and resources:
apktool d APP.apk
If apktool gives you any error, try installing the latest version
Some interesting files you should look are:
- res/values/strings.xml (and all xmls inside res/values/*)
- AndroidManifest.xml
- Any file with extension .sqlite or .db
If apktool has problems decoding the application take a look to https://ibotpeaches.github.io/Apktool/documentation/#framework-files or try using the argument -r (Do not decode resources). Then, if the problem was in a resource and not in the source code, you wonβt have the problem (you wonβt also decompile the resources).
Change smali code
You can change instructions, change the value of some variables or add new instructions. I change the Smali code using VS Code, you then install the smalise extension and the editor will tell you if any instruction is incorrect.
Some examples can be found here:
Or you can check below some Smali changes explained.
Recompile the APK
After modifying the code you can recompile the code using:
apktool b . #In the folder generated when you decompiled the application
It will compile the new APK inside the dist folder.
If apktool throws an error, try installing the latest version
Sign the new APK
Then, you need to generate a key (you will be asked for a password and for some information that you can fill randomly):
keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias <your-alias>
Finally, sign the new APK:
jarsigner -keystore key.jks path/to/dist/* <your-alias>
jarsigner still works for some quick tests, but for modern Android builds apksigner is preferred because it handles the newer APK signature schemes.
Optimize new application
zipalign is an archive alignment tool that provides important optimisation to Android application (APK) files. More information here.
zipalign [-f] [-v] <alignment> infile.apk outfile.apk
zipalign -v 4 infile.apk
If the APK contains bundled native libraries (lib/*.so), Android now recommends using -P 16 so the .so files are aligned for both 16 KiB and 4 KiB page-size devices:
zipalign -P 16 -f -v 4 infile.apk outfile.apk
Sign the new APK (again?)
If you prefer to use apksigner instead of jarsigner, you should sing the apk after applying the optimization with zipaling. BUT NOTICE THAT YOU ONLY HAVE TO SIGN THE APPLCIATION ONCE WITH jarsigner (before zipalign) OR WITH aspsigner (after zipaling).
apksigner sign --ks key.jks ./dist/mycompiled.apk
A more practical modern flow is:
apktool b . -o dist/app-unsigned.apk
zipalign -P 16 -f -v 4 dist/app-unsigned.apk dist/app-aligned.apk
apksigner sign --ks key.jks --out dist/app-signed.apk dist/app-aligned.apk
apksigner verify --verbose --print-certs dist/app-signed.apk
Important notes:
- If you modify an APK after signing it with
apksigner, the signature is invalidated and you must sign it again. apksigner verify --print-certsis useful to confirm the rebuilt APK is installable and to inspect the certificate that the target will expose at runtime.
Modifying Smali
For the following Hello World Java code:
public static void printHelloWorld() {
System.out.println("Hello World")
}
The Smali code would be:
.method public static printHelloWorld()V
.registers 2
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Hello World"
invoke-virtual {v0,v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
return-void
.end method
The Smali instruction set is available here.
Light Changes
Modify initial values of a variable inside a function
Some variables are defined at the beginning of the function using the opcode const, you can modify its values, or you can define new ones:
#Number
const v9, 0xf4240
const/4 v8, 0x1
#Strings
const-string v5, "wins"
Basic Operations
#Math
add-int/lit8 v0, v2, 0x1 #v2 + 0x1 and save it in v0
mul-int v0,v2,0x2 #v2*0x2 and save in v0
#Move the value of one object into another
move v1,v2
#Condtions
if-ge #Greater or equals
if-le #Less or equals
if-eq #Equals
#Get/Save attributes of an object
iget v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save this.o inside v0
iput v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save v0 inside this.o
#goto
:goto_6 #Declare this where you want to start a loop
if-ne v0, v9, :goto_6 #If not equals, go to: :goto_6
goto :goto_6 #Always go to: :goto_6
Bigger Changes
Smali gotchas that usually break rebuilds
- Prefer increasing
.localswhen you only need temporary registers in the body of an existing method. Parameter registers (p0,p1β¦) are mapped to the highest registers of the method, so switching blindly to.registersoften breaks argument layout. move-result,move-result-wide, andmove-result-objectmust appear immediately after the matchinginvoke-*. Inserting logging or any other opcode between them makes the method invalid.longanddoublevalues are wide values and consume a register pair. If you reuse those registers later, remember thatv10also occupiesv11.- If you need to pass many registers, or very high-numbered ones, use the
/rangevariants such asinvoke-virtual/range.
Logging
#Log win: <number>
iget v5, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Get this.o inside v5
invoke-static {v5}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; #Transform number to String
move-result-object v1 #Move to v1
const-string v5, "wins" #Save "win" inside v5
invoke-static {v5, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I #Logging "Wins: <num>"
Recommendations:
- If you are going to use declared variables inside the function (declared v0,v1,v2β¦) put these lines between the .local
and the declarations of the variables (const v0, 0x1) - If you want to put the logging code in the middle of the code of a function:
- Add 2 to the number of declared variables: Ex: from .locals 10 to .locals 12
- The new variables should be the next numbers of the already declared variables (in this example should be v10 and v11, remember that it starts in v0).
- Change the code of the logging function and use v10 and v11 instead of v5 and v1.
Patching common anti-tamper checks
When an app is repacked, one of the first things that may break is an in-app signature / installer / integrity check. Good strings to search in JADX or in the smali tree are:
GET_SIGNATURESGET_SIGNING_CERTIFICATESapkContentsSignersMessageDigestSHA-256Base64getInstallerPackageNamecom.android.vending
Modern apps often call PackageManager.getPackageInfo(..., GET_SIGNING_CERTIFICATES), hash the signer bytes with MessageDigest, and compare the result with a hardcoded constant. In practice, it is usually easier to patch the final boolean / branch than to rewrite all the signature-handling code.
Example patterns:
# Force a boolean result to "valid"
const/4 v0, 0x1
# Or invert the branch that sends execution to the tamper handler
if-eqz v0, :tamper_detected # original
if-nez v0, :tamper_detected # patched
If the verification code is noisy, look for the last comparison before the error dialog / finish() / System.exit() / telemetry call and patch there instead of touching the entire routine.
Toasting
Remember to add 3 to the number of .locals at the beginning of the function.
This code is prepared to be inserted in the middle of a function (change the number of the variables as necessary). It will take the value of this.o, transform it to String and them make a toast with its value.
const/4 v10, 0x1
const/4 v11, 0x1
const/4 v12, 0x1
iget v10, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I
invoke-static {v10}, Ljava/lang/String;->valueOf(I)Ljava/lang/String;
move-result-object v11
invoke-static {p0, v11, v12}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;
move-result-object v12
invoke-virtual {v12}, Landroid/widget/Toast;->show()V
Loading a Native Library at Startup (System.loadLibrary)
Sometimes you need to preload a native library so it initializes before other JNI libs (e.g., to enable process-local telemetry/logging). You can inject a call to System.loadLibrary() in a static initializer or early in Application.onCreate(). Example smali for a static class initializer (
.class public Lcom/example/App;
.super Landroid/app/Application;
.method static constructor <clinit>()V
.registers 1
const-string v0, "sotap" # library name without lib...so prefix
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
return-void
.end method
Alternatively, place the same two instructions at the start of your Application.onCreate() to ensure the library loads as early as possible:
.method public onCreate()V
.locals 1
const-string v0, "sotap"
invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V
invoke-super {p0}, Landroid/app/Application;->onCreate()V
return-void
.end method
Notes:
- Make sure the correct ABI variant of the library exists under lib/
/ (e.g., arm64-v8a/armeabi-v7a) to avoid UnsatisfiedLinkError. - Loading very early (class static initializer) guarantees the native logger can observe subsequent JNI activity.
Smali Static Analysis / Rule-Based Hunting
After decompiling with apktool, you can scan Smali line-by-line with regex rules to quickly spot anti-analysis logic (root/emulator checks) and likely hardcoded secrets. This is a fast triage technique: treat hits as leads that you must verify in surrounding Smali or reconstructed Java/Kotlin.
Key ideas:
- Library filtering: suppress or tag findings under common third-party namespaces so you focus on app-owned code paths.
- Context hints: require suspicious strings to appear near the APIs that consume them (within the same method, within N lines).
- Confidence: use simple levels (high/medium) to rank leads and reduce false positives.
Example library prefixes to suppress by default:
Landroidx/
Lkotlin/
Lkotlinx/
Lcom/google/
Lcom/squareup/
Lokhttp3/
Lokio/
Lretrofit2/
Example detection rules (regex + context heuristics):
{
"category": "root_check",
"regex_patterns": [
"(?i)invoke-static .*Runtime;->getRuntime\\(\\).*->exec\\(.*\\"(su|magisk|busybox)\\"",
"(?i)const-string [vp0-9, ]+\\"(/system/xbin/su|/system/bin/su|/sbin/su)\\""
],
"context_hint": "Only report when the same method also calls File;->exists/canExecute or Runtime;->exec."
}
Additional heuristics that work well in practice:
- Root package/path checks: require nearby
PackageManager;->getPackageInfoorFile;->existscalls for strings likecom.topjohnwu.magiskor/data/local/tmp. - Emulator checks: pair suspicious literals (e.g.,
ro.kernel.qemu,generic,goldfish) with nearbyBuild.*getters and string comparisons (->equals,->contains,->startsWith). - Hardcoded secrets: flag
const-stringonly when a nearby.fieldormove-resultidentifier includes keywords likepassword,token,api_key. Explicitly ignore UI-only markers such asAutofillType,InputType,EditorInfo.
Rule-driven scanners like PulseAPK Core implement this model to quickly surface anti-analysis logic and potential secrets in Smali.
References
- PulseAPK Core
- PulseAPK Smali Detection Rules
- SoTap: Lightweight in-app JNI (.so) behavior logger β github.com/RezaArbabBot/SoTap
- Android Developers: apksigner and zipalign
- apk.sh: github.com/ax/apk.sh
Tip
Learn & practice AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the π¬ Discord group or the telegram group or follow us on Twitter π¦ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.


