换了一份工作,抽空写一篇文章。
前面说自己看了ja-netfilter,这个作者肯定是个厉害的人,但是在项目工程化方面,做出来的东西未必好用是真的,不是diss别人,而是他的插件接口还是指令集的。说实话,写简单的指令还是很容易的,但是毕竟是jvm指令集,复杂一点的逻辑没办法用jvm指令写,至少容易出错。
我抽空完成了一下自己的东西,有时候辅助解密的时候,主要完成了以下几点
1 可以收集日志/参数,辅助解密
2 对class/method的修改的匹配放到db中,可远程rpc加载配置
配置参数长样,
收集的当前对象的参数/结果/当前对象 也远程rpc发到服务器中
3 配置可从远程rpc加载,也可从本地加载
其实本地加载就是为了让解密的代码生效
4 也同样实现对native的方法进行拦截,这个拦截毫无疑问是用的suffix+rename来实现的
5 学bytebuddy实现的是一个对方法流程简单的控制
实现的插件接口长下图这样,一个是入口处给你控制,另外一个返回前给你控制
好歹这个插件已经是进入到java来写了,脱离了asm字节码字令集了。
看一下拦截这个流程的成果
来看看dump出来的
待测试的class对应的java入下
public class Test2 { //private String host = "www.reqres.com"; private String host = "www.baidu.com"; public int openConnection(String a, int b) { final String x = "======3 程序中真正传入的参数值:a" + a + ",b=" + b + ",准备返回传入的参数b:" + b; System.out.println(x);; return b; } }
增强过后的class自动成了,其实可以看出这就是已经控制住了执行流程
收获:
1 对比bytebuddy,越发觉得bytebuddy有些地方的原理我终于领悟了,以前老是觉得奇怪,当然你看skywalking里面用模板方式的时候,经常对某个类写一个固定的字段,然后动态来改这个字段,其实本质就是一回事,那就是字节码在加载的时候改写的时候本质上只能写常量/或者方法参数进去,也就是
虚拟机的LDC指令,即asm的visitLdcInsn方法
2 rename也不一直都好使,原因很简单,在classloader进入你的agent的时候,有些类已经加载了,这些类是不可以做rename的(JVM的限制),所以只能在当前的方法中进行增强
3 栈桢写的时候,务必要注意栈的平衡,而不平衡的关键点都是在在一些流程跳转 if/while/for之类的,本质上对应jvm指令就是一些跳转,还有出异常的时候的指令,会导致栈桢不平衡。解密的时候一般不会在意这一个方法的性能,要想简单点,那就是尽量在每个跳转,对应asm中也就是label跳转的时候,变量都一样,这样方法中的桢栈本质上就没有发生变量插槽的变化,当然这种可能存在变量插槽的浪费。
4 特别坑的一个小点,就是基本类型和包装类型在调用的时候作为方法或者返回值的时候,要boxing/unboxing, 这个务必转化,不然的话肯定类型是不匹配的。更别提就算类型一致,但是如果你是Object来承载的,都必须来个checkcast指令。
5 因为写代码也不少有反射嘛,这个用了hutool这个框架,这个对我来说在jdk8中行,在jdk8+中不行,看了一下,因为9的模块化导致的,按理说不应该啊,翻了下源码,他们用了不在base中的时间相关的模块,我就自己下载了一个分支去掉了这玩意。当然顺序说一句,如果日志用jdk的日志来做内部小框架的日志的话也不行,因为这个也不在base模块,所以就自己写了个最最简单的日志而已。