Zobrazují se příspěvky se štítkemProcyon. Zobrazit všechny příspěvky
Zobrazují se příspěvky se štítkemProcyon. Zobrazit všechny příspěvky

sobota 27. prosince 2014

Procyon

Balík Procyon je open source projekt vyvíjený pod licencí Apache 2.0. Vývoj započal koncem roku 2012 hlavním vývojářem Mikem Strobelem. Domovské stránky s možností stažení zdrojových kódů, drobným popisem a návodem lze nalézt na adrese https://bitbucket.org/mstrobel/procyon.

Procyon je balík nástrojů pro generování a analýzu zdrojového kódu. Hlavní části (knihovny) jsou Core framework, Reflection framework, Expression framework, Compiler toolset a Java decompiler.

Je nutno podotknout, že dekompilace je poměrně náročná operace a vyžaduje hodně vypočetního času procesoru. Dekompilace velkého projektu může na průměrném stroji trvat několik jednotek až desítek minut.

Použití


Pokud je stažen již zkompilovaný procyon-decompiler.jar , lze si spuštěním tohoto programu vypsat nápovědu:

java -jar procyon-decompiler.jar -?

Nebo je možné se rovnou pokusit dekompilovat nejaký soubor. Pro vyzkoušení funkčnosti lze vyzkoušet dekompilovat standardní knihovny javy jako například java.lang.String. Soubor pro dekompilaci se zadává jako parametr programu.

java -jar procyon-decompiler.jar java.lang.String

V případě použití dekompilátoru v terminálech, které neposkytují obarvení textu (vyznačení syntaxe) bude uživatel značně ochuzen. Proto se doporučují terminály s možností zabarvování textu a pokud přesto detekce ANSI selže, je možné toto vynutit přidáním argumentu -DAnsi=true.

java -DAnsi=true -jar procyon-decompiler.jar java.lang.String

Balík Procyon umožňuje také dekompilaci celých .jar souborů. K tomu je potřeba přidat dekompilátoru argumet -jar, oznamující typ přícozího souboru a dále je možné si výsledek uložit do vybraného umístění.

java -jar procyon-decompiler.jar -jar soub.jar -o out

 

Decompiler API


Tolik k použití již hotového řešení dekompilace. Dále se podíváme na způsob použití balíku Procyon v kódu. V kódu je totiž potřeba Decompiler API, které však obsahuje pouze dvě metody.


public static void decompile(
    final String internalName, 
    final ITextOutput output
);

public static void decompile(
    final String internalName, 
    final ITextOutput output
    final DecompilerSettings settings
);

Prvním parametrem funkce decompile je proměnná typu String, která požaduje v textovém tvaru identifikaci souboru pro dekompilaci. Identifikace může být definována buď adresou v souborovém systému nebo adresu v Java notaci k standardní knihovně (např.: java.lang.String). Druhý parametr je výstup metody v datovém typu ITextOutput, který je definován v rámci balíku Procyon. Poslední parametr je volitelný a je jím možno nastavit některé parametry dekompilace. V následujícím příkladu lze vidět jednoduché použití této funkce s výstupem na terminál.



final DecompilerSettings settings = DecompilerSettings.javaDefaults();

try (final FileOutputStream stream = new FileOutputStream("path/to/file");
     final OutputStreamWriter writer = new OutputStreamWriter(stream)) {

    Decompiler.decompile(
        "java/lang/String",
        new PlainTextOutput(writer),
        settings
    );
}
catch (final IOException e) {
    // handle error
}




Zdroje:
https://bitbucket.org/mstrobel/procyon
https://bitbucket.org/mstrobel/procyon/wiki/Java%20Decompiler
https://bitbucket.org/mstrobel/procyon/wiki/Decompiler%20API

pátek 5. prosince 2014

Java Dekompilátory

Dekompilátorů Java kódu je poměrně velké množství. Mnohé již ale nejsou dále vyvíjeny a další postrádají kvalitu. V následujícím přehledu charakterizuju většinu známějších dekompilátorů.


Procyon
Nový, poměrně kvalitní dekompilátor, který je stále vyvíjen.

Licence: Apache licence 2.0
Internetové stránky: https://bitbucket.org/mstrobel/procyon


CRF (Class File Reader)
Vytvořen v Java 6. Podporuje Java 6, Java 7 i Java 8. Stále vyvíjen. Občas ještě vypisuje kontrolní výpisy.

Licence: MIT licence
Internetové stránky: http://www.benf.org/other/cfr/


