Bekannterweise verwendet die Dalvik Virtual Machine der Android Plattform ja ein eigenes Format (Dalvik Executeables) für die compilierten Java-Klassen und nicht das Standard Java .jar-Format mit den entsprechenden .class-Dateien. Somit ist ein Decompilen mit Tools wie "Jad" oder ähnlichem nicht möglich.
Diese Dalvik Executeables (.dex) Dateien befinden sich bei einer installierten Anwendung in der Android Package Datei (ist ein normales Zip-File mit der Dateiendung .apk) und heißen "classes.dex". Erstellt werden sie mit dem "dx" Tool aus den Java .class-Dateien, welches im Android SDK enthalten ist.
Auf der Suche nach einem Decompiler/Disassembler bin ich sehr schnell auf zwei Open Source Projekte gestoßen, die beide ganz einfach zu handhaben sind:
Dedexer - a disassembler tool for DEX file
http://dedexer.sourceforge.net
smali - An assembler/disassembler for Android's dex format
http://code.google.com/p/smali/
Nachfolgend eine kleine Demonstration, wie ich eine sehr einfache Demo Applikation mit Dedexer disassembliert habe:
1) Zuerst habe ich mit dem Android SDK unter Eclipse ein neues Android Projekt angelegt (TestApp) und folgenden simplen Code in die generierte Activity Klasse geschrieben:
package com.blackcap.android;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class TestApp extends Activity {
private final static String LOG_TAG = "TestApp";
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
String text = "Hallo Android";
int quersumme = 0;
for (int i = 0; i < text.length(); i++) {
char c = text.charAt(i);
quersumme += (c);
}
Log.i(LOG_TAG, "quersumme = " + quersumme);
if (quersumme == 1233) {
Log.i(LOG_TAG, "Quersumme ist korrekt!");
} else {
Log.i(LOG_TAG, "Quersumme fehlerhaft!");
}
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
2) Danach habe ich testweise die Applikation einmal im Emulator gestartet und mir im LogCat den Output angeschaut:
10-22 19:04:04.375: INFO/TestApp(862): quersumme = 1233
10-22 19:04:04.375: INFO/TestApp(862): Quersumme ist korrekt!
Perfekt, funktioniert - was soll bei den paar Zeilen Code auch schon falsch sein ;-)
3) Im bin-Verzeichnis des Android Projektes findet sich nun die Datei "TestApp.apk", welche die ganze Anwendung enthält. Der Aufruf von "unzip TestApp.apk" liefert folgendes:
Archive: /home/hschwarz/workspace/TestApp/bin/TestApp.apk
extracting: res/drawable/icon.png
inflating: res/layout/main.xml
inflating: AndroidManifest.xml
extracting: resources.arsc
inflating: classes.dex
inflating: META-INF/MANIFEST.MF
inflating: META-INF/CERT.SF
inflating: META-INF/CERT.RSA
4) Der Java-Code ist wie schon erwähnt in der Datei classes.dex, die als nächstes mit dem Dedexer zu zerlegen ist. Für die Installation von Dedexer ist nur die Datei "ddx1.7.jar" von SourceForge notwendig und danach kann man mit folgendem Befehl die classes.dex disassemblieren:
java -jar ddx1.7.jar -d . classes.dex
Als Output bekommt man folgendes:
Processing com/blackcap/android/R$attr
Processing com/blackcap/android/R$drawable
Processing com/blackcap/android/R$layout
Processing com/blackcap/android/R$string
Processing com/blackcap/android/R
Processing com/blackcap/android/TestApp
Für jede ursprüngliche .class-Datei der Anwendung erhält man so wieder eine Datei, welche den disassemblieren Code in einem Assembler-ähnlichen Format enthält. Diese Dateien haben als Dateieindung ".ddx".
Die Datei "TestApp.ddx" sieht nun wie folgt aus:
.class public com/blackcap/android/TestApp
.super android/app/Activity
.source TestApp.java
.field private static final LOG_TAG Ljava/lang/String; = "TestApp"
.method public()V
.limit registers 1
; this: v0 (Lcom/blackcap/android/TestApp;)
.line 7
invoke-direct {v0},android/app/Activity/; ()V
return-void
.end method
.method public onCreate(Landroid/os/Bundle;)V
.limit registers 9
; this: v7 (Lcom/blackcap/android/TestApp;)
; parameter[0] : v8 (Landroid/os/Bundle;)
.var 0 is c C from l4bc to l4c4
const-string v6,"TestApp"
.line 13
const-string v3,"Hallo Android"
.line 15
const/4 v2,0
.line 16
const/4 v1,0
l458:
invoke-virtual {v3},java/lang/String/length ; length()I
move-result v4
if-lt v1,v4,l4b4
.line 21
const-string v4,"TestApp"
new-instance v4,java/lang/StringBuilder
const-string v5,"quersumme = "
invoke-direct {v4,v5},java/lang/StringBuilder/; (Ljava/lang/String;)V
invoke-virtual {v4,v2},java/lang/StringBuilder/append ; append(I)Ljava/lang/StringBuilder;
move-result-object v4
invoke-virtual {v4},java/lang/StringBuilder/toString ; toString()Ljava/lang/String;
move-result-object v4
invoke-static {v6,v4},android/util/Log/i ; i(Ljava/lang/String;Ljava/lang/String;)I
.line 23
const/16 v4,1233
if-ne v2,v4,l4c4
.line 24
const-string v4,"TestApp"
const-string v4,"Quersumme ist korrekt!"
invoke-static {v6,v4},android/util/Log/i ; i(Ljava/lang/String;Ljava/lang/String;)I
l4a2:
.line 29
invoke-super {v7,v8},android/app/Activity/onCreate ; onCreate(Landroid/os/Bundle;)V
.line 30
const/high16 v4,32515
invoke-virtual {v7,v4},com/blackcap/android/TestApp/setContentView ; setContentView(I)V
.line 31
return-void
l4b4:
.line 17
invoke-virtual {v3,v1},java/lang/String/charAt ; charAt(I)C
move-result v0
l4bc:
.line 18
add-int/2addr v2,v0
.line 16
add-int/lit8 v1,v1,1
goto l458
l4c4:
.line 26
const-string v4,"TestApp"
const-string v4,"Quersumme fehlerhaft!"
invoke-static {v6,v4},android/util/Log/i ; i(Ljava/lang/String;Ljava/lang/String;)I
goto l4a2
.end method
Wenn man ein wenig Assembler-Erfahrung hat findet man sich in dem disassemblieren Code recht schnell zu Recht. Die verschiedenen Opcodes der Dalvik VM sind auf http://pallergabor.uw.hu/androidblog/dalvik_opcodes.html beschrieben.

2 Kommentare: