yichao firstname, zeaster nickname, zhang lastname

How to decompile .dex file on Android

Lucky to see the Google Android build-in Contacts is also developed by Android SDK.
This article will take it for example and discuss these How-To's:

1 How to find the Contacts App file on Google Android
2 How to find the Contacts App's classes.dex on Google Android
3 How to dump the Contacts App's classes
4 How to decompile the dumped file

{
Actually, I think the Contacts Content Provider that Google provides has some inconvenience to use.
And I will post another blog to list what's inconvenience and request some improvement from Google.
The blog link is here:
http://zeaster.blogspot.com/2007/11/inconvenienced-when-using-contact.html
}

1 How to find the Contacts App on Google Android
Using adb tool

$ adb shell
# cd /system/app
cd /system/app
# ls
ls
-rw-r--r-- root root 25519 2007-11-14 20:40 ContactsProvider.apk
-rw-r--r-- root root 7544 2007-11-14 20:40 GoogleAppsProvider.apk
-rw-r--r-- root root 16198 2007-11-14 20:40 ImProvider.apk
-rw-r--r-- root root 20308 2007-11-14 20:40 MediaProvider.apk
-rw-r--r-- root root 21272 2007-11-14 20:40 TelephonyProvider.apk
-rw-r--r-- root root 11809 2007-11-14 20:40 SettingsProvider.apk
-rw-r--r-- root root 418688 2007-11-14 20:41 Browser.apk
-rw-r--r-- root root 68077 2007-11-14 20:41 Contacts.apk
-rw-r--r-- root root 96287 2007-11-14 20:41 Development.apk
-rw-r--r-- root root 44790 2007-11-14 20:41 GoogleApps.apk
-rw-r--r-- root root 8637 2007-11-14 20:41 Fallback.apk
-rw-r--r-- root root 99431 2007-11-14 20:41 Home.apk
-rw-r--r-- root root 171614 2007-11-14 20:41 Maps.apk
-rw-r--r-- root root 424601 2007-11-14 20:41 Phone.apk
-rw-r--r-- root root 192119 2007-11-14 20:41 XmppService.apk
-rw-r--r-- root root 6614 2007-11-14 20:41 XmppSettings.apk
#

The Contacts.apk is the Contacts App and ContactsProvider.apk is the app that provides the Contact Content Provider for reading and writing "content://contacts/" resources.
we can see many system apps developed by Android Java SDK are put here.
However what is inside *.apk file. Actually it is a .zip file. It is easy to unzip and get the answer.
take Contacts.apk for example, it contains:

META-INF\
res\
AndroidManifest.xml
classes.dex
resources.arsc

The classes.dex file contains all compiled Java code.

2 How to find the Contacts App's classes.dex on Google Android
No need to unzip Contacts App's classes.dex file from Contacts.apk file.
The Dalvik VM has a cached file for us. Go head to find it.

$ adb shell
# cd /data/dalvik-cache
cd /data/dalvik-cache
# ls
ls
-rw-rw-rw- root root 73852 2007-11-27 05:16 system@app@Contacts.apk@classes.dex
-rw-rw-rw- app_0 app_0 64172 2007-11-27 05:17 system@app@ContactsProvider.apk@classes.dex
-rw-rw-rw- root root 15204 2007-11-27 05:17 system@framework@am.jar@classes.dex
-rw-rw-rw- app_3 app_3 3012 2007-11-27 07:33 system@app@Fallback.apk@classes.dex
-rw-rw-rw- root root 7252804 2007-11-27 05:16 system@framework@core.jar@classes.dex
....and many other cached files.

3 How to dump the Contacts App's classes
Unfortunately the classes.dex is not a .jar file. It's Dalvik executable format that is optimized for efficient.
so we can not pull the .class files as .jar format. However Google Android provides a tool named dexdump included in the emulator to dump .dex file. Here is how to use it.
# dexdump
dexdump: no file specified
dexdump: [-f] [-h] dexfile...