Krakatau
Stále vyvíjen. Vytvořen v jazyce Python (Python 2.7). Částečná podpora Java 8.

Licence: GNU GPL
Internetové stránky: https://github.com/Storyyeller/Krakatau/


Candle
Dekompilátor programovaný v Javě. Vývoj pravděpodobně skončil v roce 2013. Přestože je vývoj opřen o firmu Red Hat, není dekompilátor úplně dokončený.

Internetové stránky: https://github.com/bradsdavis/candle-decompiler


JBVD (Java Bytecode Viewer & Decompiler)
Vývoj skončil v roce 2011. Přestože označovaný jako OpenSource, nepodařilo se mi na internetu najít zdrojový kód.

Licence: Academic Free License (AFL)
Internetové stránky: http://jbdec.sourceforge.net/


EDJC (Emilio's Java Decompiler)
Dekompilátor vytvářen v jazyce Java. Vývoj skončil alfa verzí v roce 2011.

Licence: GNU GPL
Internetové stránky: http://sourceforge.net/projects/ejdc/


JD
Součásti JD-Code a JD-Gui programovány v jazyve C++. Podpora jdk1.1.8, jdk1.3.1, jdk1.4.2, jdk1.5.0, jdk1.6.0 a jdk1.7.0. Tento dekompilátor poskytuje plugin k vývojovému prostředí Eclipse.

Licence: MIT licence, uzavřený kód
Internetové stránky: http://jd.benow.ca/


FernFlower
Vytvořeno v jazyce Java. K projektu není žádná dokumentace ani bližší informace.

Licence: Freeware License 1.0
Internetové stránky: pravděpodobně nemá, ale zdrojový kód je ke stažení zde http://forum.xda-developers.com/showthread.php?t=2029842


JaD (JAva Decompiler)
Dekompiler podporující dekompilaci Java 7. K dispozici je také plugin pro vývojové prostředí Eclipse s názvem JadClipse.

Licence: Komerční program (pro nekomerční použití zdarma)
Internetové stránky: http://varaneckas.com/jad/


DJ
Určený pro operační system Windows. Součástí je i editor.

Licence: Komerční (poslední verze s použitím zdarma byla 3.7.7.81)
Internetové stránky: http://www.neshkov.com/dj.html (stránka http://dj.navexpress.com/ je již nefunkční)


Mocha
V roce 1996 vydána betaverze, poté vývoj skončil.

Internetové stránky: http://www.brouhaha.com/~eric/software/mocha/


Závěr
Je zřejmé, že máme k dispozici velké množství dekompilátorů pro Java bytekód. Avšak kvalitní open source dekompilátor vyvíjený v Javě je pouze Procyon.



Zdroje:
https://developer.jboss.org/people/ozizka/blog/2014/05/06/java-decompilers-a-sad-situation-of
https://bitbucket.org/mstrobel/procyon
http://www.benf.org/other/cfr/
https://github.com/Storyyeller/Krakatau/
https://github.com/bradsdavis/candle-decompiler
http://jbdec.sourceforge.net/
http://sourceforge.net/projects/ejdc/
http://jd.benow.ca/
http://forum.xda-developers.com/showthread.php?t=2029842
http://www.neshkov.com/dj.html 
http://www.brouhaha.com/~eric/software/mocha/

pondělí 10. listopadu 2014

Vytvoření AST pomocí Procyon z .jar souboru

Program pro vytvoření AST pomocí Procyonu z .jar souboru může vypadat následovně:

Potřebné knihovny:

1
2
3
4
5
6
7
8
9
import java.io.IOException;
import java.util.Enumeration;
import java.util.jar.*;
import com.strobel.assembler.*;
import com.strobel.assembler.metadata.*;
import com.strobel.core.*;
import com.strobel.decompiler.*;
import com.strobel.decompiler.languages.java.*;
import com.strobel.decompiler.languages.java.ast.*;

Funkce pro rekurzivní zanořování v AST stromě a výpis informací o tokenech:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
private static void show_me_smtng(AstNode an, int hloubka){
 
 while (an!=null){
  final String mezera = "  ";
     

  for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
  System.out.println(an.getRole()+"("+an.getNodeType()+")"+"--"+an.getRegion());
     
     if (an.getNodeType()!=NodeType.STATEMENT && an.getNodeType()!=NodeType.TYPE_DECLARATION && an.getNodeType()!=NodeType.MEMBER){
      for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
      if (an.getText()!=null) System.out.println(" \""+an.getText().replaceAll("\n", "")+"\"");
     }else{
   for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
      System.out.println(" ...");
     }
     
     if (an.hasChildren()){
   for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
      System.out.println("+childs:");
      /*if (hloubka > 2){
       an = an.getNextSibling();
       System.out.println("--utnuto--"); 
       continue;
      }*/
      
      show_me_smtng(an.getFirstChild(),hloubka+1);
     }else{
   for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
      System.out.println("-nema potomky");
     }

     System.out.println("");
     an = an.getNextSibling();
    }
}

Upravená metoda decompile třídy Decompiler. Tedy metoda, která zajistí vytvoření AST:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public static void decompile(String internalName, PlainTextOutput output, DecompilerSettings settings) {
 
 VerifyArgument.notNull(internalName, "internalName");
 VerifyArgument.notNull(settings, "settings");
 
 final ITypeLoader typeLoader = settings.getTypeLoader() != null ? settings.getTypeLoader() : new InputTypeLoader();
 final MetadataSystem metadataSystem = new MetadataSystem(typeLoader);
 
 final TypeReference type;
 
 if (internalName.length() == 1) {
     //
     // Hack to get around classes whose descriptors clash with primitive types.
     //
 
     final MetadataParser parser = new MetadataParser(IMetadataResolver.EMPTY);
     final TypeReference reference = parser.parseTypeDescriptor(internalName);
 
     type = metadataSystem.resolve(reference);
 }
 else {
     type = metadataSystem.lookupType(internalName);
 }
 
 final TypeDefinition resolvedType;
 
 if (type == null || (resolvedType = type.resolve()) == null) {
     System.out.println("!!! ERROR: Failed to load class "+ internalName+".");
     return;
 }
 
 DeobfuscationUtilities.processType(resolvedType);
 
 final DecompilationOptions options = new DecompilationOptions();
 
 options.setSettings(settings);
 options.setFullDecompilation(true);
 
 if (settings.getFormattingOptions() == null) {
     settings.setFormattingOptions(JavaFormattingOptions.createDefault());
 }
 
 CompilationUnit cu = new CompilationUnit();
 JavaLanguage jl = new JavaLanguage();
 cu=jl.decompileTypeToAst(resolvedType, options);
 
 
 AstNode an = null;
 if (cu.hasChildren()){
  an = cu.getChildren().iterator().next(); 
 }else{
  System.out.println("neni dalsi");
 }
 
 show_me_smtng(an,0);
}


A na závěr všehospouštěč main:
1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public static void main(String[] args) { 
  
 final String jarName = "/home/greg/Plocha/starship.jar";
 try {
  JarFile jarFile = new JarFile(jarName);
  JarTypeLoader jarFileloader = new JarTypeLoader(jarFile);
  PlainTextOutput output;
  
  DecompilerSettings settings = new DecompilerSettings();
  settings.setShowSyntheticMembers(false);
  settings.setTypeLoader(new CompositeTypeLoader(new ITypeLoader[] { jarFileloader, new InputTypeLoader() }));
  
  Enumeration<JarEntry> entries = jarFile.entries();

  while (entries.hasMoreElements()){
   JarEntry entry = entries.nextElement();
   System.out.println(entry.getName());
   if (entry.getName().endsWith(".class")){
    output = new PlainTextOutput();
    String name = StringUtilities.removeRight(entry.getName(), ".class");
    decompile(name, output, settings);
    System.out.print(output);
   }
  }
  
 } catch (IOException e) {
  e.printStackTrace();
 }
 
    
}


Dekompilace dekompilátoru - dekompilace .jar souborů

Info o práci s .jar soubory je na wiki Procyonu velice málo. Zkompilovaný procyon, ale toto dovede, takže někde musí být funkce main, která si s tím poradí =). Pro kontrolu jsem si vyzkoušel dekompilovat jar soubor samotného procyonu:

