jjzjj

java - Java Classloader 能否重写系统类(仅它们的副本)的字节码?

coder 2023-08-29 原文

所以我有一个类加载器 (MyClassLoader),它在内存中维护一组“特殊”类。这些特殊类被动态编译并存储在 MyClassLoader 内部的字节数组中。当 MyClassLoader 被请求一个类时,它首先检查它的 specialClasses 是否在委托(delegate)给系统类加载器之前,字典包含它。它看起来像这样:

class MyClassLoader extends ClassLoader {
    Map<String, byte[]> specialClasses;

    public MyClassLoader(Map<String, byte[]> sb) {
        this.specialClasses = sb;
    }

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        if (specialClasses.containsKey(name)) return findClass(name);
        else return super.loadClass(name);
    }

    @Override
    public Class findClass(String name) {
        byte[] b = specialClasses.get(name);
        return defineClass(name, b, 0, b.length);
    }    
}

如果我想对 specialClasses 执行转换(例如检测) , 我可以通过修改 byte[] 来做到这一点在我打电话之前 defineClass()在上面。

我还想转换系统类加载器提供的类,但系统类加载器似乎没有提供任何访问原始 byte[] 的方法。它提供的类,并给了我 Class直接对象。

我可以使用 -javaagent检测加载到 JVM 中的所有类,但这会增加我不想检测的类的开销;我真的只希望对 MyClassLoader 加载的类进行检测。

  • 有没有办法检索原始 byte[]父类加载器提供的类,所以我可以在定义自己的副本之前检测它们?
  • 或者,是否有任何方法可以模拟系统类加载器的功能,就其获取 byte[] 的位置而言。来自,以便 MyClassLoader 可以检测和定义它自己的所有系统类(对象、字符串等)的副本?

编辑:

所以我尝试了另一种方法:

  • 使用 -javaagent , 捕获 byte[]加载的每个类并将其存储在哈希表中,以类名作为键。
  • MyClassLoader 不是将系统类委托(delegate)给它的父类加载器,而是使用类名从这个哈希表加载它们的字节码并定义它

理论上,这会让 MyClassLoader 定义它自己的系统类版本,并带有检测。但是,它失败了

java.lang.SecurityException: Prohibited package name: java.lang

显然 JVM 不喜欢我定义 java.lang我自己上课,即使它(理论上)应该来自同一个 byte[]引导加载的类应该来自的来源。继续寻找解决方案。

编辑2:

我为这个问题找到了一个(非常粗略的)解决方案,但如果有人比我更了解 Java 类加载/检测的复杂性,可以想出一些不那么粗略的方法,那就太棒了。

最佳答案

所以我找到了解决这个问题的方法。这不是一个非常优雅的解决方案,它会在代码审查时引起很多愤怒的电子邮件,但它似乎有效。基本要点是:

Java代理

使用 java.lang.instrumentation和一个 -javaagent存储 Instrumentation稍后使用的对象

class JavaAgent {
    private JavaAgent() {}

    public static void premain(String agentArgs, Instrumentation inst) {
        System.out.println("Agent Premain Start");
        Transformer.instrumentation = inst;
        inst.addTransformer(new Transformer(), inst.isRetransformClassesSupported());
    }    
}

类文件转换器

添加 TransformerInstrumentation仅适用于标记的类。有点像

public class Transformer implements ClassFileTransformer {
    public static Set<Class<?>> transformMe = new Set<>()
    public static Instrumentation instrumentation = null; // set during premain()
    @Override
    public byte[] transform(ClassLoader loader,
                            String className,
                            Class<?> classBeingRedefined,
                            ProtectionDomain protectionDomain,
                            byte[] origBytes) {


        if (transformMe.contains(classBeingRedefined)) {
            return instrument(origBytes, loader);
        } else {
            return null;
        }
    }
    public byte[] instrument(byte[] origBytes) {
        // magic happens here
    }
}

类加载器

在类加载器中,显式标记每个加载的类(甚至是加载委托(delegate)给父类的类),方法是将其放置在 transformMe 中。在询问 Instrumentation 之前改造它

public class MyClassLoader extends ClassLoader{
    public Class<?> instrument(Class<?> in){
        try{
            Transformer.transformMe.add(in);
            Transformer.instrumentation.retransformClasses(in);
            Transformer.transformMe.remove(in);
            return in;
        }catch(Exception e){ return null; }
    }
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return instrument(super.loadClass(name));
    }
}

...瞧! MyClassLoader 加载的每个类被 instrument() 转化方法,包括所有系统类,如 java.lang.Object和 friend ,而所有由默认 ClassLoader 加载的类都保持不变。

