JavaAgent型內存馬基礎


Java Instrumentation

​ java Instrumentation指的是可以用獨立於應用程序之外的代理(agent)程序來監測和協助運行在JVM上的應用程序。這種監測和協助包括但不限於獲取JVM運行時狀態,替換和修改類定義等。簡單一句話概括下:Java Instrumentation可以在JVM啟動后,動態修改已加載或者未加載的類,包括類的屬性、方法。

java agent技術原理及簡單實現 - kokov - 博客園 (cnblogs.com)

什么是java agent?

IDEA + maven 零基礎構建 java agent 項目 - 一灰灰Blog - 博客園 (cnblogs.com)

java agent本質上可以理解為一個插件,該插件就是一個精心提供的jar包,這個jar包通過JVMTI(JVM Tool Interface)完成加載,最終借助JPLISAgent(Java Programming Language Instrumentation Services Agent)完成對目標代碼的修改。

java agent技術的主要功能如下:

  • 可以在加載java文件之前做攔截把字節碼做修改
  • 可以在運行期將已經加載的類的字節碼做變更
  • 還有其他的一些小眾的功能
    • 獲取所有已經被加載過的類
    • 獲取所有已經被初始化過了的類
    • 獲取某個對象的大小
    • 將某個jar加入到bootstrapclasspath里作為高優先級被bootstrapClassloader加載
    • 將某個jar加入到classpath里供AppClassloard去加載
    • 設置某些native方法的前綴,主要在查找native方法的時候做規則匹配

Instrument

(32條消息) ClassPool CtClass淺析_羅小輝的專欄-CSDN博客

​ instrument是JVM提供的一個可以修改已加載類的類庫,專門為Java語言編寫的插樁服務提供支持。它需要依賴JVMTI的Attach API機制實現。在JDK 1.6以前,instrument只能在JVM剛啟動開始加載類時生效,而在JDK 1.6之后,instrument支持了在運行時對類定義的修改。要使用instrument的類修改功能,我們需要實現它提供的ClassFileTransformer接口,定義一個類文件轉換器。接口中的transform()方法會在類文件被加載時調用,而在transform方法里,我們可以利用ASM或Javassist對傳入的字節碼進行改寫或替換,生成新的字節碼數組后返回。

image

​ 總之,transform返回值為需要替換的class的字節碼。有兩種方法獲取字節碼,一種使用文件讀取的方式,直接讀取相應class文件的字節碼,還有一種使用Javaassist包,結合反射機制進行字節碼的替換。

我們來看一下第二種的示例代碼

SimpleAgent.java 作為Javagent去注入目標程序

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.util.LinkedList;
import java.util.List;

public class SimpleAgent {

    /**
     * jvm 參數形式啟動,運行此方法
     *
     * @param agentArgs
     * @param inst
     */
    private static String className = "com.company.BaseMain";
    private static String methodName = "print";
    public static void premain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("premain");
        //instrumentation.addTransformer(new TestTransformer(className, methodName));
    }

    /**
     * 動態 attach 方式啟動,運行此方法
     *
     * @param agentArgs
     * @param instrumentation
     */
    public static void agentmain(String agentArgs, Instrumentation instrumentation) {
        System.out.println("agentmain");
        instrumentation.addTransformer(new TestTransformer(className, methodName),true);
        try {
            List<Class> needRetransFormClasses = new LinkedList<>();
            Class[] loadedClass = instrumentation.getAllLoadedClasses();//獲取所有加載的類
            for (Class c : loadedClass) {
                //System.out.println(loadedClass[i].getName());
                if (c.getName().equals(className)) {
                    System.out.println("---find!!!---");
                    Method[] methods = c.getDeclaredMethods();
                    for(Method method : methods)
                    {System.out.println(method.getName());}
                    instrumentation.retransformClasses(c);
                }
            }


        } catch (Exception e) {

        }

    }
}

TestTransformer.java 替換目標類的函數

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.Modifier;
public class TestTransformer implements ClassFileTransformer {
    //目標類名稱,  .分隔
    private String targetClassName;
    //目標類名稱,  /分隔
    private String targetVMClassName;
    private String targetMethodName;


