Java安全之Unsafe类

0x00 前言

前面使用到的一些JNI编程和Javaagent等技术,其实在安全里面的运用非常的有趣和微妙,这个已经说过很多次。后面还会发现一些比较有意思的技术,比如ASM和Unsafe这些。这下面就先来讲解Unsafe这个类的使用和实际当中的一些运用场景。

0x01 Unsafe概述

Unsafe是位于sun.misc包下的一个类,主要提供一些用于执行低级别、不安全操作的方法,如直接访问系统内存资源、自主管理内存资源等,这些方法在提升Java运行效率、增强Java语言底层资源操作能力方面起到了很大的作用。使用该类可以获取到底层的控制权,该类在sun.misc包,默认是BootstrapClassLoader加载的。

来看一下下面的两张图

Unsafe类是一个不能被继承的类且不能直接通过new的方式创建Unsafe类实例。

这里可以看到该构造方法是private所以说不能直接new该对象,里面有一个getUnsafe()会返回Unsafe的实例。

@CallerSensitive
public static Unsafe getUnsafe() {
// ----- 这里去获取当前类的ClassLoader加载器
Class var0 = Reflection.getCallerClass();
// ----- 判断var0是不是BootstrapClassLoader
if (!VM.isSystemDomainLoader(var0.getClassLoader())) {
// ----- 否:抛出SecurityException异常
throw new SecurityException("Unsafe");
} else {
// ----- 是:返回unsafe对象
return theUnsafe;
}
}

这里是调用了isSystemDomainLoader来判断是否为Bootstrap类加载器,如果是,可以正常获取Unsafe实例,否则会抛出安全异常。

public static boolean isSystemDomainLoader(ClassLoader var0) {
// ----- 重点是在这里:
// --- 当结果为true时:说明var0是Bootstrap类加载器,
// -- 当结果为false时:说明var0是Extension || App || Custom 等类加载器
// ----- 所以回到getUnsafe()函数,当这个函数返回false时,会直接抛异常,不允许加载Unsafe
return var0 == null;
}

可以来测试一下

package com.UNsafe;

import sun.misc.Unsafe;

public class test {
public static void main(String[] args) {
Unsafe unsafe = Unsafe.getUnsafe();
int i = unsafe.addressSize();
}
}

0x02 Unsafe调用

前面说到Unsafe该类功能去进行直接调用,那么这时候就会想到我们的反射机制。可以利用反射去直接调用。在这里面也有两种方式去进行反射调用。

调用方式一:

因为该类将他的实例化定义在theUnsafe成员变量里面,所以可以使用反射直接获取该变量的值。

package com.UNsafe;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

public class test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
Class<?> aClass = Class.forName("sun.misc.Unsafe");
Field theUnsafe = aClass.getDeclaredField("theUnsafe");
theUnsafe.setAccessible(true);
Unsafe o = (Unsafe)theUnsafe.get(null);
int i = o.addressSize();
System.out.println(i); }
}

结果:

8

调用方式二:

还有种方式就是反射调用getUnsafe()方法,该方法会直接返回UNsafe实例对象。那么可以反射获取该构造方法的实例,然后调用该方法

package com.UNsafe;

import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException; public class test {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
Class<?> aClass = Class.forName("sun.misc.Unsafe");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Unsafe o = (Unsafe)declaredConstructor.newInstance();
int i = o.addressSize();
System.out.println(i); }
}

结果:

8

0x03 Unsafe功能

操作内存

public native long allocateMemory(long bytes);

	//分配内存, 相当于C++的malloc函数

public native long reallocateMemory(long address, long bytes);

	//扩充内存
public native void freeMemory(long address); //释放内存
public native void setMemory(Object o, long offset, long bytes, byte value); //在给定的内存块中设置值
public native void copyMemory(Object srcBase, long srcOffset, Object destBase, long destOffset, long bytes); //内存拷贝
public native Object getObject(Object o, long offset); //获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar等
public native void putObject(Object o, long offset, Object x); //为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等
public native byte getByte(long address); //获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的)
public native void putByte(long address, byte x); //为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的)

