如何使用 IDEA 远程 Debug 功能调试 DongTai-agent-java

VSole2021-07-30 19:04:44

一、远程 Debug 原理

首先,Java程序的执行过程分为以下几个步骤:Java的文件 > 编译生成的类文件(class文件)> JVM加载类文件 > JVM运行类字节码文件 > JVM翻译器翻译成各个机器认识的不同的机器码。Java 程序是运行在Java 虚拟机上的,具有良好跨平台性,是因为Java程序统一以字节码的形式在JVM中运行,不同平台的虚拟机都统一使用这种相同的程序存储格式。因为都是类字节码文件,只要本地代码和远程服务器上的类文件相同,两个 JVM 通过调试协议进行通信。另外需要注意的是,被调试的服务器需要开启调试模式,服务器端的代码和本地代码必须保持一致,则会造成断点无法进入的问题。

总结:Java 远程调试的原理是两个JVM之间通过debug协议进行通信,然后以达到远程调试的目的。两者之间可以通过socket进行通信。

二、编译打包 DongTai-agent-java

1.Fork DongTai-agent-java[1] 项目到自己的github仓库

2.将项目 clone 到本地

3.进入 DongTai-agent-java 根目录,执行打包命令。

   & mvn clean package -Dmaven.test.skip=true

注:jdk 版本为1.8。

4.打包结束后项目根目录下会生成文件夹 release,其目录结构:

   release
   ├── iast-agent.jar
   └── lib
       ├── dongtai-servlet.jar
       ├── iast-core.jar
       └── iast-inject.jar

5.使用 IDEA 打开要使用 agent 启动的测试项目(本篇文章以自建测试项目 SpringTest 为例),将这四个 jar 包添加到项目 Libraries 中。


三、IDEA 配置远程 Debug

1.在 Run/Debug Configurations 中配置远程 Debug 启动项

打开Inteliij IDEA,顶部菜单栏选择Run-> Edit Configurations,进入下图的运行/调试配置界面。

点击左上角“+”号,选择 Remote JVM Debug。分别填写右侧三个红框中的参数:Name,Host(想要指定的远程调试端口)。

     Host:运行该项目的远程IP
     Port:远程 IP 的端口
     Command:远程主机在启动 Java 应用时需要添加的参数

2.配置 springtest 的启动命令

   $ java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -javaagent:/path/to/agent.jar -jar springtest.jar

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005:Remote Debug 配置的 JVM 参数

-javaagent:/path/to/agent.jar:被远程 Debug 的DongTAi-iast-agent

springtest.jar:将测试项目 springtest 打包,使用该 jar 包启动项目

三、远程 Debug

1.在 IDEA 中打断点

在刚刚引入了四个 jar 包中打断点,以 iast-core 的transform()方法为例。

transform() 方法会在类文件被加载时调用,在 transform 方法里,我们可以对传入的二进制字节码进行改写或替换,生成新的字节码数组后返回,JVM 会使用 transform 方法返回的字节码数据进行类的加载。

2.运行项目 springtest,然后在 IDEA 中点击 debug

使用上面配置的启动命令运行 springtest

在 IDEA 中查看到断点信息,远程 Debug 成功

四、通过 Debug 探索 transform 方法