    public TestTransformer(String className,String methodName){
        this.targetVMClassName = new String(className).replaceAll("\\.","\\/");
        this.targetMethodName = methodName;
        this.targetClassName=className;
    }
    //類加載時會執行該函數,其中參數 classfileBuffer為類原始字節碼,返回值為目標字節碼,className為/分隔
    @Override
    public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
        //判斷類名是否為目標類名
        if(!className.equals(targetVMClassName)){
            System.out.println("not do transform");
            return classfileBuffer;
        }
        try {
            System.out.println("do transform");
            ClassPool classPool = ClassPool.getDefault();
            CtClass cls = classPool.get(this.targetClassName);
            System.out.println(cls.getName());
            CtMethod ctMethod = cls.getDeclaredMethod(this.targetMethodName);
            System.out.println(ctMethod.getName());
            ctMethod.insertBefore("{ System.out.println(\"start\"); }");
            ctMethod.insertAfter("{ System.out.println(\"end\"); }");
            return cls.toBytecode();
        } catch (Exception e) {

        }
        return classfileBuffer;
    }


}

參考鏈接IDEA + maven 零基礎構建 java agent 項目 - 一灰灰Blog - 博客園 (cnblogs.com),將他們打包。

編寫測試程序

BaseMain.java

package com.company;

public class BaseMain {

    public int print(int i) {
        System.out.println("i: " + i);
        return i + 2;
    }

    public void run() {
        int i = 1;
        while (true) {
            i = print(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        BaseMain main = new BaseMain();
        main.run();
        Thread.sleep(1000 * 60 * 60);
    }
}

編寫注入程序 attachwithjps.java

import com.sun.tools.attach.AgentInitializationException;
import com.sun.tools.attach.AgentLoadException;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;

import java.io.IOException;

public class attachwithjps {
    public static void main(String[] args)
            throws IOException, AgentLoadException, AgentInitializationException, AttachNotSupportedException {
        // attach方法參數為目標應用程序的進程號,命令行使用jps -l可以查看相關jvm的進程號
        VirtualMachine vm = VirtualMachine.attach(目標應用程序的進程號);
        // 請用你自己的agent絕對地址,替換這個
       vm.loadAgent("E:/內存馬/java-agent/target/java-agent-1.0-SNAPSHOT-jar-with-dependencies.jar");
       vm.detach();
    }
}

注入步驟:

  • ​ 運行被測試程序
  • ​ cmd 輸入jps -l 查找目標進程號
  • ​ 運行attach程序

運行結果

image

web應用注入--tomcat

要在tomcat中選擇類進行替換實現webshell,需要降低對url的依賴,在tomcat處理請求流程中選擇最通用的類。

如internalDoFilter,調用了dofilter,在此之前可以插入代碼對request和response作出操作。

具體代碼參考rebeyond師傅的
利用“進程注入”實現無文件復活 WebShell - FreeBuf網絡安全行業門戶

但是,一旦重啟tomcat,內存馬就會消失,失去目標服務器的權限。要實現服務器重啟后,仍能夠維持權限,必須要在服務器關閉前將相關代碼保存下來,在重啟時自動加載。這里rebeyond師傅使用了ShutdownHook技術.

ShutdownHook是JDK提供的一個用來在JVM關掉時清理現場的機制,這個鈎子可以在如下場景中被JVM調用:

1.程序正常退出

2.使用System.exit()退出

3.用戶使用Ctrl+C觸發的中斷導致的退出

4.用戶注銷或者系統關機

5.OutofMemory導致的退出

6.Kill pid命令導致的退出所以ShutdownHook可以很好的保證在tomcat關閉時,我們有機會埋下復活的種子

相關代碼

  public static void persist() {
      try {
          Thread t = new Thread() {
              public void run() {
                  try {
                      writeFiles("inject.jar",Agent.injectFileBytes);
                      writeFiles("agent.jar",Agent.agentFileBytes);
                      startInject();
                  } catch (Exception e) {
                  }
              }
          };
          t.setName("shutdown Thread");
          Runtime.getRuntime().addShutdownHook(t);
      } catch (Throwable t) {
      }

JVM關閉前,會先調用writeFiles把inject.jar和agent.jar寫到磁盤上,然后調用startInject,startInject通過Runtime.exec啟動java -jar inject.jar。

應用:在有能夠進行命令執行的情況下,上傳agent.jar與需要注入的jar。而后運行agent.jar對其進行注入即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM