作者:Longofo@知道创宇404实验室
时间:2019年12月10日
原文链接:https://paper.seebug.org/1099/
之前在一个应用中搜索到一个类,但是在反序列化测试的时出错,错误不是class notfound
,是其他0xxx
这样的错误,通过搜索这个错误大概是类没有被加载。最近刚好看到了JavaAgent,初步学习了下,能进行拦截,主要通过Instrument Agent来进行字节码增强,可以进行字节码插桩,bTrace,Arthas 等操作,结合ASM,javassist,cglib框架能实现更强大的功能。Java RASP也是基于JavaAgent实现的。趁热记录下JavaAgent基础概念,以及简单使用JavaAgent实现一个获取目标进程已加载的类的测试。
Java平台调试器架构(Java Platform Debugger Architecture,JPDA)是一组用于调试Java代码的API(摘自维基百科):
JVMTI 提供了一套"代理"程序机制,可以支持第三方工具程序以代理的方式连接和访问 JVM,并利用 JVMTI 提供的丰富的编程接口,完成很多跟 JVM 相关的功能。JVMTI是基于事件驱动的,JVM每执行到一定的逻辑就会调用一些事件的回调接口(如果有的话),这些接口可以供开发者去扩展自己的逻辑。
JVMTIAgent是一个利用JVMTI暴露出来的接口提供了代理启动时加载(agent on load)、代理通过attach形式加载(agent on attach)和代理卸载(agent on unload)功能的动态库。Instrument Agent可以理解为一类JVMTIAgent动态库,别名是JPLISAgent(Java Programming Language Instrumentation Services Agent),是专门为java语言编写的插桩服务提供支持的代理。
以下接口是Java SE 8 API文档中[1]提供的(不同版本可能接口有变化):
void addTransformer(ClassFileTransformer transformer, boolean canRetransform)//注册ClassFileTransformer实例,注册多个会按照注册顺序进行调用。所有的类被加载完毕之后会调用ClassFileTransformer实例,相当于它们通过了redefineClasses方法进行重定义。布尔值参数canRetransform决定这里被重定义的类是否能够通过retransformClasses方法进行回滚。
void addTransformer(ClassFileTransformer transformer)//相当于addTransformer(transformer, false),也就是通过ClassFileTransformer实例重定义的类不能进行回滚。
boolean removeTransformer(ClassFileTransformer transformer)//移除(反注册)ClassFileTransformer实例。
void retransformClasses(Class<?>... classes)//已加载类进行重新转换的方法,重新转换的类会被回调到ClassFileTransformer的列表中进行处理。
void appendToBootstrapClassLoaderSearch(JarFile jarfile)//将某个jar加入到Bootstrap Classpath里优先其他jar被加载。
void appendToSystemClassLoaderSearch(JarFile jarfile)//将某个jar加入到Classpath里供AppClassloard去加载。
Class[] getAllLoadedClasses()//获取所有已经被加载的类。
Class[] getInitiatedClasses(ClassLoader loader)//获取所有已经被初始化过了的类。
long getObjectSize(Object objectToSize)//获取某个对象的(字节)大小,注意嵌套对象或者对象中的属性引用需要另外单独计算。
boolean isModifiableClass(Class<?> theClass)//判断对应类是否被修改过。
boolean isNativeMethodPrefixSupported()//是否支持设置native方法的前缀。
boolean isRedefineClassesSupported()//返回当前JVM配置是否支持重定义类(修改类的字节码)的特性。
boolean isRetransformClassesSupported()//返回当前JVM配置是否支持类重新转换的特性。
void redefineClasses(ClassDefinition... definitions)//重定义类,也就是对已经加载的类进行重定义,ClassDefinition类型的入参包括了对应的类型Class<?>对象和字节码文件对应的字节数组。
void setNativeMethodPrefix(ClassFileTransformer transformer, String prefix)//设置某些native方法的前缀,主要在找native方法的时候做规则匹配。
redefineClasses与redefineClasses:
重新定义功能在Java SE 5中进行了介绍,重新转换功能在Java SE 6中进行了介绍,一种猜测是将重新转换作为更通用的功能引入,但是必须保留重新定义以实现向后兼容,并且重新转换操作也更加方便。
在官方API文档[1]中提到,有两种获取Instrumentation接口实例的方法 :
premain对应的就是VM启动时的Instrument Agent加载,即agent on load
,agentmain对应的是VM运行时的Instrument Agent加载,即agent on attach
。两种加载形式所加载的Instrument Agent
都关注同一个JVMTI
事件 – ClassFileLoadHook
事件,这个事件是在读取字节码文件之后回调时用,也就是说premain和agentmain方式的回调时机都是类文件字节码读取之后(或者说是类加载之后),之后对字节码进行重定义或重转换,不过修改的字节码也需要满足一些要求,在最后的局限性有说明。