本文节选自霍格沃兹测试学院内部教材 Hook 技术需要预先分析目标应用的源代码和逻辑,根据目标测试场景设置目标、逻辑和数据,然后运行时动态的对目标函数参数值、逻辑或者返回值做修改,达到修改现有函数逻辑、实现目标测试场景的目的。Hook 的价值在测试中,虽然通过修改数据以实现测试场景的需求,大部分情况下都可以通过 Mock 技术实现,但是还有一小部分场景,例如需要修改应用内部函数的参数、返回值或运行逻辑等情况,这时就需要用到 Hook 技术。单元测试之外,Mock 技术的主要作用是对服务、接口进行 Mock,通过代理等方式将被测服务发送到依赖服务的请求转发给 Mock 服务,再由 Mock 服务根据规则组装预期的返回数据响应给被测服务,达到预期的测试场景。Hook 技术主要用于服务内部代码逻辑上的修改,当函数间传递的参数或者函数内的逻辑需要进行修改时,数据的传递并没有经过网络,Mock 服务无法对其进行操作,只能通过 Hook 技术通过在运行的代码中插入额外的代码或者在内存中进行操作。这种更精细更底层的修改,相比 Mock 技术能实现更多的修改范围,适用性更广,难度也更大。
JVM Sandbox 简介
JVM-Sandbox 是 alibaba 开源的一个 JVM 沙箱容器,只能处理目标为 Java 应用的场景,主要的特点是支持热插拔(可以在目标应用运行中随时进行 Hook 的加载和解除)、可以同时操作挂载多个目标应用,相互之间独立设置互不干扰、支持的目标应用 JDK 版本较广(6-11)。工具本身功能很多,在这里仅介绍和使用它用作 Hook 的部分功能。
JVM Sandbox 安装与启动
下载
项目的 github 地址:https://github.com/alibaba/jvm-sandbox。
下载所需版本的二进制压缩包,解压(演示所使用的版本为 1.3.3)。
环境准备
官方声明支持的系统有:Linux/UNIX/MacOS,这几个系统只需要下载解压缩就可以直接运行。
官方并未支持 Windows 系统,所以需要进行如下修改:
安装 Git Bash。
安装 JDK(版本 6-11,演示所用版本为 1.8.0_192),路径中不能带有空格。
在 Shell 脚本中会有 Java 命令的调用,所以电脑中需要,并且因为 Git Bash 运行 Shell 脚本时的目录问题。
修改启动脚本 bin/sandbox.sh ,将脚本中 183-188 行内容注释。
启动脚本
由于启动脚本中使用了相对路径,所以运行时需要切换到项目的 bin 目录下操作。
在 bin 目录中执行语句./sandbox.sh -p 目标应用 pid ,当出现如下提示信息,说明 JVM-Sandbox 已经成功启动了。
$ ./sandbox.sh -p 6204
NAMESPACE : default
VERSION : 1.3.3
MODE : ATTACH
SERVER_ADDR : 0.0.0.0
SERVER_PORT : 4543
UNSAFE_SUPPORT : ENABLE
SANDBOX_HOME : e:/Download/sandbox/bin/..
SYSTEM_MODULE_LIB : e:/Download/sandbox/bin/..\module
USER_MODULE_LIB : E:\Download\sandbox\sandbox-module;~/.sandbox-module;
SYSTEM_PROVIDER_LIB : e:/Download/sandbox/bin/..\provider
EVENT_POOL_SUPPORT : DISABLE
复制代码
JVM-Sandbox 同时还会对外提供接口,可以通过请求直接操作 JVM-Sandbox,这样就能方便的与自己的测试代码结合使用。
JVM Sandbox 示例
目标应用为一段简单的 Java 代码,代码中启动了一个死循环,每次循环会打印 report 方法接收到的参数值,参数值已经在代码中固定传入,所以运行之后的结果是一串相同的输出内容。具体内容如下:
public class HookTarget {
final void report(String stringParam, boolean boolParam, int intParam) {
System.out.println("stringParam is " + stringParam);
if (boolParam) {
System.out.println("boolParam is true!");
} else {
System.out.println("boolParam is false");
}
System.out.println("intParam is " + intParam);
}
final void loopReport() throws InterruptedException {
while (true) {
report("a", false, 666);
Thread.sleep(1000);
System.out.println();
}
}
public static void main(String... args) throws InterruptedException {
new HookTarget().loopReport();
}
}
复制代码
要编写符合 JVM-Sandbox 的 hook 脚本,需要引入 sandbox-api 和 sandbox-debug-module 两个依赖。
通过实现 jvm.sandbox 中的 Module 接口,在 AdviceListener 方法中重写 before 方法,这样写入的语句就会在目标方法体执行之前进行执行,能够修改目标方法收到的参数数据。通过 advice.changeParameter 方法,修改对应位置的参数数值,第一个参数为目标参数的位置,从 0 开始,第二个参数为替换的值。具体代码如下:
import com.alibaba.jvm.sandbox.api.Information;
import com.alibaba.jvm.sandbox.api.Module;
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;
import java.util.*;
@MetaInfServices(Module.class)
@Information(id = "ceshiren.com", author = "ceshiren.com")
public class hook_jvm implements Module {
@Resource
private ModuleEventWatcher moduleEventWatcher;
@Command("ceshiren")
public void ceshiren(final Map<String, String> param) {
new EventWatchBuilder(moduleEventWatcher)
.onClass("HookTarget")
.onBehavior("report")
.onWatch(new AdviceListener() {
@Override
protected void before(Advice advice) throws Throwable {
advice.changeParameter(0, "Change By Hook!");
advice.changeParameter(1, false);
advice.changeParameter(2, 965);
}
});
}
}
复制代码
}项目通过 maven 管理依赖,对应的 pom.xml 文件内容如下:
<?xml version="1.0" encoding="UTF-8"?><project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>ceshiren_book</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>com.alibaba.jvm.sandbox</groupId>
<artifactId>sandbox-api</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>com.alibaba.jvm.sandbox</groupId>
<artifactId>sandbox-debug-module</artifactId>
<version>1.3.3</version>
<scope>compile</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>attached</goal>
</goals>
<phase>package</phase>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
复制代码
</project>脚本编写完毕之后,将项目打成 Jar 包,放到下载的 JVM-Sandbox 项目下 sandbox-module 目录中。
启动写好的 java 目标程序,运行之后命令行开始循环打印之前设置好的语句,内容如下:
stringParam is a
boolParam is false
intParam is 666
stringParam is a
boolParam is false
intParam is 666
复制代码
在 gitbash 命令行中打开 sandbox/bin 目录,执行语句./sandbox.sh -p 目标应用进程号 -d 'ceshiren.com/ceshiren' ,启动 JVM-Sandbox 并对目标程序进行 Hook 操作,变更 report 方法中传入的参数值,这时再回到目标程序运行的命令行中查看,可以看到命令行中输出的内容已经变更,如下
输出内容的变更,说明 Hook 已经生效。这样在目标程序运行中修改了方法传入的参数值,达到了 Hook 的目的。现在执行语句./sandbox.sh -p 目标应用进程号 -S 可以关闭修改,命令行中输出的内容变回了原始的输出内容。示例简单展示了 JVM-Sandbox 用作 Hook 工具的功能,通过 Hook 功能就可以对 Java 项目的内部运行逻辑和参数、返回值进行修改。测试场景的构建、测试用例的执行都变得更加方便
stringParam is Change By Hook!
boolParam is false
intParam is 965
stringParam is Change By Hook!
boolParam is false
intParam is 965
复制代码
示例简单展示了 JVM-Sandbox 用作 Hook 工具的功能,通过 Hook 功能就可以对 Java 项目的内部运行逻辑和参数、返回值进行修改。测试场景的构建、测试用例的执行都变得更加方便
更多资料可点下方
https://qrcode.ceba.ceshiren.com/link?name=article&project_id=qrcode&from=juejin×tamp=1661940023&author=xueqi
评论