OpenRasp源码分析(二)–OpenRaspHook

在看了部分的涉及mongo的代码后,发现很多是用于错误处理的,那么能否直接删除这部分呢。。
(这个问题之后在看)

0x01 OpenRasp的Hook

首先有一个AbstractClassHook类

匹配是否是需要检测的类、检测的类型、检测的函数

利用insertbefore插入检测的代码

    /**
     * 在目标类的目标方法的入口插入相应的源代码
     *
     * @param ctClass    目标类
     * @param methodName 目标方法名称
     * @param desc       目标方法的描述符号
     * @param src        待插入的源代码
     */
    public void insertBefore(CtClass ctClass, String methodName, String desc, String src)
            throws NotFoundException, CannotCompileException {

        LinkedList<CtBehavior> methods = getMethod(ctClass, methodName, desc, null);
        if (methods != null && methods.size() > 0) {
            insertBefore(methods, src);
        } else {
            if (Config.getConfig().isDebugEnabled()) {
                LOGGER.info("can not find method " + methodName + " " + desc + " in class " + ctClass.getName());
            }
        }

    }

    /**
     * 在目标类的目标方法的入口插入相应的源代码
     * 可排除一定的方法
     *
     * @param ctClass     目标类
     * @param methodName  目标方法名称
     * @param excludeDesc 排除的方法描述符
     * @param src         待插入的源代码
     */
    public void insertBeforeWithExclude(CtClass ctClass, String methodName, String excludeDesc, String src)
            throws NotFoundException, CannotCompileException {

        LinkedList<CtBehavior> methods = getMethod(ctClass, methodName, null, excludeDesc);
        if (methods != null && methods.size() > 0) {
            insertBefore(methods, src);
        } else {
            if (Config.getConfig().isDebugEnabled()) {
                LOGGER.info("can not find method " + methodName +
                        " exclude desc:" + excludeDesc + " in class " + ctClass.getName());
            }
        }

    }

    private void insertBefore(LinkedList<CtBehavior> methods, String src)
            throws CannotCompileException {
        for (CtBehavior method : methods) {
            if (method != null) {
                insertBefore(method, src);
            }
        }
    }

    /**
     * 在目标类的一组重载的目标方法的入口插入相应的源代码
     *
     * @param ctClass    目标类
     * @param methodName 目标方法名称
     * @param allDesc    目标方法的一组描述符
     * @param src        待插入的源代码
     */
    public void insertBefore(CtClass ctClass, String methodName, String src, String[] allDesc)
            throws NotFoundException, CannotCompileException {
        for (String desc : allDesc) {
            insertBefore(ctClass, methodName, desc, src);
        }
    }

这里四个的区别是1和2指定方法的名称,1是确定方法的描述符,2是排除方法的描述符号;3是遍历一组方法;4是遍历一个方法的一组描述符。

