java学习系列之java基础

Java平台共分为三个主要版本Java SEJava Platform, Standard Edition,Java平台标准版)、Java EEJava Platform Enterprise Edition,Java平台企业版)、和Java MEJava Platform, Micro Edition,Java平台微型版)。

Java SE是JDK自带的标准API,内容涉及范围甚广,知识体系更是环环相扣浩瀚无边。没有基础,何来进阶!本章节整理了一些Java SE基础部分与Java SE安全相关的基础知识供初学者学习进阶。

JDK是面向开发人员使用的SDK,它提供了Java的开发环境和运行环境,JDK中包含了JRE。

JRE是Java的运行环境,是面向所有Java程序的使用者,包括开发者。

JRE= 运行环境 = JVM

如果安装了JDK,会发现电脑中有两套JRE,一套位于/Java/jre…/下,一套位于/Java/jdk…/jre下。那么问题来了,一台机器上有两套以上JRE,谁来决定运行那一套呢?这个任务就落到java.exe身上,java.exe的任务就是找到合适的JRE来运行java程序。

参:https://blog.csdn.net/pingdouble/article/details/79376535

ClassLoader(类加载机制)

我们都知道Java源文件,通过编译器,能够生产相应的.Class文件,也就是字节码文件,而字节码文件又通过Java虚拟机中的解释器,也就是前面所有的Java虚拟机中的字节码指令集….编译成特定机器上的机器码

也就是如下:

1.Java源文件—->编译器—->字节码文件

2.字节码文件—->Jvm—->机器码

Java是一个依赖于JVM(Java虚拟机)实现的跨平台的开发语言。Java程序在运行前需要先编译成class文件,Java类初始化的时候会调用java.lang.ClassLoader加载类字节码,ClassLoader会调用JVM的native方法(defineClass0/1/2)来定义一个java.lang.Class实例。

JVM架构图:

https://javasec.oss-cn-hongkong.aliyuncs.com/images/JvmSpec7.png

Java类

Java是编译型语言,我们编写的java文件需要编译成后class文件后才能够被JVM运行,学习ClassLoader之前我们先简单了解下Java类。

示例TestHelloWorld.java:

1
2
3
4
5
6
7
8
9
package com.anbai.sec.classloader;

public class TestHelloWorld {

public String hello() {
return "Hello World~";
}

}

编译TestHelloWorld.javajavac TestHelloWorld.java

我们可以通过JDK自带的javap命令反汇编TestHelloWorld.class文件对应的com.anbai.sec.classloader.TestHelloWorld类,以及使用Linux自带的hexdump命令查看TestHelloWorld.class文件二进制内容:

https://javasec.oss-cn-hongkong.aliyuncs.com/images/image-20191217171821663.png

JVM在执行TestHelloWorld之前会先解析class二进制内容,JVM执行的其实就是如上javap命令生成的字节码。

ClassLoader

一切的Java类都必须经过JVM加载后才能运行,而ClassLoader的主要作用就是Java类文件的加载。在JVM类加载器中最顶层的是Bootstrap ClassLoader(引导类加载器)Extension ClassLoader(扩展类加载器)App ClassLoader(系统类加载器)AppClassLoader是默认的类加载器,如果类加载时我们不指定类加载器的情况下,默认会使用AppClassLoader加载类,ClassLoader.getSystemClassLoader()返回的系统类加载器也是AppClassLoader

值得注意的是某些时候我们获取一个类的类加载器时候可能会返回一个null值,如:java.io.File.class.getClassLoader()将返回一个null对象,因为java.io.File类在JVM初始化的时候会被Bootstrap ClassLoader(引导类加载器)加载(该类加载器实现于JVM层,采用C++编写),我们在尝试获取被Bootstrap ClassLoader类加载器所加载的类的ClassLoader时候都会返回null

ClassLoader类有如下核心方法:

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

Java类动态加载方式

Java类加载方式分为显式隐式,显式即我们通常使用Java反射或者ClassLoader来动态加载一个类对象,而隐式指的是类名.方法名()new类实例。显式类加载方式也可以理解为类动态加载,我们可以自定义类加载器去加载任意的类。

常用的类动态加载方式:

1
2
3
4
5
// 反射加载TestHelloWorld示例
Class.forName("com.anbai.sec.classloader.TestHelloWorld");

// ClassLoader加载TestHelloWorld示例
this.getClass().getClassLoader().loadClass("com.anbai.sec.classloader.TestHelloWorld");

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

ClassLoader类加载流程

理解Java类加载机制并非易事,这里我们以一个Java的HelloWorld来学习ClassLoader