-d : disassemble code sections
-f : display summary information from file header
-h : display file header details
-C : decode (demangle) low-level symbol names
-S : compute sizes only

Now we dump the system@app@Contacts.apk@classes.dex and system@app@ContactsProvider.apk@classes.dex.

# dexdump -d -f -h -C system@app@Contacts.apk@classes.dex >> Contacts.apk.dump
# dexdump -d -f -h -C system@app@ContactsProvider.apk@classes.dex >> ContactsProvider.apk.dump

and then pull it out.
# exit
$ adb pull /data/dalvik-cache/Contacts.apk.dump ~/android
$ adb pull /data/dalvik-cache/ContactsProvider.apk.dump ~/android

Now we get the dumped files on ~/android folder.

4 How to decompile the dumped file
.dex file is optimized, so we can not decompile it as normal .class file.
Fortunately the dumped file is a bit readable for me.
As it is too big to analyze all the code, so I just take "how to create a contact" for example.
It's easy to get this info from the dumped file.
It has these classes:
...
Class name : 'com/google/android/contacts/AttachImage'
Class name : 'com/google/android/contacts/ContactEntryAdapter'
Class name : 'com/google/android/contacts/EditContactActivity'
Class name : 'com/google/android/contacts/EditContactActivity$EditEntry'
...

The EditContactActivity class has a private void create() method.
#3 : (in com/google/android/contacts/EditContactActivity)
name : 'create'
type : '()V'
access : 0x0002 (PRIVATE)

So all the code that create a contact is here from the dumped file, my comments inlined:
// private void create()
009b24: |[009b24] com/google/android/contacts/EditContactActivity.create:()V

// ContentValues v9 = new ContentValues();
009b28: 2109 3100 |0000: new-instance v9, android/content/ContentValues // class@0031
009b2c: 6f01 0f00 0900 |0002: invoke-direct {v9}, android/content/ContentValues.:()V // method@000f

// Entry v12 = this.getCurrentEntry();
009b32: f4fc a800 |0005: +iget-object-quick v12, v15, [obj+00a8]

// int v11 = v12.lines
009b36: f801 1500 0c00 |0007: +invoke-virtual-quick {v12}, [0015] // vtable #0015
009b3c: 0a0b |000a: move-result v11

// int v5 = 0
009b3e: 1205 |000b: const/4 v5, #int 0 // #0

// if v5 >= v11 goto 0046
009b40: 3ab5 3a00 |000c: if-ge v5, v11, 0046 // +003a

// EditEntry v8 = (EditEntry)this.getCurrentEntry();
009b44: f4fc a800 |000e: +iget-object-quick v12, v15, [obj+00a8]
009b48: f802 1a00 5c00 |0010: +invoke-virtual-quick {v12, v5}, [001a] // vtable #001a
009b4e: 0c0c |0013: move-result-object v12
009b50: 07c0 |0014: move-object v0, v12
009b52: 1e00 3d00 |0015: check-cast v0, com/google/android/contacts/EditContactActivity$EditEntry // class@003d
009b56: 0708 |0017: move-object v8, v0

// int v12 = v8.kind // The Instance field kind is defined in its superclass Entry. see details in dumped Entry and EditEntry sections.
009b58: f28c 1400 |0018: +iget-quick v12, v8, [obj+0014]

// int v13 = -3 // PHOTO_KIND = -3 defined in v8's superclass Entry
009b5c: 12dd |001a: const/4 v13, #int -3 // #fd

// if v12 != v13 goto 0020 // a photo entry, goto 0020
009b5e: 38dc 0500 |001b: if-ne v12, v13, 0020 // +0005

// int v5++;
009b62: d805 0501 |001d: add-int/lit8 v5, v5, #int 1 // #01

// goto 000c
009b66: 33ed |001f: goto 000c // -0013