insertafter做检测后的处理

 /**
     * 在目标类的目标方法的出口插入相应的源代码
     *
     * @param ctClass    目标类
     * @param methodName 目标方法名称
     * @param desc       目标方法的描述符号
     * @param src        待插入的源代码
     * @param asFinally  是否在抛出异常的时候同样执行该源代码
     */
    public void insertAfter(CtClass ctClass, String methodName, String desc, String src, boolean asFinally)
            throws NotFoundException, CannotCompileException {

        LinkedList<CtBehavior> methods = getMethod(ctClass, methodName, desc, null);
        if (methods != null && methods.size() > 0) {
            for (CtBehavior method : methods) {
                if (method != null) {
                    insertAfter(method, src, asFinally);
                }
            }
        } else {
            if (Config.getConfig().isDebugEnabled()) {
                LOGGER.info("can not find method " + methodName + " " + desc + " in class " + ctClass.getName());
            }
        }

    }

    private LinkedList<CtBehavior> getConstructor(CtClass ctClass, String desc) {
        LinkedList<CtBehavior> methods = new LinkedList<CtBehavior>();
        if (StringUtils.isEmpty(desc)) {
            Collections.addAll(methods, ctClass.getDeclaredConstructors());
        } else {
            try {
                methods.add(ctClass.getConstructor(desc));
            } catch (NotFoundException e) {
                // ignore
            }
        }
        return methods;
    }

    /**
     * 获取特定类的方法实例
     * 如果描述符为空,那么返回所有同名的方法
     *
     * @param ctClass    javassist 类实例
     * @param methodName 方法名称
     * @param desc       方法描述符
     * @return 所有符合要求的方法实例
     * @see javassist.bytecode.Descriptor
     */
    protected LinkedList<CtBehavior> getMethod(CtClass ctClass, String methodName, String desc, String excludeDesc) {
        if ("<init>".equals(methodName)) {
            return getConstructor(ctClass, desc);
        }
        LinkedList<CtBehavior> methods = new LinkedList<CtBehavior>();
        if (StringUtils.isEmpty(desc)) {
            CtMethod[] allMethods = ctClass.getDeclaredMethods();
            if (allMethods != null) {
                for (CtMethod method : allMethods) {
                    if (method != null
                            && !method.isEmpty()
                            && method.getName().equals(methodName)
                            && !method.getSignature().equals(excludeDesc))
                        methods.add(method);
                }
            }
        } else {
            try {
                CtMethod ctMethod = ctClass.getMethod(methodName, desc);
                if (ctMethod != null && !ctMethod.isEmpty()) {
                    methods.add(ctMethod);
                }
            } catch (NotFoundException e) {
                // ignore
            }
        }
        return methods;
    }

    /**
     * 在目标类的目标方法的入口插入相应的源代码
     *
     * @param method 目标方法
     * @param src    源代码
     */
    public void insertBefore(CtBehavior method, String src) throws CannotCompileException {
        try {
            method.insertBefore(src);
            LOGGER.info("insert before method " + method.getLongName());
        } catch (CannotCompileException e) {
            LogTool.traceError(ErrorType.HOOK_ERROR,
                    "insert before method " + method.getLongName() + " failed: " + e.getMessage(), e);
            throw e;
        }
    }

    /**
     * (none-javadoc)
     *
     * @see com.baidu.openrasp.hook.AbstractClassHook#insertAfter(CtClass, String, String, String, boolean)
     */
    public void insertAfter(CtClass invokeClass, String methodName, String desc, String src)
            throws NotFoundException, CannotCompileException {
        insertAfter(invokeClass, methodName, desc, src, false);
    }

    /**
     * 在目标类的目标方法的出口插入相应的源代码
     *
     * @param method    目标方法
     * @param src       源代码
     * @param asFinally 是否在抛出异常的时候同样执行该源代码
     */
    public void insertAfter(CtBehavior method, String src, boolean asFinally) throws CannotCompileException {
        try {
            method.insertAfter(src, asFinally);
            LOGGER.info("insert after method: " + method.getLongName());
        } catch (CannotCompileException e) {
            LogTool.traceError(ErrorType.HOOK_ERROR,
                    "insert after method " + method.getLongName() + " failed: " + e.getMessage(), e);
            throw e;
        }
    }

insertafter做检测后的处理

1和2在查找对应的方法和描述符后插入代码,区别是是否在异常时插入代码,3在直接对找到的方法插入代码。

