smali-changes.md (15701B)
1 --- 2 title: "Smali - Decompiling/[Modifying]/Compiling" 3 section: "Mobile" 4 sectionSlug: "mobile-pentesting" 5 sourcePath: "src/mobile-pentesting/android-app-pentesting/smali-changes.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/mobile-pentesting/android-app-pentesting/smali-changes.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # Smali - Decompiling/[Modifying]/Compiling 14 15 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. 16 17 **Opcodes reference:** [http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html](http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html) 18 19 ## Fast Way 20 21 Using **Visual Studio Code** and the [APKLab](https://github.com/APKLab/APKLab) extension, you can **automatically decompile**, modify, **recompile**, sign & install the application without executing any command. 22 23 Another **script** that facilitates this task a lot is [**https://github.com/ax/apk.sh**](https://github.com/ax/apk.sh)<sup>[[6]](#references)</sup> 24 25 ### Split APKs / App Bundles 26 27 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. 28 29 Quick triage from a device: 30 31 ```bash 32 adb shell pm path com.example.app 33 adb pull /data/app/.../base.apk 34 adb pull /data/app/.../split_config.arm64_v8a.apk 35 adb pull /data/app/.../split_config.en.apk 36 ``` 37 38 If the target is a split package, either rebuild the whole set or use tooling that **joins the APKs first**. [**apk.sh**](https://github.com/ax/apk.sh) is handy here because it can combine split APKs into a single patchable APK and fix public resource identifiers.<sup>[[6]](#references)</sup>\ 39 For Frida/Objection-oriented repacking workflows, also check [Android Anti-Instrumentation & SSL Pinning Bypass](/hacktricks/mobile-pentesting/android-app-pentesting/android-anti-instrumentation-and-ssl-pinning-bypass). 40 41 ## Decompile the APK 42 43 Using APKTool you can access to the **smali code and resources**: 44 45 ```bash 46 apktool d APP.apk 47 ``` 48 49 If **apktool** gives you any error, try[ installing the **latest version**](https://ibotpeaches.github.io/Apktool/install/) 50 51 Some **interesting files you should look are**: 52 53 - _res/values/strings.xml_ (and all xmls inside res/values/*) 54 - _AndroidManifest.xml_ 55 - Any file with extension _.sqlite_ or _.db_ 56 57 If `apktool` has **problems decoding the application** take a look to [https://ibotpeaches.github.io/Apktool/documentation/#framework-files](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). 58 59 ## Change smali code 60 61 You can **change** **instructions**, change the **value** of some variables or **add** new instructions. I change the Smali code using [**VS Code**](https://code.visualstudio.com), you then install the **smalise extension** and the editor will tell you if any **instruction is incorrect**.\ 62 Some **examples** can be found here: 63 64 - [Smali changes examples](/hacktricks/mobile-pentesting/android-app-pentesting/smali-changes) 65 - [Google CTF 2018 - Shall We Play a Game?](/hacktricks/mobile-pentesting/android-app-pentesting/google-ctf-2018-shall-we-play-a-game) 66 67 Or you can [**check below some Smali changes explained**](/hacktricks/mobile-pentesting/android-app-pentesting/smali-changes#modifying-smali). 68 69 ## Recompile the APK 70 71 After modifying the code you can **recompile** the code using: 72 73 ```bash 74 apktool b . #In the folder generated when you decompiled the application 75 ``` 76 77 It will **compile** the new APK **inside** the _**dist**_ folder. 78 79 If **apktool** throws an **error**, try[ installing the **latest version**](https://ibotpeaches.github.io/Apktool/install/) 80 81 ### **Sign the new APK** 82 83 Then, you need to **generate a key** (you will be asked for a password and for some information that you can fill randomly): 84 85 ```bash 86 keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias <your-alias> 87 ``` 88 89 Finally, **sign** the new APK: 90 91 ```bash 92 jarsigner -keystore key.jks path/to/dist/* <your-alias> 93 ``` 94 95 `jarsigner` still works for some quick tests, but for modern Android builds **`apksigner` is preferred** because it handles the newer APK signature schemes.<sup>[[4]](#references)</sup> 96 97 ### Optimize new application 98 99 **zipalign** is an archive alignment tool that provides important optimisation to Android application (APK) files. [More information here](https://developer.android.com/studio/command-line/zipalign).<sup>[[7]](#references)</sup> 100 101 ```bash 102 zipalign [-f] [-v] <alignment> infile.apk outfile.apk 103 zipalign -v 4 infile.apk 104 ``` 105 106 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:<sup>[[5]](#references)</sup> 107 108 ```bash 109 zipalign -P 16 -f -v 4 infile.apk outfile.apk 110 ``` 111 112 ### **Sign the new APK (again?)** 113 114 If you **prefer** to use [**apksigner**](https://developer.android.com/studio/command-line/) instead of `jarsigner`, **sign the APK** after applying the `zipalign` optimization. **Sign the application only once**: use `jarsigner` before `zipalign`, or `apksigner` after `zipalign`. 115 116 ```bash 117 apksigner sign --ks key.jks ./dist/mycompiled.apk 118 ``` 119 120 A more practical modern flow is: 121 122 ```bash 123 apktool b . -o dist/app-unsigned.apk 124 zipalign -P 16 -f -v 4 dist/app-unsigned.apk dist/app-aligned.apk 125 apksigner sign --ks key.jks --out dist/app-signed.apk dist/app-aligned.apk 126 apksigner verify --verbose --print-certs dist/app-signed.apk 127 ``` 128 129 Important notes: 130 131 - If you **modify** an APK **after** signing it with `apksigner`, the signature is invalidated and you must sign it again. 132 - `apksigner verify --print-certs` is useful to confirm the rebuilt APK is installable and to inspect the certificate that the target will expose at runtime. 133 134 ## Modifying Smali 135 136 For the following Hello World Java code: 137 138 ```java 139 public static void printHelloWorld() { 140 System.out.println("Hello World") 141 } 142 ``` 143 144 The Smali code would be: 145 146 ```java 147 .method public static printHelloWorld()V 148 .registers 2 149 sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream; 150 const-string v1, "Hello World" 151 invoke-virtual {v0,v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V 152 return-void 153 .end method 154 ``` 155 156 The Smali instruction set is available [here](https://source.android.com/devices/tech/dalvik/dalvik-bytecode#instructions). 157 158 ### Light Changes 159 160 ### Modify initial values of a variable inside a function 161 162 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: 163 164 ```bash 165 #Number 166 const v9, 0xf4240 167 const/4 v8, 0x1 168 #Strings 169 const-string v5, "wins" 170 ``` 171 172 ### Basic Operations 173 174 ```bash 175 #Math 176 add-int/lit8 v0, v2, 0x1 #v2 + 0x1 and save it in v0 177 mul-int v0,v2,0x2 #v2*0x2 and save in v0 178 179 #Move the value of one object into another 180 move v1,v2 181 182 #Condtions 183 if-ge #Greater or equals 184 if-le #Less or equals 185 if-eq #Equals 186 187 #Get/Save attributes of an object 188 iget v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save this.o inside v0 189 iput v0, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Save v0 inside this.o 190 191 #goto 192 :goto_6 #Declare this where you want to start a loop 193 if-ne v0, v9, :goto_6 #If not equals, go to: :goto_6 194 goto :goto_6 #Always go to: :goto_6 195 ``` 196 197 ### Bigger Changes 198 199 ### Smali gotchas that usually break rebuilds 200 201 - Prefer increasing **`.locals`** when 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 `.registers` often breaks argument layout. 202 - `move-result`, `move-result-wide`, and `move-result-object` **must appear immediately after** the matching `invoke-*`. Inserting logging or any other opcode between them makes the method invalid. 203 - `long` and `double` values are **wide** values and consume a **register pair**. If you reuse those registers later, remember that `v10` also occupies `v11`. 204 - If you need to pass many registers, or very high-numbered ones, use the `/range` variants such as `invoke-virtual/range`. 205 206 ### Logging 207 208 ```bash 209 #Log win: <number> 210 iget v5, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I #Get this.o inside v5 211 invoke-static {v5}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; #Transform number to String 212 move-result-object v1 #Move to v1 213 const-string v5, "wins" #Save "win" inside v5 214 invoke-static {v5, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I #Logging "Wins: <num>" 215 ``` 216 217 Recommendations: 218 219 - If you are going to use declared variables inside the function (declared `v0`, `v1`, `v2`...) put these lines after the `.locals <number>` directive and before the existing variable instructions (for example, `const v0, 0x1`). 220 - If you want to put the logging code in the middle of the code of a function: 221 - Add 2 to the number of declared variables: Ex: from _.locals 10_ to _.locals 12_ 222 - 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). 223 - Change the code of the logging function and use _v10_ and _v11_ instead of _v5_ and _v1_. 224 225 ### Patching common anti-tamper checks 226 227 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: 228 229 - `GET_SIGNATURES` 230 - `GET_SIGNING_CERTIFICATES` 231 - `apkContentsSigners` 232 - `MessageDigest` 233 - `SHA-256` 234 - `Base64` 235 - `getInstallerPackageName` 236 - `com.android.vending` 237 238 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. 239 240 Example patterns: 241 242 ```text 243 # Force a boolean result to "valid" 244 const/4 v0, 0x1 245 246 # Or invert the branch that sends execution to the tamper handler 247 if-eqz v0, :tamper_detected # original 248 if-nez v0, :tamper_detected # patched 249 ``` 250 251 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. 252 253 ### Toasting 254 255 Remember to add 3 to the number of _.locals_ at the beginning of the function. 256 257 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. 258 259 ```bash 260 const/4 v10, 0x1 261 const/4 v11, 0x1 262 const/4 v12, 0x1 263 iget v10, p0, Lcom/google/ctf/shallweplayagame/GameActivity;->o:I 264 invoke-static {v10}, Ljava/lang/String;->valueOf(I)Ljava/lang/String; 265 move-result-object v11 266 invoke-static {p0, v11, v12}, Landroid/widget/Toast;->makeText(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast; 267 move-result-object v12 268 invoke-virtual {v12}, Landroid/widget/Toast;->show()V 269 ``` 270 271 ### Loading a Native Library at Startup (System.loadLibrary) 272 273 Sometimes you need to preload a native library so it initializes before other JNI libs (e.g., to enable process-local telemetry/logging).<sup>[[3]](#references)</sup> You can inject a call to `System.loadLibrary()` in a static initializer or early in `Application.onCreate()`. Example Smali for a static class initializer (`<clinit>`): 274 275 ```text 276 .class public Lcom/example/App; 277 .super Landroid/app/Application; 278 279 .method static constructor <clinit>()V 280 .registers 1 281 const-string v0, "sotap" # library name without lib...so prefix 282 invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V 283 return-void 284 .end method 285 ``` 286 287 Alternatively, place the same two instructions at the start of your Application.onCreate() to ensure the library loads as early as possible: 288 289 ```text 290 .method public onCreate()V 291 .locals 1 292 293 const-string v0, "sotap" 294 invoke-static {v0}, Ljava/lang/System;->loadLibrary(Ljava/lang/String;)V 295 296 invoke-super {p0}, Landroid/app/Application;->onCreate()V 297 return-void 298 .end method 299 ``` 300 301 Notes: 302 - Make sure the correct ABI variant of the library exists under `lib/<abi>/` (e.g., `arm64-v8a`/`armeabi-v7a`) to avoid `UnsatisfiedLinkError`. 303 - Loading very early (class static initializer) guarantees the native logger can observe subsequent JNI activity. 304 305 ## Smali Static Analysis / Rule-Based Hunting 306 307 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.<sup>[[1]](#references)[[2]](#references)</sup> 308 309 Key ideas: 310 - **Library filtering**: suppress or tag findings under common third-party namespaces so you focus on app-owned code paths. 311 - **Context hints**: require suspicious strings to appear near the APIs that consume them (within the same method, within N lines). 312 - **Confidence**: use simple levels (high/medium) to rank leads and reduce false positives. 313 314 Example library prefixes to suppress by default: 315 ```text 316 Landroidx/ 317 Lkotlin/ 318 Lkotlinx/ 319 Lcom/google/ 320 Lcom/squareup/ 321 Lokhttp3/ 322 Lokio/ 323 Lretrofit2/ 324 ``` 325 326 Example detection rules (regex + context heuristics): 327 ```json 328 { 329 "category": "root_check", 330 "regex_patterns": [ 331 "(?i)invoke-static .*Runtime;->getRuntime\\(\\).*->exec\\(.*\\"(su|magisk|busybox)\\"", 332 "(?i)const-string [vp0-9, ]+\\"(/system/xbin/su|/system/bin/su|/sbin/su)\\"" 333 ], 334 "context_hint": "Only report when the same method also calls File;->exists/canExecute or Runtime;->exec." 335 } 336 ``` 337 338 Additional heuristics that work well in practice: 339 - **Root package/path checks**: require nearby `PackageManager;->getPackageInfo` or `File;->exists` calls for strings like `com.topjohnwu.magisk` or `/data/local/tmp`. 340 - **Emulator checks**: pair suspicious literals (e.g., `ro.kernel.qemu`, `generic`, `goldfish`) with nearby `Build.*` getters and string comparisons (`->equals`, `->contains`, `->startsWith`). 341 - **Hardcoded secrets**: flag `const-string` only when a nearby `.field` or `move-result` identifier includes keywords like `password`, `token`, `api_key`. Explicitly ignore UI-only markers such as `AutofillType`, `InputType`, `EditorInfo`. 342 343 Rule-driven scanners like PulseAPK Core implement this model to quickly surface anti-analysis logic and potential secrets in Smali. 344 345 ## References 346 - [1] [PulseAPK Core](https://github.com/deemoun/PulseAPK-Core) 347 - [2] [PulseAPK Smali Detection Rules](https://github.com/deemoun/PulseAPK-Core/blob/main/APK_ANALYSIS_RULES.md) 348 - [3] [SoTap: Lightweight in-app JNI (.so) behavior logger](https://github.com/RezaArbabBot/SoTap) 349 - [4] [Android Developers: apksigner](https://developer.android.com/tools/apksigner) 350 - [5] [Android Developers: zipalign](https://developer.android.com/tools/zipalign) 351 - [6] [apk.sh](https://github.com/ax/apk.sh) 352 - [7] [developer.android.com - Command Line - Zipalign](https://developer.android.com/studio/command-line/zipalign)