Android Reverse Engineering Notes
Prerequisite
- ApkTool: A decompiler tool;
- Android Development SDK: Recompile smali code and sign the api
- Java Development SDK: Run apktool, create keystore for signing
General Workflow
Obtain APK
Download apk file from the Internet or CIC website if have access.
Obtain APK from an Android device where the application has been installed:
1 2adb shell pm path com.xxx.xxx adb pull /output/of/the/previous/command
Decompile APK to smali code
Using apktool to decompile .dex binaries into smali code, which is similar to assembly code used in native libraries.
| |
where:
--no-debug-info: don’t write out debug info, including.param,.linedirectives.--force: overwrite the output directory if it already exists.--no-res: don’t extract resource files. This flag could speed up decompiling and recompiling, and useful if resources couldn’t be recompiled. However, this flag prevents making a release application debuggable because it does not extract the manifest file.
Identify Modification Points
Every java/kotlin class, including nested class, has a corresponding .smali file, but find the smali file for a given class isn’t straitforward as their names are obfuscated in a released apk. There are still some methods to infer their locations.
1. find string literals
String literals, such as trace names and log messages, remain the same in smali code.. For example, I want to find smali class corresponding to DefaultAudioSink of ExoPlayer, and I find DefaultAudioSink.java contains a log:
| |
I could find the smali with command:
| |
So, J1.S is obfuscated name for androidx.media3.exoplayer.audio.DefaultAudioSink.
2. compare input parameters and return values
After the target class has been located, the most efficient way to identify a smali function is to compare its input parameters and return value with candidate java functions.
For example, if we have a function:
.method private x0(Z)Ljava/util/List;we could know the java function must like:
| |
After we identify which class does x0 belong to, the function signature could help narrow down the range to search function.
3. find AOSP’s interface functions
AOSP’s interface functions like MediaCodec’s won’t be mangled in smali code. Therefore, I can directly search for a AOSP function like:
| |
By cross validation with ExoPlayer’s code, it could be known that O1.K is mangled androidx.media3.exoplayer.mediacodec.SynchronousMediaCodecAdapter
4. translate smali code to Java
Reading complex smali functions could be very hard. Converting them back to Java code could help this.
We need first install jadx, then unzip the apk and decompile a target .dex file.
| |
Now, we can check decompiled source java code in the class0 directory. jadx also supports to decompile a whole .apk file, but it’s unnecessary for most use cases.
Modify code
Once target code is located, we can modify it to:
- change behavior;
- add log;
- add trace events;
- print backtrace;
- etc.
Repack the apk
Recompile the code and resources
1apktool b release -o recompile.apkThis command simply recompile the smali code and pack them into a new apk. Add other flags as you need: You may use
apktool -advanceto check out what flags are supported.Create a keystore if not have one already
Make sure that JAVA environment has been set up properly, Run command:
1jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore resign.keystore recompile.apk recompileEnter fields interactively when the command asks. Name and organazation don’t have to be real. Please remember the password you set to it.
Align the apk(Optional)
Some applications, especially those who explicitly disable the “android:extractNativeLibs” property in the manifest, could fail to be installed after recompiled. In such cases, you will see an error message like:
1[INSTALL_FAILED_INVALID_APK: Failed to extract native libraries, res=-2]To solve this, the recompiled apk should be aligned with 4 bytes:
1zipalign -p -f 4 recompile.apk align.apkSign the apk
Make sure path of the Android SDK has been exported to $PATH. For example:
1export PATH=/Users/zaijun/Library/Android/sdk/build-tools/30.0.3:$PATHThen, sign the apk with:
1apksigner sign -verbose --v4-signing-enabled=false -ks ./resign.keystore --out signed.apk ./recompile.apkThis command will ask for the password you set in the step 2. Note that
--v4-signing-enabled=falseflag is highly recommended, as v4 schema will cause some old systems crashing.Install the application
The signed apk can now be installed on a device. It’s fine to use overwrite flag “-r”:
1adb install -r signed.apk
Troubleshooting
Practical Smali Code Snippets
Debug Log
# print out an Uri object
.method private printUri(Landroid/net/Uri;)V
.locals 2
const-string v0, "mydebug"
# tostring
invoke-virtual {p1}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v1
invoke-static {v0, v1}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;)I
.end methodUsage example
# p1 contains an Uri object
invoke-direct {p0, p1}, Lcom/dss/sdk/media/adapters/exoplayer/AdSourceEventListener;->printUri(Landroid/net/Uri;)VAdd Traces
Code
const-string v0, "MyTraceName"
invoke-static {v0}, Landroid/os/Trace;->beginSection(Ljava/lang/String;)V
# wrapped code
invoke-static {}, Landroid/os/Trace;->endSection()VPrint Backtrace
Code
.method private printBacktrace()V
.locals 3
new-instance v0, Ljava/lang/Exception;
invoke-direct {v0}, Ljava/lang/Exception;-><init>()V
const-string v1, "mydebug"
const-string v2, "printBacktrace"
invoke-static {v1, v2, v0}, Landroid/util/Log;->e(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Throwable;)I
return-void
.end methodUsage example
invoke-direct {p0}, Lcom/dss/sdk/media/adapters/exoplayer/AdSourceEventListener;->printBacktrace()VConstructor Log
Code
const-string v0, "mydebug-AdPlaybackEndEvent"
invoke-virtual {p0}, Ljava/lang/Object;->toString()Ljava/lang/String;
move-result-object v1
invoke-static {v0, v1}, Landroid/util/Log;->d(Ljava/lang/String;Ljava/lang/String;)I