动态生成静态代码

 /**
     * 获取调用静态方法的代码字符串
     *
     * @param invokeClass 静态方法所属的类
     * @param methodName  静态方法名称
     * @param paramString 调用传入的参数字符串,按照javassist格式
     * @return 整合之后的代码
     */
    public String getInvokeStaticSrc(Class invokeClass, String methodName, String paramString, Class... parameterTypes) {
        String src;
        String invokeClassName = invokeClass.getName();

        String parameterTypesString = "";
        if (parameterTypes != null && parameterTypes.length > 0) {
            for (Class parameterType : parameterTypes) {
                if (parameterType.getName().startsWith("[")) {
                    parameterTypesString += "Class.forName(\"" + parameterType.getName() + "\"),";
                } else {
                    parameterTypesString += (parameterType.getName() + ".class,");
                }
            }
            parameterTypesString = parameterTypesString.substring(0, parameterTypesString.length() - 1);
        }
        if (parameterTypesString.equals("")) {
            parameterTypesString = null;
        } else {
            parameterTypesString = "new Class[]{" + parameterTypesString + "}";
        }
        if (isLoadedByBootstrapLoader) {
            src = "com.baidu.openrasp.ModuleLoader.moduleClassLoader.loadClass(\"" + invokeClassName + "\").getMethod(\"" + methodName +
                    "\"," + parameterTypesString + ").invoke(null";
            if (!StringUtils.isEmpty(paramString)) {
                src += (",new Object[]{" + paramString + "});");
            } else {
                src += ",null);";
            }
            src = "try {" + src + "} catch (Throwable t) {if(t.getCause() != null && t.getCause().getClass()" +
                    ".getName().equals(\"com.baidu.openrasp.exceptions.SecurityException\")){throw t;}}";
        } else {
            src = invokeClassName + '.' + methodName + "(" + paramString + ");";
            src = "try {" + src + "} catch (Throwable t) {if(t.getClass()" +
                    ".getName().equals(\"com.baidu.openrasp.exceptions.SecurityException\")){throw t;}}";
        }
        return src;
    }

动态生成静态代码主要是以下几个方面

  • 运行生成代码,不在编译时硬编码
  • 适应不同的类、方法
  • 选择不同的类加载器

0x02 反序列化的检测

查看DeserializationHook

 public String getType() {
        return "deserialization";
    }

    /**
     * (none-javadoc)
     *
     * @see com.baidu.openrasp.hook.AbstractClassHook#isClassMatched(String)
     */
    @Override
    public boolean isClassMatched(String className) {
        return "java/io/ObjectInputStream".equals(className);
    }

    /**
     * (none-javadoc)
     *
     * @see com.baidu.openrasp.hook.AbstractClassHook#hookMethod(CtClass)
     */
    @Override
    protected void hookMethod(CtClass ctClass) throws IOException, CannotCompileException, NotFoundException {
        String src = getInvokeStaticSrc(DeserializationHook.class, "checkDeserializationClass",
                "$1", ObjectStreamClass.class);
        insertBefore(ctClass, "resolveClass", "(Ljava/io/ObjectStreamClass;)Ljava/lang/Class;", src);
    }

    /**
     * 反序列化监检测点
     *
     * @param objectStreamClass 反序列化的类的流对象
     */
    public static void checkDeserializationClass(ObjectStreamClass objectStreamClass) {
        if (objectStreamClass != null) {
            String clazz = objectStreamClass.getName();
            if (clazz != null) {
                HashMap<String, Object> params = new HashMap<String, Object>();
                params.put("clazz", clazz);
                HookHandler.doCheck(CheckParameter.Type.DESERIALIZATION, params);
            }
        }

    }

找的类名:java/io/ObjectInputStream

hookMethods中生成了具体调用的静态代码,利用insertBefore插入生成的代码到ctClass(具体的类),resolveClass(方法),“(Ljava/io/ObjectStreamClass;)Ljava/lang/Class;”是方法前面。

具体的检测

     /**
     * 反序列化监检测点
     *
     * @param objectStreamClass 反序列化的类的流对象
     */
    public static void checkDeserializationClass(ObjectStreamClass objectStreamClass) {
        if (objectStreamClass != null) {
            String clazz = objectStreamClass.getName();
            if (clazz != null) {
                HashMap<String, Object> params = new HashMap<String, Object>();
                params.put("clazz", clazz);
                HookHandler.doCheck(CheckParameter.Type.DESERIALIZATION, params);
            }
        }

    }

具体的:HookHandler#docheck();—>HookHandler#doCheckWithoutRequest();—>HookHandler#doRealCheckWithoutRequest();—>CheckerManager#check();
并且通过checkParameter设置的内容进行检测

0x03 小结

学习了rasp的hook,大概解决了之前的问题,为什么rasp拦截了反序列化对象的生成,还可以获取完整的代码运行链路,只需要在调用被hook的方法前后,对具体的代码进行处理就可以了。

上一篇
下一篇