RM新时代网站-首页

0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線(xiàn)課程
  • 觀看技術(shù)視頻
  • 寫(xiě)文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

javaassit如何實(shí)現(xiàn)代對(duì)目標(biāo)類(lèi)的代理

科技綠洲 ? 來(lái)源:了不起 ? 作者:了不起 ? 2023-09-25 11:18 ? 次閱讀

有沒(méi)有想過(guò),XMind是如何被破解的?那么今天我們就來(lái)看看javaassit這項(xiàng)技術(shù),其實(shí)在你接觸的很多其他工具中這個(gè)工具早就被廣泛使用了

javaassit

我們知道,java是一門(mén)面向?qū)ο蟮?a target="_blank">編程語(yǔ)言,更是一門(mén)面向切面的編程語(yǔ)言,正是這個(gè)特性,讓Java更加地靈活。

可能你寫(xiě)過(guò)基于Spring AOP的代碼,其原理都是基于JDK動(dòng)態(tài)代理或者CGLIB來(lái)實(shí)現(xiàn),其局限性在于我們只能以方法作為連接點(diǎn),來(lái)實(shí)現(xiàn)基于方法執(zhí)行過(guò)程的代理。

你可還知道更厲害的代理工具:AspectJ、javaassit,這些都是基于字節(jié)碼,屬于更底層,但是功能更強(qiáng)大的代理。

知識(shí)點(diǎn)

  • ASM

通過(guò)指令修改class字節(jié)碼,主要基于ClassReader結(jié)合JVM指令集直接操作字節(jié)碼,Cglib即是通過(guò)該技術(shù)實(shí)現(xiàn)。

  • JavaAssit

基于org.javassist:javassist類(lèi)庫(kù)提供的CtPool工具類(lèi)對(duì)字節(jié)碼進(jìn)行修改

  • Instrumentation

JVM提供的一個(gè)可以修改已加載類(lèi)的類(lèi)庫(kù),通過(guò)編寫(xiě)java代碼即可完成對(duì)字節(jié)碼的修改

  • JavaAgent

JVM加載類(lèi)之前與JVM運(yùn)行時(shí),基于JavaAssit、Instrumentation實(shí)現(xiàn)字節(jié)碼修改并加載到JVM

應(yīng)用場(chǎng)景

  • IDE的調(diào)試功能,例如 Eclipse、IntelliJ IDEA
  • 熱部署功能,例如 JRebel、XRebel、spring-loaded
  • 線(xiàn)上診斷工具,例如 Btrace、Greys,還有阿里的 Arthas
  • 性能分析工具,例如 Visual VM、JConsole、TProfiler等
  • 全鏈路性能檢測(cè)工具,例如 Skywalking、Pinpoint等

示例

下面我們基于javaagent以及運(yùn)行時(shí)Attach的模式看下javaassit如何實(shí)現(xiàn)目標(biāo)類(lèi)的代理的:

基于javaagent

  1. 編寫(xiě)代理類(lèi)

方法簽名固定,方法名為 premain ,參數(shù)分別對(duì)應(yīng)args(不是數(shù)組)以及Instrumentation

public class JavaAgent {

    private static final String TARGET_CLASS_NAME = "com.sucl.blog.javaassit.Target";

    public static void premain(String args, Instrumentation instrumentation){
        AgentHelper.create(TARGET_CLASS_NAME).proxy(args, instrumentation);
    }

}
  1. 打包代理類(lèi)

這里我們借助maven插件 maven-shade-plugin ,主要是為了打包時(shí)修改/META-INF/MANIFEST.MF文件,需要加上Premain-Class這項(xiàng)

