• 技术分享 | 专项测试技术初识Hook


    本文节选自霍格沃兹测试学院内部教材

    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安装与启动

    下载

    环境准备

    • 官方声明支持的系统有: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 已经成功启动了。
    1. $ ./sandbox.sh -p 6204
    2. NAMESPACE : default
    3. VERSION : 1.3.3
    4. MODE : ATTACH
    5. SERVER_ADDR : 0.0.0.0
    6. SERVER_PORT : 4543
    7. UNSAFE_SUPPORT : ENABLE
    8. SANDBOX_HOME : e:/Download/sandbox/bin/..
    9. SYSTEM_MODULE_LIB : e:/Download/sandbox/bin/..\module
    10. USER_MODULE_LIB : E:\Download\sandbox\sandbox-module;~/.sandbox-module;
    11. SYSTEM_PROVIDER_LIB : e:/Download/sandbox/bin/..\provider
    12. EVENT_POOL_SUPPORT : DISABLE
    • JVM-Sandbox 同时还会对外提供接口,可以通过请求直接操作 JVM-Sandbox,这样就能方便的与自己的测试代码结合使用。

    JVM Sandbox示例

    • 目标应用为一段简单的 Java 代码,代码中启动了一个死循环,每次循环会打印report 方法接收到的参数值,参数值已经在代码中固定传入,所以运行之后的结果是一串相同的输出内容。具体内容如下:
    1. public class HookTarget {
    2. final void report(String stringParam, boolean boolParam, int intParam) {
    3. System.out.println("stringParam is " + stringParam);
    4. if (boolParam) {
    5. System.out.println("boolParam is true!");
    6. } else {
    7. System.out.println("boolParam is false");
    8. }
    9. System.out.println("intParam is " + intParam);
    10. }
    11. final void loopReport() throws InterruptedException {
    12. while (true) {
    13. report("a", false, 666);
    14. Thread.sleep(1000);
    15. System.out.println();
    16. }
    17. }
    18. public static void main(String... args) throws InterruptedException {
    19. new HookTarget().loopReport();
    20. }
    21. }
    • 要编写符合 JVM-Sandbox 的 hook 脚本,需要引入sandbox-apisandbox-debug-module 两个依赖。
    • 通过实现 jvm.sandbox 中的 Module 接口,在 AdviceListener 方法中重写 before 方法,这样写入的语句就会在目标方法体执行之前进行执行,能够修改目标方法收到的参数数据。通过advice.changeParameter 方法,修改对应位置的参数数值,第一个参数为目标参数的位置,从 0 开始,第二个参数为替换的值。具体代码如下:
    1. import com.alibaba.jvm.sandbox.api.Information;
    2. import com.alibaba.jvm.sandbox.api.Module;
    3. import com.alibaba.jvm.sandbox.api.annotation.Command;
    4. import com.alibaba.jvm.sandbox.api.listener.ext.Advice;
    5. import com.alibaba.jvm.sandbox.api.listener.ext.AdviceListener;
    6. import com.alibaba.jvm.sandbox.api.listener.ext.EventWatchBuilder;
    7. import com.alibaba.jvm.sandbox.api.resource.ModuleEventWatcher;
    8. import org.kohsuke.MetaInfServices;
    9. import javax.annotation.Resource;
    10. import java.util.*;
    11. @MetaInfServices(Module.class)
    12. @Information(id = "ceshiren.com", author = "ceshiren.com")
    13. public class hook_jvm implements Module {
    14. @Resource
    15. private ModuleEventWatcher moduleEventWatcher;
    16. @Command("ceshiren")
    17. public void ceshiren(final Map param) {
    18. new EventWatchBuilder(moduleEventWatcher)
    19. .onClass("HookTarget")
    20. .onBehavior("report")
    21. .onWatch(new AdviceListener() {
    22. @Override
    23. protected void before(Advice advice) throws Throwable {
    24. advice.changeParameter(0, "Change By Hook!");
    25. advice.changeParameter(1, false);
    26. advice.changeParameter(2, 965);
    27. }
    28. });
    29. }
    30. }
    • 项目通过 maven 管理依赖,对应的pom.xml 文件内容如下:
    1. <?xml version="1.0" encoding="UTF-8"?>
    2. <project xmlns="http://maven.apache.org/POM/4.0.0"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    5. <modelVersion>4.0.0</modelVersion>
    6. <groupId>org.example</groupId>
    7. <artifactId>ceshiren_book</artifactId>
    8. <version>1.0-SNAPSHOT</version>
    9. <dependencies>
    10. <dependency>
    11. <groupId>com.alibaba.jvm.sandbox</groupId>
    12. <artifactId>sandbox-api</artifactId>
    13. <version>1.3.3</version>
    14. </dependency>
    15. <dependency>
    16. <groupId>com.alibaba.jvm.sandbox</groupId>
    17. <artifactId>sandbox-debug-module</artifactId>
    18. <version>1.3.3</version>
    19. <scope>compile</scope>
    20. </dependency>
    21. </dependencies>
    22. <build>
    23. <plugins>
    24. <plugin>
    25. <groupId>org.apache.maven.plugins</groupId>
    26. <artifactId>maven-assembly-plugin</artifactId>
    27. <executions>
    28. <execution>
    29. <goals>
    30. <goal>attached</goal>
    31. </goals>
    32. <phase>package</phase>
    33. <configuration>
    34. <descriptorRefs>
    35. <descriptorRef>jar-with-dependencies</descriptorRef>
    36. </descriptorRefs>
    37. </configuration>
    38. </execution>
    39. </executions>
    40. </plugin>
    41. </plugins>
    42. </build>
    43. </project>
    • 脚本编写完毕之后,将项目打成 Jar 包,放到下载的 JVM-Sandbox 项目下 sandbox-module 目录中。

    • 启动写好的 java 目标程序,运行之后命令行开始循环打印之前设置好的语句,内容如下:
    1. stringParam is a
    2. boolParam is false
    3. intParam is 666
    4. stringParam is a
    5. boolParam is false
    6. intParam is 666
    • 在 gitbash 命令行中打开sandbox/bin 目录,执行语句./sandbox.sh -p目标应用进程号-d 'ceshiren.com/ceshiren' ,启动 JVM-Sandbox 并对目标程序进行 Hook 操作,变更report 方法中传入的参数值,这时再回到目标程序运行的命令行中查看,可以看到命令行中输出的内容已经变更,如下:
    1. stringParam is Change By Hook!
    2. boolParam is false
    3. intParam is 965
    4. stringParam is Change By Hook!
    5. boolParam is false
    6. intParam is 965
    • 输出内容的变更,说明 Hook 已经生效。这样在目标程序运行中修改了方法传入的参数值,达到了 Hook 的目的。
    • 现在执行语句./sandbox.sh -p 目标应用进程号 -S 可以关闭修改,命令行中输出的内容变回了原始的输出内容。

    示例简单展示了 JVM-Sandbox 用作 Hook 工具的功能,通过 Hook 功能就可以对 Java 项目的内部运行逻辑和参数、返回值进行修改。测试场景的构建、测试用例的执行都变得更加方便哦~

  • 相关阅读:
    集合、collection、list的方法
    java代码审计(入门级)—基础漏洞合集
    《程序员修炼之道》读书笔记
    MD5加密后16位与32位的区别 [ 详细 ]
    【听课笔记】复旦大学遗传学_01孟德尔遗传
    驱动学习之字符设备
    设计原则四:里氏替换原则
    MinIO图片正常上传不可查看,MinIO通过页面无法设置桶为public
    全国职业技能大赛云计算--高职组赛题卷⑤(容器云)
    算法训练营day35, 二叉搜索树的范围和
  • 原文地址:https://blog.csdn.net/cebawuyue/article/details/127609891