获取系统信息

public native int addressSize();
//返回系统指针的大小。返回值为4(32位系统)或 8(64位系统)。
public native int pageSize();
//内存页的大小,此值为2的幂次方。

线程调度

public native void unpark(Object thread);
// 终止挂起的线程,恢复正常.java.util.concurrent包中挂起操作都是在LockSupport类实现的,其底层正是使用这两个方法 public native void park(boolean isAbsolute, long time); // 线程调用该方法,线程将一直阻塞直到超时,或者是中断条件出现。 @Deprecated
public native void monitorEnter(Object o);
//获得对象锁(可重入锁) @Deprecated
public native void monitorExit(Object o);
//释放对象锁 @Deprecated
public native boolean tryMonitorEnter(Object o);
//尝试获取对象锁

操作对象

// 传入一个Class对象并创建该实例对象,但不会调用构造方法
public native Object allocateInstance(Class<?> cls) throws InstantiationException; // 获取字段f在实例对象中的偏移量
public native long objectFieldOffset(Field f); // 返回值就是f.getDeclaringClass()
public native Object staticFieldBase(Field f);
// 静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f); // 获得给定对象偏移量上的int值,所谓的偏移量可以简单理解为指针指向该变量;的内存地址,
// 通过偏移量便可得到该对象的变量,进行各种操作
public native int getInt(Object o, long offset);
// 设置给定对象上偏移量的int值
public native void putInt(Object o, long offset, int x); // 获得给定对象偏移量上的引用类型的值
public native Object getObject(Object o, long offset);
// 设置给定对象偏移量上的引用类型的值
public native void putObject(Object o, long offset, Object x);); // 设置给定对象的int值,使用volatile语义,即设置后立马更新到内存对其他线程可见
public native void putIntVolatile(Object o, long offset, int x);
// 获得给定对象的指定偏移量offset的int值,使用volatile语义,总能获取到最新的int值。
public native int getIntVolatile(Object o, long offset); // 与putIntVolatile一样,但要求被操作字段必须有volatile修饰
public native void putOrderedInt(Object o, long offset, int x);

这里allocateInstance 这个方法很有意思,可以不调用该构造方法,然后去获取一个传入对象的实例。

那么在安全中会怎么去使用到该方法呢?假设一个场景,某个类的构造方法被HOOK了,该构造方法是private修饰也不能直接去进行new该对象。如果这时候不能使用 反射的机制去进行一个调用,那么这时候就可以使用到该方法进行绕过。

小案例:

定义一个Persion类,并且构造方法为private修饰。

package com.demo2;

import java.io.Serializable;

public class Person implements Serializable {
private String name;
private int age; public String getName() {
return name;
} @Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} private Person() { } private Person(String name, int age) {
this.name = name;
this.age = age;
}
}

编写调用测试代码:

package com.UNsafe;

import com.demo2.Person;
import sun.misc.Unsafe;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; public class test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException {
Class<?> aClass = Class.forName("sun.misc.Unsafe");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Unsafe unsafe = (Unsafe)declaredConstructor.newInstance();
Person person = (Person)unsafe.allocateInstance(Person.class);
person.setAge(20);
person.setName("nice0e3");
System.out.println(person);
}
}

执行结果:

Person{name='nice0e3', age=20}

不采用反射和new的反射调用构造方法。

Class相关操作

//静态属性的偏移量,用于在对应的Class对象中读写静态属性
public native long staticFieldOffset(Field f);
//获取一个静态字段的对象指针
public native Object staticFieldBase(Field f);
//判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false
public native boolean shouldBeInitialized(Class<?> c);
//确保类被初始化
public native void ensureClassInitialized(Class<?> c);
//定义一个类,可用于动态创建类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者
public native Class<?> defineClass(String name, byte[] b, int off, int len,
ClassLoader loader,
ProtectionDomain protectionDomain); //定义一个匿名类,可用于动态创建类
public native Class<?> defineAnonymousClass(Class<?> hostClass, byte[] data, Object[] cpPatches);

这里面的defineClass方法也很有意思,在前面的学习中应该会对defineClass方法有比较深刻的印象,比如命令执行 Java的webshell工具实现、还有jsp的一些免杀都会利用到ClassLoaderdefineClass这个方法去将字节码给还原成一个类。那么在这里的这个defineClass的作用上面也说明了,也是可以去定义一个匿名类,并且可以动态去进行一个创建。假设一个场景ClassLoader.defineClass不可用后就可以使用Unsafe.defineClass

动态加载类案例

package com.UNsafe;

import com.demo2.Person;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import sun.misc.Unsafe; import javax.xml.soap.SAAJResult;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate; public class test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, InstantiationException, CannotCompileException, IOException, NotFoundException {
String AbstractTranslet="com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet";
String Classname ="com.nice0e3.Commandtest";
ClassPool classPool= ClassPool.getDefault();
classPool.appendClassPath(AbstractTranslet);
CtClass payload=classPool.makeClass("com.nice0e3.Commandtest");
payload.setSuperclass(classPool.get(AbstractTranslet));
payload.makeClassInitializer().setBody("java.lang.Runtime.getRuntime().exec(\"calc\");"); byte[] bytes=payload.toBytecode();
//获取系统加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//创建默认保护域
ProtectionDomain protectionDomain = new ProtectionDomain(new CodeSource(null, (Certificate[]) null), null, systemClassLoader, null);
Class<?> aClass = Class.forName("sun.misc.Unsafe");
Constructor<?> declaredConstructor = aClass.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Unsafe unsafe = (Unsafe)declaredConstructor.newInstance();
Class<?> aClass1 = unsafe.defineClass(Classname, bytes, 0, bytes.length, systemClassLoader, protectionDomain);
Object o = aClass1.newInstance(); }
}

在JDK 11版本以后就移除了该方法。但是前面说到的defineAnonymousClass方法还是存在也可以进行使用。

参考文章

https://www.cnblogs.com/rickiyang/p/11334887.html
https://javasec.org/javase/Unsafe/

0x04 结尾

在这里面由上面的案例可以看出来,结合了分析利用链的时候学习的Javassist动态生成类,然后去做转换成字节码Unsafe去进行加载,这其实也能想到一些有趣的利用场景。

Java安全之Unsafe类的更多相关文章

  1. Java中的Unsafe类111

    1.Unsafe类介绍 Unsafe类是在sun.misc包下,不属于Java标准.但是很多Java的基础类库,包括一些被广泛使用的高性能开发库都是基于Unsafe类开发的,比如Netty.Hadoo ...

  2. Java sun.misc.Unsafe类的学习笔记

    Java未开源的Unsafe类 Unsafe类可以为我们提供高效并且线程安全方式操作变量,直接和内存数据打交道. 获取Unsafe实体的方法 private static Unsafe getUnsa ...

  3. Java sun.misc.unsafe类

    Java是一个安全的开发工具,它阻止开发人员犯很多低级的错误,而大部份的错误都是基于内存管理方面的.如果你想搞破坏,可以使用Unsafe这个类.这个类是属于sun.*API中的类,并且它不是J2SE中 ...

  4. Java双刃剑之Unsafe类详解

    前一段时间在研究juc源码的时候,发现在很多工具类中都调用了一个Unsafe类中的方法,出于好奇就想要研究一下这个类到底有什么作用,于是先查阅了一些资料,一查不要紧,很多资料中对Unsafe的态度都是 ...

  5. 【Java并发】Java中的原子操作类

    综述 JDK从1.5开始提供了java.util.concurrent.atomic包. 通过包中的原子操作类能够线程安全地更新一个变量. 包含4种类型的原子更新方式:基本类型.数组.引用.对象中字段 ...

  6. 【实战Java高并发程序设计 1】Java中的指针:Unsafe类

    是<实战Java高并发程序设计>第4章的几点. 如果你对技术有着不折不挠的追求,应该还会特别在意incrementAndGet() 方法中compareAndSet()的实现.现在,就让我 ...

  7. java编程之:Unsafe类

    Unsafe类在jdk 源码的多个类中用到,这个类的提供了一些绕开JVM的更底层功能,基于它的实现可以提高效率.但是,它是一把双刃剑:正如它的名字所预示的那样,它是 Unsafe的,它所分配的内存需要 ...

  8. Java中Unsafe类详解

    http://www.cnblogs.com/mickole/articles/3757278.html Java不能直接访问操作系统底层,而是通过本地方法来访问.Unsafe类提供了硬件级别的原子操 ...

  9. 说一说Java的Unsafe类

    最近在看Java并发包的源码,发现了神奇的Unsafe类,仔细研究了一下,在这里跟大家分享一下. Unsafe类是在sun.misc包下,不属于Java标准.但是很多Java的基础类库,包括一些被广泛 ...

随机推荐

  1. IO流读写数据简单示例

    常用的字节输入流有:InputStream ,FileInputStream,BufferedInputStream 常用的字节输出流有:OutputStream,FileOutputStream,B ...

  2. Jenkins - Linux下启动Jenkins报错hudson.WebAppMain#contextDestroyed: Shutting down a Jenkins instance that was still starting up

    报错截图 在Linux下直接运行jenkins.war报错,导致启动失败 报错原因 运行端口已被其他进程占用 解决方法 换个启动端口就可以啦!

  3. Effective Modern C++ ——条款5 优先选择auto,而非显式型别声明

    条款5 对于auto ,他的好处不仅仅是少打一些字这么简单. 首先在声明的时候, 使用auto会让我们养成初始化的习惯: auto x;//编译不通过必须初始化. 再次对于auto而言,它可以让我们定 ...

  4. mdp文件-Chapter3-NPT.mdp

    mdp系列的第三篇,对NPT模拟中的mdp文件做一简单介绍. 先上代码 1 title = OPLS Lysozyme NPT equilibration 2 define = -DPOSRES ; ...

  5. post和get、PostMapping、GetMapping和RequestMapping

    PostMapping.GetMapping和RequestMapping PostMapping和GetMapping封装了method="",限制了method,更加规范化. ...

  6. 极客mysql03

    1.务的特性:原子性.一致性.隔离性.持久性 2.多事务同时执行的时候,可能会出现的问题:脏读.不可重复读.幻读 3.事务隔离级别:读未提交.读提交.可重复读.串行化 4.不同事务隔离级别的区别: 读 ...

  7. const常量与define宏定义的区别(转)

    #define RADIUS 100; const  float   RADIUS = 100; (1) 编译器处理方式不同 define宏是在预处理阶段展开. const常量是编译运行阶段使用. ( ...

  8. 基于 Nebula Operator 的 K8s 自动化部署运维

    摘要:Nebula Operator 是 Nebula Graph 在 Kubernetes 系统上的自动化部署运维插件.在本文,你将了解到 Nebula Operator 的特性及它的工作原理. 从 ...

  9. Linux配置邮件发送信息

    背景 一般情况下,我们的IT系统都会有相关的告警的处理,有的是邮件,有的是短信,这些都能很方便的获得一些有用的信息 在某些时候我们没有这样的系统,而自己又需要定期的获取一些信息的时候,配置一个邮件发送 ...

  10. MySQL 5.x乱码问题解决

    MySQL是一款常用的开源数据库软件,但是对于初次使用者好像并不是太友好,MySQL5.x的版本中默认字符集是latin1也就是我们所知道的ISO-8859-1字符集,这个字符集编码并没有包含汉字,所 ...