Category:Architecture/Android
Description
Android OS
Android designates the OS that runs on Google devices (Android phones, tablets, ...).
Architecture
- Dalvik
- process virtual machine (VM) in Google's Android operating system that executes applications written for Android
- DEX
- Programs are commonly written in Java and compiled to bytecode for the Java virtual machine, which is then translated to Dalvik bytecode and stored in .dex (Dalvik EXecutable) and .odex (Optimized Dalvik EXecutable) files
- smali/baksmali
- assembler/disassembler for the dex format used by dalvik, Android's Java VM implementation. The syntax is loosely based on Jasmin's/dedexer's syntax, and supports the full functionality of the dex format (annotations, debug info, line info, etc.)
Activity diagram
Android framework
android sdk and android avd
android sdk
Android SDK Manager enables to update the framework and install additional tools.
On MobiSec, you can start the SDK Manager with the following command:
mobisec@ubuntu:/opt/mobisec/Android/sdk/tools$ ./android sdk
android avd
Android Virtual Device (AVD) Manager manges virtual devices:
On MobiSec, you can start the AVD Manager with the following command:
mobisec@ubuntu:/opt/mobisec/Android/sdk/tools$ ./android avd
emulator
On MobiSec, you can list the devices and start the emulator with the following commands:
mobisec@ubuntu:/opt/mobisec/Android/sdk/tools$ ./emulator -list-avds MobisecLab mobisec@ubuntu:/opt/mobisec/Android/sdk/tools$ ./emulator -avd MobisecLab -scale 0.75
Here is a screenshot of the emulator running:
adb
Description
The adb program is an interface to perform various tasks on a connected device. Only common commands are reported in this section. For a full list of supported commands, issue adb --help.
adb devices
adb devices shows connected devices:
$ adb devices List of devices attached emulator-5554 device
adb shell
adb shell enables one to connect to a device and run commands via a terminal:
$ adb shell 127|root@generic:/ # id uid=0(root) gid=0(root) context=u:r:shell:s0
adb push and adb pull
- adb push pushes a file to the connected device from the local computer
- adb pull pulls a file from the connected device to the local computer
adb install
adb install application.apk will install an application to the connected device.
adb uninstall
List installed appplications:
$ adb shell "pm list packages" [REMOVED] package:com.app.ndh [REMOVED]
Uninstall the application:
$ adb uninstall com.app.ndh Success
adb forward
This command enables remote debugging by forwarding the debugging session from the android device to the local computer:
$ adb forward tcp:23946 tcp:23946
APK format
Analyze an APK
apktool
You can decompile an APK with apktool:
mobisec@ubuntu:/data$ java -jar apktool_2.0.1.jar d android.apk I: Using Apktool 2.0.1 on android.apk I: Loading resource table... I: Decoding AndroidManifest.xml with resources... I: Loading resource table from file: /home/mobisec/apktool/framework/1.apk I: Regular manifest package... I: Decoding file-resources... I: Decoding values */* XMLs... I: Baksmaling classes.dex... I: Copying assets and libs... I: Copying unknown files... I: Copying original files...
Online resources
Check if an application is debuggable
Once you have decompiled an APK, analyze the AndroidManifest.xml file. The application is debuggable only if the AndroidManifest.xml file contains the "android:debuggable" string.
It won't be debugglable if the file:
- does not contain the "android:debuggable" string or
- does contain the android:debuggable="false" string
Make an application debuggable
If you want to debug an application that is not debuggable, you will need to decompile it, modfiy the AndroidManifest.xml file and rebuild it. Below is the procedure to follow:
Decompile APK
- Decompile the APK as explained above with apktool
Make application debuggable
- Add android:debuggable="true" in the AndroidManifest.xml file as follows:
<?xml version="1.0" encoding="utf-8" standalone="no"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.flare_on.flare" platformBuildVersionCode="22" platformBuildVersionName="5.1.1-1819727"> <application android:debuggable="true" android:allowBackup="true" android:icon="@drawable/icon" android:label="@string/app_name" android:theme="@style/AppTheme"> <activity android:label="@string/app_name" android:name="com.flareon.flare.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> <activity android:label="@string/title_activity_validate_email" android:name="com.flareon.flare.ValidateActivity" android:parentActivityName="com.flareon.flare.MainActivity"> <meta-data android:name="android.support.PARENT_ACTIVITY" android:value="com.flareon.flare.MainActivity"/> </activity> </application> </manifest>
Rebuild APK
$ java -jar apktool_2.0.1.jar b android -o android-debug.apk I: Using Apktool 2.0.1 I: Checking whether sources has changed... I: Smaling smali folder into classes.dex... I: Checking whether resources has changed... I: Building resources... I: Copying libs... (/lib) I: Building apk file...
Sign modified application
You need to sign your modified application. If you don't do it, chances are that you won't be able to install the modified application. To do that, first download SignApk.zip. Unzip the content of the file that you have just downloaded and sign the application as follows:
Usage: signapk publickey.x509[.pem] privatekey.pk8 input.jar output.jar
Below is an example:
$ java -jar signapk.jar certificate.pem key.pk8 android-debug.apk android-debug-signed.apk
You should now be able to install your modified application as follows:
$ adb install android-debug-signed.apk
Debugging
Android Remote debugging
Remote Debugging
gdbserver
Architecture:
To be able to perform remote debugging with gdbserver, you will need Android NDK (different than SDK), which contains gdserver.
On MobiSec, enable port forwarding:
$ cd /opt/mobisec/Android/sdk/platform-tools/ $ ./adb forward tcp:1234 tcp:1234
Transfering gdbserver from MobiSec to the Android Virtual Device (it will be copied to the /data/ directory):
$ ./adb push -p /opt/mobisec/Android/ndk/prebuilt/android-arm/gdbserver/gdbserver /data Transferring: 409940/409940 (100%) 2609 KB/s (409940 bytes in 0.153s)
Now, start the application to debug on your Android Virtual Device and identify the Process ID with the ps | grep app_name command.
Start gdbserver (we will attach it to the PID gathered previously, 1436 in our example)
# ./adb shell # cd /data # chmod 744 gdbserver # ./gdbserver :1234 --attach 1436
Now start gdb on MobiSec:
# cd /opt/mobisec/Android/ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/ # ./arm-linux-androideabi-gdb (gdb) target remote :1234 Remote debugging using :1234 0xb6eb1f9c in ?? () (gdb) x/10i $pc => 0xb6f075cc: mov r7, r12 0xb6f075d0: cmn r0, #4096 ; 0x1000 0xb6f075d4: bxls lr 0xb6f075d8: rsb r0, r0, #0 0xb6f075dc: b 0xb6f22b28 0xb6f075e0: mov r12, r7 0xb6f075e4: mov r7, #316 ; 0x13c 0xb6f075e8: svc 0x00000000 0xb6f075ec: mov r7, r12 0xb6f075f0: cmn r0, #4096 ; 0x1000
IDA Pro remote debugging
To be able to perform remote debugging of an Android application with IDA-Pro, you will need to get android_server from your [[[IDA-Pro]] installation directory (it should be in C:\Program Files (x86)\IDA 6.6\dbgsrv\) and push it to your Android Virtual Device:
$ ./adb push android_server /data
Then ensure the server will be executable and start it (it will listen to tcp:23946 by default):
$ ./adb shell $ cd /data/ $ chmod 744 android_server $ ./android_server -v -PmyAwesomePassword
Back to your MobiSec distribution, enter the following commands in a terminal:
$ ./adb forward tcp:23946 tcp:23946 $ sudo apt-get install redir $ redir --lport=23946 --laddr=192.168.65.130 --cport=23946 --caddr=0.0.0.0
Now, from your Windows VM, open IDA-Pro and go to Debugger > Attach > Remote ARMLinux/Android debugger:
You should be presented with the list of running processes:
To be able to debug Android native shared libraries, we need to get the system libraries from the Android device (will be used to find debug symbols).
(mobisec)$ mkdir /data/flareon/system_lib/ (mobisec)$ cd /data/flareon/system_lib/ (mobisec)$ adb pull /system/lib
Also eventually get the shared libraries specific to the Android application to debug (in my example libvalidate.so)
(mobisec)$ adb pull /data/app-lib/com.flare_on.flare-1/libvalidate.so
Once this is done, push gdbserver to the Android device:
(mobisec)$ adb push /opt/android-ndk-r10e/prebuilt/android-arm/gdbserver/gdbserver /data/
Enable port forwarding from the Android device to your local debugger:
(mobisec)$ adb forward tcp:1234 tcp:1234
Make gdbserver executable:
(mobisec)$ adb shell (avd)# cd /data/ (avd)# chmod 744 gdbserver
Start the application to be debugged on the Android device, get its PID and attach the debugger to it:
(avd)# ps | grep flare u0_a56 1278 57 196576 28304 ffffffff b6ed65cc S com.flare_on.flare (avd)# ./gdbserver :1234 --attach 1278 Attached; pid = 1278 Listening on port 1234
Now, you can start debugging:
(mobisec)$ cd /opt/mobisec/Android/ndk/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/ (mobisec)$ ./arm-linux-androideabi-gdb (gdb) target remote :1234 Remote debugging using :1234 0xb6ed65cc in ?? ()
Load symbols from your local directory:
(gdb) set solib-search-path /data/flareon/system_lib/ [REMOVED] Reading symbols from /data/flareon/system_lib/libvalidate.so...(no debugging symbols found)...done. Loaded symbols for /data/flareon/system_lib/libvalidate.so [REMOVED]
Now, check the memory location the shared library has been loaded to:
(gdb) info sharedlibrary From To Syms Read Shared Object Library [REMOVED] 0xab143e20 0xab145038 Yes (*) /data/flareon/system_lib/libvalidate.so (*): Shared library is missing debugging information.
Display the disassembled code at this location:
(gdb) x/20i 0xab143e20 0xab143e20: ldr r0, [pc, #4] ; 0xab143e2c 0xab143e24: add r0, pc, r0 0xab143e28: b 0xab143da8 0xab143e2c: ldrdeq r4, [r0], -r4 ; <UNPREDICTABLE> 0xab143e30: cmp r0, #0 0xab143e34: push {r3, lr} 0xab143e38: popeq {r3, pc} 0xab143e3c: blx r0 0xab143e40: pop {r3, pc} 0xab143e44: mov r1, r0 0xab143e48: ldr r2, [pc, #12] ; 0xab143e5c 0xab143e4c: ldr r0, [pc, #12] ; 0xab143e60 0xab143e50: add r2, pc, r2 0xab143e54: add r0, pc, r0 0xab143e58: b 0xab143d9c 0xab143e5c: andeq r4, r0, r8, lsr #3 0xab143e60: ; <UNDEFINED> instruction: 0xffffffd4 0xab143e64 <Java_com_flareon_flare_ValidateActivity_validate>: push {r4, r5, r6, r7, lr} 0xab143e66 <Java_com_flareon_flare_ValidateActivity_validate+2>: ldr r4, [pc, #320] ; (0xab143fa8 <Java_com_flareon_flare_ValidateActivity_validate+324>) 0xab143e68 <Java_com_flareon_flare_ValidateActivity_validate+4>: adds r5, r0, #0
That's it, we can now set a breakpoint at the entry point:
(gdb) b Java_com_flareon_flare_ValidateActivity_validate Breakpoint 1 at 0xab143e74 (gdb) c Continuing.
On the Android device, you should be able to perfom the actions (e.g. provide a password and click on a validate button) that will trigger the breakpoint. Once the breakpoint is reached, the Android application freezes and the debugger shows the following output:
Breakpoint 1, 0xab143e74 in Java_com_flareon_flare_ValidateActivity_validate () from /data/flareon/system_lib/libvalidate.so
From now, you are able to put other breakpoints to continue debugging the native shared library:
(gdb) b *0xab143f3c Breakpoint 2 at 0xab143f3c (gdb) c Continuing. Breakpoint 2, 0xab143f3c in Java_com_flareon_flare_ValidateActivity_validate () from /data/flareon/system_lib/libvalidate.so (gdb) x/10i $pc => 0xab143f3c <Java_com_flareon_flare_ValidateActivity_validate+216>: bcc.n 0xab143ecc <Java_com_flareon_flare_ValidateActivity_validate+104> 0xab143f3e <Java_com_flareon_flare_ValidateActivity_validate+218>: b.n 0xab143f6e <Java_com_flareon_flare_ValidateActivity_validate+266> 0xab143f40 <Java_com_flareon_flare_ValidateActivity_validate+220>: adds r3, r6, r3 0xab143f42 <Java_com_flareon_flare_ValidateActivity_validate+222>: ldrb r3, [r3, #1] 0xab143f44 <Java_com_flareon_flare_ValidateActivity_validate+224>: adds r4, r2, #0 0xab143f46 <Java_com_flareon_flare_ValidateActivity_validate+226>: cmp r3, #0 0xab143f48 <Java_com_flareon_flare_ValidateActivity_validate+228>: beq.n 0xab143ee0 <Java_com_flareon_flare_ValidateActivity_validate+124> 0xab143f4a <Java_com_flareon_flare_ValidateActivity_validate+230>: lsls r4, r2, #8 0xab143f4c <Java_com_flareon_flare_ValidateActivity_validate+232>: ldr r2, [pc, #112] ; (0xab143fc0 <Java_com_flareon_flare_ValidateActivity_validate+348>) 0xab143f4e <Java_com_flareon_flare_ValidateActivity_validate+234>: orrs r4, r3
Android Anti-debug techniques
Check IMEI
The Android emulator has no IMEI as depicted on the following screenshot. An Android application could verify that the IMEI is not zero, as shown on the following code extract:
To bypass this anti-debugging technique, you should patch the Android application. Below are the steps to do it.
First decompile the APK with apktool:
$ java -jar /data/tools/apktool_2.0.1.jar d NDH.apk I: Using Apktool 2.0.1 on NDH.apk I: Loading resource table... I: Decoding AndroidManifest.xml with resources... I: Loading resource table from file: /home/unknown/apktool/framework/1.apk I: Regular manifest package... I: Decoding file-resources... I: Decoding values */* XMLs... I: Baksmaling classes.dex... I: Copying assets and libs... I: Copying unknown files... I: Copying original files...
Then locate the file in the smali directory where it is checked:
$ grep -R getDeviceId NDH/smali NDH/smali/com/app/ndh/NDHActivity$2.smali: invoke-virtual {v1}, Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String;
Edit the smali file to replace the conditional jump to an unconditional one:
.method public onClick(Landroid/view/View;)V .locals 4 .param p1, "v" # Landroid/view/View; .prologue .line 48 iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$test:Landroid/telephony/TelephonyManager; invoke-virtual {v1}, Landroid/telephony/TelephonyManager;->getDeviceId()Ljava/lang/String; move-result-object v1 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-nez v1, :cond_0 + goto :cond_0 .line 49 iget-object v1, p0, Lcom/app/ndh/NDHActivity$2;->val$builder:Landroid/app/AlertDialog$Builder; const-string v2, "Bad Password"
Recompile the Android application:
$ apktool b -o NDH_patched.apk NDH/ I: Using Apktool 2.0.0-RC3 on NDH I: Checking whether sources has changed... I: Smaling smali folder into classes.dex... I: Checking whether resources has changed... I: Building resources... I: Copying libs... I: Building apk file... I: Copying unknown files/dir...
You will also need to sign the modified version. Refer to this section to check how to achieve this.
Now, uninstall the previous version and reinstall the new one.
Time checks
Another anti-debugging check consists in placing 2 time checks along with a call to the difftime function to check the time elapsed between the 2 checkpoints, as shown below:
- First checkpoint:
.text:00000F90 MOVS R0, #0 ; timer
.text:00000F92 BLX time
.text:00000F96 MOVS R3, R0 ; R3 = time (1st checkpoint)
- Second checkpoint:
.text:000010C2 MOVS R0, #0 ; timer
.text:000010C4 BLX time
.text:000010C8 MOVS R3, R0 ; R3 = time (2nd checkpoint)
- Check time elapsed between the 2 checkpoints:
.text:000010D8 LDR R2, [SP,#0x140+time1]
.text:000010DA LDR R3, [SP,#0x140+time0]
.text:000010DC MOVS R0, R2 ; time1
.text:000010DE MOVS R1, R3 ; time0
.text:000010E0 BLX difftime
.text:000010E4 MOVS R2, R0 ; R2 = difftime (should be 1sec)
.text:000010E4 ; If >1 sec, app being debugged)
Identify arguments
In a shared library, arguments are often as follows:
- R0: JNIEnv ((a pointer to pointers to function tables).
- R1: argument from JNI
- R2: argument passed to the function (e.g. user input)
Example:
.text:00000F60 PUSH {R4,R5,LR}
.text:00000F62 SUB SP, SP, #0x134
.text:00000F64 LDR R4, =(_GLOBAL_OFFSET_TABLE_ - 0xF6A)
.text:00000F66 ADD R4, PC ; _GLOBAL_OFFSET_TABLE_
.text:00000F68 STR R0, [SP,#0x140+var_134] ; JNIEnv
.text:00000F6A STR R1, [SP,#0x140+var_138]
.text:00000F6C STR R2, [SP,#0x140+var_13C] ; user_input
Resolve JNI functions
Each function is accessible at a fixed offset through the JNIEnv argument. The JNIEnv type is a pointer to a structure storing all JNI function pointers. It is defined as follows:
typedef const struct JNINativeInterface *JNIEnv;
We can resolve the JNI function by looking at the vtable definition here: https://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/functions.html.
Here is an example:
.text:00000F68 STR R0, [SP,#0x140+var_134] ; JNIEnv
.text:00000F6A STR R1, [SP,#0x140+var_138]
.text:00000F6C STR R2, [SP,#0x140+var_13C] ; user_input
.text:00000F6E LDR R3, =(__stack_chk_guard_ptr - 0x3AD0)
.text:00000F70 LDR R3, [R4,R3] ; __stack_chk_guard
.text:00000F72 LDR R3, [R3]
.text:00000F74 STR R3, [SP,#0x140+var_14]
.text:00000F76 LDR R3, [SP,#0x140+var_134]
.text:00000F78 LDR R2, [R3]
.text:00000F7A MOVS R3, #0x2A4
.text:00000F7E LDR R3, [R2,R3]
.text:00000F80 LDR R1, [SP,#0x140+var_134]
.text:00000F82 LDR R2, [SP,#0x140+var_13C]
.text:00000F84 MOVS R0, R1
.text:00000F86 MOVS R1, R2
.text:00000F88 MOVS R2, #0
.text:00000F8A BLX R3
At offset 0xF7E, R3 holds R2 + 0x2A4 with R2 pointing to the data referenced in R3 (JNIEnv). As we are dealing with 32-bit ARM, we obtain the correct index into the JNI vtable by dividing the offset by 4, which results in:
0x2A4 / 4 = 0xa9 = 169
Looking in this page for 169 leads to the GetStringUTFChars function. Hence, we deduce that the BLX R3 instruction at offset 0xF8A is a call to the GetStringUTFChars function.
GetStringUTFChars | |
---|---|
Syntax |
const char * GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy); |
Description |
Returns a pointer to an array of bytes representing the string in modified UTF-8 encoding. This array is valid until it is released by ReleaseStringUTFChars(). If isCopy is not NULL, then *isCopy is set to JNI_TRUE if a copy is made; or it is set to JNI_FALSE if no copy is made. |
Linkage | Index 169 in the JNIEnv interface function table. |
Parameters |
|
Returns | Returns a pointer to a modified UTF-8 string, or NULL if the operation fails. |
Subcategories
This category has the following 2 subcategories, out of 2 total.
A
Pages in category "Architecture/Android"
The following 9 pages are in this category, out of 9 total.