ClassLoader加载com.anbai.sec.classloader.TestHelloWorld类重要流程如下:

  1. ClassLoader会调用public Class<?> loadClass(String name)方法加载com.anbai.sec.classloader.TestHelloWorld类。
  2. 调用findLoadedClass方法检查TestHelloWorld类是否已经初始化,如果JVM已初始化过该类则直接返回类对象。
  3. 如果创建当前ClassLoader时传入了父类加载器(new ClassLoader(父类加载器))就使用父类加载器加载TestHelloWorld类,否则使用JVM的Bootstrap ClassLoader加载。
  4. 如果上一步无法加载TestHelloWorld类,那么调用自身的findClass方法尝试加载TestHelloWorld类。
  5. 如果当前的ClassLoader没有重写了findClass方法,那么直接返回类加载失败异常。如果当前类重写了findClass方法并通过传入的com.anbai.sec.classloader.TestHelloWorld类名找到了对应的类字节码,那么应该调用defineClass方法去JVM中注册该类。
  6. 如果调用loadClass的时候传入的resolve参数为true,那么还需要调用resolveClass方法链接类,默认为false。
  7. 返回一个被JVM加载后的java.lang.Class类对象。

自定义ClassLoader

java.lang.ClassLoader是所有的类加载器的父类,java.lang.ClassLoader有非常多的子类加载器,比如我们用于加载jar包的java.net.URLClassLoader其本身通过继承java.lang.ClassLoader类,重写了findClass方法从而实现了加载目录class文件甚至是远程资源文件。

既然已知ClassLoader具备了加载类的能力,那么我们不妨尝试下写一个自己的类加载器来实现加载自定义的字节码(这里以加载TestHelloWorld类为例)并调用hello方法。

如果com.anbai.sec.classloader.TestHelloWorld类存在的情况下,我们可以使用如下代码即可实现调用hello方法并输出:

1
2
3
TestHelloWorld t = new TestHelloWorld();
String str = t.hello();
System.out.println(str);

但是如果com.anbai.sec.classloader.TestHelloWorld根本就不存在于我们的classpath,那么我们可以使用自定义类加载器重写findClass方法,然后在调用defineClass方法的时候传入TestHelloWorld类的字节码的方式来向JVM中定义一个TestHelloWorld类,最后通过反射机制就可以调用TestHelloWorld类的hello方法了。

TestClassLoader示例代码:

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
57
58
59
60
61
62
63
copypackage com.anbai.sec.classloader;

import java.lang.reflect.Method;