DongTai-agent-java 对应用的每个类进行字节码插桩:

    public byte[] transform(ClassLoader loader, String internalClassName, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] srcByteCodeArray) {
        boolean isRunning = EngineManager.isLingzhiRunning();
        if (isRunning) {
            EngineManager.turnOffLingzhi();
        }

        StopWatch clock = null;
        if (this.logger.isDebugEnabled()) {
            clock = new StopWatch();
            clock.start();
        }

        try {
            CodeSource codeSource = protectionDomain != null ? protectionDomain.getCodeSource() : null;
            if (codeSource != null && internalClassName != null && !internalClassName.startsWith("com/secnium/iast/")) {
                this.COMMON_UTILS.scanCodeSource(codeSource);
            }

            if (ConfigMatcher.isHookPoint(internalClassName, loader)) {
                byte[] sourceCodeBak = new byte[srcByteCodeArray.length];
                System.arraycopy(srcByteCodeArray, 0, sourceCodeBak, 0, srcByteCodeArray.length);
                ClassReader cr = new ClassReader(sourceCodeBak);
                int flags = cr.getAccess();
                int targetClassLoaderObjectID = ObjectIDs.instance.identity(loader);
                String[] interfaces = cr.getInterfaces();
                String superName = cr.getSuperName();
                String className = cr.getClassName();
                this.COMMON_UTILS.setLoader(loader);
                this.COMMON_UTILS.saveAncestors(className, superName, interfaces);
                HashSet<String> ancestors = this.COMMON_UTILS.getAncestors(className, superName, interfaces);
                ClassWriter cw = this.createClassWriter(loader, cr);
                ClassVisitor cv = this.PLUGINS.initial(cw, IastContext.build(className, className, ancestors, interfaces, superName, flags, sourceCodeBak, codeSource, loader, this.listenerId, this.namespace, targetClassLoaderObjectID));
                if (cv instanceof AbstractClassVisitor) {
                    cr.accept(cv, 8);
                    AbstractClassVisitor dumpClassVisitor = (AbstractClassVisitor)cv;
                    if (dumpClassVisitor.hasTransformed()) {
                        ++this.transformClassCount;
                        if (this.logger.isDebugEnabled() && null != clock) {
                            clock.stop();
                            this.logger.debug("conversion class {} is successful, and it takes {}ms, total {}.", new Object[]{internalClassName, clock.getTime(), this.transformClassCount});
                        }

                        byte[] var20 = this.dumpClassIfNecessary(cr.getClassName(), cw.toByteArray(), srcByteCodeArray);
                        return var20;
                    }
                } else if (this.logger.isDebugEnabled() && null != clock) {
                    clock.stop();
                    this.logger.debug("failed to convert the class {}, and it takes {} ms", internalClassName, clock.getTime());
                }
            }
        } catch (Throwable var24) {
            ErrorLogReport.sendErrorLog(ThrowableUtils.getStackTrace(var24));
        } finally {
            if (isRunning) {
                EngineManager.turnOnLingzhi();
            }

        }

        return srcByteCodeArray;
    }

1.transform 方法参数、返回值的意义

• loader:ClassLoader类对象,类加载器,将class文件加载到jvm虚拟机中去。

• internalClassName:被扫描类的类名

• classBeingRedefined:要重定义的类所对应的 Class 对象

• protectionDomain:定义权限,ProtectionDomain类封装了域的特征,该域包含一组类,这些类的实例在代表给定的Principal集执行时被授予一组权限

• srcByteCodeArray:被扫描类的原始字节码

• return:如果从 transform 方法中return null 的话,将会告诉运行时环境我们并没有对这个类进行变更。如果要修改类的字节的话,需要在 transform 中提供字节码操纵的逻辑并 return 修改后的字节。

2.DongTai-agent-java 中 transform 方法对每个类做了什么

• this.COMMON_UTILS.scanCodeSource(codeSource) :对每个类所依赖的 jar 包进行扫描,并将信息发送至洞态IAST云端,在云端对这些 jar 包进行扫描(在云端称为应用组件),将有安全漏洞的组件进行展示并提示该组件的安全版本。

•if (ConfigMatcher.isHookPoint(internalClassName, loader)) :在对类进行 HOOK 前需要判断该类是否在DongTai-agent-java 自定义的 HOOK 黑名单中,以下类会出现在 HOOK 黑名单:

• agent自身的类

• 已知的框架类、中间件类

• 类名为null

• JDK内部类且不在hook点配置白名单中

• 接口

• 将不在黑名单中的类进行 HOOK,将信息发送至洞态IAST云端

