易语言无模块注入_[笔记]利用JVM SandBox注入异常

题记:大名鼎鼎的ChaosBlade(混沌工具)和 jvm-sandbox-repeater(无侵入录制回放工具)底层都是基于JVM SandBox。JVM SandBox 使用起来非常很简单,但是 JVM SandBox 背后所涉及到的底层技术原理、实现细节却不简单,比如 Java Agent、Attach、JVMTI、Instrument、Class 字节码修改、ClassLoader、代码锁、事件驱动设计等等。学无止境。

下面是JVM SandBox官网修复时钟demo。举一反三,如何注入异常?

写一个正常的时钟,下述demo中,checkState() 去掉异常,直接return,运行就可以看到一个正常时钟。使用下述代码即可注入异常。步骤不再赘述,参考下方所附修复demo。

package com.alibaba.jvm.sandbox.demo;import com.alibaba.jvm.sandbox.api.Information;import com.alibaba.jvm.sandbox.api.Module;import com.alibaba.jvm.sandbox.api.ProcessController;import com.alibaba.jvm.sandbox.api.annotation.Command;import com.alibaba.jvm.sandbox.api.listener.ext.Advice;import com.alibaba.jvm.sandbox.api.listener.ext.AdviceListener;import com.alibaba.jvm.sandbox.api.listener.ext.EventWatchBuilder;import com.alibaba.jvm.sandbox.api.resource.ModuleEventWatcher;import org.kohsuke.MetaInfServices;import javax.annotation.Resource;@MetaInfServices(Module.class)@Information(id = "normal-clock-tinker")public class NormalClockTinkerModule implements Module {    @Resource    private ModuleEventWatcher moduleEventWatcher;    @Command("injectCheckState")    public void injectCheckState() {        new EventWatchBuilder(moduleEventWatcher)                .onClass("com.taobao.demo.NormalClock")                .onBehavior("checkState")                .onWatch(new AdviceListener() {                    /**                     * 对{@code com.taobao.demo.NormalClock#checkState()}方法注入异常                     */                    @Override                    protected void afterReturning(Advice advice) throws Throwable {                        // 在此,你可以通过ProcessController来改变原有方法的执行流程                        // 这里的代码意义是:改变原方法抛出异常的行为,变更为立即返回;void返回值用null表示                        ProcessController.throwsImmediately(new IllegalStateException("STATE ERROR!"));                    }                });    }}

注入异常效果

易语言无模块注入_[笔记]利用JVM SandBox注入异常_第1张图片

下面是官网修复时钟的摘抄:


修复一个损坏了的钟

一个损坏的钟

我们定义了一个钟,期望可以实现每隔一定的时间进行报时。

package com.taobao.demo;/** * 报时的钟 */public class Clock {// 日期格式化private final java.text.SimpleDateFormat clockDateFormat= new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");/**     * 状态检查     */final void checkState() {throw new IllegalStateException("STATE ERROR!");
}/** * 获取当前时间 * * @return 当前时间 */final java.util.Date now() {return new java.util.Date();
}/** * 报告时间 * * @return 报告时间 */final String report() {
checkState();return clockDateFormat.format(now());
}/** * 循环播报时间 */final void loopReport() throws InterruptedException {while (true) {try {System.out.println(report());
} catch (Throwable cause) {
cause.printStackTrace();
}Thread.sleep(1000);
}
}public static void main(String... args) throws InterruptedException {new Clock().loopReport();
}
}

运行代码

很明显,这个钟的实现有问题,运行起来没有正确的报时,但却一直在报异常!

java.lang.IllegalStateException: STATE ERROR!
at com.taobao.demo.Clock.checkState(Clock.java:16)
at com.taobao.demo.Clock.report(Clock.java:34)
at com.taobao.demo.Clock.loopReport(Clock.java:44)
at com.taobao.demo.Clock.main(Clock.java:53)
java.lang.IllegalStateException: STATE ERROR!
at com.taobao.demo.Clock.checkState(Clock.java:16)
at com.taobao.demo.Clock.report(Clock.java:34)
at com.taobao.demo.Clock.loopReport(Clock.java:44)
at com.taobao.demo.Clock.main(Clock.java:53)
java.lang.IllegalStateException: STATE ERROR!
at com.taobao.demo.Clock.checkState(Clock.java:16)
at com.taobao.demo.Clock.report(Clock.java:34)
at com.taobao.demo.Clock.loopReport(Clock.java:44)
at com.taobao.demo.Clock.main(Clock.java:53)

问题分析

问题出在了checkState()方法上,这个方法中抛出了异常。接下来我们通过构建一个沙箱模块作为例子,修复这个损坏的钟!

解决方案

如果能直接修改checkState()方法的执行逻辑,让其不再抛异常,而是直接返回,那么一切就迎刃而解。但如何在不修改目标代码、不重启目标应用的情况下实现这个功能呢?

编写一个模块修复损坏的钟

创建一个Java工程clock-tinker

假设用的是MAVEN,这里通过将parent指向sandbox-module-starter来简化我们的配置工作


com.alibaba.jvm.sandboxgroupId>
sandbox-module-starterartifactId>
1.2.0version>parent>

编写模块代码

package com.alibaba.jvm.sandbox.demo;import com.alibaba.jvm.sandbox.api.Information;import com.alibaba.jvm.sandbox.api.Module;import com.alibaba.jvm.sandbox.api.ProcessController;import com.alibaba.jvm.sandbox.api.annotation.Command;import com.alibaba.jvm.sandbox.api.listener.ext.Advice;import com.alibaba.jvm.sandbox.api.listener.ext.AdviceListener;import com.alibaba.jvm.sandbox.api.listener.ext.EventWatchBuilder;import com.alibaba.jvm.sandbox.api.resource.ModuleEventWatcher;import org.kohsuke.MetaInfServices;import javax.annotation.Resource;@MetaInfServices(Module.class)@Information(id = "broken-clock-tinker")public class BrokenClockTinkerModule implements Module {@Resourceprivate ModuleEventWatcher moduleEventWatcher;@Command("repairCheckState")public void repairCheckState() {new EventWatchBuilder(moduleEventWatcher)
.onClass("com.taobao.demo.Clock")
.onBehavior("checkState")
.onWatch(new AdviceListener() {/** * 拦截{@code com.taobao.demo.Clock#checkState()}方法,当这个方法抛出异常时将会被 * AdviceListener#afterThrowing()所拦截 */@Overrideprotected void afterThrowing(Advice advice) throws Throwable {// 在此,你可以通过ProcessController来改变原有方法的执行流程// 这里的代码意义是:改变原方法抛出异常的行为,变更为立即返回;void返回值用null表示ProcessController.returnImmediately(null);
}
});
}
}

编译部署clock-tinker模块

  1. 运行命令完成打包

    mvn clean package
  2. 将打好的包复制到用户模块目录下

    cp target/clock-tinker-1.0-SNAPSHOT-jar-with-dependencies.jar ~/.sandbox-module/
  3. 下载并安装最新版本沙箱:

  • 下载地址:https://ompc.oss.aliyuncs.com/jvm-sandbox/release/sandbox-stable-bin.zip

  • 执行安装

    unzip sandbox-stable-bin.zipcd sandbox

启动沙箱

假设目标进程号:64229

可以看到broken-clock-tinker模块已经正确被沙箱所加载

  • 启动沙箱

    ./sandbox.sh -p 64229
    NAMESPACE : default
    VERSION : 1.2.0
    MODE : ATTACH
    SERVER_ADDR : 0.0.0.0
    SERVER_PORT : 56854
    UNSAFE_SUPPORT : ENABLE
    SANDBOX_HOME : /Users/vlinux/opt/sandbox
    SYSTEM_MODULE_LIB : /Users/vlinux/opt/sandbox/module
    USER_MODULE_LIB : ~/.sandbox-module;
    SYSTEM_PROVIDER_LIB : /Users/vlinux/opt/sandbox/provider
    EVENT_POOL_SUPPORT : DISABLE
  • 查看模块

    ./sandbox.sh -p 64229 -l
    broken-clock-tinker ACTIVE LOADED 0 0 UNKNOW_VERSION UNKNOW_AUTHOR
    sandbox-info ACTIVE LOADED 0 0 0.0.4 [email protected]
    sandbox-module-mgr ACTIVE LOADED 0 0 0.0.2 [email protected]
    sandbox-control ACTIVE LOADED 0 0 0.0.3 [email protected]
    total=4

修复clock#checkState()方法

接下来就是重头戏,如何在不影响目标应用的情况下,无声无息的修复这个故障!

触发broken-clock-tinker模块的repairCheckState(),让修复逻辑生效!

执行命令:触发BrokenClockTinkerModule#repairCheckState()方法执行

./sandbox.sh -p 64229 -d 'broken-clock-tinker/repairCheckState'

问题修复

过一会,模块生效完成,你就会发现原本一直抛异常的钟已经开始在刷新时间了,

java.lang.IllegalStateException: STATE ERROR!
at com.taobao.demo.Clock.checkState(Clock.java:16)
at com.taobao.demo.Clock.report(Clock.java:34)
at com.taobao.demo.Clock.loopReport(Clock.java:44)
at com.taobao.demo.Clock.main(Clock.java:53)
java.lang.IllegalStateException: STATE ERROR!
at com.taobao.demo.Clock.checkState(Clock.java:16)
at com.taobao.demo.Clock.report(Clock.java:34)
at com.taobao.demo.Clock.loopReport(Clock.java:44)
at com.taobao.demo.Clock.main(Clock.java:53)
java.lang.IllegalStateException: STATE ERROR!
at com.taobao.demo.Clock.checkState(Clock.java:16)
at com.taobao.demo.Clock.report(Clock.java:34)
at com.taobao.demo.Clock.loopReport(Clock.java:44)
at com.taobao.demo.Clock.main(Clock.java:53)
2018-10-23 22:31:39
2018-10-23 22:31:40
2018-10-23 22:31:41
2018-10-23 22:31:42
2018-10-23 22:31:43
2018-10-23 22:31:44

恢复被修复的check()方法

当你卸载掉JVM-SANDBOX时候,你就会发现原本已经被修复好的钟,又开始继续报错了。原因是原来通过clock-tinker模块修复的checkState()方法随着沙箱的卸载又恢复成原来错误的代码流程。

  • 卸载沙箱

    ./sandbox.sh -p 64229 -S
    jvm-sandbox[default] shutdown finished.
  • 故障继续

    2018-10-23 23:44:10
    2018-10-23 23:44:11
    2018-10-23 23:44:12
    java.lang.IllegalStateException: STATE ERROR!
    at com.taobao.demo.Clock.checkState(Clock.java:16)
    at com.taobao.demo.Clock.report(Clock.java:34)
    at com.taobao.demo.Clock.loopReport(Unknown Source)
    at com.taobao.demo.Clock.main(Unknown Source)
    java.lang.IllegalStateException: STATE ERROR!
    at com.taobao.demo.Clock.checkState(Clock.java:16)
    at com.taobao.demo.Clock.report(Clock.java:34)
    at com.taobao.demo.Clock.loopReport(Unknown Source)
    at com.taobao.demo.Clock.main(Unknown Source)
    java.lang.IllegalStateException: STATE ERROR!
    at com.taobao.demo.Clock.checkState(Clock.java:16)
    at com.taobao.demo.Clock.report(Clock.java:34)
    at com.taobao.demo.Clock.loopReport(Unknown Source)
    at com.taobao.demo.Clock.main(Unknown Source)

如何调试模块

如何调试这一类的程序代码,可以参考GREYS项目中的一个ISSUES:怎么调试啊?

小结

在这个教程中给大家演示了如何利用沙箱的模块改变了原有方法的执行流程,这里涉及到了沙箱最核心的类ModuleEventWatcher,这个类的实现可以通过@Resource注释注入进来。

通过在THROWS事件环节的改变流程,可以让原本应该抛出异常的checkState()方法转变为正常返回值。你甚至可以窥探、篡改入参、返回值、抛出的异常等等,这些都将可以通过沙箱模块来实现。沙箱模块还能帮你实现很多有意思的功能,期待你的想象。

其他自带例子

沙箱分发包中自带了实用工具的例子./example/sandbox-debug-module.jar,代码在沙箱的sandbox-debug-module模块,也是非常不错的实用工具和学习资料。

例子 例子说明
DebugWatchModule.java 模仿GREYS的watch命令
DebugTraceModule.java 模仿GREYES的trace命令
DebugRalphModule.java 无敌破坏王,故障注入(延时、熔断、并发限流、TPS限流)
LogExceptionModule.java 记录下你的应用都发生了哪些异常
$HOME/logs/sandbox/debug/exception-monitor.log
LogServletAccessModule.java 记录下你的应用的HTTP服务请求
$HOME/logs/sandbox/debug/servlet-access.log

你可能感兴趣的:(易语言无模块注入)