File : NDH.apk

We were asked to reverse an Android application, coming as an APK file.

The first move was to get the Java source of that application with dex2jar in order to decompile the APK into a plain JAR file.

If you ever want more information about this step, check out that link http://tinyurl.com/blcp353.

Once we have the JAR file, we opened it with JD-GUI and located the main Activity, that is to say the entry point of an application.

Here's the code :

package com.app.ndh;
 
import android.app.Activity;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import android.telephony.TelephonyManager;
import android.text.Editable;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TableLayout;
import android.widget.TextView;
 
public class NDHActivity extends Activity
{
  static
  {
    System.loadLibrary("verifyPass");
  }
 
  private native String print(String paramString);
 
  public void onCreate(Bundle paramBundle)
  {
    super.onCreate(paramBundle);
    TextView localTextView = new TextView(this);
    localTextView.setText("Enter the code to activate this software :");
    AlertDialog.Builder localBuilder1 = new AlertDialog.Builder(this);
    AlertDialog.Builder localBuilder2 = localBuilder1.setCancelable(false);
    1 local1 = new DialogInterface.OnClickListener()
    {
      public void onClick(DialogInterface paramDialogInterface, int paramInt)
      {
      }
    };
    AlertDialog.Builder localBuilder3 = localBuilder1.setPositiveButton("OK", local1);
    EditText localEditText = new EditText(this);
    localEditText.setInputType(0);
    Button localButton = new Button(this);
    localButton.setText("Activation");
    TelephonyManager localTelephonyManager = (TelephonyManager)getSystemService("phone");
    2 local2 = new View.OnClickListener(localTelephonyManager, localBuilder1, localEditText)
    {
      public void onClick(View paramView)
      {
        if (Integer.decode(this.val$test.getDeviceId()).intValue() == 0)
          AlertDialog.Builder localBuilder1 = this.val$builder.setMessage("Bad Password");
        while (true)
        {
          this.val$builder.create().show();
          return;
          AlertDialog.Builder localBuilder2 = this.val$builder;
          NDHActivity localNDHActivity = NDHActivity.this;
          String str1 = this.val$tv2.getText().toString();
          String str2 = localNDHActivity.print(str1);
          AlertDialog.Builder localBuilder3 = localBuilder2.setMessage(str2);
        }
      }
    };
    localButton.setOnClickListener(local2);
    TableLayout localTableLayout = new TableLayout(this);
    localTableLayout.addView(localTextView);
    localTableLayout.addView(localEditText);
    localTableLayout.addView(localButton);
    setContentView(localTableLayout);
  }
}

Before going any further with that code, we wanted it to be running on a Android emulator and we had to patch few things.

This time, we completely disassembled the APK with apktool. This tool is BakSmaling (disassembling) the compiled code whose assembly representation is called Smali and unpacking all the files contained in an APK (certificates, libs etc.).
It also disassembles the graphical resources files and the AndroidManifest.xml file, which lists all the required settings for an application to run (platform version, permissions etc.).

