Java安全之Unsafe类

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,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,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,int x);

// 获得给定对象偏移量上的引用类型的值
public native Object getObject(Object o,long offset);
// 设置给定对象偏移量上的引用类型的值
public native void putObject(Object o,Object x););

// 设置给定对象的int值,使用volatile语义,即设置后立马更新到内存对其他线程可见
public native void putIntVolatile(Object o,int x);
// 获得给定对象的指定偏移量offset的int值,使用volatile语义,总能获取到最新的int值。
public native int getIntVolatile(Object o,long offset);

// 与putIntVolatile一样,但要求被操作字段必须有volatile修饰
public native void putOrderedInt(Object o,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,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,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,bytes.length,protectionDomain);
        Object o = aClass1.newInstance();
        

    }
}

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

参考文章

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

0x04 结尾

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

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 连接 连接池产生原因 连接池实现原理 小结 TEMPERANCE:Eat not to dullness;drink not to elevation.节制
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 一个优秀的工程师和一个普通的工程师的区别,不是满天飞的架构图,他的功底体现在所写的每一行代码上。-- 毕玄 1. 命名风格 【书摘】类名用 UpperCamelC
今天犯了个错:“接口变动,伤筋动骨,除非你确定只有你一个人在用”。哪怕只是throw了一个新的Exception。哈哈,这是我犯的错误。一、接口和抽象类类,即一个对象。先抽象类,就是抽象出类的基础部分,即抽象基类(抽象类)。官方定义让人费解,但是记忆方法是也不错的 —包含抽象方法的类叫做抽象类。接口
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket一、引子文件,作为常见的数据源。关于操作文件的字节流就是 —FileInputStream&amp;FileOutputStream。
作者:泥沙砖瓦浆木匠网站:http://blog.csdn.net/jeffli1993个人签名:打算起手不凡写出鸿篇巨作的人,往往坚持不了完成第一章节。交流QQ群:【编程之美 365234583】http://qm.qq.com/cgi-bin/qm/qr?k=FhFAoaWwjP29_Aonqz
本文目录 线程与多线程 线程的运行与创建 线程的状态 1 线程与多线程 线程是什么? 线程(Thread)是一个对象(Object)。用来干什么?Java 线程(也称 JVM 线程)是 Java 进程内允许多个同时进行的任务。该进程内并发的任务成为线程(Thread),一个进程里至少一个线程。 Ja
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket在面向对象编程中,编程人员应该在意“资源”。比如?1String hello = &quot;hello&quot;; 在代码中,我们
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 这是泥瓦匠的第103篇原创 《程序兵法:Java String 源码的排序算法(一)》 文章工程:* JDK 1.8* 工程名:algorithm-core-le
摘要: 原创出处 https://www.bysocket.com 「公众号:泥瓦匠BYSocket 」欢迎关注和转载,保留摘要,谢谢! 目录 一、父子类变量名相同会咋样? 有个小故事,今天群里面有个人问下面如图输出什么? 我回答:60。但这是错的,答案结果是 40 。我知错能改,然后说了下父子类变
作者:泥瓦匠 出处:https://www.bysocket.com/2021-10-26/mac-create-files-from-the-root-directory.html Mac 操作系统挺适合开发者进行写代码,最近碰到了一个问题,问题是如何在 macOS 根目录创建文件夹。不同的 ma
作者:李强强上一篇,泥瓦匠基础地讲了下Java I/O : Bit Operation 位运算。这一讲,泥瓦匠带你走进Java中的进制详解。一、引子在Java世界里,99%的工作都是处理这高层。那么二进制,字节码这些会在哪里用到呢?自问自答:在跨平台的时候,就凸显神功了。比如说文件读写,数据通信,还
1 线程中断 1.1 什么是线程中断? 线程中断是线程的标志位属性。而不是真正终止线程,和线程的状态无关。线程中断过程表示一个运行中的线程,通过其他线程调用了该线程的 方法,使得该线程中断标志位属性改变。 深入思考下,线程中断不是去中断了线程,恰恰是用来通知该线程应该被中断了。具体是一个标志位属性,
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want需求 项目在设计表的时候,要处理并发多的一些数据,类似订单号不能重复,要保持唯一。原本以为来个时间戳,精确到毫秒应该不错了。后来觉得是错了,测试环境下很多一
纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 纯技术交流群 每日推荐 - 技术干货推送 跟着泥瓦匠,一起问答交流 扫一扫,我邀请你入群 加微信:bysocket01
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocketReprint it anywhere u want.文章Points:1、介绍RESTful架构风格2、Spring配置CXF3、三层初设计,实现WebService接口层4、撰写HTTPClient 客户
Writer :BYSocket(泥沙砖瓦浆木匠)什么是回调?今天傻傻地截了张图问了下,然后被陈大牛回答道“就一个回调…”。此时千万个草泥马飞奔而过(逃哈哈,看着源码,享受着这种回调在代码上的作用,真是美哉。不妨总结总结。一、什么是回调回调,回调。要先有调用,才有调用者和被调用者之间的回调。所以在百
Writer :BYSocket(泥沙砖瓦浆木匠)一、什么大小端?大小端在计算机业界,Endian表示数据在存储器中的存放顺序。百度百科如下叙述之:大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加
What is a programming language? Before introducing compilation and decompilation, let&#39;s briefly introduce the Programming Language. Programming la
Writer :BYSocket(泥沙砖瓦浆木匠)微 博:BYSocket豆 瓣:BYSocketFaceBook:BYSocketTwitter :BYSocket泥瓦匠喜欢Java,文章总是扯扯Java。 I/O 基础,就是二进制,也就是Bit。一、Bit与二进制什么是Bit(位)呢?位是CPU
Writer:BYSocket(泥沙砖瓦浆木匠)微博:BYSocket豆瓣:BYSocket一、前言 泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标:专注很重要。专注Java 基础 + H5(学习) 其他操作系统,算法,数据结构当成课外书博览。有时候,就是那样你越是专注方面越