// String v7 = v8.getData();
009b68: f801 0c00 0800 |0020: +invoke-virtual-quick {v8}, [000c] // vtable #000c
009b6e: 0c07 |0023: move-result-object v7

// boolean v12 = TextUtils.isEmpty(v7)
009b70: 7001 4200 0700 |0024: invoke-static {v7}, android/text/TextUtils.isEmpty:(Ljava/lang/CharSequence;)Z // method@0042
009b76: 0a0c |0027: move-result v12

if v12!=false/0 goto 0030
009b78: 3e0c 0800 |0028: if-nez v12, 0030 // +0008

// String v12 = v8.column // The Instance field column is defined in EditEntry. see details in dumped EditEntry section.
009b7c: f48c 2800 |002a: +iget-object-quick v12, v8, [obj+0028]

// v9.put(v12,v7) // v9 is a ContentValues, v12 is the column name, v7 is the value.
009b80: f803 0b00 c907 |002c: +invoke-virtual-quick {v9, v12, v7}, [000b] // vtable #000b

// goto 001d
009b86: 33ee |002f: goto 001d // -0012

// String v12 = v8.column // The Instance field column is defined in EditEntry. see details in dumped EditEntry section.
009b88: f48c 2800 |0030: +iget-object-quick v12, v8, [obj+0028]

// String v13 = "name";
009b8c: 180d 1c00 |0032: const-string v13, "name" // string@001c

// boolean v12 = v12.equals(v13);
009b90: ee02 0300 dc00 |0034: +execute-inline {v12, v13}, java/lang/String.equals:(Ljava/lang/Object;)Z // inline #0003
009b96: 0a0c |0037: move-result v12

// if v12 != false/0, goto 001d
009b98: 3d0c e5ff |0038: if-eqz v12, 001d // -001b

// String v12 = "EditContactActivity"
009b9c: 180c 0100 |003a: const-string v12, "EditContactActivity" // string@0001

// String v13 = "Name is required"
009ba0: 180d 0200 |003c: const-string v13, "Name is required" // string@0002

// Log.e(v12, v13); //log info using android/util/Log
009ba4: 7002 4700 dc00 |003e: invoke-static {v12, v13}, android/util/Log.e:(Ljava/lang/String;Ljava/lang/String;)I // method@0047
009baa: 0a0c |0041: move-result v12

// call this.XXX()
009bac: f801 8400 0f00 |0042: +invoke-virtual-quick {v15}, [0084] // vtable #0084

// return void
009bb2: 0e00 |0045: return-void

// ContentResolver v12 = this.getContentResolver();
009bb4: f4fc a400 |0046: +iget-object-quick v12, v15, [obj+00a4]

// CONTENT_URI v13 = People.CONTENT_URI;
009bb8: 610d 0300 |0048: sget-object v13, android/provider/Contacts$People.CONTENT_URI:Landroid/net/ContentURI; // field@0003

// CONTENT_URI v10 = v12.insert(v13, v9) // CONTENT_URI v10 = getContentResolver().insert(uri, values);
// insert people into contacts
009bbc: f803 1100 dc09 |004a: +invoke-virtual-quick {v12, v13, v9}, [0011] // vtable #0011
009bc2: 0c0a |004d: move-result-object v10

// List v12 = this.mContactEntries
009bc4: f4fc c000 |004e: +iget-object-quick v12, v15, [obj+00c0]
009bc8: 7001 8900 0c00 |0050: invoke-static {v12}, com/google/android/contacts/ContactEntryAdapter.countEntries:(Ljava/util/ArrayList;)I // method@0089
009bce: 0a06 |0053: move-result v6

// int v5 = v11 //contact Entry Count
009bd0: 01b5 |0054: move v5, v11

// if v5 >= v6, goto 009e
009bd2: 3a65 4900 |0055: if-ge v5, v6, 009e // +0049

// List v12 = this.mContactEntries
009bd6: f4fc c000 |0057: +iget-object-quick v12, v15, [obj+00c0]