我已经尝试使用内存分析 instrument()方法,它插入回调 Hook 以跟踪检测字节码中的内存分配,并可以确认 MyClassLoad类在它们的方法运行时触发回调(甚至系统类),而“普通”类则没有。

胜利!

当然,这是糟糕的代码。无处不在的共享可变状态、非本地副作用、全局变量,以及您可能想象到的一切。可能也不是线程安全的。但它表明这样的事情是可能的,您可以确实有选择地检测类的字节码,甚至是系统类,作为自定义 ClassLoader 操作的一部分,同时让程序的“其余部分”保持不变。

开放问题

如果其他人有任何想法如何让这段代码不那么糟糕,我会很高兴听到。我想不出一个办法:

  • 制作Instrumentation 通过retransformClasses()按需提供的乐器类(class)并且以其他方式加载仪器类
  • 在每个 Class<?> 中存储一些元数据允许 Transformer 的对象在没有全局可变哈希表查找的情况下判断它是否应该被转换。
  • 在不使用 Instrumentation.retransformClass() 的情况下转换系统类方法。如前所述,任何动态 defineClass 的尝试一个byte[]进入 java.lang.*由于 ClassLoader.java 中的硬编码检查,类失败。

如果有人能找到解决这些问题中的任何一个的方法,它就会变得不那么粗略。无论如何,我猜测能够检测(例如用于分析)某些子系统(即您感兴趣的子系统),同时保持 JVM 的其余部分不变(没有检测开销)将对其他人有用我,就在这里。

关于java - Java Classloader 能否重写系统类(仅它们的副本)的字节码?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/13032918/

有关java - Java Classloader 能否重写系统类(仅它们的副本)的字节码?的更多相关文章

  1. ruby - 什么是填充的 Base64 编码字符串以及如何在 ruby​​ 中生成它们? - 2

    我正在使用的第三方API的文档状态:"[O]urAPIonlyacceptspaddedBase64encodedstrings."什么是“填充的Base64编码字符串”以及如何在Ruby中生成它们。下面的代码是我第一次尝试创建转换为Base64的JSON格式数据。xa=Base64.encode64(a.to_json) 最佳答案 他们说的padding其实就是Base64本身的一部分。它是末尾的“=”和“==”。Base64将3个字节的数据包编码为4个编码字符。所以如果你的输入数据有长度n和n%3=1=>"=="末尾用于填充n%

  2. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  3. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  4. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  5. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  6. 报告回顾丨模型进化狂飙,DetectGPT能否识别最新模型生成结果? - 2

    导读语言模型给我们的生产生活带来了极大便利,但同时不少人也利用他们从事作弊工作。如何规避这些难辨真伪的文字所产生的负面影响也成为一大难题。在3月9日智源Live第33期活动「DetectGPT:判断文本是否为机器生成的工具」中,主讲人Eric为我们讲解了DetectGPT工作背后的思路——一种基于概率曲率检测的用于检测模型生成文本的工具,它可以帮助我们更好地分辨文章的来源和可信度,对保护信息真实、防止欺诈等方面具有重要意义。本次报告主要围绕其功能,实现和效果等展开。(文末点击“阅读原文”,查看活动回放。)Ericmitchell斯坦福大学计算机系四年级博士生,由ChelseaFinn和Chri

  7. 电脑0x0000001A蓝屏错误怎么U盘重装系统教学 - 2

      电脑0x0000001A蓝屏错误怎么U盘重装系统教学分享。有用户电脑开机之后遇到了系统蓝屏的情况。系统蓝屏问题很多时候都是系统bug,只有通过重装系统来进行解决。那么蓝屏问题如何通过U盘重装新系统来解决呢?来看看以下的详细操作方法教学吧。  准备工作:  1、U盘一个(尽量使用8G以上的U盘)。  2、一台正常联网可使用的电脑。  3、ghost或ISO系统镜像文件(Win10系统下载_Win10专业版_windows10正式版下载-系统之家)。  4、在本页面下载U盘启动盘制作工具:系统之家U盘启动工具。  U盘启动盘制作步骤:  注意:制作期间,U盘会被格式化,因此U盘中的重要文件请注

  8. Ruby - 如何将消息长度表示为 2 个二进制字节 - 2

    我正在使用Ruby,我正在与一个网络端点通信,该端点在发送消息本身之前需要格式化“header”。header中的第一个字段必须是消息长度,它被定义为网络字节顺序中的2二进制字节消息长度。比如我的消息长度是1024。如何将1024表示为二进制双字节? 最佳答案 Ruby(以及Perl和Python等)中字节整理的标准工具是pack和unpack。ruby的packisinArray.您的长度应该是两个字节长,并且按网络字节顺序排列,这听起来像是n格式说明符的工作:n|Integer|16-bitunsigned,network(bi

  9. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  10. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

随机推荐