【问题标题】:Run jar file with file in memory使用内存中的文件运行 jar 文件
【发布时间】:2021-09-25 08:40:23
【问题描述】:

我创建了一个对 jar 文件进行加密和解密的应用。我希望这个应用程序解密文件,而不必将文件写入磁盘,运行应用程序。有可能吗?

我用 C# 创建了这个应用程序,但它也可以是另一种语言。

【问题讨论】:

  • 通常在 Java 中解密的结果是 byte[]。如果该数组包含一个 jar,则可以在其上创建一个类加载器,然后运行它。当然,在 Unix 系统上写入挂载共享内存的文件系统会容易得多,但我不确定 Windows 世界中是否存在这样的东西

标签: java c# security


【解决方案1】:

概念证明,主要感谢this overflower。你需要this class 来运行它。当然,你的数组不是来自嵌入的 Base64 字符串,而是来自解密的byte[]JARENC一个jar文件:

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;
        }
    }
}

【讨论】:

  • 几乎可以工作了。当我尝试运行时返回此消息:Caused by: java.lang.UnsupportedClassVersionError: javafx/application/Application 已由 Java Runtime 的更新版本(类文件版本 54.0)编译,此版本的 Java Runtime 仅识别类文件版本高达 52.0。但我的电脑里只有 JDK8。你有过类似的问题吗?
  • 好吧,如果您的 jar 编译的版本比您处理它的版本高,那么您就有问题了。您将需要等于或超过它。或者你的意思是你正在用我的代码尝试它? (因为里面的jar是用1.16编译的)
  • 如果是这种情况,请使用文件here中的字符串
  • 有了这个新的 JARENC 作品。对于原始 JARENC,消息是:com/technojeeves/shm/HelloWorld 已由 Java 运行时的更新版本(类文件版本 60.0)编译,此版本的 Java 运行时仅识别最高 52.0 的类文件版本。第一个和第二个 JARENC 有什么区别?
  • 我无意中使用了 1.16 jDK 作为第一个。第二个使用 1.8
猜你喜欢
  • 2014-05-24
  • 2015-07-03
  • 2023-03-08
  • 2012-02-18
  • 1970-01-01
  • 1970-01-01
  • 2022-01-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多