能否在原始类内部运行时检测字节码篡改并告警?
目前存在JavaAssist、ASM等多款工具可在运行时重新生成字节码,应用性能管理(APM)便是典型场景之一——其提供的Agent会在应用启动阶段重写现有字节码。
例如,原始类A包含方法:void sayHello(String name){},这类Agent可生成如下新方法:
void NewA:sayHello(String name){ Long start = System.currentTimeMillis(); A::sayHello(name); Long elapsed = System.currentTimeMillis() - start; }
JVM加载类后会执行NewA而非原始类A。
现咨询:能否在原始类A内部通过运行时检测识别此类修改,进而告警当前执行的字节码并非交付的原始字节码?
可以实现,以下是几种可行的思路:
字节码哈希校验
在原始类A的代码中,预先计算自身目标方法(比如sayHello)的原始字节码哈希值(如MD5、SHA-256)并硬编码在类中。运行时,通过Class.getDeclaredMethod()获取当前方法的字节码,计算哈希后与预存值对比。若不一致,说明字节码被篡改,触发告警。
注意:要避免校验逻辑本身被篡改,可将哈希值和核心校验逻辑放在单独的强签名模块中,或使用Java的SecurityManager限制对校验代码的修改权限。栈帧检测
在原始方法sayHello内部,通过Thread.currentThread().getStackTrace()获取当前调用栈。若发现调用栈中存在异常类(如APM Agent注入的代理类NewA),或方法调用者不符合预期,就触发告警。
局限性:仅能检测直接代理调用,对更隐蔽的字节码修改(如直接在原始方法内插入代码而非生成代理类)可能无效。方法体结构校验
借助ASM或JavaAssist的API,在运行时解析当前类的方法体结构,对比原始方法的指令序列。比如原始sayHello是空实现,运行时解析后发现方法体内多了计时、调用原始方法的指令,即可判定为被篡改。
注意:需预先保存原始方法的指令特征(如指令数量、关键指令类型),避免因JVM版本差异导致的指令细微变化引发误判。类加载器校验
检查当前类A的类加载器是否为预期加载器(如应用类加载器AppClassLoader)。若为Agent使用的自定义类加载器加载的修改后类,触发告警。但这种方式对同一类加载器内的字节码修改无效。
需要说明的是,这些方案无法做到100%防篡改——若攻击者拥有足够权限,仍可修改校验逻辑本身。但针对APM Agent这类合法工具的篡改检测,这些方案已足够实用。
内容的提问来源于stack exchange,提问作者shijie xu

