Java代理agent实践

前言

        java代理提供了一种机制使得开发者能够在一定程度上操纵虚拟机实现一些特殊的功能,从而更大程度的对虚拟机进行管理和监控。结合实际应用情况,可以发现一些应用程序实际上就是借助了这个机制实现诸如软件破解、防破解、jvm资源监控、热更新、调试等。比如我们常用的开发工具idea就可以通过在jvm虚拟机参数中加入了 -javaagent 选项来实现破解,因为idea启动的时候装载了一个jvm进程,这也是破解的前提。

类似的在idea中运行java程序的时候从控制台的命令行参数中也可以找到javaagent的影子

一、javaagent、agentlib、agentpath

打开cmd.exe查看java命令帮助时,列出了这三种代理的使用方法

1)对于正在运行的java进程和未启动的java进程时使用方法有所不同

     对于未启动的进程只需要在修改命令行参数 添加-agentlib,-agentpath和-javaagent等选项即可

     对于正在运行的进程则需要编写一个独立的java进程,将jar包或者dll动态链接库注入到正在运行的java进程之中。这需要借助tools.jar包,这个包位于jdk目录\lib下面。这个包功能非常强大,jdk常用的工具在这个包中都有对应的实现,比如javac,jinfo,jar等。

上述的例子程序中,首先列出所有的jvm进程,一个WindowsVirtualMachine类对象代表一个

正在运行的java进程,通过loadAgent方法将jar包文件路径注入到对应的jvm进程。我们知道jdk工具jps也可以列出所有的java进程,原理大致若此。

loadAgent传的jar包需要满足一定的规范才可以注入成功,jar包中需要实现一个类,这个类包含两个方法premain和agentmain,这两个方法参数时一致的,在调用loadAgent或者命令行参数指定-javaagent的时候jvm会将控制权转移到这两个方法,在这里,

instrumentation参数是最核心的对象,我们的agent插件主要操作都围绕着这个对象进行。

使用redefineClasses我们可以实现对一个在jvm中已加载的类进行重定义,重定义的结果是class类可以在运行时被修改,也就是我们不用重启java应用就可以改变类的执行逻辑。

不过这个重定义存在一定的限制,一般使用的hotspot虚拟机只能实现对类方法体的修改,一些非主流的虚拟机实现能够实现添加/修改/删除字段和方法。

 

这个类的实现位于jdk目录\bin\instrument.dll中

可以发现 dll中的导出函数和instrument接口类中的方法名基本上都有对应,InstrumentImpl实现类都是绑定的native方法,查看InstrumentImpl实现类也验证了这一点,loadClassAndCallFromPremain和 loadClassAndCallAgentmain则分别调用了premain和agentmain入口方法

打印调用堆栈也确实是这样

一般我们在类中声明的native方法可以通过System.loadLibrary来实现绑定。

 

2) 使用-javaagent的话我们只需要编写java代码就可以实现,但是使用-agentlib和-agentpath的话我们有一定的c/c++的编程基础。显而易见的是使用java编写的agent jar包在对jvm的控制粒度上

是弱于使用c/c++实现的agent库的,使用dll编写的代理库更加强大。当然,它需要满足一定的规范,

需要实现两个函数Agent_OnLoad和Agent_OnUnload

我们发现上面的instrument.dll实际上也实现了这两个函数,所以javagent实际上也是在agentlib之上加了一层做了控制权的转移。

使用agentlib和agentpath,我们直接操作jniEnv对象进行一些更细粒度的操作。比如使用DefineClass直接根据字节数组定义一个内存类,而且这个内存类的ClassLoader可以是null。

使用命令行参数调用 示例如下

发现Agent确实被调用了

猜你喜欢

转载自blog.csdn.net/weixin_38526093/article/details/128944040