Native Android Reverse Engineering Tutorial#1: Patching/Modifying String within Native Android App


Aim:

To learn to:
  1. Decompile the Native Android App File (.apk) into Java code (.java)
  2. Disassemble the Shared Object File (.so) into ARM Assembler Opcodes.
  3. Use of tools like IDA (Interactive Disassembler)dex2jarjd-gui, etc.
This tutorial introduces you to the basics of reverse engineering a Native Android app on Ubuntu.


Prerequisite:


    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:
    Extraction of all the files from the crackme APK(which is actually a ZIP file).

    $ 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:

    Conversion of the .jar file to *.java files using jd-gui Java decompiler.

    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":

    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
    */
    Its quite evident that the static code block which gets executed first is loading a native library named "hello-jni".
    So its full name will be "libhello-jni.so"

    Step#10:
    Verifying the shared object file "libhello-jni.so"

    $ file ./lib/armeabi/libhello-jni.so
    ./lib/armeabi/libhello-jni.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped
    Also you can observe from the "HelloJni.java" file, that the "setText()" method sets the text using the native method declared as:
    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".

    RE-SIGNING the APK FILE

    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.so 
    updating: lib/armeabi/libhello-jni.so
    zip warning: Local Entry CRC does not match CD: lib/armeabi/libhello-jni.so
    (deflated 61%)
    Step#22:Re-signing the updated .apk file.

    $ 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:

    $ cd /opt/android/adt-bundle-linux-x86/sdk/platform-tools/
    P.S.: The emulator is still running.

    Step#24:
    Install modified APK (crackme.native-1-SIGNED.apk) in Emulator using adb.

    $ ./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.