目录
探针Java Agent原理概述与示例
基于Java Agent实现动态修改代码
基于Java Agent实现字节码注入
基于Java Agent实现内存Webshell注入
基于Java Agent实现内存Webshell检测
程序目标
注入自定义Java代码,实现JVM内存和GC监控(搜了下,其实很多APM都会使用字节码注入的方式)
项目代码
使用IDEA新建一个maven项目,项目结构如下:
main程序和agent程序,可以分开成两个工程,这里为了简便就放在一个工程下。
main.java
其中main.java是需要被动态修改的源程序,代码如下:
package main;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.util.List;public class Main { public static void main(String[] args) throws InterruptedException { System.out.println("=====main====="); while (true) { Listlist = new ArrayList(); list.add("xxxxx"); list.add("xxxxx"); list.add("xxxxx"); list.add("xxxxx"); list.add("xxxxx"); Thread.sleep(2000); System.out.println("main running: " + new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date())); } }}
pom-main.xml
我们使用maven对main.java进行打包,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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <groupId>org.lynngroupId> <artifactId>JavaAgent_DynamicChangeartifactId> <version>1.0-SNAPSHOTversion> <properties> <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> <maven.compiler.source>1.8maven.compiler.source> <maven.compiler.target>1.8maven.compiler.target> <maven.compiler.compilerVersion>1.8maven.compiler.compilerVersion> properties> <build> <finalName>mainfinalName> <plugins> <plugin> <groupId>org.apache.maven.pluginsgroupId> <artifactId>maven-compiler-pluginartifactId> <version>3.8.1version> <configuration> <source>${maven.compiler.source}source> <target>${maven.compiler.target}target> configuration> plugin> <plugin> <groupId>org.apache.maven.pluginsgroupId> <artifactId>maven-jar-pluginartifactId> <version>3.2.0version> <configuration> <archive> <addMavenDescriptor>falseaddMavenDescriptor> <manifest> <mainClass>main.MainmainClass> manifest> archive> <excludes> <exclude>**/agent/exclude> excludes> configuration> plugin> plugins> build>project>
JVMInfo.java
下面编写获取JVM信息程序,代码如下:
package agent;import java.lang.management.GarbageCollectorMXBean;import java.lang.management.ManagementFactory;import java.lang.management.MemoryMXBean;import java.lang.management.MemoryUsage;import java.util.Arrays;import java.util.List;class JVMInfo { private static final long MB = 1048576L; public static void getMemoryInfo() { MemoryMXBean memory = ManagementFactory.getMemoryMXBean(); MemoryUsage headMemory = memory.getHeapMemoryUsage(); String info = String.format("init: %s\t max: %s\t used: %s\t committed: %s\t use rate: %s", headMemory.getInit() / MB + "MB", headMemory.getMax() / MB + "MB", headMemory.getUsed() / MB + "MB", headMemory.getCommitted() / MB + "MB", headMemory.getUsed() * 100 / headMemory.getCommitted() + "%" ); System.out.println("==========================Head Info========================="); System.out.println(info); System.out.println("============================================================"); System.out.println(); MemoryUsage nonheadMemory = memory.getNonHeapMemoryUsage(); info = String.format("init: %s\t max: %s\t used: %s\t committed: %s\t use rate: %s", nonheadMemory.getInit() / MB + "MB", nonheadMemory.getMax() / MB + "MB", nonheadMemory.getUsed() / MB + "MB", nonheadMemory.getCommitted() / MB + "MB", nonheadMemory.getUsed() * 100 / nonheadMemory.getCommitted() + "%" ); System.out.println("==========================nonHead Info======================"); System.out.println(info); System.out.println("============================================================"); System.out.println(); } public static void getGCInfo() { List<GarbageCollectorMXBean> garbages = ManagementFactory.getGarbageCollectorMXBeans(); for (GarbageCollectorMXBean garbage : garbages) { String info = String.format("name: %s\t count:%s\t took:%s\t pool name:%s", garbage.getName(), garbage.getCollectionCount(), garbage.getCollectionTime(), Arrays.deepToString(garbage.getMemoryPoolNames())); System.out.println("==========================Garbage Info=========================="); System.out.println(info); System.out.println("================================================================"); System.out.println(); } }}
agent.java
下面编写我们的Java Agent程序,我们使用一个ScheduleExecutor来定时获取JVM的内存和GC信息,代码如下:
package agent;import java.lang.instrument.Instrumentation;import java.util.concurrent.Executors;import java.util.concurrent.TimeUnit;public class Agent { public static void premain(String args, Instrumentation instrumentation) { System.out.println("======agent premain====="); // 创建并执行并结束一个runnable在延迟指定initialDelay时间 // 然后,每隔initialDelay+period*n时间执行一次 Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> { JVMInfo.getMemoryInfo(); JVMInfo.getGCInfo(); }, 0, 5, TimeUnit.SECONDS); }}
pom-agent.xml
使用maven对agent.java进行打包,使用maven-shade-plugin来将其打包成一个jar文件,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 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0modelVersion> <groupId>org.lynngroupId> <artifactId>JavaAgent_DynamicChangeartifactId> <version>1.0-SNAPSHOTversion> <properties> <project.build.sourceEncoding>UTF-8project.build.sourceEncoding> <maven.compiler.source>1.8maven.compiler.source> <maven.compiler.target>1.8maven.compiler.target> <maven.compiler.compilerVersion>1.8maven.compiler.compilerVersion> <premain.class>agent.Agentpremain.class> <can.redefine.classes>truecan.redefine.classes> <can.retransform.classes>truecan.retransform.classes> properties> <build> <finalName>agentfinalName> <plugins> <plugin> <groupId>org.apache.maven.pluginsgroupId> <artifactId>maven-shade-pluginartifactId> <version>3.2.4version> <executions> <execution> <phase>packagephase> <goals> <goal>shadegoal> goals> <configuration> <createSourcesJar>falsecreateSourcesJar> <shadeSourcesContent>falseshadeSourcesContent> <shadedArtifactAttached>falseshadedArtifactAttached> <createDependencyReducedPom>falsecreateDependencyReducedPom> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <manifestEntries> <Premain-Class>${premain.class}Premain-Class> <Can-Redefine-Classes>${can.redefine.classes}Can-Redefine-Classes> <Can-Retransform-Classes>${can.retransform.classes}Can-Retransform-Classes> manifestEntries> transformer> transformers> <filters> <filter> <artifact>net.bytebuddy:byte-buddyartifact> <excludes> <exclude>META-INF/versions/9/module-info.classexclude> excludes> filter> <filter> <artifact>*:*artifact> <excludes> <exclude>main/**exclude> <exclude>**/maven/exclude> excludes> filter> filters> configuration> execution> executions> plugin> plugins> build>project>
运行示例
运行我们的示例,可以看到执行了监控的代码,不定时的输出JVM信息。
main running: 2020-07-31 12:53:00main running: 2020-07-31 12:53:02==========================Head Info=========================init: 256MB max: 3614MB used: 10MB committed: 245MB use rate: 4%======================================================================================nonHead Info======================init: 2MB max: 0MB used: 7MB committed: 8MB use rate: 92%======================================================================================Garbage Info==========================name: PS Scavenge count:0 took:0 pool name:[PS Eden Space, PS Survivor Space]==========================================================================================Garbage Info==========================name: PS MarkSweep count:0 took:0 pool name:[PS Eden Space, PS Survivor Space, PS Old Gen]================================================================main running: 2020-07-31 12:53:04
下一节,我们开始探讨下针对SpringBoot的内存马注入。