com.yworks.yguard.obf.GuardDB Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of retroguard Show documentation
Show all versions of retroguard Show documentation
The open-source Java obfuscation tool working with Ant and Gradle by yWorks - the diagramming experts
/*
* YGuard -- an obfuscation library for Java(TM) classfiles.
*
* Original Copyright (c) 1999 Mark Welsh ([email protected])
* Modifications Copyright (c) 2002 yWorks GmbH ([email protected])
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* The author may be contacted at [email protected]
*
* Java and all Java-based marks are trademarks or registered
* trademarks of Sun Microsystems, Inc. in the U.S. and other countries.
*/
package com.yworks.yguard.obf;
import java.io.*;
import java.util.*;
import java.util.zip.*;
import java.util.jar.*;
import java.security.*;
import com.yworks.yguard.*;
import com.yworks.yguard.obf.classfile.*;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
/**
* Classfile database for obfuscation.
*
* @author Mark Welsh
*/
public class GuardDB implements ClassConstants
{
// Constants -------------------------------------------------------------
private static final String STREAM_NAME_MANIFEST = "META-INF/MANIFEST.MF";
private static final String MANIFEST_NAME_TAG = "Name";
private static final String MANIFEST_DIGESTALG_TAG = "Digest-Algorithms";
private static final String CLASS_EXT = ".class";
private static final String SIGNATURE_PREFIX = "META-INF/";
private static final String SIGNATURE_EXT = ".SF";
private static final String LOG_MEMORY_USED = " Memory in use after class data structure built: ";
private static final String LOG_MEMORY_TOTAL = " Total memory available : ";
private static final String LOG_MEMORY_BYTES = " bytes";
private static final String WARNING_SCRIPT_ENTRY_ABSENT = "");
}
}
}
/** Remap each class based on the remap database, and remove attributes. */
public void remapTo(File[] out,
Filter fileFilter,
PrintWriter log,
boolean conserveManifest
) throws java.io.IOException, ClassNotFoundException
{
// Build database if not already done
if (classTree == null)
{
buildClassTree(log);
}
// Generate map table if not already done
if (!hasMap)
{
createMap(log);
}
oldManifest = new Manifest[out.length];
newManifest = new Manifest[out.length];
parseManifest();
StringBuffer replaceNameLog = new StringBuffer();
StringBuffer replaceContentsLog = new StringBuffer();
JarOutputStream outJar = null;
// Open the entry and prepare to process it
DataInputStream inStream = null;
OutputStream os = null;
for(int i = 0; i < inJar.length; i++)
{
os = null;
outJar = null;
//store the whole jar in memory, I known this might be alot, but anyway
//this is the best option, if you want to create correct jar files...
List jarEntries = new ArrayList();
try
{
// Go through the input Jar, removing attributes and remapping the Constant Pool
// for each class file. Other files are copied through unchanged, except for manifest
// and any signature files - these are deleted and the manifest is regenerated.
Enumeration entries = inJar[i].entries();
fireObfuscatingJar(inJar[i].getName(), out[i].getName());
ByteArrayOutputStream baos = new ByteArrayOutputStream(2048);
while (entries.hasMoreElements())
{
// Get the next entry from the input Jar
JarEntry inEntry = (JarEntry)entries.nextElement();
// Ignore directories
if (inEntry.isDirectory())
{
continue;
}
inStream = new DataInputStream(
new BufferedInputStream(
inJar[i].getInputStream(inEntry)));
String inName = inEntry.getName();
if (inName.endsWith(CLASS_EXT))
{
if (fileFilter == null || fileFilter.accepts(inName)){
// Write the obfuscated version of the class to the output Jar
ClassFile cf = ClassFile.create(inStream);
fireObfuscatingClass(Conversion.toJavaClass(cf.getName()));
cf.remap(classTree, replaceClassNameStrings, log);
JarEntry outEntry = new JarEntry(cf.getName() + CLASS_EXT);
DataOutputStream classOutputStream;
MessageDigest[] digests;
if (digestStrings == null){
digestStrings = new String[]{"SHA-1", "MD5"};
}
digests = new MessageDigest[digestStrings.length];
OutputStream stream = baos;
// Create an OutputStream piped through a number of digest generators for the manifest
for (int j = 0; j < digestStrings.length; j++) {
String digestString = digestStrings[j];
MessageDigest digest = MessageDigest.getInstance(digestString);
digests[j] = digest;
stream = new DigestOutputStream(stream, digest);
}
classOutputStream = new DataOutputStream(stream);
// Dump the classfile, while creating the digests
cf.write(classOutputStream);
classOutputStream.flush();
jarEntries.add(new Object[]{outEntry, baos.toByteArray()});
baos.reset();
// Now update the manifest entry for the class with new name and new digests
updateManifest(i, inName, cf.getName() + CLASS_EXT, digests);
}
}
else if (STREAM_NAME_MANIFEST.equals(inName.toUpperCase()) ||
(inName.length() > (SIGNATURE_PREFIX.length() + 1 + SIGNATURE_EXT.length()) &&
inName.indexOf(SIGNATURE_PREFIX) != -1 &&
inName.substring(inName.length() - SIGNATURE_EXT.length(), inName.length()).equals(SIGNATURE_EXT)))
{
// Don't pass through the manifest or signature files
continue;
}
else
{
// Copy the non-class entry through unchanged
long size = inEntry.getSize();
if (size != -1)
{
// Create an OutputStream piped through a number of digest generators for the manifest
MessageDigest shaDigest = MessageDigest.getInstance("SHA");
MessageDigest md5Digest = MessageDigest.getInstance("MD5");
DataOutputStream dataOutputStream =
new DataOutputStream(new DigestOutputStream(new DigestOutputStream(baos,
shaDigest),
md5Digest));
String outName;
StringBuffer outNameBuffer = new StringBuffer(80);
if(resourceHandler != null && resourceHandler.filterName(inName, outNameBuffer))
{
outName = outNameBuffer.toString();
if(!outName.equals(inName))
{
replaceNameLog.append(" \n");
}
}
else
{
outName = classTree.getOutName(inName);
}
if(resourceHandler == null || !resourceHandler.filterContent(inStream, dataOutputStream, inName))
{
byte[] bytes = new byte[(int)size];
inStream.readFully(bytes);
// outName = classTree.getOutName(inName);
// Dump the data, while creating the digests
dataOutputStream.write(bytes, 0, bytes.length);
}
else
{
replaceContentsLog.append(" \n");
}
dataOutputStream.flush();
JarEntry outEntry = new JarEntry(outName);
jarEntries.add(new Object[]{outEntry, baos.toByteArray()});
baos.reset();
// Now update the manifest entry for the entry with new name and new digests
MessageDigest[] digests =
{shaDigest, md5Digest};
updateManifest(i , inName, outName, digests);
}
}
}
os = new FileOutputStream(out[i]);
if (conserveManifest){
outJar = new JarOutputStream(new BufferedOutputStream(os),oldManifest[i]);
} else {
outJar = new JarOutputStream(new BufferedOutputStream(os),newManifest[i]);
}
if (Version.getJarComment() != null) {
outJar.setComment( Version.getJarComment());
}
// sort the entries in ascending order
Collections.sort(jarEntries, new Comparator(){
public int compare(Object a, Object b){
Object[] array1 = (Object[]) a;
JarEntry entry1 = (JarEntry) array1[0];
Object[] array2 = (Object[]) b;
JarEntry entry2 = (JarEntry) array2[0];
return entry1.getName().compareTo(entry2.getName());
}
});
// Finally, write the big bunch of data
Set directoriesWritten = new HashSet();
for (int j = 0; j < jarEntries.size(); j++){
Object[] array = (Object[]) jarEntries.get(j);
JarEntry entry = (JarEntry) array[0];
String name = entry.getName();
// make sure the directory entries are written to the jar file
if (!entry.isDirectory()){
int index = 0;
while ((index = name.indexOf("/", index + 1))>= 0){
String directory = name.substring(0, index+1);
if (!directoriesWritten.contains(directory)){
directoriesWritten.add(directory);
JarEntry directoryEntry = new JarEntry(directory);
outJar.putNextEntry(directoryEntry);
outJar.closeEntry();
}
}
}
// write the entry itself
byte[] bytes = (byte[]) array[1];
outJar.putNextEntry(entry);
outJar.write(bytes);
outJar.closeEntry();
}
}
catch (Exception e)
{
// Log exceptions before exiting
log.println();
log.println("");
throw new IOException("An error ('"+e.getMessage()+"') occured during the remapping! See the log!)");
}
finally
{
inJar[i].close();
if (inStream != null)
{
inStream.close();
}
if (outJar != null)
{
outJar.close();
}
if (os != null){
os.close();
}
}
}
// Write the mapping table to the log file
classTree.dump(log);
if(replaceContentsLog.length() > 0 || replaceNameLog.length() > 0)
{
log.println("");
}
}
/** Close input JAR file. */
public void close() throws java.io.IOException
{
for(int i = 0; i < inJar.length; i++)
{
if (inJar[i] != null)
{
inJar[i].close();
inJar[i] = null;
}
}
}
// Parse the RFC822-style MANIFEST.MF file
private void parseManifest()throws java.io.IOException
{
for(int i = 0; i < oldManifest.length; i++)
{
// The manifest file is the first in the jar and is called
// (case insensitively) 'MANIFEST.MF'
oldManifest[i] = inJar[i].getManifest();
if (oldManifest[i] == null){
oldManifest[i] = new Manifest();
}
// Create a fresh manifest, with a version header
newManifest[i] = new Manifest();
// copy all main attributes
for (Iterator it = oldManifest[i].getMainAttributes().entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry) it.next();
Attributes.Name name = (Attributes.Name) entry.getKey();
String value = (String) entry.getValue();
if (resourceHandler != null) {
name = new Attributes.Name(resourceHandler.filterString(name.toString(), "META-INF/MANIFEST.MF"));
value = resourceHandler.filterString(value, "META-INF/MANIFEST.MF");
}
newManifest[i].getMainAttributes().putValue(name.toString(), value);
}
newManifest[i].getMainAttributes().putValue("Created-by", "yGuard Bytecode Obfuscator " + Version.getVersion());
// copy all directory entries
for (Iterator it = oldManifest[i].getEntries().entrySet().iterator();
it.hasNext();){
Map.Entry entry = (Map.Entry) it.next();
String name = (String) entry.getKey();
if (name.endsWith("/")){
newManifest[i].getEntries().put(name, (Attributes) entry.getValue());
}
}
}
}
// Update an entry in the manifest file
private void updateManifest(int manifestIndex, String inName, String outName, MessageDigest[] digests)
{
// Create fresh section for entry, and enter "Name" header
Manifest nm = newManifest[manifestIndex];
Manifest om = oldManifest[manifestIndex];
Attributes oldAtts = om.getAttributes(inName);
Attributes newAtts = new Attributes();
//newAtts.putValue(MANIFEST_NAME_TAG, outName);
// copy over non-name and none digest entries
if (oldAtts != null){
for(Iterator it = oldAtts.entrySet().iterator(); it.hasNext();){
Map.Entry entry = (Map.Entry) it.next();
Object key = entry.getKey();
String name = key.toString();
if (!name.equalsIgnoreCase(MANIFEST_NAME_TAG) &&
name.indexOf("Digest") == -1){
newAtts.remove(name);
newAtts.putValue(name, (String)entry.getValue());
}
}
}
// Create fresh digest entries in the new section
if (digests != null && digests.length > 0)
{
// Digest-Algorithms header
StringBuffer sb = new StringBuffer();
for (int i = 0; i < digests.length; i++)
{
sb.append(digests[i].getAlgorithm());
if (i < digests.length -1){
sb.append(", ");
}
}
newAtts.remove(MANIFEST_DIGESTALG_TAG);
newAtts.putValue(MANIFEST_DIGESTALG_TAG, sb.toString());
// *-Digest headers
for (int i = 0; i < digests.length; i++)
{
newAtts.remove(digests[i].getAlgorithm() + "-Digest");
newAtts.putValue(digests[i].getAlgorithm() + "-Digest", Tools.toBase64(digests[i].digest()));
}
}
if (!newAtts.isEmpty()) {
// Append the new section to the new manifest
nm.getEntries().put(outName, newAtts);
}
}
// Create a classfile database.
private void buildClassTree(PrintWriter log)throws java.io.IOException
{
// Go through the input Jar, adding each class file to the database
classTree = new ClassTree();
classTree.setPedantic(isPedantic());
classTree.setReplaceClassNameStrings(replaceClassNameStrings);
ClassFile.resetDangerHeader();
Map parsedClasses = new HashMap();
for(int i = 0; i < inJar.length; i++)
{
Enumeration entries = inJar[i].entries();
fireParsingJar(inJar[i].getName());
while (entries.hasMoreElements())
{
// Get the next entry from the input Jar
ZipEntry inEntry = (ZipEntry)entries.nextElement();
String name = inEntry.getName();
if (name.endsWith(CLASS_EXT))
{
fireParsingClass(Conversion.toJavaClass(name));
// Create a full internal representation of the class file
DataInputStream inStream = new DataInputStream(
new BufferedInputStream(
inJar[i].getInputStream(inEntry)));
ClassFile cf = null;
try
{
cf = ClassFile.create(inStream);
}
catch (Exception e)
{
log.println(ERROR_CORRUPT_CLASS + createJarName(inJar[i], name) + " -->");
e.printStackTrace(log);
throw new ParseException( e );
}
finally
{
inStream.close();
}
if (cf != null){
final String cfn = cf.getName();
final String key =
"module-info".equals(cfn) ? createModuleKey(cf) : cfn;
Object[] old = (Object[]) parsedClasses.get(key);
if (old != null){
int jarIndex = ((Integer)old[0]).intValue();
String warning = "yGuard detected a duplicate class definition " +
"for \n " + Conversion.toJavaClass(cfn) +
"\n [" + createJarName(inJar[jarIndex], old[1].toString()) + "] in \n [" +
createJarName(inJar[i], name) + "]";
log.write("\n");
if (jarIndex == i){
throw new IOException(warning + "\nPlease remove inappropriate duplicates first!");
} else {
if (pedantic){
throw new IOException(warning + "\nMake sure these files are of the same version!");
}
}
} else {
parsedClasses.put(key, new Object[]{new Integer(i), name});
}
// Check the classfile for references to 'dangerous' methods
cf.logDangerousMethods(log, replaceClassNameStrings);
classTree.addClassFile(cf);
}
}
}
}
// set the java access modifiers from the containing class (muellese)
final ClassTree ct = classTree;
ct.walkTree(new TreeAction()
{
public void classAction(Cl cl)
{
if (cl.isInnerClass())
{
Cl parent = (Cl) cl.getParent();
cl.access = parent.getInnerClassModifier(cl.getInName());
}
}
});
}
private static String createJarName(JarFile jar, String name){
return "jar:"+jar.getName() + "|" + name;
}
private static String createModuleKey( final ClassFile cf ) {
return "module-info:" + cf.findModuleName();
}
// Generate a mapping table for obfuscation.
private void createMap(PrintWriter log) throws ClassNotFoundException
{
// Traverse the class tree, generating obfuscated names within
// package and class namespaces
classTree.generateNames();
// Resolve the polymorphic dependencies of each class, generating
// non-private method and field names for each namespace
classTree.resolveClasses();
// Signal that the namespace maps have been created
hasMap = true;
// Write the memory usage at this point to the log file
Runtime rt = Runtime.getRuntime();
rt.gc();
log.println("");
}
protected void fireParsingJar(String jar){
if (listenerList == null) return;
for (int i = 0, j = listenerList.size(); i < j; i++){
((ObfuscationListener)listenerList.get(i)).parsingJar(jar);
}
}
protected void fireParsingClass(String className){
if (listenerList == null) return;
for (int i = 0, j = listenerList.size(); i < j; i++){
((ObfuscationListener)listenerList.get(i)).parsingClass(className);
}
}
protected void fireObfuscatingJar(String inJar, String outJar){
if (listenerList == null) return;
for (int i = 0, j = listenerList.size(); i < j; i++){
((ObfuscationListener)listenerList.get(i)).obfuscatingJar(inJar, outJar);
}
}
protected void fireObfuscatingClass(String className){
if (listenerList == null) return;
for (int i = 0, j = listenerList.size(); i < j; i++){
((ObfuscationListener)listenerList.get(i)).obfuscatingClass(className);
}
}
/** Registers Listener to receive events.
* @param listener The listener to register.
*/
public synchronized void addListener(com.yworks.yguard.ObfuscationListener listener)
{
if (listenerList == null )
{
listenerList = new java.util.ArrayList();
}
listenerList.add(listener);
}
/** Removes Listener from the list of listeners.
* @param listener The listener to remove.
*/
public synchronized void removeListener(com.yworks.yguard.ObfuscationListener listener)
{
if (listenerList != null )
{
listenerList.remove(listener);
}
}
/** Getter for property replaceClassNameStrings.
* @return Value of property replaceClassNameStrings.
*
*/
public boolean isReplaceClassNameStrings()
{
return this.replaceClassNameStrings;
}
/** Setter for property replaceClassNameStrings.
* @param replaceClassNameStrings New value of property replaceClassNameStrings.
*
*/
public void setReplaceClassNameStrings(boolean replaceClassNameStrings)
{
this.replaceClassNameStrings = replaceClassNameStrings;
}
/** Getter for property pedantic.
* @return Value of property pedantic.
*
*/
public boolean isPedantic()
{
return this.pedantic;
}
/** Setter for property pedantic.
* @param pedantic New value of property pedantic.
*
*/
public void setPedantic(boolean pedantic)
{
this.pedantic = pedantic;
Cl.setPedantic(pedantic);
}
/**
* Returns the obfuscated file name of the java class.
* The ending ".class" is omitted.
* @param javaClass the fully qualified name of an unobfuscated class.
*/
public String translateJavaFile(String javaClass)
{
Cl cl = classTree.findClassForName(javaClass.replace('/','.'));
if(cl != null)
{
return cl.getFullOutName();
}
else
{
return javaClass;
}
}
public String translateJavaClass(String javaClass)
{
Cl cl = classTree.findClassForName(javaClass);
if(cl != null)
{
return cl.getFullOutName().replace('/', '.');
}
else
{
return javaClass;
}
}
public void setDigests(String[] digestStrings) {
this.digestStrings = digestStrings;
}
public void setAnnotationClass(String annotationClass) {
ObfuscationConfig.annotationClassName = annotationClass;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy