前置介绍
运行时应用程序自我保护(RASP)是一种在应用上运行的技术,旨在实时检测针对应用程序的攻击。它将防护功能“注入”到应用程序中,与应用程序融为一体,通过Hook少量关键函数来实时观测程序运行期间的内部情况。当应用出现可疑行为时,RASP根据当前上下文环境精准识别攻击事件,并给予实时阻断,使应用程序具备自我防护能力,而不需要进行人工干预。
Java中的RASP主要作用就是Hook掉了一些恶意类
,比如Runtime
、ProcessBuilder
其中。Runtime.exec
调用的是ProcessBuilder.start
,ProcessBuilder.start
的底层会调用ProcessImpl
类。那么这时候只需要去Hook掉ProcessImpl
就无法进行执行命令了。
Java Agent机制
提到RASP就绕不过Java Agent。一句话简单总结一下二者关系,Java Agent 是一种通用的机制,而RASP 是 Java Agent 专注于安全领域的一种特定用途
Java Agent 就像一个秘密特工,悄悄地在 Java 应用程序的幕后工作。它是一种特殊的软件,可以附加到运行中的 Java 进程,而无需修改应用程序的代码,即可对目标JVM造成影响,有点类似于之前写Frida的感觉,其中Java Agent有以下这么几个比较重要的特点或者说是功能需要我们掌握一下。
- 附加(Attachment):Java Agent 使用
-javaagent
命令行选项附加到运行中的 Java 进程。这个选项指定了 Java Agent JAR 文件的路径。当 Java 进程启动时,Agent 被加载。
- 字节码增强(Instrumentation):Java Agent 使用字节码增强技术来修改或增强 Java 类。它可以在方法前后添加代码、更改方法体,甚至创建新的类。
- 生命周期钩子(Lifecycle Hooks):Java Agent 有生命周期钩子,比如
premain
和 agentmain
。
premain
:在应用程序的 main
方法之前执行。
agentmain
:动态执行(例如,附加到运行中的进程时)。
- 类转换(Class Transformation):Agent 可以使用
ClassFileTransformer
转换类。它拦截类加载并修改字节码。
光看文字必然学不出来什么东西,让我们动手实践一下
Agent 实例(一)
通常情况下,我们可以用以下命令启动Agent机制。
1
| java -javaagent:/path/to/myagent.jar -jar myapp.jar
|
j这里主要看到-javaagent
这个参数,javaagent
是java命令提供的一个参数,这个参数可以指定一个jar包,在真正的程序没有运行之前先运行指定的jar包。并且该jar包有两个要求:
- jar包的MANIFEST.MF文件必须指定Premain-Class
- Premain-Class指定的类必须实现premain()方法。
值得一提的是,这里的javaagent可以指定多个jar,jvm会依次执行不同的jar中PreMainclass#premain()
方法,而premain方法则有两种定义方式
1 2 3
| public static void premain(String agentArgs, Instrumentation inst)
public static void premain(String agentArgs)
|
此处会优先调用第一种写法。这种方法可以在JDK1.5及之后的版本使用。现在,我们开始着手创造第一个agent
,打开idea随便起一个maven项目,并将以下内容写入pom.xml
中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <configuration> <archive> <manifestEntries> <Premain-Class>org.example.PreMain</Premain-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </archive> </configuration> </plugin> </plugins> </build>
|
之后再准备好PreMain.java
,这个transform
之前没有接触过,其参数所代表实际含义如下所示,有兴趣可以看看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public class PreMain { public static void premain(String agentArgs, Instrumentation inst){ System.out.println("agentArgs:" + agentArgs); inst.addTransformer(new DefineTransformer(), true); }
static class DefineTransformer implements ClassFileTransformer{ @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println("premain load Class: " + className); return classfileBuffer; } } }
|
- ClassLoader loader:
- 这是要转换的类的类加载器。如果类是由引导类加载器加载的,则此参数可能为
null
。类加载器是 Java 中用于加载类文件(.class
文件)到 JVM 的组件。
- String className:
- 完全限定的类名,这个名称表示当前正在被 JVM 加载的类。
- classBeingRedefined:
- 如果正在重新定义的类不是
null
,则这是一个对该类的引用。在正常类加载过程中,这个参数通常是 null
。它只在使用了类重新定义或类重转换功能时才有值,这些功能允许在运行时动态修改类的定义。
- ProtectionDomain protectionDomain:
- 这是类的保护域。保护域是与类关联的一组权限,这些权限由类的加载器在类加载时赋予。保护域可以包含从哪个位置加载类的信息、类签名的信息,以及类可以执行的操作的权限信息。
- byte[] classfileBuffer:
- 这是类的原始字节码数组。这些是从
.class
文件中读取的未经修改的字节码。在 transform
方法中,我们可以分析这些字节码、修改它们或者返回原始字节码。
准备完毕之后拿maven打个包
到这里我们的agent包就搞好了,再接着准备一下运行的jar包,
1 2 3 4 5 6 7
| package org.example; public class Main { public static void main(String[] args) { System.out.println("Main.main() in test project"); } }
|
同样还是用maven打包,只需要在<manifestEntries>
标签下添加一个<Main-Class>
即可。
运行结果如下,可以看到premain先按照逻辑输出了加载的各种类,然后再main执行,最后结束时,premain还加载了一些结束时需要的类,到此agent机制我们就算是碰到一点点门槛了。
Agent实例(二)
前面的方法需要在main函数启动前执行agent,但有些时候,jvm已经启动了,而且服务不能轻易暂停,这时候我们就可以引入此处要介绍的Attach机制了。jdk1.6之后在Instrumentation中添加了一种agentmain的代理方法,可以在main函数执行之后再运行。和premain函数一样,开发者可以编写一个包含agentmain函数的Java类,它也有两种写法:
1 2 3
| public static void agentmain (String agentArgs, Instrumentation inst)
public static void agentmain (String agentArgs)
|
attach机制的具体实现在com.sun.tools.attach
中,这里介绍如下两个关键类:
VirtualMachine
字面意义表示一个Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了获取系统信息(比如获取内存dump、线程dump,类信息统计(比如已加载的类以及实例个数等), loadAgent,Attach 和 Detach (Attach 动作的相反行为,从 JVM 上面解除一个代理)等方法,可以实现的功能可以说非常之强大 。该类允许我们通过给attach方法传入一个jvm的pid(进程id),远程连接到jvm上
代理类注入操作只是它众多功能中的一个,通过loadAgent
方法向jvm注册一个代理程序agent,在该agent的代理程序中会得到一个Instrumentation实例,该实例可以 在class加载前改变class的字节码,也可以在class加载后重新加载。在调用Instrumentation实例的方法时,这些方法会使用ClassFileTransformer接口中提供的方法进行处理。
VirtualMachineDescriptor
则是一个描述虚拟机的容器类,配合 VirtualMachine 类完成各种功能
具体实现过程: 通过VirtualMachine类的attach(pid)
方法,便可以attach到一个运行中的java进程上,之后便可以通过loadAgent(agentJarPath)
来将agent的jar包注入到对应的进程,然后对应的进程会调用agentmain方法。
这里跟着实操一下,首先是起一个可以长时间运行的jar包,打包之类的过程不再细谈,直接看源码
1 2 3 4 5 6 7 8
| package org.example; public class Main { public static void main(String[] args) throws InterruptedException { System.out.println("Main.main() in test project start!!"); Thread.sleep(300000000); System.out.println("Main.main() in test project end!!"); } }
|
运行结果如下所示,这个32420的pid我们记录一下,之后要用到。
整完这个jar包,我们再整一个agent包,其中pom.xml部分把<Agent-Class>org.example.AgentMain</Agent-Class>
添加到manifestEntries
即可
1 2 3 4 5 6
| public class AgentMain { public static void agentmain(String agentArgs, Instrumentation instrumentation) { System.out.println("agentmain start!"); System.out.println(instrumentation.toString()); } }
|
打完Jar包之后,我们直接在idea里起个java程序,正儿八经开始我们的测试,这里可能会报不存在com.sun.tools
,手动添加一下tools.jar就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package org.example; import java.io.IOException; import com.sun.tools.attach.AgentInitializationException; import com.sun.tools.attach.AgentLoadException; import com.sun.tools.attach.AttachNotSupportedException; import com.sun.tools.attach.VirtualMachine; public class AttachTest { public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException { VirtualMachine attach = VirtualMachine.attach("32420"); attach.loadAgent("F:\\RaspDemo2\\Myagent.jar"); attach.detach(); System.out.println("xxxx"); } }
|
可以看到运行结果发生了改变,在Test.jar
中成功执行了agentmain()
的逻辑,拿到了Instrumentation
对象。之后我们就可以通过对Instrumentation对象添加transformer类,来实现类转换(Class Transform),也就是在transform函数中结合修改字节码的方法(ASM、Javassist、cglib等)来进一步实现RASP。
RASP简易Demo
前置探索
在前面的例子中,我们可以通过Instrumentation
对象来添加 Transformer
来对对加载的类进行动态修改,那么显然我们可以通过对危险类进行动态修改监听来实现RASP。但实际情况确往往没有这么简单,我们可以用下面手敲下面几个例子来做个简单分析。
Main
入口类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public class Main { public static class A{ public void t(){System.out.println("Main$A.t()");} } public static void main(String[] args) throws InterruptedException, IOException { System.out.println("-------Main.main() start-------"); Runtime.getRuntime().exec("calc"); String a = "a"; System.out.println(a); A a1 = new A(); a1.t(); System.out.println("-------Main.main() end-------"); } }
|
PreMain
类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| public class PreMain { public static void premain(String agentArgs, Instrumentation inst) throws IOException { System.out.println("++++++++Premain start++++++++"); System.out.println(ClassLoader.getSystemClassLoader().toString()); inst.addTransformer(new DefineTransformer(), true); System.out.println("++++++++Premain end++++++++"); } public static class DefineTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { System.out.println(className.toString() + " " + loader.toString()); System.out.println(""); return classfileBuffer; } } }
|
按照之前的配置打jar包运行,结果如下
注意一下,这里的PreMain和Main这种我们自己编写的类都是由AppClassLoader
来进行加载的,非常符合我们对于双亲委派的认知,但这种加载方式却会对我们RASP实现造成一定的干扰,我们跟着看下两个比较简单的例子。先将如下两个部分插入pom.xml对应位置中去
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
| <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.2.4</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Premain-Class>org.example.PreMain</Premain-Class> <Main-Class>org.example.Main</Main-Class> <Agent-Class>org.example.AgentMain</Agent-Class> <Can-Redefine-Classes>true</Can-Redefine-Classes> <Can-Retransform-Classes>true</Can-Retransform-Classes> </manifestEntries> </transformer> </transformers> </configuration> </execution> </executions> </plugin>
<dependency> <groupId>org.javassist</groupId> <artifactId>javassist</artifactId> <version>3.25.0-GA</version> </dependency>
|
之后于org.example创建一个被代理的A类
1 2 3 4 5 6 7
| package org.example; public class A { public void call(){ System.out.println("The A is called!"); } }
|
修改PreMain代码,对A类的call方法进行代理拦截,具体实现主要依赖javasist修改字节码来完成。
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
| package org.example; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.io.IOException; import javassist.ClassPool; import javassist.CtClass; import javassist.CtMethod; import javassist.LoaderClassPath; public class PreMain { public static void premain(String agentArgs, Instrumentation inst) { System.out.println("++++++++Premain start++++++++"); inst.addTransformer(new DefineTransformer(), true); System.out.println("++++++++Premain end++++++++"); } public static class DefineTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (className.equals("org/example/A")) { try { ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(new LoaderClassPath(loader)); CtClass ctClass = classPool.get("org.example.A"); CtMethod method = ctClass.getDeclaredMethod("call"); method.insertBefore("{ System.out.println(\"Before call method\"); }"); byte[] byteCode = ctClass.toBytecode(); ctClass.detach(); return byteCode; } catch (Exception e) { e.printStackTrace(); } } return classfileBuffer; } } }
|
Main这块也做个小改,调用a.call()
1 2 3 4 5 6 7 8 9 10
| package org.example; import java.io.IOException; public class Main { public static void main(String[] args) throws Exception { System.out.println("-------Main.main() start-------"); A a = new A(); a.call(); System.out.println("-------Main.main() end-------"); } }
|
运行结果如下,成功代理拦截A#call()
。
理论上通过这种方法,我们就可以实现对任意method的拦截修改,不过这里还是要改一改代码接着研究,Main
的类源码如下所示,插了一段zip的压缩代码和经典弹计算机,重点看下我下面贴的这张图,此处的newFileSystem
最终会调用到ZipFileSystemProvider#newFileSystem()
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
| package org.example; import java.io.IOException; import java.net.URI; import java.nio.file.*; import java.util.HashMap; import java.util.Map; public class Main { public static void main(String[] args) throws Exception { System.out.println("-------Main.main() start-------"); A a = new A(); Runtime.getRuntime().exec("calc"); Path zipPath = Paths.get("example.zip"); URI uri = URI.create("jar:" + zipPath.toUri()); Map<String, String> env = new HashMap<>(); env.put("create", "true"); try (FileSystem zipfs = FileSystems.newFileSystem(uri, env)) { Path externalTxtFile = Paths.get("F:\\test.txt"); Path pathInZipfile = zipfs.getPath("/somefile.txt"); Files.copy(externalTxtFile, pathInZipfile, StandardCopyOption.REPLACE_EXISTING); } System.out.println("-------Main.main() end-------"); } }
|
PreMain
代码如下,拦截ZipFileSystemProvider#newFileSystem()
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
| public class PreMain { public static void premain(String agentArgs, Instrumentation inst) { System.out.println("++++++++Premain start++++++++"); inst.addTransformer(new DefineTransformer(), true); System.out.println("++++++++Premain end++++++++"); } public static class DefineTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (className.equals("com/sun/nio/zipfs/ZipFileSystemProvider")) { try { ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(new LoaderClassPath(loader)); CtClass ctClass = classPool.get("com.sun.nio.zipfs.ZipFileSystemProvider"); CtMethod method = ctClass.getDeclaredMethod("newFileSystem"); method.insertBefore("{ System.out.println(\"Before call method\"); }"); byte[] byteCode = ctClass.toBytecode(); ctClass.detach(); return byteCode; } catch (Exception e) { e.printStackTrace(); } } return classfileBuffer; } } }
|
拦截是成功的,但让我们继续换一种拦截方式看看
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
| public class PreMain { public static void premain(String agentArgs, Instrumentation inst) { System.out.println("++++++++Premain start++++++++"); inst.addTransformer(new DefineTransformer(), true); System.out.println("++++++++Premain end++++++++"); } public static class DefineTransformer implements ClassFileTransformer { @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { if (className.equals("com/sun/nio/zipfs/ZipFileSystemProvider")) { System.out.println("类名:"+className+" 类加载器:"+loader.toString()); System.out.println(ClassLoader.getSystemClassLoader()); try { ClassPool classPool = ClassPool.getDefault(); classPool.appendClassPath(new LoaderClassPath(loader)); CtClass ctClass = classPool.get("com.sun.nio.zipfs.ZipFileSystemProvider"); CtMethod method = ctClass.getDeclaredMethod("newFileSystem"); String codeToInsert = "org.example.B.before();"; method.insertBefore(codeToInsert); byte[] byteCode = ctClass.toBytecode(); ctClass.detach(); return byteCode; } catch (Exception e) { e.printStackTrace(); } } return classfileBuffer; } } } --------------------------------------------------------------------------------- public class B { public static void before(){ System.out.println("B called"); } } --------------------------------------------------------------------------------
|
发现提示ClassNotFound
,这里的原因可以归结到双亲委派这一块。虽然同样都是用javaasist修改字节码,但法一的修改方式并不涉及到类加载层面的问题,而法二method.insertBefore("org.example.B.before();")
却并不尽然地对org.example.B
进行了类加载。在法二形势下,用来加载ZipFileSystem
的ExtClassloader
被强行用来加载本该由AppClassLoader
进行加载的org.example.B
,依据双亲委派原则,ExtClassLoader
在找不到类的情况只得向上委派给更高层级的BootstrapClassLoader
,而无法将问题抛给更低层级的AppClassLoader
,最终导致ClassNotFound
。
这里本来是想Hook一下ProcessBuilder
类的,但发现在类加载hook的模式没有办法解决这个问题,诸如ProccessBuilder
的系统类早在JVM
启动初期就已经完成了加载,其加载进度相较于PreMain
更加提前,故难以通过对类加载hook来对此种系统类进行修改。
Instrumentation详探
为了方便用户对JVM进行操作,JDK1.5之后引入了这个Instrumentation特性,通过Instrumentation的实例对象,可以对jvm进行一定的操作,例如修改字节码、插桩等等。
它的实现原理是JVMTI(JVM Tool Interface),即JVM向用户提供的操作jvm的接口。JVMTI是事件驱动的,当发生一定的处理逻辑时,才会调用回调接口,而这些接口可以让用户扩展一些逻辑。例如前面的transform函数调用,就是JVMTI监听到类加载,就会基于这个事件,回调instrumentation中的所有ClassTransformer.transform函数,进行类转换(Class transform)。所以我们可以理解为获得instrumentation对象,就可以实现对一个jvm的一定操作,获取的这个对象的方法就是前文提到的javaagent和attach方法。
然后以下是一些常用的Instrumentation
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| void addTransformer(ClassFileTransformer transformer, boolean canRetransform)
void addTransformer(ClassFileTransformer transformer)
boolean removeTransformer(ClassFileTransformer transformer)
void retransformClasses(Class<?>... classes)
void appendToBootstrapClassLoaderSearch(JarFile jarfile)
void appendToSystemClassLoaderSearch(JarFile jarfile)
Class[] getAllLoadedClasses() Class[] getInitiatedClasses(ClassLoader loader) boolean isModifiableClass(Class<?> theClass) void redefineClasses(ClassDefinition... definitions)
|
JVMTIAgent(JVMTI Agent)是实现了 JVMTI 规范的代理程序,它是基于 JVMTI 接口开发的具体应用,简而言之,JVMTI 是一种规范和接口,定义了与 Java 虚拟机交互的方式,而 JVMTIAgent 则是基于 JVMTI 规范实现的具体代理程序,用于实现各种监控、调试和分析功能。像这里的Instrumentation就可以理解为一种JVMTI Agent,而以上两种JavaAgent例子其实就是两种不同的
instrument agent
运行过程,我们可以用下面这种方式来描述他们。
一、启动时加载instrument agent过程:
- 创建并初始化 JPLISAgent;
- 监听
VMInit
事件,在 JVM 初始化完成之后做下面的事情:
- 创建 InstrumentationImpl 对象 ;
- 监听 ClassFileLoadHook 事件 ;
- 调用 InstrumentationImpl 的
loadClassAndCallPremain
方法,在这个方法里会去调用 javaagent 中 MANIFEST.MF 里指定的Premain-Class 类的 premain 方法 ;
- 解析 javaagent 中 MANIFEST.MF 文件的参数,并根据这些参数来设置 JPLISAgent 里的一些内容。
二、运行时加载instrument agent过程:
通过 JVM 的attach机制来请求目标 JVM 加载对应的agent,过程大致如下:
- 创建并初始化JPLISAgent;
- 解析 javaagent 里 MANIFEST.MF 里的参数;
- 创建 InstrumentationImpl 对象;
- 监听 ClassFileLoadHook 事件;
- 调用 InstrumentationImpl 的
loadClassAndCallAgentmain
方法,在这个方法里会去调用javaagent里 MANIFEST.MF 里指定的Agent-Class
类的agentmain
方法。
最终解决方案
通过以上对于Instrumentation
的具体学习,我们显然有了突破限制的思路。虽然没有办法在类加载阶段修改字节码,但我们可以等到所有类都加载进JVM之后再对字节码来进行修改,Agent代码部分如下
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
| package org.example; import java.io.BufferedReader; import java.io.InputStreamReader; import java.lang.instrument.Instrumentation; import java.lang.instrument.UnmodifiableClassException; import java.io.IOException; public class PreMain { public static void premain(String agentArgs, Instrumentation inst) throws IOException, UnmodifiableClassException { System.out.println("\n"); ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command("cmd", "/c", "chdir"); Process process = processBuilder.start(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), "gbk")); System.out.println(bufferedReader.readLine()); ProcessBuilderHook processBuilderHook = new ProcessBuilderHook(inst); inst.addTransformer(processBuilderHook, true); Class[] allLoadedClasses = inst.getAllLoadedClasses(); for (Class aClass : allLoadedClasses) { if (inst.isModifiableClass(aClass) && !aClass.getName().startsWith("java.lang.invoke.LambdaForm")){ inst.retransformClasses(new Class[]{aClass}); } } System.out.println("++++++++++++++++++hook finished++++++++++++++++++\n"); } }
|
Main部分如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| package org.example; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; public class Main { public static void main(String[] args) throws InterruptedException, IOException { System.out.println("main start!"); ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command("cmd", "/c", "calc"); Process process = processBuilder.start(); InputStream inputStream = process.getInputStream(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "gbk")); System.out.println(bufferedReader.readLine()); } }
|
运行结果如下,成功实现简易版的RASP
如果这里想用之前ZipFileSystem
那里提到的类加载式拦截也不是不可以,但是记得加上这么一段代码,把我们的代理类加到BootstrapClassloader
里面去,这里就不实操了
1
| // localJarPath为代理jar包的绝对路径 inst.appendToBootstrapClassLoaderSearch(new JarFile(localJarPath))
|