diff --git a/README.md b/README.md index 35821d1..aefaadd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ -# DHook ![2.2.1 (shields.io)](https://img.shields.io/badge/2.2.1-brightgreen.svg) +# DHook ![2.3 (shields.io)](https://img.shields.io/badge/2.3-brightgreen.svg) DHook是一个交互式自定义动态hook的工具。通过`javaagent`+`ASM`技术对运行时的java应用进行字节码修改,并可以配置文件的方式来增加hook点,修改执行方法的返回值以及参数等。 -## 兼容性 -* java 8-11 +## 环境 +* jdk 8-11 ## 快速开始 @@ -18,7 +18,7 @@ DHook是一个交互式自定义动态hook的工具。通过`javaagent`+`ASM`技 | | \ :| .--. || | | || | | || . ' | '--' /| | | |' '-' '' '-' '| |\ \ `-------' `--' `--' `-----' `-----' `--' '--' - :: DHook ::2.0 springboot: (v2.6.2) + :: DHook ::2.3 springboot: (v2.6.2) 2022-02-13 15:17:03.856 INFO 12496 --- [ main] com.keven1z.DHookServerApplication : Starting DHookServerApplication v2.0 using Java 1.8.0_171 on zii with PID 12496 2022-02-13 15:17:03.856 INFO 12496 --- [ main] com.keven1z.DHookServerApplication : No active profile set, falling back to default profiles: default @@ -87,6 +87,10 @@ agent所捕获的Hook的所有类名 #### 导出agent 将会导出包含hook点的agent,该agent不与服务端绑定,去除了多余的api调用,仅作用hook点的修改,体积很小。 +### 插件 +> 插件编写见[plugin](./plugin.md) + +插件与agent id绑定,进入对应的agent的导入插件,插件放在plugins文件夹中。 ### 案例 @@ -106,27 +110,39 @@ agent所捕获的Hook的所有类名 ## 更新 -### 1.0版本 -* 支持hook接口,当填写的类为接口时,默认会hook所有实现的子类 -* 支持更改hook类的返回类型为string,int,boolean的返回值 -* 支持打印hook方法的所有参数值 +### 2.3版本 2022/6/26 + +* 增加插件功能 + +### 2.2版本 + +* 增加导出仅包含hook点信息的agent + +### 2.1版本 + +* 可以增加方法执行前后静态方法执行参数 +* 增加方法执行前后,直接return + +### 2.0版本 + +* 增加交互式的hook操作 +* 增加方法执行前后的修改 ### 1.1版本 + * 增加通过`*`打印该类的所有方法 * 支持打印返回值 * 支持反编译代码 * 支持修改参数 -### 2.0版本 +### 1.0版本 +* 支持hook接口,当填写的类为接口时,默认会hook所有实现的子类 +* 支持更改hook类的返回类型为string,int,boolean的返回值 +* 支持打印hook方法的所有参数值 + + -* 增加交互式的hook操作 -* 增加方法执行前后的修改 -### 2.1版本 -* 可以增加方法执行前后静态方法执行参数 -* 增加方法执行前后,直接return -### 2.2版本 -* 增加导出仅包含hook点信息的agent \ No newline at end of file diff --git a/dHook.zip b/dHook.zip new file mode 100644 index 0000000..9e0393d Binary files /dev/null and b/dHook.zip differ diff --git a/dhook.sqlite b/dhook.sqlite index 7f08bb1..630cd9b 100644 Binary files a/dhook.sqlite and b/dhook.sqlite differ diff --git a/plugin.md b/plugin.md new file mode 100644 index 0000000..65db948 --- /dev/null +++ b/plugin.md @@ -0,0 +1,62 @@ +# 编写你的插件 +## API +1. IDHookExtender: DHook抽象类,包含onMethodExit,onMethodEnter,onVisitCode,修改方法不同调用时机的抽象方法 +2. IDHookExtenderCallbacks:DHook抽象类帮助类,主要负责设置插件名,hook点等信息 + +## 编写插件 +1. 下载[dHook.zip](./dHook.zip) +2. 解压dHook.zip,放入你的插件项目中。 +3. 编写名为`DHookExtender`的类继承IDHookExtender,实现对应的抽象方法 +4. 在registerExtenderCallbacks中通过callbacks对象设置hook点等信息 + +## 案例 +```java +public class DHookExtender extends IDHookExtender { + public void registerExtenderCallbacks(IDHookExtenderCallbacks idHookExtenderCallbacks) { + ArrayList list = new ArrayList<>(); + list.add("java.sql.Statement.executeQuery(Ljava/lang/String;)Ljava/sql/ResultSet;"); + idHookExtenderCallbacks.setExtensionHooks(list); + idHookExtenderCallbacks.setExtensionName("sql语句打印插件"); + idHookExtenderCallbacks.setExtensionDesc("该工具可以打印所有应用执行的语句"); + } + + public void onMethodExit(AdviceAdapter adviceAdapter, int opcode) { + if (opcode == 177) { + adviceAdapter.visitInsn(1); + } else if (opcode == 176 || opcode == 191) { + adviceAdapter.dup(); + } else { + if (opcode == 173 || opcode == 175) { + adviceAdapter.dup2(); + } else { + adviceAdapter.dup(); + } + adviceAdapter.box(Type.getReturnType(getDesc())); + } + Type type = Type.getType(DHookExtender.class); + Method method = new Method("doHook", "(Ljava/lang/Object;[Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V"); + adviceAdapter.loadArgArray(); + adviceAdapter.visitLdcInsn(getClassName()); + adviceAdapter.visitLdcInsn(getMethod()); + adviceAdapter.visitLdcInsn(getDesc()); + adviceAdapter.invokeStatic(type, method); + } + + public void onMethodEnter(AdviceAdapter adviceAdapter) {} + + public void onVisitCode(AdviceAdapter adviceAdapter) {} + + public static void doHook(Object returnValue, Object[] args, String className, String method, String desc) { + StringBuilder sb = new StringBuilder(); + for (Object arg : args) + sb.append(arg).append("\t"); + String argStr = sb.toString(); + System.out.println(className + "." + method + desc + "\n\t"+ argStr); + if (returnValue != null) + System.out.println("\t"+ returnValue); + } +} +``` +> jdk9+ 编写插件时,tomcat应用需要修改catalina.sh以下选项 +> JDK_JAVA_OPTIONS="$JDK_JAVA_OPTIONS --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED" +> JDK_JAVA_OPTIONS="$JDK_JAVA_OPTIONS --add-opens=jdk.zipfs/jdk.nio.zipfs=ALL-UNNAMED" \ No newline at end of file diff --git a/plugins/SqlPrinter.jar b/plugins/SqlPrinter.jar new file mode 100644 index 0000000..ff22ea1 Binary files /dev/null and b/plugins/SqlPrinter.jar differ diff --git a/pom.xml b/pom.xml index 931f04b..86a8299 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ com.keven1z DHookServer - 2.2 + 2.3 DHookServer DHookServer diff --git a/src/main/java/com/keven1z/DHookServerApplication.java b/src/main/java/com/keven1z/DHookServerApplication.java index 19560ed..60fdbc8 100644 --- a/src/main/java/com/keven1z/DHookServerApplication.java +++ b/src/main/java/com/keven1z/DHookServerApplication.java @@ -1,6 +1,5 @@ package com.keven1z; -import com.keven1z.utils.PluginUtil; import io.netty.channel.ChannelFuture; import org.mybatis.spring.annotation.MapperScan; import org.slf4j.Logger; @@ -17,7 +16,6 @@ @MapperScan(basePackages = {"com.keven1z.dao"}) public class DHookServerApplication implements CommandLineRunner { private static final Logger logger = LoggerFactory.getLogger(NettyServer.class); - private static final PluginUtil pluginUtil = new PluginUtil(); @Resource NettyServer nettyServer; public static void main(String[] args) { @@ -28,8 +26,6 @@ public void run(String... args) throws InterruptedException, IOException, ClassN // 开启服务 int port = 7070; ChannelFuture future = nettyServer.start("localhost", port); - /* 加载插件*/ -// pluginUtil.initPlugins(); if (future.isSuccess()){ logger.info("Netty started on port(s): "+port); } diff --git a/src/main/java/com/keven1z/controller/AgentController.java b/src/main/java/com/keven1z/controller/AgentController.java index b99bd16..5272828 100644 --- a/src/main/java/com/keven1z/controller/AgentController.java +++ b/src/main/java/com/keven1z/controller/AgentController.java @@ -79,7 +79,7 @@ public int add(@RequestParam String name){ /** * 导出agent - * @param id dHook.jar id + * @param id dHook.jar1 id * @return */ @GetMapping("/export") diff --git a/src/main/java/com/keven1z/controller/HookController.java b/src/main/java/com/keven1z/controller/HookController.java index 9abe9c5..c926774 100644 --- a/src/main/java/com/keven1z/controller/HookController.java +++ b/src/main/java/com/keven1z/controller/HookController.java @@ -6,22 +6,16 @@ import com.keven1z.utils.GsonUtil; import com.keven1z.utils.HttpUtil; import com.keven1z.utils.JarUtil; -import com.keven1z.utils.PluginUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.io.InputStreamResource; import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; -import javax.servlet.http.HttpServletResponse; import java.io.*; -import java.util.ArrayList; import java.util.List; -import java.util.Map; /** * @author keven1z @@ -50,7 +44,7 @@ public List allHook() { /** * 通过agent id查找hook数据 * - * @param id dHook.jar id + * @param id dHook.jar1 id * @return hook的json格式数据 */ @GetMapping("/find") @@ -72,7 +66,7 @@ public int del(String hookId) { /** * 导出配置文件 * - * @param id dHook.jar id + * @param id dHook.jar1 id * @return */ @GetMapping("/export") @@ -101,33 +95,4 @@ public ResponseEntity export(@RequestParam(value = "id") String id) thro return HttpUtil.responseSource(fileName, resource, jar_new.length); } - @GetMapping("/get-plugins") - public String getPlugins(HttpServletResponse response) throws IOException { - Map pluginJarMap = PluginUtil.pluginJarMap; - ArrayList list = new ArrayList<>(); - for (Map.Entry plugin : pluginJarMap.entrySet()) { - String name = plugin.getKey(); - list.add(name); - } - return GsonUtil.toJsonString(list); - } - - @GetMapping("/getPluginByName") - public ResponseEntity getPlugins(String name) { - Map pluginJarMap = PluginUtil.pluginJarMap; - String path = pluginJarMap.get(name); - if (path == null) return ResponseEntity.badRequest().body("0"); - - File file = new File(path); - - try { - InputStreamResource inputStreamResource = new InputStreamResource(new FileInputStream(file)); - return HttpUtil.responseSource(name + ".jar", inputStreamResource, file.length()); - } catch (Exception e) { - return ResponseEntity.badRequest().body("0"); - } - } - - - } diff --git a/src/main/java/com/keven1z/controller/PluginController.java b/src/main/java/com/keven1z/controller/PluginController.java index 30f1348..8605d57 100644 --- a/src/main/java/com/keven1z/controller/PluginController.java +++ b/src/main/java/com/keven1z/controller/PluginController.java @@ -7,7 +7,6 @@ import com.keven1z.service.IPluginService; import com.keven1z.utils.HttpUtil; import com.keven1z.utils.JarUtil; -import com.keven1z.utils.PluginUtil; import dHook.IDHookExtenderCallbacks; import org.springframework.core.io.InputStreamResource; @@ -21,7 +20,6 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -import java.util.Map; /** diff --git a/src/main/java/com/keven1z/utils/JarUtil.java b/src/main/java/com/keven1z/utils/JarUtil.java index a80cce4..d4df1a6 100644 --- a/src/main/java/com/keven1z/utils/JarUtil.java +++ b/src/main/java/com/keven1z/utils/JarUtil.java @@ -10,7 +10,6 @@ import org.springframework.core.io.support.ResourcePatternResolver; import java.io.*; -import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.*; @@ -57,7 +56,7 @@ public static byte[] updateField(String className, String fieldName, String fiel changeNodes.add(classNode); } // String inJarPath = file.getAbsolutePath(); -// String outJarPath = System.getProperty("java.io.tmpdir") + File.separator +"dHook.jar"; +// String outJarPath = System.getProperty("java.io.tmpdir") + File.separator +"dHook.jar1"; return JarLoader.saveToJar(agent, changeNodes); } diff --git a/src/main/resources/agent/dHook.jar b/src/main/resources/agent/dHook.jar index 70c1079..3c70c10 100644 Binary files a/src/main/resources/agent/dHook.jar and b/src/main/resources/agent/dHook.jar differ diff --git a/src/main/resources/static/js/hook.js b/src/main/resources/static/js/hook.js index cd68273..07387e9 100644 --- a/src/main/resources/static/js/hook.js +++ b/src/main/resources/static/js/hook.js @@ -44,7 +44,7 @@ var TableInit = function () { paginationPreText: "上一页", paginationNextText: "下一页", onClickRow: function (row, $element) { - console.log("click:" + row["id"]) + // console.log("click:" + row["id"]) }, columns: [ { @@ -80,7 +80,7 @@ var TableInit = function () { return oTableInit; }; -function refresh() { +function refresh_hook() { $('#tb_departments').bootstrapTable('refresh', { query: { pageNumber: 1 @@ -100,7 +100,7 @@ function del_hook() { url: "/hook/del?hookId=" + id, type: "get", success: function (data) { - refresh(); + refresh_hook(); }, error: function () { } diff --git a/src/main/resources/templates/hook.html b/src/main/resources/templates/hook.html index 32ad110..0170ac6 100644 --- a/src/main/resources/templates/hook.html +++ b/src/main/resources/templates/hook.html @@ -337,7 +337,7 @@ dataType: 'html', contentType: 'application/json', }).done(function (data) { - refresh(); + refresh_hook(); setTimeout(function () { $('#add_hook_dialog').modal('hide'); }, 200); diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html index b94e701..0011d8f 100644 --- a/src/main/resources/templates/index.html +++ b/src/main/resources/templates/index.html @@ -173,7 +173,7 @@ // valign: 'middle', width: '200px', formatter: function (value, row, index) { - return "编辑Hook"; + return "编辑Hook"; } } ]