android@honeynet:~/tools/apktool$ apktool d ~/ndh/NDH.apk ~/ndh/NDH_Decomp
I: Baksmaling...
I: Loading resource table...
I: Loaded.
I: Loading resource table from file: /home/android/apktool/framework/1.apk
I: Loaded.
I: Decoding file-resources...
I: Decoding values*/* XMLs...
I: Done.
I: Copying assets and libs...

Here's the AndroidManifest.xml :

<?xml version="1.0" encoding="utf-8"?>
<manifest android:versionCode="1" android:versionName="1.0" package="com.app.ndh"
  xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-sdk android:minSdkVersion="15" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <application android:label="@string/app_name" android:icon="@drawable/ndh">
        <activity android:label="@string/app_name" android:name=".NDHActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

That manifest requires Android 4.0.3 (API 15) as minimum SDK version. Having a 2.3.3 (API 10) emulator, we just removed that line in the Manifest.

Now, let's go back into the code.

Just by reading the source, we're aware that this application graphically consists of a EditText (a textbox) and a Button. So basically, we have to enter an activation code and hit the button for it to be checked.

Once hit, the first check is :

if (Integer.decode(this.val$test.getDeviceId()).intValue() == 0)
	AlertDialog.Builder localBuilder1 = this.val$builder.setMessage("Bad Password");

To be read as : if the IMEI of the terminal equals 0, then always display "Bad Password", whatever activation code you typed (even the good one :)).

As the emulated terminals have no real IMEI, this value is set to 0. This first check is actually an anti-emulator feature :)

In order to remove that check, we had to directly patch the Smali code. Here's the original Smali section, in the NDH_Decomp/smali/com/app/ndh/NDHActivity$2.smali file :

iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$test:Landroid/telephony/TelephonyManager;
 
; Grab the terminal's IMEI
invoke-virtual {v1}, Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; 
move-result-object v1
 
; Decode it as an integer value
invoke-static {v1}, Ljava/lang/Integer;->decode(Ljava/lang/String;)Ljava/lang/Integer; 
move-result-object v1
invoke-virtual {v1}, Ljava/lang/Integer;->intValue()I
move-result v1
 
; if (v1 == IMEI) != 0 then jump to the cond_0 label (the legit "while(true){}" section), else print "Bad Password"
if-nez v1, :cond_0  
 
iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$builder:Landroid/app/AlertDialog$Builder;
const-string v2, "Bad Password"

We simply transformed the conditional jump "if-nez v1, :cond_0" to a unconditional jump, that's to say : "goto :cond_0"

Having both the Smali code and the Manifest patched, we rebuilt the application with apktool :

android@honeynet:~/tools/apktool$ apktool b ~/ndh/NDH_Decomp ~/ndh/New_NDH.apk
I: Checking whether sources has changed...
I: Checking whether resources has changed...
I: Building apk file...

Still, we needed to sign the application, as the emulator won't install an unsigned APK. Here comes the signing command, with a self-signed certificate :

android@honeynet:~/tools/apktool$ jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore /home/android/my-release-key.keystore -storepass my_pass ~/ndh/New_NDH.apk maz
  adding: META-INF/MANIFEST.MF
  adding: META-INF/MAZ.SF
  adding: META-INF/MAZ.RSA
  signing: lib/armeabi/libverifyPass.so
  signing: res/drawable-hdpi/ic_launcher.png
  signing: res/drawable-hdpi/ndh.png
  signing: res/drawable-ldpi/ic_launcher.png
  signing: res/drawable-ldpi/ndh.png
  signing: res/drawable-mdpi/ic_launcher.png
  signing: res/drawable-mdpi/ndh.png
  signing: res/drawable-xhdpi/ic_launcher.png
  signing: res/drawable-xhdpi/ndh.png
  signing: res/layout/main.xml
  signing: AndroidManifest.xml
  signing: classes.dex
  signing: resources.arsc

The patched APK can be found here. So now we can install and execute that application on an emulator.

app_home.png

Let's reverse that crackme ! Going back into the code, we have those 3 code sections:

static
{
	System.loadLibrary("verifyPass");
}
private native String print(String paramString);
NDHActivity localNDHActivity = NDHActivity.this;
String str1 = this.val$tv2.getText().toString();
String str2 = localNDHActivity.print(str1);
AlertDialog.Builder localBuilder3 = localBuilder2.setMessage(str2);

While the first instruction loads a native library, a .so file written in C and compiled with the NDK (Native Development Kit), the second stands for the declaration of the native method, here called "print".

The last part indicates that the text from the EditText element is passed to the native "print" method and its result is then displayed in a popup.

All in all, the validation routine is contained in that lib.

Thus, the next step was to reverse the "libverifyPass.so" file.
As said, apktool unpacked every file in the APK.

android@honeynet:~/ndh/NDH_Decomp$ file lib/armeabi/libverifyPass.so
lib/armeabi/libverifyPass.so: ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked, stripped

Let's start by opening libverifyPass in IDA.
In the "Strings" window, we only have two strings : "Bad password" & "Good password, this is the flag too".
Those two strings are only referenced in one function : "Java_com_app_ndh_NDHActivity_print", which is the function called from the Java source.
Here is a global view of this function :

crackme_android_ida.png

We can split it in a few basic blocks :

  • purple : initialization
  • yellow : main loop
  • blue : final tests
  • red : fail (references "bad password")
  • green : success (references "Good password")

Before going any further, let's see a few specific details of the JNI (Java Native Interface) :

.text:00000F68                 STR     R0, [SP,#0x140+ptr_vtable_like]
.text:00000F6A                 STR     R1, [SP,#0x140+not_used]
.text:00000F6C                 STR     R2, [SP,#0x140+user_string]

We can see in the above snippet of the initialization block, that the function takes 3 parameters (in R0-R2) that are immediatly saved in local variables on the stack. But the Java source only use one argument, so the first two must be implicit arguments from the JNI.

The second is unused, but the first is used like a pointer on a vtable in C++, containing some function pointers used to do virtual calls, as in the following snippet :

.text:00000F78                 LDR     R2, [R3]								; Address of the "vtable-like" in R2
.text:00000F7A                 MOVS    R3, 0x2A4							; Offset = 0x2A4
.text:00000F7E                 LDR     R3, [R2,R3]							; Address of the "virtual function"
.text:00000F80                 LDR     R1, [SP,#0x140+ptr_vtable_like]
.text:00000F82                 LDR     R2, [SP,#0x140+user_string]
.text:00000F84                 MOVS    R0, R1								; Arg 1 -> our pointer
.text:00000F86                 MOVS    R1, R2 								; Arg 2 -> our string
.text:00000F88                 MOVS    R2, #0 							; Arg 3 
.text:00000F8A                 BLX     R3									; "virtual call"

As explained in the wiki page, this is how the JNI works. All functions take a JNIEnv pointer as first parameter, which is a structure containing pointers to all the required method to interface with the JVM :

/* Example of a JNI function, taking a JNIEnv pointer as first parameter */
JNIEXPORT void JNICALL Java_ClassName_MethodName(JNIEnv *env, jobject obj, jstring javaString)
{
	/* Example of a function call, using a function pointer in JNIEnv */
	const char *nativeString = (*env)->GetStringUTFChars(env, javaString, 0);
	[...]
}

