2

I'm trying to load a jar from a byte array without having it written to a file (loading it into memory). I have made a custom ClassLoader but when I try to use it and load a class it gives me ClassNotFoundException.

ClassLoader

public class NetworkClassLoader extends ClassLoader 
{
/*
 * Default ClassLoader.
 */
private final ClassLoader startup;

/*
 * Byte array used to load classes.
 */
private final byte[] bytes;

/*
 * HashMap used to contain cached classes.
 */
private HashMap<String, byte[]> classes = new HashMap<>();

/*
 * Initializes byte array used for loading classes.
 * @param ClassLoader classLoader
 * @param byte[] bytes
 */
public NetworkClassLoader(ClassLoader classLoader, byte[] bytes) 
{
    this.startup = classLoader;
    this.bytes = bytes;
}

/*
 * Loads class from name.
 * (non-Javadoc)
 * @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
 * @param String name
 * @param boolean resolve
 * @throws ClassNotFoundException
 * @returns clazz
 */
@Override
public Class<?> loadClass(String name, boolean resolve) 
        throws ClassNotFoundException 
{
    Class<?> clazz = findLoadedClass(name);
    if (clazz == null) 
    {
        try 
        {
            InputStream in = getResourceAsStream(name.replace('.', '/') + ".class");
            if (in == null) return null;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            IOUtils.writeStream(in, out);
            in.close();
            byte[] bytes = out.toByteArray();
            out.close();
            clazz = defineClass(name, bytes, 0, bytes.length);
            if (resolve) 
            {
                resolveClass(clazz);
            }
        } 
        catch (Exception e) 
        {
            clazz = super.loadClass(name, resolve);
        }
    }
    return clazz;
}

/*
 * Returns resource.
 * (non-Javadoc)
 * @see java.lang.ClassLoader#getResource(java.lang.String)
 * @param String name
 */
@Override
public URL getResource(String name) 
{
    return null;
}

/*
 * Returns resource as stream.
 * (non-Javadoc)
 * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String)
 * @param String name
 * @return ByteArrayInputStream
 */
@Override
public InputStream getResourceAsStream(String name) 
{
    InputStream jarRes = this.startup.getResourceAsStream(name);
    if (jarRes != null) 
    {
        return jarRes;
    }
    if (!this.classes.containsKey(name)) 
    {
        return null;
    }
    return new ByteArrayInputStream((byte[])this.classes.get(name));
}

/*
 * Loads classes using byte array.
 */
public void inject()
{
    if (bytes == null) return;
     try
     {
         JarInputStream jis = new JarInputStream(new ByteArrayInputStream(bytes));
          try
          {
              JarEntry entry;
              while ((entry = jis.getNextJarEntry()) != null)
              {
                  String entryName = entry.getName();
                  ByteArrayOutputStream out = new ByteArrayOutputStream();
                  IOUtils.writeStream(jis, out);
                  byte[] bytes = out.toByteArray();
                  this.classes.put(entryName, bytes);
                  this.loadClass(entryName, false);
              }
          }
          catch (Exception e)
          {
              e.printStackTrace();
          }
     }
     catch (Exception e)
        {
          e.printStackTrace();
        }
 }

}

Main

byte[] array =      
IOUtils.readFileBytes(new File("C:\\Users\\o_m_a\\Desktop\\HWID.jar"));
ByteClassLoader loader = new ByteClassLoader(Main.class.getClassLoader(), array);
 loader.inject();
//System.out.println(Arrays.toString(array));

    try {
        Class<?> clazz = loader.loadClass("Main", true).newInstance();
        Method m = clazz.getMethod(method, (Class<?>[]) null);
        m.setAccessible(true);
        m.invoke(clazz.newInstance(), (Object[]) null);
    } catch (InstantiationException | IllegalAccessException | ClassNotFoundException e) {
        e.printStackTrace();
    }

It loads my class correctly and runs it but I keep getting random errors.

  • Could you add the exception message and stacktrace to the question? – Lukas Körfer Jul 20 '17 at 10:33
  • Just did @lu.koerfer –  Jul 20 '17 at 10:35
  • Is it throwing the exception on this line: `Class> clazz = findLoadedClass(name);`? – CraigR8806 Jul 20 '17 at 10:38
  • both clazz = super.loadClass(name, resolve); on ByteClassLoader and loader.loadClass("Main", true).newInstance(); on main @CraigR8806 –  Jul 20 '17 at 10:40
  • Can you please place a `e.printStackTrace()` above `clazz=super.loadClass(name, resolve);` this will tell us where it is failing in the `try` block – CraigR8806 Jul 20 '17 at 10:43
  • Check the last code block I added it @CraigR8806 –  Jul 20 '17 at 10:49
  • Is `Main` the full [binary name](https://docs.oracle.com/javase/7/docs/api/java/lang/ClassLoader.html#name) of the class in the `Test.jar` file? You may need to add the package where the class is located. – Lukas Körfer Jul 20 '17 at 10:51
  • @CraigR8806 when I remove copy stream it gives me Exception in thread "main" java.lang.ClassFormatError: Truncated class file –  Jul 20 '17 at 10:52
  • @lu.koerfer it's not in a package –  Jul 20 '17 at 10:52
  • @OmarAhmed Okay I posted an answer with my suggestion. Hopefully it works for you :) – CraigR8806 Jul 20 '17 at 10:59

2 Answers2

1

After doing a bit of research, it looks like you're actually using the wrong library.

Instead of IOUtils.copyStream(in, out); try using StreamUtils.writeTo(in, out);

CraigR8806
  • 1,584
  • 13
  • 21
  • But this isn't included in java's api. –  Jul 20 '17 at 20:12
  • Nevermind, I found the library with it on github but I’m still recieving the same error I might have an Idea of creating a class instance with a method I made that returns the defined class and invoking the method. However I would need to have the classbyte to define it. –  Jul 20 '17 at 21:50
0

This is a bit hackish but do his works, this code basically create a fake url scheme (myjarprotocol) that when opened return the jarBytes field (where your real jar data goes). Then via reflection it call SystemClassLoader.addURL that take a URL as parameter of the jar he have to load. In conclusion the SystemClassLoader is tricked to load a jar from a fake url scheme that return any InputStream you want.

public class JarLoader {

    private static final byte[] jarBytes = new byte[] { 0x00 /* .... etc*/ };

    public static void main(String[] args) throws Exception {
        URL.setURLStreamHandlerFactory(new URLStreamHandlerFactory() {
            public URLStreamHandler createURLStreamHandler(String urlProtocol) {
                System.out.println("Someone asked for protocol: " + urlProtocol);
                if ("myjarprotocol".equalsIgnoreCase(urlProtocol)) {
                    return new URLStreamHandler() {
                        @Override
                        protected URLConnection openConnection(URL url) throws IOException {
                            return new URLConnection(url) {
                                public void connect() throws IOException {}
                                public InputStream getInputStream() throws IOException {
                                    System.out.println("Someone is getting my jar!!");
                                    return new ByteArrayInputStream(jarBytes);
                                }
                            };
                        }
                    };
                }
                return null;
            }
        });

        System.out.println("Loading jar with fake protocol!");
        loadJarFromURL(new URL("myjarprotocol:fakeparameter"));
    }

    public static final void loadJarFromURL(URL jarURL) throws Exception {
        URLClassLoader systemClassloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        Method systemClassloaderMethod = URLClassLoader.class.getDeclaredMethod("addURL", URL.class);
        systemClassloaderMethod.setAccessible(true);
        systemClassloaderMethod.invoke(systemClassloader, jarURL);

        // This make classloader open the connection
        systemClassloader.findResource("/resource-404");
    }

}
Emax
  • 1,343
  • 2
  • 19
  • 30
  • let me test this –  Jul 20 '17 at 20:18
  • output is Loading jar with fake protocol! Someone asked for protocol: myjarprotocol Getting jar Getting jar –  Jul 20 '17 at 20:29
  • does this load jar classes to into the one I’m using or runs the jar? –  Jul 23 '17 at 04:12
  • Yes, the jar is loaded in the system classloader – Emax Jul 23 '17 at 12:03
  • Sorry just one more thing, It loads the jar twice. Do you know a solution? –  Jul 25 '17 at 10:13
  • Try remove systemClassloader.findResource("/resource-404"); – Emax Jul 25 '17 at 11:38
  • I thinks it's normal, the classloader need to load your jar multiple times it depends on the implementation of it. – Emax Jul 26 '17 at 09:51
  • Look at this: https://stackoverflow.com/questions/8503706/can-a-java-classloader-load-a-class-more-than-once – Emax Jul 26 '17 at 09:52
  • I don't think it's normal when I invoke a method it runs twice. –  Jul 26 '17 at 23:59
  • Note that you can [pass a `URLStreamHandler` directly to a `URL` constructor](https://docs.oracle.com/javase/8/docs/api/java/net/URL.html#URL-java.net.URL-java.lang.String-java.net.URLStreamHandler-) so you don’t need to implement a `URLStreamHandlerFactory` nor to register it globally, just for the sake of a single URL… – Holger Mar 23 '18 at 16:29