// EditEntry v8 = (EditEntry) getEntry(v12, v5);
009bda: 7002 8b00 5c00 |0059: invoke-static {v12, v5}, com/google/android/contacts/ContactEntryAdapter.getEntry:(Ljava/util/ArrayList;I)Lcom/google/android/contacts/ContactEntryAdapter$Entry; // method@008b
009be0: 0c0c |005c: move-result-object v12
009be2: 07c0 |005d: move-object v0, v12
009be4: 1e00 3d00 |005e: check-cast v0, com/google/android/contacts/EditContactActivity$EditEntry // class@003d
009be8: 0708 |0060: move-object v8, v0

// int v12 = v8.kind // The Instance field kind is defined in its superclass Entry. see details in dumped Entry and EditEntry sections.
009bea: f28c 1400 |0061: +iget-quick v12, v8, [obj+0014]

// int v13 = -1 // CONTACT_KIND = -1 defined in v8's superclass Entry
009bee: 12fd |0063: const/4 v13, #int -1 // #ff

// if v12 == v13 goto 007e
009bf0: 37dc 1a00 |0064: if-eq v12, v13, 007e // +001a

// v9.clear() // ContentValues.clear()
009bf4: f801 1500 0900 |0066: +invoke-virtual-quick {v9}, [0015] // vtable #0015

// boolean v12 = v8.toValues(v9) // boolean EditEnty.toValues(ContentValues cv)
009bfa: f802 0d00 9800 |0069: +invoke-virtual-quick {v8, v9}, [000d] // vtable #000d
009c00: 0a0c |006c: move-result v12

//if v12 !=false/0, goto 007b
009c02: 3d0c 0e00 |006d: if-eqz v12, 007b // +000e

// ContentResolver v12 = this.getContentResolver();
009c06: f4fc a400 |006f: +iget-object-quick v12, v15, [obj+00a4]

// String v13 = v8.contentDirectory
009c0a: f48d 2c00 |0071: +iget-object-quick v13, v8, [obj+002c]

// v10.addPath(v13);
009c0e: f802 1800 da00 |0073: +invoke-virtual-quick {v10, v13}, [0018] // vtable #0018
009c14: 0c0d |0076: move-result-object v13

// CONTENT_URI v12 = v12.insert(v13, v9) // CONTENT_URI v12 = getContentResolver().insert(uri, values);
009c16: f803 1100 dc09 |0077: +invoke-virtual-quick {v12, v13, v9}, [0011] // vtable #0011
009c1c: 0c0c |007a: move-result-object v12

// int v5++;
009c1e: d805 0501 |007b: add-int/lit8 v5, v5, #int 1 // #01

// goto 0055
009c22: 33d8 |007d: goto 0055 // -0028

// String v7 = v8.getData();
009c24: f801 0c00 0800 |007e: +invoke-virtual-quick {v8}, [000c] // vtable #000c
009c2a: 0c07 |0081: move-result-object v7

// v9.lines
009c2c: f801 1500 0900 |0082: +invoke-virtual-quick {v9}, [0015] // vtable #0015

// boolean v12 = TextUtils.isEmpty(v7)
009c32: 7001 4200 0700 |0085: invoke-static {v7}, android/text/TextUtils.isEmpty:(Ljava/lang/CharSequence;)Z // method@0042
009c38: 0a0c |0088: move-result v12

if v12!=false/0 goto 007b
009c3a: 3e0c f2ff |0089: if-nez v12, 007b // -000e

// String v12 = v8.column // The Instance field column is defined in EditEntry. see details in dumped EditEntry section.
009c3e: f48c 2800 |008b: +iget-object-quick v12, v8, [obj+0028]

// v9.put(v12,v7) // v9 is a ContentValues, v12 is the column name, v7 is the value.
009c42: f803 0b00 c907 |008d: +invoke-virtual-quick {v9, v12, v7}, [000b] // vtable #000b