$ java -jar procyon-decompiler-0.5.27.jar -jar procyon-decompiler-0.5.27.jar -o out

Zabere to ale nějakou chvíli. Výsledek se uloží do složky "out". V této složce jsem dále spustil příkaz:


$ find | grep .java | while read soub; do echo $soub;cat $soub | grep "main("; done

Tento kód vypíše s názvem souboru, zda našel řetězec "main(", což by mělo jednoznačně specfikovat funkci main.

Funkce main se nachází v
./out/com/strobel/decompiler/DecompilerDriver.java

Podle kódu okolo mainu lze už práci s jar soubory nějak pochopit. V následujícím kódu demonstruju jednoduchý způsob kompletní dekompilace (výsledek je dekompilovaný kód vypsaný do terminálu) jar souboru.

1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
import java.io.IOException;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import com.strobel.assembler.InputTypeLoader;
import com.strobel.assembler.metadata.CompositeTypeLoader;
import com.strobel.assembler.metadata.ITypeLoader;
import com.strobel.assembler.metadata.JarTypeLoader;
import com.strobel.core.StringUtilities;
import com.strobel.decompiler.*;

public class test {
 public static void main(String[] args) { 
  

  final String internalName = "/path/file.jar";
  try {
   JarFile jarFile = new JarFile(internalName);
   JarTypeLoader jarFileloader = new JarTypeLoader(jarFile);
   PlainTextOutput output;
   
   DecompilerSettings settings = new DecompilerSettings();
   settings.setShowSyntheticMembers(false);
   settings.setTypeLoader(new CompositeTypeLoader(new ITypeLoader[] { jarFileloader, new InputTypeLoader() }));
   
   Enumeration<JarEntry> entries = jarFile.entries();

   while (entries.hasMoreElements()){
    JarEntry entry = entries.nextElement();
    System.out.println(entry.getName());
    if (entry.getName().endsWith(".class")){
     output = new PlainTextOutput();
     String name = StringUtilities.removeRight(entry.getName(), ".class");
     Decompiler.decompile(name, output, settings);
     System.out.print(output);
    }
   }
   
  } catch (IOException e) {
   e.printStackTrace();
  }
 }
}

Zdroje:
https://bitbucket.org/mstrobel/procyon/downloads
https://bitbucket.org/mstrobel/procyon/wiki/Java%20Decompiler

Procyon příklady

Následující příklad ukazuje jednoduchou dekompilaci souboru .class na adrese /path/file.class. Výsledkem této dekompilace bude výpis dekompilovaného kódu zadaného souboru do terminálu.

1
2
3
4
5
6
7
8
9
import com.strobel.decompiler.*;

    public class test {
        public static void main(String[] args) {
            PlainTextOutput output = new PlainTextOutput();
            Decompiler.decompile("/path/file.class",output);
            System.out.print(output);
        }
    }

Více práce už dá, pokud chceme získat pomocí Procyon abstraktní syntaktický strom (AST). K tomuto účelu jsem si musel napsat vlastní metodu podobnou Decompiler.decompile(...) (viz zdrojáky Procyon) a výsledný AST procházet rekurzivně.

1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
import com.strobel.assembler.InputTypeLoader;
import com.strobel.assembler.metadata.DeobfuscationUtilities;
import com.strobel.assembler.metadata.IMetadataResolver;
import com.strobel.assembler.metadata.ITypeLoader;
import com.strobel.assembler.metadata.MetadataParser;
import com.strobel.assembler.metadata.MetadataSystem;
import com.strobel.assembler.metadata.TypeDefinition;
import com.strobel.assembler.metadata.TypeReference;
import com.strobel.core.VerifyArgument;
import com.strobel.decompiler.*;
import com.strobel.decompiler.languages.java.JavaFormattingOptions;
import com.strobel.decompiler.languages.java.JavaLanguage;
import com.strobel.decompiler.languages.java.ast.AstNode;
import com.strobel.decompiler.languages.java.ast.CompilationUnit;
import com.strobel.decompiler.languages.java.ast.NodeType;

public class test {
	public static void main(String[] args) { 
  
		final String internalName = "/home/greg/workspace/test-procyon/bin/test.class";
  
		/////**** zkopirovano z metody  Decompiler.decompile(...)
		final DecompilerSettings settings = new DecompilerSettings();
  
		VerifyArgument.notNull(internalName, "internalName");
		VerifyArgument.notNull(settings, "settings");
  
		final ITypeLoader typeLoader = settings.getTypeLoader() != null ? settings.getTypeLoader() : new InputTypeLoader();
		final MetadataSystem metadataSystem = new MetadataSystem(typeLoader);
  
		final TypeReference type;
  
		if (internalName.length() == 1) {
			//
			// Hack to get around classes whose descriptors clash with primitive types.
			//

			final MetadataParser parser = new MetadataParser(IMetadataResolver.EMPTY);
			final TypeReference reference = parser.parseTypeDescriptor(internalName);

			type = metadataSystem.resolve(reference);
		}
		else {
			type = metadataSystem.lookupType(internalName);
		}
  
		final TypeDefinition resolvedType;

		if (type == null || (resolvedType = type.resolve()) == null) {
			System.out.println("!!! ERROR: Failed to load class "+ internalName+".");
			return;
		}

		DeobfuscationUtilities.processType(resolvedType);

		final DecompilationOptions options = new DecompilationOptions();

		options.setSettings(settings);
		options.setFullDecompilation(true);

		if (settings.getFormattingOptions() == null) {
			settings.setFormattingOptions(JavaFormattingOptions.createDefault());
		}
		//////******* konec casti zkopirovane z Decompiler.decompile(...)



		CompilationUnit cu = new CompilationUnit();
		JavaLanguage jl = new JavaLanguage();
		cu=jl.decompileTypeToAst(resolvedType, options); // misto decompileType dekompilujeme do AST


		AstNode an = null;
		if (cu.hasChildren()){
			an = cu.getChildren().iterator().next(); 
		}else{
			System.out.println("neni dalsi");
		}

		// pro vypis vsech urovni AST stromu bude tuto metodu potreba volat rekurzivne
		show_me_smtng(an,0);
 }
  
