javaassit如何实现代对目标类的代理

有没有想过,xmind是如何被破解的?那么今天我们就来看看javaassit这项技术,其实在你接触的很多其他工具中这个工具早就被广泛使用了
javaassit我们知道,java是一门面向对象的编程语言,更是一门面向切面的编程语言,正是这个特性,让java更加地灵活。
可能你写过基于spring aop的代码,其原理都是基于jdk动态代理或者cglib来实现,其局限性在于我们只能以方法作为连接点,来实现基于方法执行过程的代理。
你可还知道更厉害的代理工具:aspectj、javaassit,这些都是基于字节码,属于更底层,但是功能更强大的代理。
知识点asm通过指令修改class字节码,主要基于classreader结合jvm指令集直接操作字节码,cglib即是通过该技术实现。
javaassit基于org.javassist:javassist类库提供的ctpool工具类对字节码进行修改
instrumentationjvm提供的一个可以修改已加载类的类库,通过编写java代码即可完成对字节码的修改
javaagentjvm加载类之前与jvm运行时,基于javaassit、instrumentation实现字节码修改并加载到jvm
应用场景ide的调试功能,例如 eclipse、intellij idea热部署功能,例如 jrebel、xrebel、spring-loaded线上诊断工具,例如 btrace、greys,还有阿里的 arthas性能分析工具,例如 visual vm、jconsole、tprofiler等全链路性能检测工具,例如 skywalking、pinpoint等示例下面我们基于javaagent以及运行时attach的模式看下javaassit如何实现目标类的代理的:
基于javaagent
编写代理类方法签名固定,方法名为 premain ,参数分别对应args(不是数组)以及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); }}打包代理类这里我们借助maven插件 maven-shade-plugin ,主要是为了打包时修改/meta-inf/manifest.mf文件,需要加上premain-class这项
org.apache.maven.plugins maven-shade-plugin 2.3 package shade com.sucl.blog.agent.javaagent com.sucl.blog.agent.attachagent true true 编写测试类目的很简单,每隔3秒打印当前时间
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); } }}@slf4jclass target { public void print(object obj) { log.info(打印内容:{}, obj); }}配置代理如何让我们编写的代理生效,这里提供两种方法:
当你使用idea启动时,可以在config configurations中通过配置vm option,添加如下内容:-javaagent:/your_jar_path/agent.jar=param=value
当你使用java命令启动时:java -javaagent:/path/agent.jar=param=value -jar xxx.jar
测试执行测试类main方法,你可以看到,在打印时间前后,分别会打印“开始执行方法:print”,“结束执行方法:print”,这也是我们代理类实现的功能。
>> > 开始执行方法:print14:46:09.457 [main] info com.sucl.blog.javaassit.target - 打印内容:fri mar 10 14:46:09 cst 2023 >> > 结束执行方法:print基于attach
编写代理类方法签名固定,方法名为 attachmain ,参数分别对应args(不是数组)以及instrumentation; 和上面的相比唯一的不同是方法名称。
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)); }}打包代理类同样借助插件 maven-shade-plugin ,主要是为了打包时修改/meta-inf/manifest.mf文件,需要加上agent-class这项
com.sucl.blog.agent.attachagent注意,这里我们使用了classpool、ctclass、ctmethod相关的类,记得在pom.xml中引入对应的依赖
org.javassist javassist编写测试类测试类完全一样,由于启动代理织入的方式不一样,因此分为两个类
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); } }}执行代理如何将编写的代码(attachagent)织入到目标类完成对目标类(target)方法的代理?
这里我们需要用到jdk中的tool.jar,你可以在测试模块中添加下面的依赖:
com.sun tools 1.8 system ${java.home}/../lib/tools.jar如何在运行时进行代理织入:
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); // 通过jps命令找到attachagentmain执行的pid virtualmachine virtualmachine = virtualmachine.attach(pid); virtualmachine.loadagent(jar_path.substring(1)); virtualmachine.detach(); }}测试a. 先执行测试代码(attachagentmain.java),此时每间隔3秒会打印当前时间。b. 执行代理织入方法(attachagenttests#attachagent)c. 观察测试代码输出结果,你会会发现此时每次打印时间前后都会有“开始执行方法:print”,“结束执行方法: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 { // 无法加载 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(+++++ 代理类名:%s, classname)); try { ctclass ctclass = classpool.get(classname.replace(/,.)); ctmethod[] ctmethods = ctclass.getdeclaredmethods(); for (ctmethod ctmethod : ctmethods) { // 所有类方法 ctmethod.insertbefore(string.format({system.out.println( >> > 开始执行方法:%s);},ctmethod.getname())); ctmethod.insertafter(string.format({system.out.println( >> > 结束执行方法:%s);},ctmethod.getname())); } return ctclass.tobytecode(); } catch (notfoundexception | cannotcompileexception | ioexception e) { system.out.println(string.format(+++++ 代理出错:%s,e.getmessage())); e.printstacktrace(); } return classfilebuffer; } }}通过上面的例子可以看到,两种方式的比对如下:
对比javaagentattachagent
/meta-inf/manifest.mf premain-class agent-class
代理类方法名称 premain attachmain
代理入口 vm配置:-javaagent jvm attach进程id
代理时机 jvm加载字节码时 程序运行时
作用 java桌面程序 web应用
原理代理可以发送在编译时,类加载时或者是运行时。
这里你要清楚, java程序的入口是main方法 ,不管是普通程序(比如桌面应用、可执行jar)或是web应用(在web容器中运行的基于servlet的应用)
以javaagent为例,是在执行main方法前对已经加载到jvm的类进行修改,从而实现对目标类的代理,这里的修改是在字节码层面的,当然我们可以基于asm工具库来实现,但是门槛太高。
基于instrumentation可以与编写java代码一样,实现修改字节码来
classpool:保存ctclass的池子,通过classpool.get(类全路径名)来获取ctclass ctclass:编译时类信息,它是一个class文件在代码中的抽象表现形式 ctmethod:对应类中的方法 ctfield:对应类中的属性、变量
xmind还记得xmind8的破解之法吗?
是不是需要在xmind.ini文件中插入这样一段:-javaagent:.../xmindcrack.jar 要是你打开这个jar,你会看到这样的内容:
首先你需要知道其原理,是通过/plugins/net.xmind.verify.jar中提供的方法licenseverifier#dochecklicensekeyblacklisted来进行身份校验
我们是不是只用修改license的校验方法 dochecklicensekeyblacklisted ,忽略其校验过程并直接返回true就完事了?当然截图中就是这样做的,如果你想看懂那几行代码,可能你先要去学习asm相关的知识。
insnlist insnlist = methodnode.instructions;insnlist.clear();insnlist.add((abstractinsnnode)new insnnode(4));insnlist.add((abstractinsnnode)new insnnode(172));methodnode.exceptions.clear();methodnode.visitend();以上代码其实就是讲方法体清除,并写入“return true”
结束语通过示例了解javaassit如何实现代对目标类的代理。是不是觉得java应用程序都能被修改,那不是太不安全了?所以,你觉得呢...

电池级碳酸锂市场紧缺的根本原因在哪
三金锂电“锚定”高镍三元前驱体
家用臭氧发生器的危害
机顶盒成为下一步国网整合的障碍,捆绑的广播电视媒体处于不利地位
双十一买盒子必看:史上最良心的电视盒子排行榜
javaassit如何实现代对目标类的代理
人脸识别满天飞,我们隐私是否还安全?
VB6单片机如何和单片机建立联系?
超详细的化工装置流程图!
iPhone辉煌五年背后:光环渐褪遭全面围剿
液晶电视丨USB连接保护线路
SMT贴片加工中的相关检测技术
崇越节能新推10瓦/1,000流明的LED灯泡
高频DC/DC稳压器ADP2121的性能特点及应用范围
浅谈汽车线束与防水接插件的结构设计
从模拟芯片到信号链芯片,芯海科技在探索中不断发展
基于ARM的嵌入式操作系统该如何设计
全自动影像仪筛网怎么检测的?
电动汽车充电桩监控平台系统的设计与应用
战报来袭!双11南卡耳机支付金额突破1000万,比去年增长150%!