< plugin >
    < groupId >org.apache.maven.plugins< /groupId >
    < artifactId >maven-shade-plugin< /artifactId >
    < version >2.3< /version >
    < executions >
        < execution >
            < phase >package< /phase >
            < goals >
                < goal >shade< /goal >
            < /goals >
            < configuration >
                < transformers >
                    < transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer" >
                        < manifestEntries >
                            < Premain-Class >com.sucl.blog.agent.JavaAgent< /Premain-Class >
                            < Agent-Class >com.sucl.blog.agent.AttachAgent< /Agent-Class >
                            < Can-Redefine-Classes >true< /Can-Redefine-Classes >
                            < Can-Retransform-Classes >true< /Can-Retransform-Classes >
                        < /manifestEntries >
                    < /transformer >
                < /transformers >
            < /configuration >
        < /execution >
    < /executions >
< /plugin >
  1. 編寫(xiě)測(cè)試類(lèi)

目的很簡(jiǎn)單,每隔3秒打印當(dāng)前時(shí)間

public class JavaAgentMain {
    public static void main(String[] args) throws InterruptedException {
        Target target = new Target();
        while (true) {
            target.print(new Date());
            TimeUnit.SECONDS.sleep(3);
        }
    }
}

@Slf4j
class Target {
    public void print(Object obj) {
        log.info("打印內(nèi)容:{}", obj);
    }
}
  1. 配置代理

如何讓我們編寫(xiě)的代理生效,這里提供兩種方法:

  • 當(dāng)你使用IDEA啟動(dòng)時(shí),可以在Config Configurations中通過(guò)配置VM OPTION,添加如下內(nèi)容:

-javaagent:/your_jar_path/agent.jar=param=value

  • 當(dāng)你使用java命令啟動(dòng)時(shí):

java -javaagent:/path/agent.jar=param=value -jar xxx.jar

  1. 測(cè)試

執(zhí)行測(cè)試類(lèi)main方法,你可以看到,在打印時(shí)間前后,分別會(huì)打印“開(kāi)始執(zhí)行方法:print”,“結(jié)束執(zhí)行方法:print”,這也是我們代理類(lèi)實(shí)現(xiàn)的功能。

>> > 開(kāi)始執(zhí)行方法:print
14:46:09.457 [main] INFO com.sucl.blog.javaassit.Target - 打印內(nèi)容:Fri Mar 10 14:46:09 CST 2023
 >> > 結(jié)束執(zhí)行方法:print

基于Attach

  1. 編寫(xiě)代理類(lèi)

方法簽名固定,方法名為 attachmain ,參數(shù)分別對(duì)應(yīng)args(不是數(shù)組)以及Instrumentation; 和上面的相比唯一的不同是方法名稱(chēng)。

public class AttachAgent {
    
    private static final String TARGET_CLASS_NAME = "com.sucl.blog.javaassit.Target";
    
    public static void agentmain(String args, Instrumentation instrumentation){
        System.out.println(String.format(" >> > agentmain starting, args: %s",args));
        AgentHelper.create(TARGET_CLASS_NAME).proxy(args, instrumentation);
        System.out.println(String.format(" >> > agentmain finished"));
    }

}
  1. 打包代理類(lèi)

同樣借助插件 maven-shade-plugin ,主要是為了打包時(shí)修改/META-INF/MANIFEST.MF文件,需要加上Agent-Class這項(xiàng)

< !-- 省略 ...-- >
< Agent-Class >com.sucl.blog.agent.AttachAgent< /Agent-Class >
< !-- 省略 ...-- >

注意,這里我們使用了ClassPool、CtClass、CtMethod相關(guān)的類(lèi),記得在pom.xml中引入對(duì)應(yīng)的依賴(lài)

< dependency >
    < groupId >org.javassist< /groupId >
    < artifactId >javassist< /artifactId >
< /dependency >
  1. 編寫(xiě)測(cè)試類(lèi)

測(cè)試類(lèi)完全一樣,由于啟動(dòng)代理織入的方式不一樣,因此分為兩個(gè)類(lèi)

public class AttachAgentMain {

    public static void main(String[] args) throws InterruptedException {
        Target target = new Target();
        while (true) {
            target.print(new Date());
            TimeUnit.SECONDS.sleep(3);
        }
    }

}
  1. 執(zhí)行代理

如何將編寫(xiě)的代碼(AttachAgent)織入到目標(biāo)類(lèi)完成對(duì)目標(biāo)類(lèi)(Target)方法的代理?

