0

I created an app that encrypts and decrypts a jar file. I would like that this application decrypt the file, and without having to write the file to disk, run the application. It is possible?

I created this app in C#, but it could be in another language too.

MarceloSouza
  • 429
  • 5
  • 10
  • Typically the results of decryption in Java is a `byte[]`. If that array contains a jar, it might be possible to create a classloader on it and then run it. Of course it would be a lot easier on Unix systems to write to file systems mounting shared memory but I'm not sure such things exist in the Windows world – g00se Jul 16 '21 at 14:31

1 Answers1

0

Proof of concept, thanks mainly to this overflower. You will need this class to run it. And of course, your array will come, not from an embedded Base64 string, but from a decrypted byte[]. JARENC is a jar file:

package com.technojeeves.shm;

import com.technojeeves.io.IOUtils;
import java.util.Base64;
import java.util.zip.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.Method;

public class Loader {
    public static void main(String[] args) {
         final String JARENC = "UEsDBBQACAgIABqp8FIAAAAAAAAAAAAAAAAlAAQAY29tL3RlY2hub2plZXZlcy9zaG0vSGVsbG9Xb3JsZC5jbGFzc/7KAABtUMtOwkAUPZdXaa2CIPgkwsIEXNiNO4wbE+OCqAkGF65KmcCQtmPKgPGzdKGJCz/AjzLeaUzQhFncx5nzSO7X98cngFM0HGSQtZBzkUeBUJ76C98L/Xjs3QynItCEwpmMpT4nZNudgY0ibAuOizW4/+j955kWEbPUnEW1XvojlXebyFj3dSL8qFvEBsG9EmGomk8qCUctB2VsWqi4qGKLUF2hIliPZgtjtm33/iRqhsfdzsBCnXAYqMjTIpjEairEQsy82STy0qx7E0XIXaiRIJR6MhbX82gokjt/GDKSi3zJ5vX2wyp3gtNX8yQQl9KQS0vLE8NGCzW+oXkZkLki123eGtyJe/74HfTCA2GHayEFs7Cxi71f6lEqBexXWJX1N5SWdCf1zvPZjXA/TTn4AVBLBwhqV5QAMQEAAL8BAABQSwECFAAUAAgICAAaqfBSaleUADEBAAC/AQAAJQAEAAAAAAAAAAAAAAAAAAAAY29tL3RlY2hub2plZXZlcy9zaG0vSGVsbG9Xb3JsZC5jbGFzc/7KAABQSwUGAAAAAAEAAQBXAAAAiAEAAAAA";


        try {
            Base64.Decoder dec = Base64.getDecoder();
            byte[] jarBytes = dec.decode(JARENC);
            RemoteClassLoader memJarLoader = new RemoteClassLoader(jarBytes);
            Class<?> hw = memJarLoader.loadClass("com.technojeeves.shm.HelloWorld", true);
            Method main = hw.getMethod("main", String[].class);
            String[] params = null;
            main.invoke(null, (Object)params);
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

    static class RemoteClassLoader extends ClassLoader {

        private final byte[] jarBytes;
        private final Set<String> names;

        public RemoteClassLoader(byte[] jarBytes) throws IOException {
            this.jarBytes = jarBytes;
            this.names = RemoteClassLoader.loadNames(jarBytes);
        }

        /**
         * This will put all the entries into a thread-safe Set
         */
        private static Set<String> loadNames(byte[] jarBytes) throws IOException {
            Set<String> set = new HashSet<>();
            try (ZipInputStream jis = new ZipInputStream(new ByteArrayInputStream(jarBytes))) {
                ZipEntry entry;
                while ((entry = jis.getNextEntry()) != null) {
                    set.add(entry.getName());
                }
            }
            return Collections.unmodifiableSet(set);
        }

        @Override
        public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
            Class<?> clazz = findLoadedClass(name);
            if (clazz == null) {
                try {
                    String toLoad = name.replace('.', '/') + ".class";
                    //System.out.printf("Attempting to load %s%n", toLoad);
                    InputStream in = getResourceAsStream(name.replace('.', '/') + ".class");
                    ByteArrayOutputStream out = new ByteArrayOutputStream();
                    //StreamUtils.writeTo(in, out);
                    IOUtils.copyStream(in, out);
                    byte[] bytes = out.toByteArray();
                    clazz = defineClass(name, bytes, 0, bytes.length);
                    if (resolve) {
                        resolveClass(clazz);
                    }
                } catch (Exception e) {
                    clazz = super.loadClass(name, resolve);
                }
            }
            return clazz;
        }

        @Override
        public InputStream getResourceAsStream(String name) {
            // Check first if the entry name is known
            if (!names.contains(name)) {
                return null;
            }
            // I moved the JarInputStream declaration outside the
            // try-with-resources statement as it must not be closed otherwise
            // the returned InputStream won't be readable as already closed
            boolean found = false;
            ZipInputStream jis = null;
            try {
                jis = new ZipInputStream(new ByteArrayInputStream(jarBytes));
                ZipEntry entry;
                while ((entry = jis.getNextEntry()) != null) {
                    if (entry.getName().equals(name)) {
                        found = true;
                        return jis;
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                // Only close the stream if the entry could not be found
                if (jis != null && !found) {
                    try {
                        jis.close();
                    } catch (IOException e) {
                        // ignore me
                    }
                }
            }
            return null;
        }
    }
}

g00se
  • 3,207
  • 2
  • 5
  • 9
  • Almost working. When I try to run return this message: Caused by: java.lang.UnsupportedClassVersionError: javafx/application/Application has been compiled by a more recent version of the Java Runtime (class file version 54.0), this version of the Java Runtime only recognizes class file versions up to 52.0. But I have only JDK8 in my pc. Have you had a similar problem? – MarceloSouza Jul 16 '21 at 19:26
  • Well if you have a jar that's been compiled with a higher version than the one you're processing it with, you've got a problem. You're going to need to equal it or surpass it. Or do you mean you're trying it with my code literally? (since the jar in it was compiled with 1.16) – g00se Jul 16 '21 at 20:06
  • If that is the case, please use the string in the file [here](http://technojeeves.com/tech/helloworld.jar.base64) – g00se Jul 16 '21 at 20:16
  • With this new JARENC works. With the original JARENC the message was: com/technojeeves/shm/HelloWorld has been compiled by a more recent version of the Java Runtime (class file version 60.0), this version of the Java Runtime only recognizes class file versions up to 52.0. what is the difference between the first and second JARENC? – MarceloSouza Jul 17 '21 at 14:55
  • I used a 1.16 jDK for the first one by accident. The second uses 1.8 – g00se Jul 17 '21 at 14:59
  • I think in my case, the problem is that I'm using JavaFX (org.opensfx) and even compiling with jdk 1.8, it affects the jar. – MarceloSouza Jul 17 '21 at 16:34
  • My idea is to create an open source project that allows you to encrypt a jar file and just decrypt the jar at runtime. That way I wouldn't need to obfuscate the code. And I believe this would make it even more difficult to decompile the code – MarceloSouza Jul 17 '21 at 16:39
  • Yes I see. So that's proof of concept done ;) The thihg of course with security is to work against not only the possibility of a successful attack but also to have a working feel of the expense of a successful attack. Ultimately your Java code will reach the virtual machine and that VM could be anything, including one that will log the instruction stack and thus effectively be a tool for reverse engineering – g00se Jul 17 '21 at 17:10