I created myself a framework with "reverse-RSA" (Encryption with PrivateKey) in java and wanted to expand this to loading classes at runtime. I know that this is no efficient protection against theft of my code but my goal is to prevent (with best effort) people to alter my code.
For the past few hours I have searched for a method to load classes at runtime (and found some answers here, but none of them helped me), given only a byteArray containing the decrypted JarFile. I am working with a custom Classloader which is supposed to load the .class file which you can see below:
public class MemoryClassLoader extends ClassLoader{
private byte[] current = null;
public MemoryClassLoader(){
super(ClassLoader.getSystemClassLoader());
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class res = null;
try {
res = defineClass(name, current,0,current.length,null);
}catch(SecurityException e) {
System.out.println("Could not load the Class with our custom method(" + e.getMessage() + ") Falling back to the super method.");
res = super.loadClass(name,true);
}catch(Exception e){
System.out.println("An error occured while loading the class: " + e.getMessage());
}
return res;
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return findClass(name);
}
public final void setContents(final byte[] data){
current = new byte[data.length];
System.arraycopy(data, 0, current, 0, data.length);
}
I am feeding the loader like this:
public final void loadClasses(final File jarFile) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException, NoSuchPaddingException, IOException, ClassNotFoundException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException, InstantiationException{
JarInputStream in = new JarInputStream(new ByteArrayInputStream(decrypt(jarFile)));//Block-like decryption
JarEntry je;
MemoryClassLoader loader = new MemoryClassLoader();
byte[] buffer = new byte[251658240];//30 MB
while((je=in.getNextJarEntry())!=null){
if(je.isDirectory() || !je.getName().endsWith(".class"))continue;
in.read(buffer);
buffer = trim(buffer);//Trim off all 0's at the end
loader.setContents(buffer);
Class c = loader.loadClass(je.getName().substring(0,je.getName().length()-6).replace("/", "."));//Try to load the class
}
in.close();
}
The decryption and the rest works just fine, but the class loader prints
Could not load the Class with our custom method(Prohibited package name: java.lang) Falling back to the super method.
into the console, then uses the superordinate loadClass which surprisingly works (since the original file remains encrypted).
This works thought but my question is, why does this error occur and how can I fix it?
Thank you in advance,
Daniel
I think your current class loader is trying to load Java SDK classes as well. That's why you get that error. Try checking the package names in findClass() and use the System Class Loader to load the ones that are not in your packages.
Related
So I have a Spring Boot application that loads external jars from the paths below:
java -cp "main-0.0.1-SNAPSHOT.jar" -Dloader.path="%USERPROFILE%\Addons\" -Dloader.main=moe.ofs.backend.BackendApplication org.springframework.boot.loader.PropertiesLauncher
The main jar doesn't know external jars at compile time. External jars are loaded like "plugins" or "addons" by specifying
-Dloader.path=...
All of external jars depend on an interface from "main-0.0.1-SNAPSHOT.jar", and they are supposed to do object serializations more or less.
The interface is called Configurable, and it provides two default methods like these:
default <T extends Serializable> void writeFile(T object, String fileName) throws IOException {
Path configFilePath = configPath.resolve(fileName + ".data");
FileOutputStream fileOutputStream = new FileOutputStream(configFilePath.toFile());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
}
default <T extends Serializable> T readFile(String fileName) throws IOException, ClassNotFoundException {
Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
return (T) objectInputStream.readObject();
}
Classes in external jars implement this interface, and they call readFile() and writeFile().
writeFile() works perfectly fine and doesn't seem to cause any problem; readFile(), however, throws a ClassNotFoundException, and that's what I'm trying to figure out.
java.lang.ClassNotFoundException: moe.ofs.addon.navdata.domain.Navaid
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:719)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1922)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1805)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2096)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at java.util.ArrayList.readObject(ArrayList.java:797)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2232)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2123)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at moe.ofs.backend.Configurable.lambda$readFile$0(Configurable.java:186)
at java.lang.Thread.run(Thread.java:748)
After some testing it seems to me that ClassNotFoundException is thrown by Class.forName() because the default ClassLoader has a hard time looking for moe.ofs.addon.navdata.domain.Navaid, which is the class I'm trying to deserialize.
Navaid implements Serializable, and it also has a static final long serialVersionUID.
I had hoped that I could solve this by setting a context class loader for current thread, so that ObjectInputStream will use Spring Boot class loader to resolve Navaid class:
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
This, when printed out, gives something like
Thread.currentThread().getContextClassLoader() = org.springframework.boot.loader.LaunchedURLClassLoader#7a0b4753
Except that ObjectInputStream#readObject still throws ClassNotFoundException.
If I explicitly make a call to load Navaid class from Spring Boot loader such as:
getClass().getClassLoader().loadClass("moe.ofs.addon.navdata.domain.Navaid");
It returns a Navaid instance without any issue.
And as expected, when directly calling
Class.forName("moe.ofs.addon.navdata.domain.Navaid")
a ClassNotFoundException is thrown, even if the thread context loader has been explicitly set to LaunchedURLClassLoader; ObjectInputStream#readObject always tries to resolve the class by making a call to system default classloader to load the class.
Then I tried to load an ObjectInputStream using LaunchedURLClassLoader, but the instance still used Class.forName() from system default class loader.
ClassLoader cl = getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(cl);
System.out.println("Thread.currentThread().getContextClassLoader() = " + Thread.currentThread().getContextClassLoader());
Class<?> tClass = getClass().getClassLoader().loadClass("java.io.ObjectInputStream");
System.out.println("tClass = " + tClass);
Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
Constructor<?> constructor = tClass.getConstructor(InputStream.class);
ObjectInputStream objectInputStream = (ObjectInputStream) constructor.newInstance(fileInputStream);
objectInputStream.readObject(); // throws ClassNotFoundException
Any input is appreciated. Thanks in advance.
As far as i know, you should override the method resolveClass on ObjectInputStream
Something like that:
default <T extends Serializable> T readFile(String fileName, ClassLoader loader) throws IOException, ClassNotFoundException {
Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream){
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
try {
return Class.forName(desc.getName(), false, loader);
} catch(ClassNotFoundException cnfe) {
return super.resolveClass(desc);
}
}
};
return (T) objectInputStream.readObject();
}
Never tried it myself, but it is worth a shot.
There is also http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/input/ClassLoaderObjectInputStream.html if you aready have commons-io in your project.
https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/input/ClassLoaderObjectInputStream.java
For this package, one of my next steps is to write a series of FileTypeDetector to have the method Files.probeContentType() be smarter than what is is by default (the default provided file type detector relies on "file name extensions" only).
As the javadoc of the aforementioned method mentions, this method relies on instances of FileTypeDetectors be declared in a META-INF/services file.
I have first tested with a simple provider to detect PNG files using the file header:
public final class PngFileTypeDetector
extends FileTypeDetector
{
private static final byte[] PNG_HEADER = {
(byte) 0x89,
(byte) 0x50, (byte) 0x4E, (byte) 0x47,
(byte) 0x0D, (byte) 0x0A,
(byte) 0x1A,
(byte) 0x0A
};
private static final int PNG_HEADER_SIZE = PNG_HEADER.length;
#Override
public String probeContentType(final Path path)
throws IOException
{
final byte[] buf = new byte[PNG_HEADER_SIZE];
try (
final InputStream in = Files.newInputStream(path);
) {
if (in.read(buf) != PNG_HEADER_SIZE)
return null;
}
return Arrays.equals(buf, PNG_HEADER) ? "image/png" : null;
}
}
It works. Now, after a quick glance at the API, I thought this would be a good way to detect whether a file was a zip:
public final class ZipFileTypeDetector
extends FileTypeDetector
{
#Override
public String probeContentType(final Path path)
throws IOException
{
// Rely on what the JDK has to offer...
try (
final InputStream in = Files.newInputStream(path);
final ZipInputStream z = new ZipInputStream(in);
) {
z.getNextEntry();
return "application/zip";
} catch (ZipException ignored) {
return null;
}
}
}
The content of META-INF/services/java.nio.file.spi.FileTypeDetector was this:
com.github.fge.filesystem.ftd.PngFileTypeDetector
com.github.fge.filesystem.ftd.ZipFileTypeDetector
With the current tests, it worked; for the zip I created an empty zip file, for the PNG test I used this image.
Full test:
public final class FileTypeDetectorTest
{
private FileSystem fs;
private Path path;
#BeforeMethod
public void initfs()
throws IOException
{
fs = MemoryFileSystemBuilder.newLinux().build("testfs");
path = fs.getPath("/foo");
}
#DataProvider
public Iterator<Object[]> samples()
{
final List<Object[]> list = new ArrayList<>();
String resourcePath;
String mimeType;
resourcePath = "/ftd/sample.png";
mimeType = "image/png";
list.add(new Object[] { resourcePath, mimeType });
resourcePath = "/ftd/sample.zip";
mimeType = "application/zip";
list.add(new Object[] { resourcePath, mimeType });
return list.iterator();
}
#Test(dataProvider = "samples")
public void fileTypeDetectionTest(final String resourcePath,
final String mimeType)
throws IOException
{
#SuppressWarnings("IOResourceOpenedButNotSafelyClosed")
final InputStream in
= FileTypeDetectorTest.class.getResourceAsStream(resourcePath);
if (in == null)
throw new IOException(resourcePath + " not found in classpath");
try (
final InputStream inref = in;
) {
Files.copy(inref, path);
}
assertThat(Files.probeContentType(path)).isEqualTo(mimeType);
}
#AfterMethod
public void closefs()
throws IOException
{
fs.close();
}
}
However...
If I invert the list of implementations in the services file, that is the file now is:
com.github.fge.filesystem.ftd.ZipFileTypeDetector
com.github.fge.filesystem.ftd.PngFileTypeDetector
then the PNG file is detected as being a zip file!
After some debugging I noticed that:
opening the PNG as a ZipInputStream did not fail...
... and .getNextEntry() returned null!
I'd have expected at least .getNextEntry() to throw ZipException.
Why didn't it? How can I detect reliably whether a file is a zip?
Further note: this is for Paths; therefore anything File is unusable.
Why didn't it?
Well, the JavaDoc for getNextEntry() says that a ZipException or IOException occurs,
if a ZIP file error has occurred
if an I/O error has occurred
respectively.
Based on that wonderfully helpful information (cough), we can't make any assumptions that it will throw an exception if it encounters an invalid entry.
How can I detect reliably whether a file is a zip?
The ZIP file format specification, which was originally PKZip, can be found here. While its all a good read :), take a look at section 4; 4.3.16 in particular. It specifies the "End of central directory record", which all ZIP files have (even empty ones).
I am developing a wicket application which is running on jetty. My application should be able to load plugins from jar-files.
Thus I created a URLClassLoader which should load the plugin classes.
As a standard Java-application everything works, but when I want to load plugins on the server I get following errors: http://pastebin.com/e2JRAYTr
The Module interface that can not be found is defined in another project, but if I output the class-name in Wickets init() method like this
System.out.println(Module.class.getName())`
I get no error. So I think the other project is loaded correctly by Maven but there are issues with my custom jar-classloader.
The classloaders constructor where the action happens looks like this:
public WorkableLoader(final File jarFile) throws IOException, ClassNotFoundException, InstantiationException,
IllegalAccessException {
final URL[] urls = new URL[] { jarFile.toURI().toURL() };
classLoader = new URLClassLoader(urls);
final Class<?> cls = loadWorkableClass(jarFile);
workable = (Workable) cls.newInstance();
}
private Class<?> loadWorkableClass(final File jar) throws ClassNotFoundException, IOException {
try (final JarFile jarFile = new JarFile(jar)) {
final Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
final JarEntry jarEntry = jarEntries.nextElement();
if (isClassFile(jarEntry.getName())) {
final String className = jarEntry.getName().replace("/", ".").replace(".class", "");
final Class<?> cls = classLoader.loadClass(className);
for (final Class<?> iface : cls.getInterfaces()) {
if (iface.equals(Module.class) || iface.equals(Bundle.class)) {
return cls;
}
}
}
}
} catch (final IOException e) {
throw e;
}
throw new ClassNotFoundException("Es konnte keine Workable-Klasse gefunden werden.");
}
The method isClass() just checks if the file ends with .class.
The last error of my code is at com.siemens.importer.workables.WorkableLoader.loadWorkableClass(WorkableLoader.java:137). Line 137 is the line
final Class<?> cls = classLoader.loadClass(className); of the loadWorkableClass() method. So how can I load classes with a custom classloader on a
jetty server?
Sorry for the huge output and thanks in advance!
Frankly, I do not know even it is possible or not.
But what I am trying to do is just like below.
I made a class file from ClassFile.java via javac command in terminal.
Then I want to get an instance from .java file or .class file.
Next, I made another project in eclipse, As you guess this project path and upper file path are completely different. For instance, ClassFile.java/class file can be located in '~/Downloads' folder, the other hand, new eclipse project can be in '~/workspace/'.
So I read file which referred in step 1 by FileInputStream.
From here, I just paste my code.
public class Main {
private static final String CLASS_FILE_PATH =
"/Users/juneyoungoh/Downloads/ClassFile.class";
private static final String JAVA_FILE_PATH =
"/Users/juneyoungoh/Downloads/ClassFile.java";
private static Class getClassFromFile(File classFile) throws Exception {
System.out.println("get class from file : [" + classFile.getCanonicalPath() + " ]");
Object primativeClz = new Object();
ObjectInputStream ois = null;
ois = new ObjectInputStream(new FileInputStream(classFile));
primativeClz = ois.readObject();
ois.close();
return primativeClz.getClass();
}
public static void main(String[] args) throws Exception {
getClassInfo(getClassFromFile(new File(CLASS_FILE_PATH)));
}
}
just like your assumption, this code has errors.
For example, it shows :
java.io.StreamCurruptedException: invalid stream header : CAFEBABE
this there any way to get object instance from .class file or .java file?
P.S.
I wish do not use extra libraries.
private static final String CLASS_FOLDER =
"/Users/juneyoungoh/Downloads/";
private static Class getClassFromFile(String fullClassName) throws Exception {
URLClassLoader loader = new URLClassLoader(new URL[] {
new URL("file://" + CLASS_FOLDER)
});
return loader.loadClass(fullClassName);
}
public static void main( String[] args ) throws Exception {
System.out.println((getClassFromFile("ClassFile"));
}
It's a project downloaded from Web and I just changed the path to find the class which I want to make it load dynamically.Here are the codes in which I try to load the class on runtime.But I got a ClassNotFoundException eventually:
private static IExample newInstanceWithThrows() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
URLClassLoader tmp =
new URLClassLoader(new URL[] {getClassPath()}) {
public Class<?> loadClass(String name)
throws ClassNotFoundException {
if ("example.Example".equals(name)
|| "example.Leak".equals(name))
return findClass(name);
return super.loadClass(name);
}
};
return (IExample) tmp.loadClass("example.Example")
.newInstance();
}
private static URL getClassPath() {
String dir = "/Users/longtuan/develop/rjc2011/classes/";
try {
//return new URL(dir);
File path = new File(dir);
return path.toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
And the path to class example.Example is:
/Users/longtuan/develop/java/rjc2011/classes/example
And the run command I used is:
java -classpath ./bin example.Main
Current directory is:
/Users/longtuan/develop/java/rjc2011
All the things of current directory are like this:
Example is the directory in which I put all the java files.I place the compiled class file in directory bin except the class example.Example which is put in classes lonely.
Above are all the informations I can give, thanks for any help.