středa 12. listopadu 2014

BCEL - výpis

Následující kód ukazuje možnost výpisu všech zajímavých informací o třídě z .class souboru pomocí BCEL knihovny.

Získat pomocí BCEL importované balíky je možné po prozkoumání kódu a nalezení volání funkcí, které se nenachází v dané třídě a následné vyhledání těchto metod v balících.


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
package test_bcel;

import org.apache.bcel.Repository;
import org.apache.bcel.classfile.*;
import org.apache.bcel.generic.Type;

public class test {

 public static void main(String[] args) {

  try {
   JavaClass java_class;
         String name = "java/lang/String";
         //String name = "/home/greg/workspace/starshipmap/bin/ssView/Policko.class";
   //String name = "/home/greg/workspace/test-procyon/bin/test.class";
         
         try{
          // pro zabudovane tridy
          // napr: String name = "java/lang/String";
          java_class = Repository.lookupClass(name);
         }catch(ClassNotFoundException cnfe){
          // pro vlastni tridy
          // napr: String name = "/home/greg/workspace/starshipmap/bin/ssView/Policko.class";
          java_class = new ClassParser(name).parse();
         }
         int x = 0;
         printf(x+"**");x++;
         printf("getClassName: "+java_class.getClassName());
         printf(x+"**");x++;
         printf("getFileName: "+java_class.getFileName());
         printf(x+"**");x++;
         printf("getPackageName: "+java_class.getPackageName());
         printf(x+"**");x++;
         printf("getSourceFileName: "+java_class.getSourceFileName());
         printf(x+"**");x++;
         printf("getSuperclassName: "+java_class.getSuperclassName());
         printf(x+"**");x++;
         printf("getAccessFlags: "+java_class.getAccessFlags()+"");
         printf(x+"**");x++;
         JavaClass [] allinterfaces = java_class.getAllInterfaces();
         printf("***ALL INTERFACES");
         for (int i = 0 ; i < allinterfaces.length ; i++){
          printf("getClassName: "+allinterfaces[i].getClassName());
         }
         printf(x+"**");x++;
         JavaClass [] interfaces = java_class.getInterfaces();
         printf("***INTERFACES");
         for (int i = 0 ; i < interfaces.length ; i++){
          printf("getClassName: "+interfaces[i].getClassName());
         }
         printf(x+"**");x++;
         printf("***SUPER CLASSES");
         JavaClass [] superclases = java_class.getSuperClasses();
         for (int i = 0 ; i < superclases.length ; i++){
          printf("getClassName: "+superclases[i].getClassName());
         }
         printf(x+"**");x++;
         Attribute [] atributes = java_class.getAttributes();
         printf("***ATTRIBUTES");
         for (int i = 0 ; i < atributes.length ; i++){
          printf("atributes["+i+"].toString(): "+atributes[i].toString());
         }
         printf(x+"**");x++;
         Field [] fields = java_class.getFields();
         printf("***FIELDS");
         for (int i = 0 ; i < fields.length ; i++){
          printf("fields["+i+"].getName(): "+fields[i].getName());
          printf("fields["+i+"].getType(): "+fields[i].getType().toString());
          printf("fields["+i+"].getSignature(): "+fields[i].getSignature());
         }
         printf(x+"**");x++;
         Method [] methods = java_class.getMethods();
         for (int i = 0 ; i < methods.length ; i++){
          printf("***METODA");
          printf("methods["+i+"].getName(): "+methods[i].getName());
          printf("methods["+i+"].getSignature(): "+methods[i].getSignature());
          printf("methods["+i+"].getReturnType().toString(): "+methods[i].getReturnType().toString());
          printf("methods["+i+"].getReturnType().getSignature(): "+methods[i].getReturnType().getSignature());
          /*printf("***ATRIBUTY"); // nezajimave
          Attribute [] atributesmeth = methods[i].getAttributes();
          for (int y = 0 ; y < atributesmeth.length ; y++){
           printf("atributesmeth[y].toString() "+atributesmeth[y].toString());
          }*/
          printf("***TYPY");
          Type [] types = methods[i].getArgumentTypes();
          for (int y = 0 ; y < types.length ; y++){
           printf("types["+y+"].toString(): "+types[y].toString());
          }
         }
         printf(x+"**");x++;
         
         printf("getMajor: "+java_class.getMajor()+"");
         printf(x+"**");x++;
         printf("getMinor: "+java_class.getMinor()+"");
  } catch (Exception e) {
   e.printStackTrace();
  }
    }
 
 public static void printf(String string){
  System.out.println(string);
 }


}

Zdroje:
http://commons.apache.org/proper/commons-bcel/manual.html
http://commons.apache.org/proper/commons-bcel/apidocs/org/apache/bcel/classfile/package-summary.html
vztahy mezi třídama v .jar http://stackoverflow.com/questions/26760153/parse-jar-file-and-find-relationships-between-classes

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