 /*
  * vypisuje a zanoruje se hloubeji do ast stromu
  */
	private static void show_me_smtng(AstNode an, int hloubka){
  
		while (an!=null){
			final String mezera = "  "; // identifikator hloubky ve strome

			for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
			System.out.println(an.getRole()+"("+an.getNodeType()+")"+"--"+an.getRegion()); // vypis nejzajimavejsich informaci o uzlu

			// ignorovat vypis STATEMENT, TYPE_DECLARATION a MEMBER - zbytecne moc textu
			if (an.getNodeType()!=NodeType.STATEMENT && an.getNodeType()!=NodeType.TYPE_DECLARATION && an.getNodeType()!=NodeType.MEMBER){
				for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
				if (an.getText()!=null) System.out.println(" \""+an.getText().replaceAll("\n", "")+"\"");
			}else{
				for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
				System.out.println(" ...");
			}

			// zanoreni hloubeji do stromu
			if (an.hasChildren()){
				for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
				System.out.println("+childs:");

				// v pripade potreby omezeni hloubky stromu
				/*if (hloubka > 2){
					an = an.getNextSibling();
					System.out.println("--utnuto--"); 
					continue;
				}*/

				show_me_smtng(an.getFirstChild(),hloubka+1);
			}else{
				for (int i = 0 ; i < hloubka ; i++) System.out.print(mezera);
				System.out.println("-nema potomky");
			}

			System.out.println("");
			an = an.getNextSibling(); // nasledujici soused
		}
	}
}



Zdroje:
https://bitbucket.org/mstrobel/procyon/wiki/Decompiler%20API

úterý 21. října 2014

Instalace balíku Procyon

Zdrojáky staženy z repozitáře https://bitbucket.org/mstrobel/procyon pomocí programu hg.
 hg clone https://bitbucket.org/mstrobel/procyon
Na první pohled drobné zklamání, chybí soubor build.xml, takže kompilace programem ant asi neprovedu. Složka ale obsahuje soubor build.gradle, takže máme co dočinění s programem gradle a ten teda ještě neznám. Podle www.zdrojak.cz vám gradle dokáže i vyvenčit psa, tak jsem zvědav.

$ sudo apt-get install gradle
Čtu seznamy balíků… Hotovo
Vytváří se strom závislostí      
Čtu stavové informace… Hotovo
Některé balíky nemohly být instalovány. To může znamenat, že požadujete
nemožnou situaci, nebo, pokud používáte nestabilní distribuci, že
vyžadované balíky ještě nebyly vytvořeny nebo přesunuty z Příchozí fronty.
Následující informace vám mohou pomoci vyřešit tuto situaci:

Následující balíky mají nesplněné závislosti:
 gradle : Závisí na: libgradle-plugins-java (= 1.4-2ubuntu1) ale nebude se instalovat
E: Nelze opravit problémy, některé balíky držíte v porouchaném stavu.
Gradle se zjevně nemá rad s eclipse a to kvůli knihovně libtomcat6-java, kterou gradle vyžaduje, zatímco eclipse vyžaduje libtomcat7-java. Takže po odinstalování eclipse a nainstalování gradle spouštím.

Příkazem gradle tasks lze získat zajímavý výpis všech "tasků", které, je možné spustit. Takže spouštím gradle jar a gradle javadoc. Příkaz gradle install končí s chybou, takže si s přidáním .jar souborů do vývojového prostředí snad poradím ručně.

Soubory .jar úspěšně vytvořeny nyní už jen zpátky nainstalovat eclipse :-)

Zdroje:
https://bitbucket.org/mstrobel/procyon
http://www.zdrojak.cz/clanky/gradle-moderni-nastroj-na-automatizaci/