public class TestClassLoader extends ClassLoader {

// TestHelloWorld类名
private static String testClassName = "com.anbai.sec.classloader.TestHelloWorld";

// TestHelloWorld类字节码
private static byte[] testClassBytes = new byte[]{
-54, -2, -70, -66, 0, 0, 0, 51, 0, 17, 10, 0, 4, 0, 13, 8, 0, 14, 7, 0, 15, 7, 0,
16, 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, 5, 104, 101, 108, 108, 111, 1, 0, 20, 40, 41, 76, 106, 97, 118, 97, 47, 108,
97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 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, 5, 0, 6, 1, 0, 12, 72, 101, 108, 108, 111,
32, 87, 111, 114, 108, 100, 126, 1, 0, 40, 99, 111, 109, 47, 97, 110, 98, 97, 105, 47,
115, 101, 99, 47, 99, 108, 97, 115, 115, 108, 111, 97, 100, 101, 114, 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, 0, 33, 0, 3, 0, 4, 0, 0, 0, 0, 0, 2, 0, 1,
0, 5, 0, 6, 0, 1, 0, 7, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0,
1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 7, 0, 1, 0, 9, 0, 10, 0, 1, 0, 7, 0, 0, 0, 27, 0, 1,
0, 1, 0, 0, 0, 3, 18, 2, -80, 0, 0, 0, 1, 0, 8, 0, 0, 0, 6, 0, 1, 0, 0, 0, 10, 0, 1, 0, 11,
0, 0, 0, 2, 0, 12
};

@Override
public Class<?> findClass(String name) throws ClassNotFoundException {
// 只处理TestHelloWorld类
if (name.equals(testClassName)) {
// 调用JVM的native方法定义TestHelloWorld类
return defineClass(testClassName, testClassBytes, 0, testClassBytes.length);
}

return super.findClass(name);
}

public static void main(String[] args) {
// 创建自定义的类加载器
TestClassLoader loader = new TestClassLoader();

try {
// 使用自定义的类加载器加载TestHelloWorld类
Class testClass = loader.loadClass(testClassName);

// 反射创建TestHelloWorld类,等价于 TestHelloWorld t = new TestHelloWorld();
Object testInstance = testClass.newInstance();

// 反射获取hello方法
Method method = testInstance.getClass().getMethod("hello");

// 反射调用hello方法,等价于 String str = t.hello();
String str = (String) method.invoke(testInstance);

System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}

}

利用自定义类加载器我们可以在webshell中实现加载并调用自己编译的类对象,比如本地命令执行漏洞调用自定义类字节码的native方法绕过RASP检测,也可以用于加密重要的Java类字节码(只能算弱加密了)。

URLClassLoader

URLClassLoader继承了ClassLoaderURLClassLoader提供了加载远程资源的能力,在写漏洞利用的payload或者webshell的时候我们可以使用这个特性来加载远程的jar来实现远程的类方法调用。

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
34
35
36
37
38
39
40
41
42
43
44
45
package com.anbai.sec.classloader;

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

public class TestURLClassLoader {

public static void main(String[] args) {
try {
// 定义远程加载的jar路径
URL url = new URL("<https://anbai.io/tools/cmd.jar>");

// 创建URLClassLoader对象,并加载远程jar包
URLClassLoader ucl = new URLClassLoader(new URL[]{url});

// 定义需要执行的系统命令
String cmd = "ls";

// 通过URLClassLoader加载远程jar包中的CMD类
Class cmdClass = ucl.loadClass("CMD");

// 调用CMD类中的exec方法,等价于: Process process = CMD.exec("whoami");
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();
}
}

}

远程的cmd.jar中就一个CMD.class文件,对应的编译之前的代码片段如下:

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

public class CMD {

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

}

程序执行结果如下:

1
2
3
4
5
6
copyREADME.md
gitbook
javaweb-sec-source
javaweb-sec.iml
jni
pom.xml

ClassLoader总结总结

ClassLoader 是JVM中一个非常重要的组成部分, ClassLoader 可以为我们加载任意的java类,通过

自定义 ClassLoader 更能够实现自定义类加载行为,在后面的几个章节我们也将讲解 ClassLoader 的

实际利用场景。

https://xz.aliyun.com/t/9002#toc-9

补充

java.lang.Class类

一、Class类是什么

****Class是一个类,位于java.lang包下。在Java中每个类都有一个相对应的Class类的对象,换句话说:Java程序在启动运行时 一个XXX.java类经过编译生成XXX.class文件后,就会在JVM虚拟机中产生一个XXX类对应的Class类的对象,用于表示这个XXX类的类型信息。

二、Class类常用的方法

****Class类是反射中的核心类,它有如下的方法:

获取类中的属性:

    • getFields(): 获取类中public类型的属性
    • getField(String name): 获取类特定的方法,name参数指定了属性的名称
    • getDeclaredFields(): 获取类中所有的属性(public、protected、default、private),但不包括继承的属性。
    • getDeclaredField(String name): 获取类特定的方法,name参数指定了属性的名称

获取类中的构造函数:

    • getConstructors():获取类中的公共方法
    • getConstructor(Class[] params): 获取类的特定构造方法,params参数指定构造方法的参数类型
    • getDeclaredConstructors(): 获取类中所有的构造方法(public、protected、default、private)
    • getDeclaredConstructor(Class[] params): 获取类的特定构造方法,params参数指定构造方法的参数类型

获取类中的方法:

    • getMethods(): 获得类的public类型的方法
    • getMethod(String name, Class[] params): 获得类的特定方法,name参数指定方法的名字,params参数指定方法的参数类型
    • getDeclaredMethods(): 获取类中所有的方法(public、protected、default、private)
    • getDeclaredMethod(String name, Class[] params): 获得类的特定方法,name参数指定方法的名字,params参数指定方法的参数类型

其它重要方法:

    • newInstance(): 通过类的不带参数 的构造方法创建这个类的一个对象
    • forName(String className): 获取className参数指定的类的class对象
    • forName(String className,boolean initialize,ClassLoader): 使用指定的类加载器获取className参数指定的类的class对象
    • getClassLoader(): 获取类加载器
    • getName(): 获取类名
    • getPackage(): 获取类所在的包名

三、获取Class对象的三种方法

  • 第一种: 调用Class类的静态方法forName,比如 Class.forName(“java.lang.String”)
  • 第二种: 使用类的.class语法,比如 : Class cls = String.class
  • 第三种: 调用对象的getClass方法, 比如:String str = “123”; Class cls = str.getClass(); (PS:在java.lang.Object类中定义了getClass()方法,因此对于任意一个Java对象,都可以通过此方法获得对象的类型)
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!

扫一扫,分享到微信

微信分享二维码
  • Copyrights © 2021-2023 Wh1tecell
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~