/* * Copyright, Aspect Security, Inc. * * This file is part of JavaSnoop. * * JavaSnoop is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * JavaSnoop is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with JavaSnoop. If not, see <http://www.gnu.org/licenses/>. */ package com.aspect.snoop.agent; import com.aspect.snoop.util.IOUtil; import com.aspect.snoop.util.RandomUtil; import com.aspect.snoop.util.StringUtil; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FilenameFilter; import java.io.IOException; import java.net.URL; import java.util.Enumeration; import java.util.jar.Attributes; import java.util.jar.Manifest; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.apache.log4j.Logger; public class AgentJarCreator { private static String nl = System.getProperty("line.separator"); public static final String[] jarsToNotBootClasspath = { "bsh-2.0b4.jar", "jython.jar", "appframework-1.0.3.jar", "swing-worker-1.1.jar", "xom-1.1.jar", "rsyntaxtextarea.jar", "xstream-1.3.1.jar" }; private static Logger logger = Logger.getLogger(AgentJarCreator.class); public static void main(String[] args) throws Exception { String agentLocation = createAgentJar(true); System.out.println("Finished jar now: " + agentLocation); IOUtil.copyFile(new File(agentLocation), new File("JavaSnoop.zip")); } public static String createAgentJar(boolean attachingOnStartup) throws IOException { URL url = ClassLoader.getSystemClassLoader().getResource(""); String file = null; boolean testing = false; if ( url == null ) { file = new File(AgentJarCreator.class.getProtectionDomain().getCodeSource().getLocation().getPath()).getAbsolutePath(); } else { testing = true; file = url.getFile(); } if (testing) { // this is a test environment, get it from the working // directory. String buildDirectory = file; File f = new File( System.getProperty("java.io.tmpdir"), "JavaSnoop" + RandomUtil.randomString(6) + ".jar"); file = f.getAbsolutePath(); zip(file, buildDirectory); f.deleteOnExit(); } // step #1: create a Manifest that uses the Agent StringBuilder sbuf = new StringBuilder(); sbuf.append("Manifest-Version: 1.0" + nl); /* * Doesn't hurt to add both. */ sbuf.append( "Premain-Class: " + SnoopAgent.class.getName() + nl); sbuf.append( "Agent-Class: " + SnoopAgent.class.getName() + nl); sbuf.append("Can-Redefine-Classes: true" + nl); sbuf.append("Can-Retransform-Classes: true" + nl); sbuf.append("Boot-Class-Path: " + getJarPaths(testing, "Boot-Class-Path: ".length())); // step #2: unzip the jar we're using right now to modify String tmpDir = null; if ( testing ) { tmpDir = System.getProperty("java.io.tmpdir") + RandomUtil.randomString(10); } else { tmpDir = new File(file).getParent() + File.separator + "working"; } File f = new File(tmpDir); if ( f.exists() ) { boolean didDelete = f.delete(); if ( ! didDelete ) { logger.error("Warning - could not delete working directory!"); } } f = new File(tmpDir); // load it again, mkdir() will fail otherwise boolean success = f.mkdir(); if ( ! success ) { logger.error("Could not create dir: " + f.getAbsolutePath()); } unzip(file, tmpDir); // step #3: overwrite the manifest String metaInfDir = tmpDir + File.separator + "META-INF"; String manifestLocation = metaInfDir + File.separator + "MANIFEST.MF"; File metaDir = new File(metaInfDir); metaDir.mkdirs(); File newManifestFile = new File(manifestLocation); if ( ! newManifestFile.exists() ) { newManifestFile.createNewFile(); newManifestFile.deleteOnExit(); } new FileOutputStream(newManifestFile).write(sbuf.toString().getBytes()); // step #4: zip it back up if ( ! testing ) { // have to set it to a new location so // as not to overwrite the same jar we // ran from. file = tmpDir + File.separator + "JavaSnoop.jar"; } zip(file, tmpDir); // step #5: clean up all temp files f.deleteOnExit(); // step #6: return the location of said zip return file; } private static void zip(String zipFileName, String dir) throws IOException { File dirObj = new File(dir); ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName)); addDir(dirObj.getAbsolutePath(), dirObj, out); out.close(); } private static void addDir(String root, File dirObj, ZipOutputStream out) throws IOException { File[] files = dirObj.listFiles(); byte[] tmpBuf = new byte[1024]; for (int i = 0; i < files.length; i++) { if (files[i].isDirectory()) { addDir(root, files[i], out); continue; } if ( root.equals(dirObj.getAbsolutePath()) && files[i].getName().equals("JavaSnoop.jar")) { continue; } FileInputStream in = new FileInputStream(files[i].getAbsolutePath()); String fileWithoutRootDir = files[i].getAbsolutePath(); fileWithoutRootDir = fileWithoutRootDir.substring(root.length()+1); fileWithoutRootDir = fileWithoutRootDir.replaceAll("\\\\", "/"); ZipEntry entry = new ZipEntry(fileWithoutRootDir); out.putNextEntry(entry); int len; while ((len = in.read(tmpBuf)) > 0) { out.write(tmpBuf, 0, len); } out.closeEntry(); in.close(); } } public static void unzip(String zip, String dir) throws IOException { ZipFile zipFile = new ZipFile(zip); Enumeration enumeration = zipFile.entries(); while (enumeration.hasMoreElements()) { ZipEntry zipEntry = (ZipEntry) enumeration.nextElement(); BufferedInputStream bis = new BufferedInputStream(zipFile.getInputStream(zipEntry)); int size; byte[] buffer = new byte[2048]; File newFile = new File(dir + File.separator + zipEntry.getName()); boolean isDir = zipEntry.getName().endsWith("/"); if ( isDir ) { newFile.mkdir(); continue; } if ( ! newFile.getParentFile().exists() ) { newFile.getParentFile().mkdirs(); } newFile.createNewFile(); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(newFile), buffer.length); while ((size = bis.read(buffer, 0, buffer.length)) != -1) { bos.write(buffer, 0, size); } bos.flush(); bos.close(); bis.close(); } } private static String getJarPaths(boolean testing, int prefixLength) { if ( ! testing ) { try { Class clazz = AgentJarCreator.class; String className = clazz.getSimpleName(); String classFileName = className + ".class"; String pathToThisClass = clazz.getResource(classFileName).toString(); int mark = pathToThisClass.indexOf("!") ; String pathToManifest = pathToThisClass.toString().substring(0,mark+1) ; pathToManifest += "/META-INF/MANIFEST.MF" ; Manifest m = new Manifest(new URL(pathToManifest).openStream()); Attributes attrs = m.getMainAttributes(); String cp = attrs.getValue("Class-Path"); cp = cp.replaceAll("lib/","../lib/"); String[] entries = cp.split("\\s"); StringBuilder cpBuff = new StringBuilder(); for(int i=0;i<entries.length;i++) { String entry = entries[i]; boolean shouldIgnore = false; for(String ignoreJar : jarsToNotBootClasspath) { if ( entry.endsWith(ignoreJar) ) { shouldIgnore = true; } } if ( ! shouldIgnore ) { cpBuff.append(entry); if ( i != entries.length-1 ) cpBuff.append(" "); } } cp = cpBuff.toString(); //System.out.println("Afterwards: " + cp); return getManifestRepresentation( (72-(nl.length()+prefixLength)), cp ); } catch (IOException ex) { logger.fatal(ex); } } String classpath = System.getProperty("java.class.path"); String[] entries = classpath.split(";"); for ( String entry : entries ) { if ( entry.contains("javassist.jar") ) { String dir = entry.substring(0, entry.indexOf("javassist.jar")); File[] jars = new File(dir).listFiles( new FilenameFilter() { public boolean accept(File dir, String name) { return name.endsWith(".jar") && ! StringUtil.isIn(name,jarsToNotBootClasspath); } }); StringBuilder sb = new StringBuilder(); for (int i=0;i<jars.length;i++) { File jar = jars[i]; sb.append( jar.getAbsolutePath().replaceAll("\\\\", "/") ); if ( i != jars.length-1 ) { sb.append(" "); } } String cp = sb.toString(); cp = getManifestRepresentation( (72-(nl.length()+prefixLength)), cp ); return cp; } } return null; } public static String getManifestRepresentation(int firstRowLength, String payload) { StringBuilder sb = new StringBuilder(); if ( payload.length() <= firstRowLength ) { return payload; } sb.append( payload.substring(0, firstRowLength ) + nl); int currentIndex = firstRowLength; int whatsLeft = payload.length()-currentIndex; while( whatsLeft >= (72-(1 + nl.length())) ) { sb.append(" " + payload.substring(currentIndex,currentIndex+(72-(1 + nl.length()))) + nl); whatsLeft -= (72-(1 + nl.length())); currentIndex += (72-(1+nl.length())); } if ( whatsLeft > 0 ) { sb.append(" " + payload.substring(currentIndex) + nl); } return sb.toString(); } }