Java mit Jad decompilen

In diesem Blogpost möchte ich das Tool „jad“ zum Decompilen von Java-Programmen vorstellen. Ich habe dazu ein kleines CrackMe geschrieben, welches wir „cracken“ werden.

Voraussetzungen

Bevor wir jedoch mit dem Cracking anfangen können, müsst Ihr einige Voraussetzungen erfüllen:

  • Java Runtime Environment (JRE) muss installiert sein (java)
  • Java Development Kit (JDK) muss installiert sein (javac)
  • Jad musst installiert sein (http://www.varaneckas.com/jad/ oder Paketquellen)

Nachdem diese Tools installiert sind, kann es mit dem Lesen weitergehen :)

Das CrackMe

Ich habe ein einfaches CrackMe geschrieben, welches von der Console ein Passwort entgegen nimmt, und überprüft, ob dieses mit einem hinterlegten übereinstimmt. Ist dies der Fall, so wird banal „right password“ ausgegeben.

Um dieses CrackMe umzusetzen, benötigen wir zwei Dateien:

  • main.java
  • cracks/CrackMe.java

main.java

Ich gehe davon aus, dass man sich für dieses Projekt ein eigenes Verzeichnis angelegt hat. Darin legen wir die main.java mit folgendem Inhalt an:

import cracks.CrackMe;

public class main {

    public static void main(String[] args) {

        CrackMe crackme = new CrackMe();

        crackme.crackMe();

    }

}

Die Main-Klasse macht nichts anderes, als das sie ein neues CrackMe-Objekt erzeugt und dessen Validierungsroutine aufruft.

CrackMe.java

Die interessantere Datei ist das CrackMe selbst. In unserem Projektverzeichnis legen wir einen Unterordner namens „cracks“ an. Darin speichern wir dann die CrackMe.java mit folgendem Inhalt:

package cracks;
import java.io.Console;

public class CrackMe {

    private String passwd = "1337Me";

    public void crackMe() {

        Console console = System.console();

        System.out.println("Enter password: ");

        String input = console.readLine();

        if(this.passwd.equals(input)) {
            System.out.println("Right password");
        } else {
            System.out.println("Wrong password");
        }
    }
}

Die „crackMe“-Funktion liest von der Konsole eine Zeile ein, und überprüft daraufhin, ob die Eingabe mit der privaten Variable „passwd“ übereinstimmt.

Kompilieren & Ausprobieren

Mit reinem Sourcecode können wir natürlich nicht viel anfangen. Deswegen bemühen wir zwei Aufrufe von javac um aus dem Quelltext die Class-Dateien zu erstellen:

javac main.java
javac cracks/CrackMe.java

Ein Aufruf von ls sollte uns dann die Existenz der Dateien bestätigen:

-> % ls *
-rw-r--r-- 1 gehaxelt users  311 23. Mär 22:28 main.class
-rw-r--r-- 1 gehaxelt users  172 23. Mär 22:28 main.java

cracks:
insgesamt 16K
drwxr-xr-x 2 gehaxelt users 4,0K 23. Mär 22:27 .
drwxr-xr-x 3 gehaxelt users 4,0K 23. Mär 22:28 ..
-rw-r--r-- 1 gehaxelt users  790 23. Mär 22:27 CrackMe.class
-rw-r--r-- 1 gehaxelt users  452 23. Mär 22:27 CrackMe.java

Wenn wir unser kleines Programm nun ausprobieren wollen müssen wir einfach die main aufrufen:

-> % java main
Enter password: 
TestPasswort
Wrong password

 CrackMe cracken

Wer sich den Quelltext angeschaut hat, der wird das Passwort bereits kennen. Da dies alles nur ein Beispiel sein soll, ist dieser Fakt mal zu vernachlässigen ;) Es gibt verschiedene Wege an das Passwort zu gelangen:

  • BruteForce
  • Strings
  • Jad

BruteForce

Man könnte bruteforce-artig das Programm aufrufen und verschiedene Eingaben ausprobieren, bis man entsprechend das richtige Passwort erraten hat. Dies ist aber kaum effizient, weswegen wir diesen Weg nicht weiter betrachten werden.

Strings

Ich hatte auf meinem alten Blog strings & Co bereits erläutert. Wenn man davon ausgeht, dass das zu überprüfende Passwort im Programm nicht weiter verschlüsselt ist, könnte dies ein erster Ansatz sein. Wir rufen einfach strings mit der CrackMe.class als Parameter auf:

-> % strings cracks/CrackMe.class
passwd
Ljava/lang/String;

Code
LineNumberTable
crackMe
StackMapTable
SourceFile
CrackMe.java
1337Me
Enter password: 
Right password
Wrong password
cracks/CrackMe
java/lang/Object
java/io/Console
java/lang/String
java/lang/System
console
()Ljava/io/Console;
Ljava/io/PrintStream;
java/io/PrintStream
println
(Ljava/lang/String;)V
readLine
()Ljava/lang/String;
equals
(Ljava/lang/Object;)Z

