Native Android Reverse Engineering Tutorial#1: Patching/Modifying String within Native Android App
27 Apr 2013Aim:
To learn to:
- Decompile the Native Android App File (.apk) into Java code (.java)
- Disassemble the Shared Object File (.so) into ARM Assembler Opcodes.
- Use of tools like IDA (Interactive Disassembler), dex2jar, jd-gui, etc.
Prerequisite:
- http://www.shubhamaher.blogspot.in/2013/01/android-reverse-engineering-tutorial1.html
- Knowledge of basic UNIX commands.
Tools Needed:
- Android SDK (for adb and Emulators )
- Dex2jar
- AXMLPrinter2.jar (for converting a binary XML into textual XML)
- jd-gui
- signapk.jar (for re-signing the modified APK)
Overview:
- The general overview of steps required to carry for reversing a Native Android app are:
Steps:
DOWNLOAD
Step#1:
I have put together all the tools and the target crackme APK file in one directory called "mycracklab", zipped and uploaded it. Download the mycracklab.zip and extract it anywhere you like. I have the "mycracklab" directory at /home/shubhuntu/mycracklab/
TESTING the TARGET CRACKME
$ pwd
/home/shubhuntu/mycracklab
$ ls
crackme.native-1.apk tools
Step#2:
Viewing files in the APK. An APK is a ZIP file with a .apk extension. So we use the "zipinfo" command.
$ zipinfo crackme.native-1.apk
Archive: crackme.native-1.apk
Zip file size: 153346 bytes, number of entries: 8
-rw---- 2.0 fat 1436 bX defN 13-Apr-08 16:45 AndroidManifest.xml
-rw---- 1.0 fat 576 b- stor 13-Apr-08 16:45 resources.arsc
-rw---- 2.0 fat 2924 bl defN 13-Apr-08 16:45 classes.dex
-rw---- 2.0 fat 268812 bl defN 13-Mar-13 22:29 lib/armeabi/gdbserver
-rw---- 2.0 fat 13432 bl defN 13-Apr-08 16:26 lib/armeabi/libhello-jni.so
-rw---- 2.0 fat 409 bl defN 13-Apr-08 16:45 META-INF/MANIFEST.MF
-rw---- 2.0 fat 462 bl defN 13-Apr-08 16:45 META-INF/CERT.SF
-rw---- 2.0 fat 1203 bl defN 13-Apr-08 16:45 META-INF/CERT.RSA
8 files, 289254 bytes uncompressed, 152306 bytes compressed: 47.3%
Step#3:
Starting the emulator.
Using the Android's AVD Manager I have started an emulator running Android 4.2 that is API Level 17:
Step#4:
Installing the APK in the emulator using adb.
To install, I will use the Android Debug Bridge i.e. adb which is present in the "platform-tools" directory of the Android SDK.
$ cd /opt/android/adt-bundle-linux-x86/sdk/platform-tools/
shubhuntu@elf:/opt/android/adt-bundle-linux-x86/sdk/platform-tools$ ls
aapt adb aidl api dexdump dx fastboot lib llvm-rs-cc NOTICE.txt renderscript source.properties
$ ./adb install /home/shubhuntu/mycracklab/crackme.native-1.apk
987 KB/s (153346 bytes in 0.151s)
pkg: /data/local/tmp/crackme.native-1.apk
Success
If the command succeeds, that means it is installed and an icon for the app appears in the menu of emulator:
Step#5:
Click on the app to start the crackme:
As seen the crackme displays the message "Hello from JNI !". Our aim is to modify this message string.
Let us begin our hunt for the message.
Remember NOT to close the emulator !
DECOMPILATION (.dex to .jar to *.java files)
Step#6:
$ cd /home/shubhuntu/mycracklab/
$ unzip -d extracted crackme.native-1.apk
Archive: crackme.native-1.apk
inflating: extracted/AndroidManifest.xml
extracting: extracted/resources.arsc
inflating: extracted/classes.dex
inflating: extracted/lib/armeabi/gdbserver
inflating: extracted/lib/armeabi/libhello-jni.so
inflating: extracted/META-INF/MANIFEST.MF
inflating: extracted/META-INF/CERT.SF
inflating: extracted/META-INF/CERT.RSA
$ cd extracted/
$ ls
AndroidManifest.xml classes.dex lib META-INF resources.arsc
Step#7:
Conversion of the "classes.dex" file to "classes-dex2jar.jar" file.
$ sh ../tools/dex2jar-0.0.9.13/d2j-dex2jar.sh classes.dex
dex2jar classes.dex -> classes-dex2jar.jar
Step#8:
Step#8.1:
Start the jd-gui tool.
$ jd-gui classes-dex2jar.jar &
[1] 4371
Step#8.2:
Navigate to "File" ==> "Save All Sources" inside the jd-gui.
Keep the default filename as it is: "classes-dex2jar.src.zip"
Click on "Save" button.
Step#8.3:
Extraction of the *.java files into a directory called "javacode"
$ ls
AndroidManifest.xml classes.dex classes-dex2jar.jar classes-dex2jar.src.zip jd-gui.cfg lib META-INF resources.arsc
$ unzip classes-dex2jar.src.zip -d javacode
Archive: classes-dex2jar.src.zip
creating: javacode/android/
creating: javacode/android/annotation/
inflating: javacode/android/annotation/SuppressLint.java
inflating: javacode/android/annotation/TargetApi.java
creating: javacode/com/
creating: javacode/com/example/
creating: javacode/com/example/hellojni/
inflating: javacode/com/example/hellojni/BuildConfig.java
inflating: javacode/com/example/hellojni/HelloJni.java
inflating: javacode/com/example/hellojni/R.java
IDENTIFYING the PACKAGES, CLASSES and METHODS to PATCH
Step#9:
Converting the Binary XML into a Textual XML using AXMLPrinter2.jar
$ java -jar ../tools/AXMLPrinter2.jar AndroidManifest.xml > AndroidManifest.xml.text
$ ls
AndroidManifest.xml AndroidManifest.xml.text classes.dex classes-dex2jar.jar classes-dex2jar.src.zip javacode jd-gui.cfg lib META-INF resources.arsc
$ vim AndroidManifest.xml.text
This is how the textual XML will look:
We come to know that the initial activity is ".HelloJni" which corresponds to "HelloJni.java" source file and is in "com.example.hellojni" package.
Open "HelloJni.java" in any of your favourite Java editor.
Following is the code of "HelloJni.java":
Its quite evident that the static code block which gets executed first is loading a native library named "hello-jni".
package com.example.hellojni;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class HelloJni extends Activity
{
static
{
System.loadLibrary("hello-jni");
}
public void onCreate(Bundle paramBundle)
{
super.onCreate(paramBundle);
TextView localTextView = new TextView(this);
localTextView.setText(stringFromJNI());
setContentView(localTextView);
}
public native String stringFromJNI();
public native String unimplementedStringFromJNI();
}
/* Location: classes-dex2jar.jar
* Qualified Name: com.example.hellojni.HelloJni
* JD-Core Version: 0.6.2
*/
So its full name will be "libhello-jni.so"
Step#10:
Verifying the shared object file "libhello-jni.so"
Also you can observe from the "HelloJni.java" file, that the "setText()" method sets the text using the native method declared as:
$ file ./lib/armeabi/libhello-jni.so
./lib/armeabi/libhello-jni.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped
public native String stringFromJNI();
DISASSEMBLING the LIBRARY FILE
Step#11:
We will now look for this "stringFromJNI()" method in the disassembled view of IDA inside the native file "libhello-jni.so".
I am running the Windows version of IDA on Ubuntu using Wine but you can build and use the Linux version if you want.
Select the file to disassemble in IDA:
Step#12:
It will detect it automatically as an ELF Shared Object File. Click OK :
Step#13:
ARM and THUMB SWITCH INSTRUCTIONS warning. Just click OK:
Step#14:
This is the disassembled view of the "libhello-jni.so" file. Click on the "Exports" tab for viewing all functions exported by this library:
Step#15:
Here is the list of all exports. The Java methods that are made native and implemented using this library are by default named in "Java_complete_package_name_ClassName_methodName" format.
For e.g.:
In our case, package name is "com.example.hellojni", class name is "HelloJni" and method name is "stringFromJNI" so the function name in exports will be "Java_com_example_hellojni_HelloJni_stringFromJNI".
Click on this function, and IDA will show you the ARM opcodes for this function:
Step#16:
This is the disassembled view of the function. IDA is so powerful that it comments the code where strings are used. Thus as you can see that IDA has commentend our string (I have highlighted it with a red box below). Also we come to know that the string "Hello from JNI !" is declared as a variable named "aHelloFromJni".
Double-click on "aHelloFromJni" and it will show you the declaration of string.
Step#17:
As you can see, "aHelloFromJni" is defined as "Hello from JNI !" with the DCB assembler directive.
DCB is an ARM Assembler Directive that stands for Define Constant Byte.
Step#18:
Now click on the "Hex View" tab, and you will see the Hex Dump for the defined string.
Click on the first character of the string i.e. 'H', and at the status bar at the bottom you will get the file offset of the beginning of the string literal.
In this case it is 0x2030.
MODIFYING the LIBRARY FILE
Step#19:
Using any of your favourite Hex Editor (I have used Bless Hex Editor), open the file "libhello-jni.so" and go to file offset 0x2030.
Step#20:
Replace the original string bytes with new bytes and save the file.
I have replaced the original string "Hello from JNI !" with "Bye.. from JNI !"
Warning#1: As it is a binary ELF shared object file, take care that you replace only the original bytes in the string "Hello from JNI !" starting from the offset 0x2030 till 0x203F.
Warning#2: Also see that you "REPLACE" and not "INSERT".
Now as we have modified the "libhello-jni.so" file we need to update the modified file in the "crackme.native-1.apk" file and also resign the "crackme.native-1.apk" !
Step#21:
Updating the modified library file inside the .apk file.
$ zip ../crackme.native-1.apk -u lib/armeabi/libhello-jni.soStep#22:Re-signing the updated .apk file.
updating: lib/armeabi/libhello-jni.so
zip warning: Local Entry CRC does not match CD: lib/armeabi/libhello-jni.so
(deflated 61%)
$ cd ..
$ java -jar ./tools/signapk.jar ./tools/testkey.x509.pem ./tools/testkey.pk8 crackme.native-1.apk crackme.native-1-SIGNED.apk
TESTING the MODIFIED CRACKME
Merely installing the new crackme will give error as the fully qualified name of both the original and modified crackme will conflict because of being same (com.example.hellojni).
So before installing our modified apk, we need to uninstall the previous one.
Step#23:
Uninstall the previous crackme manually by navigating to "Settings" ==> "Manage Applications".
In order to test our new modified apk, we need to go to "platform-tools" directory of the SDK:
P.S.: The emulator is still running.
$ cd /opt/android/adt-bundle-linux-x86/sdk/platform-tools/
Step#24:
$ ./adb install /home/shubhuntu/mycracklab/crackme.native-1-SIGNED.apk
970 KB/s (153453 bytes in 0.154s)
pkg: /data/local/tmp/crackme.native-1-SIGNED.apk
Success
Step#25:
Click the icon to run the crackme.