// ContentResolver v12 = this.getContentResolver();
009c48: f4fc a400 |0090: +iget-object-quick v12, v15, [obj+00a4]

// int v13 = 0
009c4c: 120d |0092: const/4 v13, #int 0 // #0

// int v14 = 0
009c4e: 120e |0093: const/4 v14, #int 0 // #0

009c50: 07c0 |0094: move-object v0, v12
009c52: 07a1 |0095: move-object v1, v10
009c54: 0792 |0096: move-object v2, v9
009c56: 07d3 |0097: move-object v3, v13
009c58: 07e4 |0098: move-object v4, v14

// int v12 = v12.update(v1,v2,v3,v4) // getContentResolver(),update(uri, values, null, null)
// refer to this method, public final int update(ContentURI uri, ContentValues values, String where, String[] selectionArgs)
009c5a: f905 1400 0000 |0099: +invoke-virtual-quick/range {v0, v1, v2, v3, v4}, [0014] // vtable #0014
009c60: 0a0c |009c: move-result v12

// goto 007b
009c62: 33de |009d: goto 007b // -0022

// int v12 = 4
009c64: 124c |009e: const/4 v12, #int 4 // #4

009c66: f5fc c800 |009f: +iput-quick v12, v15, [obj+00c8]
009c6a: f7fa 9000 |00a1: +iput-object-quick v10, v15, [obj+0090]

// int v12 = -1
009c6e: 12fc |00a3: const/4 v12, #int -1 // #ff
009c70: f801 0700 0a00 |00a4: +invoke-virtual-quick {v10}, [0007] // vtable #0007
009c76: 0c0d |00a7: move-result-object v13
009c78: f803 7f00 cf0d |00a8: +invoke-virtual-quick {v15, v12, v13}, [007f] // vtable #007f

// goto 0045
009c7e: 339a |00ab: goto 0045 // -0066
exceptions : (none)
positions : 30
0x0000 line=471
.....
0x00a3 line=515
locals : 10
//the 10 local variables defined here
0x0000 - 0x00ac reg=15 this Lcom/google/android/contacts/EditContactActivity;
0x0002 - 0x00ac reg=9 values Landroid/content/ContentValues;
0x000b - 0x00ac reg=11 contactEntryCount I
0x000c - 0x00ac reg=5 i I
0x0018 - 0x0045 reg=8 entry Lcom/google/android/contacts/EditContactActivity$EditEntry;
0x0024 - 0x0045 reg=7 data Ljava/lang/String;
0x004e - 0x00ac reg=10 contactUri Landroid/net/ContentURI;
0x0054 - 0x00ac reg=6 entryCount I
0x0061 - 0x009e reg=8 entry Lcom/google/android/contacts/EditContactActivity$EditEntry;
0x0082 - 0x009e reg=7 data Ljava/lang/String;

7 comments:

payal said...

After extracting .apk file i am not able to see xml file in any editor. it has some unrecongnized characters. hot to see it?

Yichao said...

The .xml files are converted by aapt into an internal format for efficient use on device.
and I don't know the format neither.

meng_xx said...

it is a wbxml encoded file

strazzere said...

be this is a dumb question - but how did you know what the vtable actually linked to as a function?

zeaster said...

@strazzere,
Actually I do not know what the vtable exactly linked to.
I just guess.
The locals section gives us all local variables number and their type.
That gives me many hints to guess.

Hope this helps.
Thanks.

kaoul said...

Does anybody know a working wbxml encoder-decoder for android?

Jontte said...

I found a way to encode/decode the AndroidManifest.xml found inside .apk. You need to rip the xml parser part out of Android tools:

http://android.git.kernel.org/?p=platform/frameworks/base.git;a=tree;f=tools/aapt;h=32308145fa7cc6ab28e7a6812e1af577151f6791;hb=HEAD

It does require some work, but I got it to work.