Our program use this type of call 3 times, with the indexes 0x2A4, 0x290 and 0x29C. To retreive the names of the corresponding fonctions, we can use directly the header file jni.h or this documentation which contains the indexes in the JNIEnv interface function table :

0x24A / 4 = 169 --> const jbyte* GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy);
0x290 / 4 = 164 --> jsize GetStringLength(JNIEnv *env, jstring string);
0x29C / 4 = 167 --> jstring NewStringUTF(JNIEnv *env, const char *bytes);

We can now analyse our routine with a full knowledge of which function it's using :)
Starting with the end (the blue "final tests" block), we can see that the block doesn't really test anything on the supplied string, except that the result of a previous call to "GetStringLength" must equals 0xC.
Besides, there's just some really basic anti debug trick here : a call to difftime() is made, with the return values of two calls to time() before and after the main loop, and the result is tested against a hardcoded double value, that it must not exeed (R2=0x00000000 R3=0x3FF00000, so our maximum value is 1.0, but we really don' care since we are doing this in static analysis :p)

So let's skip to the interesting part. In the initialization block, two counters are set to 0, and a call to GetStringLength is made (but not directly tested).

.text:00000FFE                 MOVS    R3, #0
.text:00001000                 STR     R3, [SP,#0x140+counter1]
.text:00001002                 MOVS    R3, #0
.text:00001004                 STR     R3, [SP,#0x140+counter2]

More important, some structures in the .rodata section are loaded into the stack, as we can see in the following snippet :

.text:00000F9A                 ADD     R3, SP, #0x140+loaded_27E0		; R3 = stack buffer
.text:00000F9C                 LDR     R2, =(dword_27E0 - 0xFA2)			; R2 = rodata buffer
.text:00000F9E                 ADD     R2, PC
.text:00000FA0                 LDMIA   R2!, {R0,R1,R5}					; Load from R2 into multiple registers (R0, R1 and R5), and increment R2
.text:00000FA2                 STMIA   R3!, {R0,R1,R5}					; Store to R3 from multiple registers, and increment R3
.text:00000FA4                 LDMIA   R2!, {R0,R1,R5}
.text:00000FA6                 STMIA   R3!, {R0,R1,R5}
.text:00000FA8                 LDMIA   R2!, {R0,R1,R5}
.text:00000FAA                 STMIA   R3!, {R0,R1,R5}
.text:00000FAC                 LDMIA   R2!, {R0,R1,R5}
.text:00000FAE                 STMIA   R3!, {R0,R1,R5}

Using R0, R1, and R5, and repeating LDMIA/STMIA 4 times, we can see that this code loads 12 dwords. And there are 5 blocks like this one. The last action of the initialization block is to load the first char of our string somewhere in the stack (SP+0x20+0xFF) :

.text:00000FF2                 MOV     R3, SP 						; stack
.text:00000FF4                 ADDS    R3, #0x20 					
.text:00000FF6                 ADDS    R3, #0xFF 					
.text:00000FF8                 LDR     R2, [SP,#0x140+c_string]		; our string, result of GetStringUTFChars
.text:00000FFA                 LDRB    R2, [R2] 					; first char
.text:00000FFC                 STRB    R2, [R3] 					; store it !

Now let's finish this crackme with the main loop :

It first exits the loop to the final block if the current character (stored in SP+0x20+0xFF) is zero, this is our stop condition. After entering the loop, it also exits and prints "bad password" if the counter1 is greater than 0xC. Otherwise, (this part is the interesting one) it computes a xor between two values in the previously loaded on the stack data, and compares the result with the current password :

.text:00001040                 LDR     R2, [SP,#0x140+counter1]			
.text:00001042                 ADD     R3, SP, #0x140+loaded_2810		
.text:00001044                 LSLS    R2, R2, #2 						; R2 = counter1 * 4
.text:00001046                 LDR     R2, [R2,R3] 						; R2 = loaded_2810[ counter1 * 4 ]
.text:00001048                 LDR     R1, [SP,#0x140+counter1] 		
.text:0000104A                 ADD     R3, SP, #0x140+loaded_2840		
.text:0000104C                 LSLS    R1, R1, #2 						; R1 = counter1 * 4
.text:0000104E                 LDR     R3, [R1,R3] 						; R3 = loaded_2840[ counter1 * 4 ]
.text:00001050                 EORS    R2, R3 							; R2 = loaded_2810[ counter1 * 4 ] ^ loaded_2840[ counter1 * 4 ]
.text:00001052                 LDR     R3, [SP,#0x140+counter1] 		
.text:00001054                 LDR     R1, [SP,#0x140+c_string]
.text:00001056                 ADDS    R3, R1, R3
.text:00001058                 LDRB    R3, [R3] 						; R3 = c_string[ counter1 ]
.text:0000105A                 CMP     R2, R3 							; compare R2 / R3

So this is it :)
The last block of the function doesn't do anything really important (increment the counters, update the current char tested at the beginning of the loop, do some xor on some previously loaded on the stack but unused data, ...)

We now have to dump the 2*12 dwords in the .rodata section, at 0x2810 and 0x2840, and xor them to obtain the password :

>>> a = [0x52,0x1A,0x09,0x7B,0x4B,0x5C,0x20,0x72,0x10,0x67,0x5E,0x49]
>>> b = [0x01,0x4E,0x4C,0x3A,0x00,0x08,0x14,0x20,0x44,0x53,0x0C,0x0C]
>>> "".join( [ chr(a[i] ^ b[i]) for i in range(len(a)) ] )
'STEAKT4RT4RE'

Pwned !

app_validation.png