Wer genau hinschaut, findet das richtige Passwort in der Ausgabe. Falls man das Passwort nicht kennt, könnte man alle ausgegebenen Strings als Eingabe prüfen. Dies wäre aber keine gute Lösung, falls die Validierung von äußeren Faktoren abhängen würde (Serverabfragen, etc).

Jad

Der Blogpost soll eigentlich um das Tool „jad“ gehen. Mit diesem Tool kann man aus den .class-Dateien den Quelltext soweit wie möglich wieder herstellen.

Tipp: .jar-Dateien kann man einfach mit unzip entpacken, da sie auch nur Archive sind.

Für unseren CrackMe erstellen wir erstmal einen neuen Ordner „jad“ und kopieren die .class Dateien dorthin:

mkdir jad
mkdir jad/cracks
cp main.class jad/main.class
cp cracks/CrackMe.class jad/cracks/CrackMe.class
cd jad

Wir wechseln in das neu erstellte Verzeichnis, da wir darin operieren werden.

Das Decompilen

Wir müssen nun die zwei .class-Dateien dekompilieren. Wir brauchen dazu zwei Aufrufe von jad:

-> % jad -v -stat main.class
Parsing main.class...The class file version is 51.0 (only 45.3, 46.0 and 47.0 are supported)
 Generating main.jad
Method 
Method main
#classes: 1, #methods: 2, #fields: 0, elapsed time: 0.001s
-> % jad -v -stat -d cracks cracks/CrackMe.class
Parsing cracks/CrackMe.class...The class file version is 51.0 (only 45.3, 46.0 and 47.0 are supported)
 Generating CrackMe.jad
Method 
Method crackMe
#classes: 1, #methods: 2, #fields: 1, elapsed time: 0.002s

Der Parameter „-v“ lässt den Decompiler gesprächiger sein, und „-stat“ zeigt ein paar Statistiken an. Mit dem Parameter „-d“ setzen wir den Output-Ordner, welchen wir im zweiten Aufruf anpassen müssen.

Wir sollten erfolgreich die Dateien dekompiliert, und jad sollte diese als .jad Datei angelegt haben. Der Inhalt sollte so ähnlich aussehen:

main.jad

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   main.java

import cracks.CrackMe;

public class main
{

    public main()
    {
    }

    public static void main(String args[])
    {
        CrackMe crackme = new CrackMe();
        crackme.crackMe();
    }
}

Diese Main-Datei interessiert uns nicht wirklich, da darin keine Validierung der Eingabe stattfindet.

 CrackMe.jad

Diese Datei ist viel interessanter, und deren Inhalt ist folgender:

// Decompiled by Jad v1.5.8e. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.geocities.com/kpdus/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   CrackMe.java

package cracks;

import java.io.Console;
import java.io.PrintStream;

public class CrackMe
{

    public CrackMe()
    {
        passwd = "1337Me";
    }

    public void crackMe()
    {
        Console console = System.console();
        System.out.println("Enter password: ");
        String s = console.readLine();
        if(passwd.equals(s))
            System.out.println("Right password");
        else
            System.out.println("Wrong password");
    }

    private String passwd;
}

Man erkennt eindeutig, dass der Decompiler ein etwas anderen Source erzeugt, als wir ihm geschrieben haben. Das wichtigste ist jedoch, dass wir nun Einsicht in den Quelltext haben.

Wenn wir nun das Programm „cracken“ wollen, können wir den einfachen Weg nehmen und die If-Abfrage abändern. Aus

if(passwd.equals(s))

wird

if(true)

Damit ist die Bedingung immer wahr, und man wird immer durchgelassen. Ziel soweit erfolgreich.

Als letztes müssen wir die Dateien nur noch neu kompilieren. Dazu benennen wir diese zunächst von .jad nach .java um:

cp main.jad main.java
cp cracks/CrackMe.jad cracks/CrackMe.java

Ich habe hier bewusst auf „cp“ gesetzt, damit wir ein Backup vom Decompiler-Output haben. Unsere umbenannten Dateien müssen wir nur noch mit dem javac bearbeiten.

javac main.java
javac cracks/CrackMe.java

Das CrackMe sollte nun gecracked sein, und jede beliebige Eingabe als korrekt durchlassen. Wer dies ausprobieren möchte, muss einfach wieder die main ausführen:

-> % java main
Enter password: 
bestimmt nicht das richtige Passwort :(
Right password

Fazit

Falls Ihr Projekte in Java schreibt, dann solltet ihr wissen, dass man sehr leicht an den Quelltext gelangen kann. Falls man darin wichtige Informationen verstecken möchte, dann hilft ggf. ein Obfuscator oder die Verschlüsselung der Informationen. Alles in allem wird es aber einen Weg geben, an diese Informationen zu gelangen.

Dieser Blogpost soll nur für Lernzwecke dienen und keinesfalls zu illegalen Aktivitäten anstiften.

~ Sebastian