五、总结

远程 Debug 不仅为研发人员在编写、调优、测试 DongTai-agent-java 提供方便,也为想要了解 DongTai-agent-java 的同学对其实现原理提供极大地便利,欢迎对该技术感兴趣的同学进行尝试。

DongTai-agent-java[2]

    •DongTai[3]

References

[1] DongTai-agent-java: https://github.com/HXSecurity/DongTai-agent-java

[2] DongTai-agent-java: https://github.com/HXSecurity/DongTai-agent-java

[3] DongTai: https://github.com/HXSecurity/DongTai

jvm原理idea
本作品采用《CC 协议》,转载必须注明作者和本文链接
一、远程 Debug 原理首先,Java程序的执行过程分为以下几个步骤:Java的文件 > 编译生成的类文件
深入理解 RMI 之漏洞原理篇环境是 jdk8u65本文侧重于理解原理,攻击篇会放到后续一篇中讲。0x01 前言RMI 作为后续漏洞中最为基本的利用手段之一,学习的必要性非常之大。RMI 依赖的通信协议为 JRMP,该协议为 Java 定制,要求服务端与客户端都为 Java 编写。这个协议就像 HTTP 协议一样,规定了客户端和服务端通信要满足的规范。
浅谈Java反序列化漏洞
2022-05-11 15:48:41
本篇文章参考P神知识星球的《Java漫谈》系列文章,有条件的可以为p牛充电!(星球名:代码审计,我已经冲了) 然后推荐想初步理解java反序列化、并且深度了解一两个利用链的萌新,一步一步跟着文中在idea里调试理解每一步,不要求很快看完,可以收藏或者点赞后,慢慢看 , 希望能收获一点东西 然后大佬们可以退了,因为写的都是java反序列化很浅的东西,顺便别骂QAQ
最常见的情况是使用 Tomcat 作为 Java Web 服务器,使用 Spring 提供的开箱即用的强大 的功能,并依赖其他开源库来完成负责的业务功能实现
Filebeat监视您指定的日志文件或位置,收集日志事件,并将它们转发到Elasticsearch或 Logstash进行索引。使用Kibana,可以通过各种图表进行高级数据分析及展示。
前言最近注意到了Apache Commons Configuration 在2.7版本已经不安全了,能够直接影响该组件,来分析学一下漏洞原理漏洞分析前置Commons Configuration是一个java应用程序的配置管理类库。可以从properties或者xml文件中加载软件的配置信息,用来构建支撑软件运行的基础环境。在一些配置文件较多较的复杂的情况下,使用该配置工具比较可以简化配置文件的解析和管理。也提高了开发效率和软件的可维护性。同样可以使用这种变量插值影响范围2.4 ~ 2.7漏洞首先引入Commons-Configuration的依赖
JSP Webshell的检测工具
2021-12-13 12:04:53
在11月初,我做了一些JSP Webshell的免杀研究,主要参考了三梦师傅开源的代码。然后加入了一些代码混淆手段,编写了一个免杀马生成器JSPHorse,没想到在Github上已收获500+的Star
Java命名和目录接口是Java编程语言中接口的名称( JNDI )。它是一个API(应用程序接口),与服务器一起工作,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口。 可以使用命名约定从数据库获取文件。JNDI为Java⽤户提供了使⽤Java编码语⾔在Java中搜索对象的⼯具。 简单来说呢,JNDI相当与是Java里面的一个api,它可以通过命名来查找数据和对象。
近期,“核弹级漏洞” Apache Log4j2 远程代码执行漏洞细节被公开,攻击者利用漏洞可以远程执行代码。 Apache Log4j2 是一款优秀的 Java 日志框架,该工具重写了 Log4j 框架,并且引入了大量丰富的特性。该日志框架被大量用于业务系统开发,用来记录日志信息。 漏洞原理简述
VSole
网络安全专家