這里我們需要用到j(luò)dk中的tool.jar,你可以在測(cè)試模塊中添加下面的依賴(lài):

< dependency >
    < groupId >com.sun< /groupId >
    < artifactId >tools< /artifactId >
    < version >1.8< /version >
    < scope >system< /scope >
    < systemPath >${java.home}/../lib/tools.jar< /systemPath >
< /dependency >

如何在運(yùn)行時(shí)進(jìn)行代理織入:

public class AttachAgentTests {

    private static String JAR_PATH = AttachAgentTests.class.getClassLoader().getResource("").getPath().replace("test-classes/","")+"agent.jar";
    
    @Test
    public void attachAgent() throws Exception {
        String pid = findPid(KEY); // 通過(guò)jps命令找到AttachAgentMain執(zhí)行的pid
        VirtualMachine virtualMachine = VirtualMachine.attach(pid);
        virtualMachine.loadAgent(JAR_PATH.substring(1));
        virtualMachine.detach();
    }
}
  1. 測(cè)試
  • a. 先執(zhí)行測(cè)試代碼(AttachAgentMain.java),此時(shí)每間隔3秒會(huì)打印當(dāng)前時(shí)間。
  • b. 執(zhí)行代理織入方法(AttachAgentTests#attachAgent)
  • c. 觀察測(cè)試代碼輸出結(jié)果,你會(huì)會(huì)發(fā)現(xiàn)此時(shí)每次打印時(shí)間前后都會(huì)有“開(kāi)始執(zhí)行方法:print”,“結(jié)束執(zhí)行方法:print”

AgentHelper

public class AgentHelper {

    private String targetClassName;

    private AgentHelper(String targetClassName) {
        this.targetClassName = targetClassName;
    }

    public static AgentHelper create(String targetClassName){
        AgentHelper agentHelper = new AgentHelper(targetClassName);
        return agentHelper;
    }

    public void proxy(String args, Instrumentation instrumentation){
        Class targetClass = obtainTargetClass(instrumentation);

        try {
            instrumentation.addTransformer(new SimpleTransformer(targetClassName), true);
            instrumentation.retransformClasses(targetClass); //
        } catch (Exception e) {
            System.out.println(String.format(" >> > agentmain failure, error: %s: %s", e.getClass().getName(),e.getLocalizedMessage()));
            e.printStackTrace();
        }
    }

    private Class obtainTargetClass(Instrumentation instrumentation) {
        Class targetClass = null;
        for (Class loadedClass : instrumentation.getAllLoadedClasses()) {
            if(targetClassName.equals(loadedClass.getName())){
                targetClass = loadedClass;
            }
        }

        if(targetClass == null){
            try {
                // 無(wú)法加載
                targetClass = Class.forName(targetClassName);
            } catch (ClassNotFoundException e) {
                System.out.println(String.format(" >> > Class [%s] not found", targetClassName));
            }
        }
        return targetClass;
    }

    public static class SimpleTransformer implements ClassFileTransformer {

        private String targetClassName;

        public SimpleTransformer(String targetClassName) {
            this.targetClassName = targetClassName;
        }

        @Override
        public byte[] transform(ClassLoader loader, String className, Class< ? > classBeingRedefined,
                                ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
            if(!className.equals(targetClassName.replaceAll(".","/"))){
                return null;
            }

            ClassPool classPool = ClassPool.getDefault();
            System.out.println(String.format("+++++ 代理類(lèi)名:%s", className));
            try {
                CtClass ctClass = classPool.get(className.replace("/","."));
                CtMethod[] ctMethods = ctClass.getDeclaredMethods();
                for (CtMethod ctMethod : ctMethods) { // 所有類(lèi)方法
                    ctMethod.insertBefore(String.format("{System.out.println(" >> > 開(kāi)始執(zhí)行方法:%s");}",ctMethod.getName()));
                    ctMethod.insertAfter(String.format("{System.out.println(" >> > 結(jié)束執(zhí)行方法:%s");}",ctMethod.getName()));
                }
                return ctClass.toBytecode();
            } catch (NotFoundException | CannotCompileException | IOException e) {
                System.out.println(String.format("+++++ 代理出錯(cuò):%s",e.getMessage()));
                e.printStackTrace();
            }
            return classfileBuffer;
        }
    }
}

通過(guò)上面的例子可以看到,兩種方式的比對(duì)如下:

對(duì)比JavaAgentAttachAgent
/META-INF/MANIFEST.MFPremain-ClassAgent-Class
代理類(lèi)方法名稱(chēng)premainattachmain
代理入口VM配置:-javaagentJVM attach進(jìn)程ID
代理時(shí)機(jī)JVM加載字節(jié)碼時(shí)程序運(yùn)行時(shí)
作用Java桌面程序Web應(yīng)用

原理

代理可以發(fā)送在編譯時(shí),類(lèi)加載時(shí)或者是運(yùn)行時(shí)。

這里你要清楚, java程序的入口是main方法 ,不管是普通程序(比如桌面應(yīng)用、可執(zhí)行jar)或是Web應(yīng)用(在Web容器中運(yùn)行的基于Servlet的應(yīng)用)

以javaagent為例,是在執(zhí)行main方法前對(duì)已經(jīng)加載到JVM的類(lèi)進(jìn)行修改,從而實(shí)現(xiàn)對(duì)目標(biāo)類(lèi)的代理,這里的修改是在字節(jié)碼層面的,當(dāng)然我們可以基于ASM工具庫(kù)來(lái)實(shí)現(xiàn),但是門(mén)檻太高。

基于Instrumentation可以與編寫(xiě)java代碼一樣,實(shí)現(xiàn)修改字節(jié)碼來(lái)

ClassPool:保存CtClass的池子,通過(guò)classPool.get(類(lèi)全路徑名)來(lái)獲取CtClass CtClass:編譯時(shí)類(lèi)信息,它是一個(gè)class文件在代碼中的抽象表現(xiàn)形式 CtMethod:對(duì)應(yīng)類(lèi)中的方法 CtField:對(duì)應(yīng)類(lèi)中的屬性、變量

XMind

還記得XMind8的破解之法嗎?

是不是需要在XMind.ini文件中插入這樣一段:-javaagent:.../XMindCrack.jar 要是你打開(kāi)這個(gè)jar,你會(huì)看到這樣的內(nèi)容:

圖片

首先你需要知道其原理,是通過(guò)/plugins/net.xmind.verify.jar中提供的方法LicenseVerifier#doCheckLicenseKeyBlacklisted來(lái)進(jìn)行身份校驗(yàn)

我們是不是只用修改License的校驗(yàn)方法 doCheckLicenseKeyBlacklisted ,忽略其校驗(yàn)過(guò)程并直接返回true就完事了?當(dāng)然截圖中就是這樣做的,如果你想看懂那幾行代碼,可能你先要去學(xué)習(xí)ASM相關(guān)的知識(shí)。

InsnList insnList = methodNode.instructions;
insnList.clear();
insnList.add((AbstractInsnNode)new InsnNode(4));
insnList.add((AbstractInsnNode)new InsnNode(172));
methodNode.exceptions.clear();
methodNode.visitEnd();

以上代碼其實(shí)就是講方法體清除,并寫(xiě)入“return true”

結(jié)束語(yǔ)

通過(guò)示例了解javaassit如何實(shí)現(xiàn)代對(duì)目標(biāo)類(lèi)的代理。是不是覺(jué)得java應(yīng)用程序都能被修改,那不是太不安全了?所以,你覺(jué)得呢...

聲明:本文內(nèi)容及配圖由入駐作者撰寫(xiě)或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問(wèn)題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • JAVA
    +關(guān)注

    關(guān)注

    19

    文章

    2966

    瀏覽量

    104701
  • 編程語(yǔ)言
    +關(guān)注

    關(guān)注

    10

    文章

    1942

    瀏覽量

    34707
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4779

    瀏覽量

    68521
  • 代理
    +關(guān)注

    關(guān)注

    1

    文章

    44

    瀏覽量

    11203
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    JDK動(dòng)態(tài)代理的原理

    在Java中,動(dòng)態(tài)代理是一種機(jī)制,允許在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建代理對(duì)象來(lái)代替某個(gè)實(shí)際對(duì)象,從而在其前后執(zhí)行額外的邏輯。 為什么JDK動(dòng)態(tài)代理只能代理接口實(shí)
    的頭像 發(fā)表于 09-30 10:51 ?577次閱讀

    求教labview是否實(shí)現(xiàn)代碼編程? 多謝

    本帖最后由 leasor 于 2013-7-15 14:11 編輯 求教labview是否實(shí)現(xiàn)代碼編程? 多謝, 當(dāng)我看見(jiàn)一些基本的編程結(jié)構(gòu), 也使用圖形方式, 我可恥的吐了, 使用腳本一類(lèi)的編程方式對(duì)于這些大牛來(lái)說(shuō), 實(shí)現(xiàn)
    發(fā)表于 07-15 13:58

    適配器模式和代理模式的區(qū)別

      代理模式  組成:  抽象角色:通過(guò)接口或抽象類(lèi)聲明真實(shí)角色實(shí)現(xiàn)的業(yè)務(wù)方法?! ?b class='flag-5'>代理角色:實(shí)現(xiàn)抽象角色,是真實(shí)角色的
    發(fā)表于 10-22 15:17

    實(shí)現(xiàn)代碼自動(dòng)生成的步驟

    文章目錄一、 目的二、 基本思想三、 代碼實(shí)現(xiàn)四、 其他工作五、 補(bǔ)充一、 目的工作中有時(shí)候感覺(jué)編程也是一種重復(fù)性勞動(dòng),尤其是涉及到讀寫(xiě)數(shù)據(jù)一類(lèi)的內(nèi)容,還有一些需要進(jìn)行配置的場(chǎng)合,有時(shí)候就想,既然是
    發(fā)表于 08-17 09:14

    基于聚類(lèi)融合的多目標(biāo)跟蹤算法

    目標(biāo)跟蹤是多傳感器數(shù)據(jù)融合中的一個(gè)重要問(wèn)題?;谀J阶R(shí)別理論,提出了一種通過(guò)對(duì)傳感器測(cè)量數(shù)據(jù)集類(lèi),以區(qū)分源于不同目標(biāo)的測(cè)量數(shù)據(jù)集合。對(duì)各個(gè)類(lèi)對(duì)應(yīng)的
    發(fā)表于 07-01 08:40 ?18次下載

    java動(dòng)態(tài)代理機(jī)制詳解的類(lèi)和接口描述

    的我們的功能,我們更需要學(xué)習(xí)的是其底層是怎么樣的一個(gè)原理,而AOP的原理就是java的動(dòng)態(tài)代理機(jī)制,所以本篇隨筆就是對(duì)java的動(dòng)態(tài)機(jī)制進(jìn)行一個(gè)回顧。 在java的動(dòng)態(tài)代理機(jī)制中,有兩個(gè)重要的類(lèi)或接口
    發(fā)表于 09-28 13:33 ?0次下載

    基于聚類(lèi)的多目標(biāo)遺傳算法在類(lèi)職責(zé)分配中的應(yīng)用

    在面向?qū)ο筌浖O(shè)計(jì)與實(shí)現(xiàn)過(guò)程中,類(lèi)職責(zé)分配是其中最重要且復(fù)雜的步驟之一,它在很大程度上影響軟件質(zhì)量。為了實(shí)現(xiàn)類(lèi)職責(zé)自動(dòng)分配的目標(biāo),從軟件內(nèi)聚
    發(fā)表于 11-28 17:35 ?0次下載
    基于聚<b class='flag-5'>類(lèi)</b>的多<b class='flag-5'>目標(biāo)</b>遺傳算法在<b class='flag-5'>類(lèi)</b>職責(zé)分配中的應(yīng)用

    空間鄰近的點(diǎn)目標(biāo)類(lèi)實(shí)現(xiàn)方法

    了基于空間鄰近的點(diǎn)目標(biāo)類(lèi)方法,通過(guò)Voronoi建模識(shí)別點(diǎn)目標(biāo)間的空間鄰近關(guān)系,并以Voronoi勢(shì)力范圍來(lái)定義相似度準(zhǔn)則,最終構(gòu)建樹(shù)結(jié)構(gòu)以實(shí)現(xiàn)點(diǎn)
    發(fā)表于 12-19 10:47 ?0次下載
    空間鄰近的點(diǎn)<b class='flag-5'>目標(biāo)</b>聚<b class='flag-5'>類(lèi)</b><b class='flag-5'>實(shí)現(xiàn)</b>方法

    java的動(dòng)態(tài)代理

    代理類(lèi)的對(duì)象與一個(gè)委托類(lèi)的對(duì)象關(guān)聯(lián),代理類(lèi)的對(duì)象本身并不真正實(shí)現(xiàn)服務(wù),而是通過(guò)調(diào)用委托
    發(fā)表于 03-12 14:12 ?0次下載

    基于JDK和CGLB分別實(shí)現(xiàn)的動(dòng)態(tài)代理

    本文檔內(nèi)容介紹了基于JDK和CGLB分別實(shí)現(xiàn)的動(dòng)態(tài)代理及源代碼
    發(fā)表于 03-12 14:56 ?0次下載

    如何在Golang中實(shí)現(xiàn)反向代理

    【導(dǎo)讀】在本文中,我們將了解反向代理,它的應(yīng)用場(chǎng)景以及如何在 Golang 中實(shí)現(xiàn)它。 反向代理是位于 Web 服務(wù)器前面并將客戶(hù)端(例如 Web 瀏覽器)的請(qǐng)求轉(zhuǎn)發(fā)到 Web 服務(wù)器的服務(wù)器。它們
    的頭像 發(fā)表于 08-23 10:22 ?2153次閱讀

    http代理概述及代碼實(shí)現(xiàn)方法

    本文詳細(xì)介紹了Golang 實(shí)現(xiàn) http 代理實(shí)現(xiàn),在實(shí)際業(yè)務(wù)中有需求的同學(xué)可以學(xué)起來(lái)了!
    的頭像 發(fā)表于 05-14 15:02 ?4018次閱讀

    Golang實(shí)現(xiàn)一個(gè)簡(jiǎn)單的http代理

    本文詳細(xì)介紹了Golang 實(shí)現(xiàn) http 代理實(shí)現(xiàn),在實(shí)際業(yè)務(wù)中有需求的同學(xué)可以學(xué)起來(lái)了!
    的頭像 發(fā)表于 04-10 11:29 ?1443次閱讀

    mybatis接口動(dòng)態(tài)代理原理

    ,從而實(shí)現(xiàn)數(shù)據(jù)庫(kù)操作的動(dòng)態(tài)生成和執(zhí)行。接下來(lái),我將詳細(xì)介紹MyBatis接口動(dòng)態(tài)代理的原理。 動(dòng)態(tài)代理概念介紹 在Java語(yǔ)言中,動(dòng)態(tài)代理是一種使用
    的頭像 發(fā)表于 12-03 11:52 ?933次閱讀

    Python庫(kù)解析:通過(guò)庫(kù)實(shí)現(xiàn)代理請(qǐng)求與數(shù)據(jù)抓取

    在Python中,有多個(gè)庫(kù)可以幫助你實(shí)現(xiàn)代理請(qǐng)求和數(shù)據(jù)抓取。這些庫(kù)提供了豐富的功能和靈活的API,使得你可以輕松地發(fā)送HTTP請(qǐng)求、處理響應(yīng)、解析HTML/XML/JSON數(shù)據(jù),以及進(jìn)行復(fù)雜的網(wǎng)絡(luò)操作。
    的頭像 發(fā)表于 10-24 07:54 ?164次閱讀
    RM新时代网站-首页