11package io .github .benjaminsoelberg .jft ;
22
3- import com .sun .tools .attach .VirtualMachine ;
4-
53import java .io .File ;
64import java .io .FileOutputStream ;
75import java .io .IOException ;
86import java .lang .instrument .Instrumentation ;
9- import java .net .URL ;
10- import java .nio .charset .StandardCharsets ;
117import java .util .Arrays ;
128import java .util .Comparator ;
139import java .util .jar .JarOutputStream ;
@@ -18,66 +14,78 @@ public class ClassDumper {
1814 // Public and non-final for testing purposes only.
1915 public static String TEST_AGENT_CMD_LINE = null ;
2016
21- public static final int DUMP_BATCH_SIZE = 500 ;
17+ public static final int DUMP_BATCH_SIZE = 50 ;
2218
19+ @ SuppressWarnings ("ReassignedVariable" )
2320 public static void agentmain (String cmdline , Instrumentation instrumentation ) throws Exception {
21+ /* Stage 0: Override command line options if running a unit test */
2422 // We are unable to parse arguments to agentmain while running unit test, hence this inject-hook
2523 if (TEST_AGENT_CMD_LINE != null ) {
2624 cmdline = TEST_AGENT_CMD_LINE ;
2725 }
2826
27+ /* Stage 1: decode options */
2928 String [] args = Utils .decodeArgs (cmdline );
3029 Options options = new Options (args );
3130
32- Report report = new Report (getHeader (), options .isVerbose (), options .isLogToStdErr ());
31+ /* Stage 2: initialize report */
32+ Report report = new Report (Utils .getVersionHeader (), options .isVerbose (), options .isLogToStdErr ());
3333 report .println ("Agent loaded with options: %s%n" , String .join (" " , args ));
3434
35+ /* Stage 3: query all loaded classes */
3536 report .println ("Querying classes..." );
3637 Class <?>[] classes = getAllLoadedClasses (instrumentation , options );
3738 report .println ("" );
3839
39- // The transformer could (as a side effect by the JVM) be called with classes not in the list which is why we pass the filtered classes to it as well
40+ /* Stage 4: initialize transformer */
41+ // The transformer could (as a side effect) be called with classes not in the list which is why we pass the filtered classes list
4042 Transformer dumper = new Transformer (report , classes );
4143
4244 if (classes .length > 0 ) {
45+ /* Stage 5: add transformer */
4346 report .println ("%d classes found.%n" , classes .length );
4447 instrumentation .addTransformer (dumper , true );
4548
46- report .println ("Dumping started..." );
49+ /* Stage 6: dump all classes in filtered list */
50+ report .println ("Dumping classes..." );
4751 // Invoke the transformer and remove it when filtered classes are processed
4852 try {
4953 retransformClasses (instrumentation , classes , report );
5054 } finally {
5155 instrumentation .removeTransformer (dumper );
5256 }
53- } else {
54- report .println ("WARNING: No classes found, bad filter ?%n" );
55- }
56- report .println ("" );
57+ report .println ("" );
5758
58- // Validate that no exceptions were generated during the dump process
59- if (dumper .getLastException () != null ) {
60- report .println ("WARNING: One or more transformer exceptions occurred while dumping classes." );
61- report .dump (dumper .getLastException ());
59+ /* Stage 7: dump class loader & class tree */
60+ report .println ("Class loader & class tree..." );
61+ //TODO: Print tree
62+ // ClassTree classTree1 = new ClassTree(dumper.getClassInfos());
63+ // classTree = classTree1.getRoot();
6264 report .println ("" );
65+ } else {
66+ report .println ("WARNING: No classes found, bad filter ?%n" );
6367 }
6468
65- File destination = new File (options .getDestination ());
66-
69+ /* Stage 8: create the jar */
6770 report .println ("Creating jar..." );
71+ File destination = new File (options .getDestination ());
6872 try (JarOutputStream jar = new JarOutputStream (new FileOutputStream (destination ))) {
69- dumper .getClassInfos ().entrySet ().stream ().sorted (Comparator .comparing (o -> o .getKey ().getName ())).forEach (entry -> {
70- Class <?> clazz = entry .getKey ();
71- byte [] bytecode = entry .getValue ();
72- try {
73- report .dump (clazz , bytecode );
74- writeZipEntry (jar , Utils .toNativeClassName (clazz .getName ()) + ".class" , bytecode );
75- } catch (IOException e ) {
76- throw new RuntimeException (e );
77- }
78- });
79- writeZipEntry (jar , "report.txt" , report .generate ().getBytes (StandardCharsets .UTF_8 ));
73+
74+ ClassTree .Node root = dumper .getClassTree ().getRoot ();
75+ String base = Utils .toClassLoaderName (root .getLoader ()) + "/" ;
76+ addNodeToJar (jar , report , root , base );
77+
78+ // Validate that no exceptions were generated during the dump process and if so display it last in the report
79+ if (dumper .getLastException () != null ) {
80+ report .println ("WARNING: One or more transformer exceptions occurred while dumping classes." );
81+ report .dump (dumper .getLastException ());
82+ report .println ("" );
83+ }
84+
85+ writeZipEntry (jar , "report.txt" , Utils .fromUtf8String (report .generate ()));
8086 }
87+
88+ /* Stage 9: finalize the dump */
8189 report .println ("%nDumped classes, including report.txt, can be found in: %s" , options .getDestination ());
8290 }
8391
@@ -98,7 +106,16 @@ private static void retransformClasses(Instrumentation instrumentation, Class<?>
98106 try {
99107 // Transform the full batch in one go, and if this throws an exception some classes may have been dumped but we deduplicate later on.
100108 instrumentation .retransformClasses (batch );
109+ } catch (ClassFormatError cfe ) {
110+ System .out .println (cfe .getMessage ());
111+ System .out .println (cfe .getLocalizedMessage ());
112+ System .out .println (cfe .getCause ());
113+ Arrays .stream (cfe .getSuppressed ()).forEach (throwable -> throwable .printStackTrace ());
114+ cfe .printStackTrace ();
115+ System .exit (1 );
101116 } catch (Throwable ignored ) {
117+ ignored .printStackTrace ();
118+
102119 // Transform classes one-by-one if batch transformation failed
103120 for (Class <?> clazz : batch ) {
104121 try {
@@ -111,82 +128,24 @@ private static void retransformClasses(Instrumentation instrumentation, Class<?>
111128 }
112129 }
113130
114-
115- private static void writeZipEntry (JarOutputStream jar , String name , byte [] data ) throws IOException {
116- jar .putNextEntry (new ZipEntry (name ));
117- jar .write (data );
118- }
119-
120- @ SuppressWarnings ("ConcatenationWithEmptyString" )
121- private static String getHeader () {
122- return "" +
123- "---------------------------------------------------------%n" +
124- "--> Java Forensics Toolkit v1.1.0 by Benjamin Sølberg <--%n" +
125- "---------------------------------------------------------%n" +
126- "https://github.com/BenjaminSoelberg/JavaForensicsToolkit%n%n" ;
127- }
128-
129- private static void showUsage () {
130- System .out .println ("usage: java -jar JavaForensicsToolkit.jar [-v] [-e] [-d destination.jar] [-s] [-p] [-f filter]... [-x] <pid>" );
131- System .out .println ();
132- System .out .println ("options:" );
133- System .out .println ("-v\t verbose agent logging" );
134- System .out .println ("-e\t agent will log to stderr instead of stdout" );
135- System .out .println ("-d\t jar file destination of dumped classes" );
136- System .out .println ("\t Relative paths will be relative with respect to the target process." );
137- System .out .println ("\t A jar file in temp will be generated if no destination was provided." );
138- System .out .println ("-s\t ignore system class loader (like java.lang.String)" );
139- System .out .println ("-p\t ignore platform class loader (like system extensions)" );
140- System .out .println ("-f\t regular expression class name filter" );
141- System .out .println ("\t Can be specified multiple times." );
142- System .out .println ("-x\t exclude classes matching the filter" );
143- System .out .println ("pid\t process id of the target java process" );
144- System .out .println ();
145- System .out .println ("example:" );
146- System .out .println ("java -jar JavaForensicsToolkit.jar -d dump.jar -f 'java\\ \\ ..*' -f 'sun\\ \\ ..*' -f 'jdk\\ \\ ..*' -f 'com\\ \\ .sun\\ \\ ..*' -x 123456" );
147- }
148-
149- /**
150- * Get the absolut file location of the jar embedding this class
151- *
152- * @return absolut file location of jar
153- */
154- private static String getJarLocation () {
155- URL url = ClassDumper .class .getProtectionDomain ().getCodeSource ().getLocation ();
156- String file = url .getFile ();
157- if (url .getProtocol ().equals ("file" ) && file .startsWith ("/" ) && file .endsWith (".jar" )) {
158- return file ;
159- }
160- return null ;
161- }
162-
163- public static void main (String [] args ) throws Exception {
164- System .out .printf (getHeader ());
165- String absolutJarLocation = getJarLocation ();
166- if (args .length < 1 || absolutJarLocation == null ) {
167- showUsage ();
168- System .exit (1 );
169- }
170-
171- // We pre-parse the command line to be sure that it is syntactically correct prior to sending it to agentmain
172- Options options = new Options (args );
173-
174- String pid = options .getPid ();
175- System .out .println ("Injecting agent into JVM with pid: " + pid );
176- VirtualMachine vm = VirtualMachine .attach (pid );
177- try {
178- System .out .println ("Dumping classes to: " + options .getDestination ());
179- String [] cmdLine = options .getArgs ();
180- vm .loadAgent (absolutJarLocation , Utils .encodeArgs (cmdLine ));
181- } finally {
131+ private static void addNodeToJar (JarOutputStream jar , Report report , ClassTree .Node node , String base ) {
132+ node .getClasses ().forEach ((clazz , bytecode ) -> {
182133 try {
183- vm .detach ();
184- } catch (IOException ioe ) {
185- System .out .println ("Unable to detach from process" );
134+ report .dump (clazz , bytecode );
135+ writeZipEntry (jar , base + Utils .toNativeClassName (clazz .getName ()) + ".class" , bytecode );
136+ } catch (IOException e ) {
137+ throw new RuntimeException (String .format ("Failed to add %s with size %d to jar" , clazz .getName (), bytecode .length ), e );
186138 }
139+ });
140+
141+ for (ClassTree .Node child : node .getChildren ()) {
142+ addNodeToJar (jar , report , child , base + Utils .toClassLoaderName (child .getLoader ()) + "/" );
187143 }
144+ }
188145
189- System .out .println ("Done" );
146+ private static void writeZipEntry (JarOutputStream jar , String name , byte [] data ) throws IOException {
147+ jar .putNextEntry (new ZipEntry (name ));
148+ jar .write (data );
190149 }
191150
192151}
0 commit comments