Goby 利用内存马中的一些技术细节【技术篇】

GobySec  557天前

0×01 前言

投稿在 Goby 社区的内存马文章已经写了两篇,在第一篇 Shell 中的幽灵王者-JAVAWEB 内存马【认知篇】中介绍了 JavaWeb 内存马技术的历史演变、分类,从认知层面对常见的 JavaWeb 内存马技术进行了介绍;第二篇 用Goby通过反序列化漏洞一键打入内存马【利用篇】中主要介绍了如何将内存马与漏洞进行初步结合,使 Goby 可以通过反序列化漏洞一键打入内存马,并与 Goby 的 PoC、插件系统融合,使用者只需要点点点就可以完成一键化漏洞的打入。

本篇作为 Goby 社区内存马文章的第三篇,主要从技术方面介绍一下,在前两篇文章的基础上,在使用 Goby 通过反序列化漏洞一键打入内存马的过程中,所使用的一些技术细节。

当然,在使用 Goby PoC 进行打入的过程中,使用者无需知道这些细节,但是了解和学习技术有助于掌握一些通用的思路。

本文主要分为三个部分:“前置漏洞的利用”、“内存马的生成”以及“内存马的使用”,给大家分享一些 Goby 中相关的技术点和其中的细节或坑,欢迎大家一起讨论。

这里简单演示一下相关的技术的一些使用效果,以下视频演示了使用 Goby 通过反序列化一键注入 Filter 型内存马,并通过自定义 URLClassLoader 携带虚假信息,来躲避安全人员的排查,并且通过清除日志来达到无痕的目的。

【使用 Goby 通过反序列化一键注入 Filter 型内存马】 https://www.bilibili.com/video/BV18g4y1s7Ai/?share_source=copy_web&vd_source=340f53bec5403bcaa7bdfdef9a3b14a5

Goby一键打入内存马功能可在社区版免费使用,https://gobysec.net/updates

0×02 前置漏洞利用

首先来说下前置漏洞利用部分,之前的文章提到,作为实战化漏洞利用、武器化开发的视角,我们更倾向于在漏洞利用过程中,一键化打入内存马,而不是先拿到 JSP webshell 再转为内存马,因此,这里需要考虑在漏洞利用过程中如何直接执行内存马的植入动作。

2.1 动态加载与类的初始化

在目前大多数的漏洞利用中,如果想要执行复杂的恶意攻击逻辑,一般都是使用新建 URLClassLoader、当前线程的类加载器、自定义类加载器等来进行恶意类字节码的装载及初始化。

在不同的利用场景下,可以看情况选取不同的类加载器来实现,但也有的时候没办法选择,需要看情况进行调整:

  • 使用新建 URLClassLoader,如果没有指定,则默认使用系统类加载器作为 parent ClassLoader,也就是 AppClassLoader;

  • 使用当前线程的上下文类加载器,一般使用Thread.currentThread().getContextClassLoader() 获取;

  • 新建自定义类加载器,一般是定义一个通过字节码进行类加载的方法,就相当于封装一个 public 的 defineClass 方法;

  • 在某些利用场景下,无法自定义 ClassLoader ,例如 BCEL ClassLoader 的利用途径等。

在不同情况下,使用不同的 ClassLoader 加载恶意类,将会面临不同的问题:

  • 使用当前线程的上下文类加载器,或无法控制类加载器时,可能存在同一类名无法加载两次的情况,需要额外处理;

  • 在使用例如 BCEL ClassLoader 等特殊的 ClassLoader 时,由于跨类加载加载的问题,需要通过纯反射对一些类和接口进行访问和调用,面临比较大的体力工作。

在漏洞利用的过程中进行动态类加载时,普遍的情况是需要人为的打破双亲委派机制,将恶意类注入到系统中。

载息息相关的就是类的初始化,通常在恶意代码中,会写一些初始化的恶意逻辑,一般可以写在 static 语句块或 public 无参构造方法中:

  • static 语句块在类加载的时候执行一次,在其生命周期只执行一次;
  • public 无参构造方法在类初始化的时候进行调用,每次新建类实例会调用。

此,可以根据具体的情况选择类加载器,与将恶意逻辑放置在什么位置。

2.2 回显与内存马

在 Goby 反序列化打入内存马的插件上线后,我对漏洞库中反序列化漏洞的利用均进行了增强与修正。

熟悉 Goby 的朋友可能知道,Goby 对于漏洞利用的检测分为 PoC 与 EXP,在面对 Java 原生反序列化时,原本的检测和利用程序为:

  • PoC 使用 URLDNS 配合 Goby 自带的 dnslog 平台 GodServer 进行漏洞检测;

  • EXP 使用 YSOSERIAL 的字节码,动态替换命令执行部分的 hex 值,进行命令执行的写入。

使用上述逻辑漏洞的检出,是大部分人员面对反序列化漏洞的检测方式,技术上这种检测方式并没有问题,但是实际执行中会遇到如下问题:

1. 由于网络或 DNSLOG 平台不稳定,可能导致收不到 DNSLOG 的问题,或者 DNSLOG 有较长的延时;

2. 漏洞利用仅仅进行命令执行,时常无法得知漏洞利用是否成功,也无法知道漏洞执行的结果;

3. 在不出网的情况下,无法进行反弹 shell,也无法执行更高级的动作,对实战来说,实用性很差。

因此,为了解决实战化的可用性问题,在后续更新的漏洞利用 PoC 均采用了回显的技术,将命令执行的结果返回 response 中;而 EXP 中则是直接打入内存马,节省了中间的很多过程。


1.png

在构造回显时,则涉及到关键 request 的定位,搜索内存等技术点,而内存马的打入,则又需要针对漏洞环境准备高可用的内存马,有了这些技术加持,可以解决上面提到的问题,无需第三方 dnslog、OOB 等,直接进行漏洞的高精准检测与利用。

2.3 条条大路通内存马

漏洞种类有非常多,能提供任意代码执行的种类也非常多,例如 Java 原生反序列化漏洞、Fastjson/Jackson/XStream 反序列化漏洞、SpEL/Ognl 等表达式注入等。但有很多情况需要额外的利用方式,才能打通漏洞利用的流程,这里以原生反序列化利用为例,列举一些利用链的改造,使其直接可以进行内存马的打入。

  • Transformer[] 利用链,最经典的利用链,一般是 chain 一个 Runtime.getRuntime().exec() 或者 new ProcessBuilder().start() 来进行命令执行。

    如果想执行一些额外的功能,还可以使用 new URLClassLoader().loadClass() 来进行远程类加载。

    在不出网的情况下,可以通过 com.sun.org.apache.bcel.internal.util.ClassLoader.loadClass()org.mozilla.ja vasc ript.DefiningClassLoader().defineClass()new sc riptEngineManager().getEngineByName("ja vasc ript").eval() 的方式写入 JS 进行恶意类的打入,一键利用内存马。

2.png

  • BeanShell 利用链,虽然 Bsh 支持全部 Java 语法及很多松散写法,但是说到底还是脚本语言的解析,如果使用了这些写法或脚本中使用了数组等,在反序列化过程中会调用相关实现类的相关方法,可能用到 Interpreter 对象,报空指针,因此还是可以使用 sc riptEngineManager 解析 JS 的方式执行内存马。这里随便举了几个反序列化利用链串联到内存马的技术思路,除此之外还有更多的漏洞利用情况,可以酌情进行“曲线救国”,考虑到行文的篇幅,这里就不在赘述更多的思路。

3.png

  • C3P0 在原版中,C3P0 链使用了 PoolBackedDataSource 进行远程类加载来进行漏洞利用。但实际上,C3P0 还可以通过Tomcat 的 getob jectInstance 方法调用 ELProcessor 的 eval 方法实现表达式注入,可以通过 EL 表达式来进行内存马的打入,除了 EL 表达式,还可以使用 Groovy、SnakeYaml 等。

4.png

这里随便举了几个反序列化利用链串联到内存马的技术思路,除此之外还有更多的漏洞利用情况,可以酌情进行“曲线救国”,考虑到行文的篇幅,这里就不在赘述更多的思路。

0×03 内存马的生成

说过了前置的漏洞利用方向,接下来,讲下在内存马生成过程中涉及到的一些技术细节。

3.1 动态生成技术

考虑到不同漏洞利用点,不同的漏洞利用场景和需求,不同人员的习惯不同等,在实际环境中,内存马的内容不能是一成不变的,需要根据种种配置进行动态生成。

因此,这里使用 javassist 进行内存马恶意类字节码的动态生成与写入。在内存马的准备过程中,将会面临一些的一些需求:

  • 漏洞利用方式是固定的,例如命令执行、常用的冰蝎、哥斯拉或者自研的 webshell 交互工具,基本都是可复用的自定义漏洞利用方式;

  • 内存马能够自定义 URL 、自定义密码,除了常见的 AES 密钥,还可以设置额外的鉴权机制;

  • 可以随意选用任意一种内存马技术,使用任意一种利用方式,均可以快速动态生成。

因此,我这里将关键逻辑最终抽象成一个相同的方法,这个方法的前两个参数分别是 Request 和 Response 对象,无论是命令执行、冰蝎、哥斯拉等等,都可以在这个方法下注入自己的逻辑。对于不同的中间件,由于封装和实现和不同,在进入关键逻辑前进行额外的判定以及处理,使最终处理逻辑一致。

例如下面是冰蝎的核心逻辑:

5.png

下面是哥斯拉的核心逻辑:

6.png

下面是命令执行的逻辑:

7.png

在确定了利用的参数后,则可以根据不同的内存马类型、不同的利用方式进行字节码的组装,将关键方法依次插入恶意类,最终形成一个完整的内存马。

3.2 ClassLoader 的问题

之前提到了在恶意类的动态加载和初始化时,要考虑 ClassLoader 的选取问题,这里在内存马加载后也一样,有关 ClassLoader 的问题需要额外注意。

第一种情况,作为内存马文件本身,一般需要将自身的实例放在处理路由的关键位置中,如全局上下文的某个 Map 成员变量中,这种情况就需要传递一个实例的引用,只要在内存马恶意类初始化时将自身对象的一个实例注册在系统内关键位置即可。

但是也有例外,例如在 Struts2 框架中,关键位置中储存的不是类实例,而是类名,在处理路由时,如果找到映射,则动态创建类实例并调用其 execute 方法进行处理,因此,在进行恶意内存马的注入时,不应单单进行类名和路由的映射,还应该将内存马自身类加载到关键上下文中,使其在实例化类时能够找到我们注入的恶意类。

在利用方式上,除了命令执行回显外,内存马的关键逻辑依旧还是通过传递类字节码来实现的。涉及到类加载的部分,除了之前提到的 URLClassLoader、自定义 ClassLoader、线程上下文类加载器之外,依旧可以玩出很多的花样:

  • 使用 java.lang.reflect.Proxy#defineClass0() 注册类;

  • 使用 sun.misc.Unsafe#defineAnonymousClass() 向 JVM 中直接注册类;

也可以使用一些封装类,这些封装类可能调用一些不常见的 ClassLoader 等:

  • 使用 jdk.nashorn.internal.runtime.sc riptLoader#installClass() 注册对象

  • 使用 com.sun.naming.internal.Version helper#loadClass()

除了上面这些,还有 JavaSec 群友分享了他找到一些方法:

  • jxxload_help.PathVFSJavaLoader#loadClassFromBytes

  • org.python.core.BytecodeLoader1#loadClassFromBytes

  • sun.org.mozilla.ja vasc ript.internal.DefiningClassLoader#defineClass

  • java.security.SecureClassLoader#defineClass

  • org.mozilla.classfile.DefiningClassLoader#defineClass

3.3 利用方式

对于内存马利用方式,最常见的三种利用即是,命令执行回显、冰蝎及哥斯拉,三种各有各的优势:

  • 命令执行回显:简单命令执行,可以看到回显;

  • 冰蝎、哥斯拉马:都提供了一些高级的利用功能,可以按需选择。

除了常规的 Webshell 利用方式,最近比较流行的就是隧道马的打入。攻击人员在获取 Webshell 之后,往往进行的下一步动作将会是利用这个机器作为跳板,进行进一步的内网渗透,此时就需要一条打通的流量隧道。

在以前的做法通常是在目标服务器上上传一个流量转发工具如 FRP 等,借由这个工具进行流量转发。若在网络层面不是全端口映射,还要牵扯到端口复用等技术。

但有了内存马,则可以直接一键打入隧道马,直接使用相应的客户端进行连接,真正实现“一把梭”。

8.png

3.4 Agent No File

由 rebeyond 师傅实现的  AgentNoFile 技术,给我们提供了无需提供 Agent.jar 或者 Agent.so 来直接调用 JVMTI 接口的能力,借助这种能力,我们可以实现无需文件落地进行 Agent 型内存马的注入。

在 Linux 平台下,通过修改 /proc/self/mem 的方式来执行 shellcode,在 Windows 平台下通过 Java 向 PID 为 -1 的进程植入运行shellcode ,从而构造 JPLISAgent 对象,获取调用 Java Agent 的所有能力。

在 BeichenDream 的 Kcon2021Code 项目中,也分享了类似技术思路的代码。

在内存马的实现上,则是通过向目标环境不落地的注入一个 Javassist 依赖 jar,并动态修改目标关键类,注入恶意逻辑,实现 Agent 马的动态修改,例如下图是实现了一个对 ApplicationFilterChain 的 doFilter 方法进行 Hook 的逻辑,实现冰蝎内存马的注入,并从服务器 dump 出来的 class。

9.png

0×04 内存马的使用

通过漏洞直打内存马的问题解决了,内存马的生成和利用方式的问题解决了,接下来要解决的问题就是在内存马使用过程中遇到的问题。

之前的文章中提到,内存马技术的提出主要为了对抗落地文件会有安全防护设备发现告警的问题,因此,内存马技术从一诞生,就面临并承担着与各种防护能力进行对抗的责任和使命。

4.1 安全防护的绕过

首先面临的是流量侧设备的绕过,这部分实际上是 WebShell 管理端和内存马之间通信协议的流量特征。由于主流使用 AES 加解密,少部分使用 DES 加解密等,以及在行为方面会有规律,例如在连接 WebShell 时会发几个包,基于这两项会有一些检测 webshell 连接的手段。因此无论是冰蝎还是哥斯拉,如果没有经过魔改,在流量上的基本特征都会被发现。

但是基本上大家都有魔改的习惯,因此流量层的特征还是不容易统一防护的,而且最新的冰蝎客户端已经支持了自定义的通信协议加解密程序,这样攻击者可以将冰蝎流量伪装为类似业务数据的流量,如 Restful API 返回数据,或类似 ba se64 图片资源返回数据等。

其次是主机层面的绕过,在主机层面,可能面临着一些 EDR 设备等主机层的防御,这部分防御可能会对 Java 进程调用系统资源进行监控,但大多数时候来讲,这一层的防御几乎无法识别 Java 层面的操作是否属于敏感操作。

最后是 Java 层面的绕过,在 Java 层面,可能面临一些 RASP 产品的防护,或自研安全规则的防御。这些防御措施在一些敏感函数执行的位置进行 Hook ,并基于堆栈或行为等方式拦截可疑行为。

此时我们可以通过反射来进行绕过,无论是通过反射调用更深层次的代码乃至 native 方法,还是反射获取系统里面对指定方法有封装的对象去执行,目的都是为了扰乱堆栈或行为调用链条,使 Java 层面的防御无法判断你是恶意操作还是系统行为,以此来绕过检测逻辑。

例如通过反射调用 native 方法来对命令执行的防御进行绕过:

10.jpg

或者使用乱七八糟的反射来使调用链难以捉摸,无法判断:

11.png

在或者使用 unsafe 这样的 API 来进行恶意类的创建,都有可能绕过一定的安全防御:

12.png

除了安全防护,内存马再使用上还有一些有趣的方向可以关注,例如可以使用 Websocket 型的内存马以创建长连接的方式来绕过负载均衡,避免打进去的马找不到的问题。

4.2 防检测

在之前的内存马文章也提到过,很多工具已经提供了扫描指定位置来检查是否存在内存马的检测手段,此时检查会包含一些维度的判断,同样的,我们需要再这些维度上进行进行一定的处理,来进行防检测,例如:

1. 恶意类名、包名的检测:对于一些防御手段,会禁止一些已知的恶意包名、类名的加载,因此这里通过动态拼接和生成恶意类包名的方式来迷惑防御系统或管理员。

13.png

2. 从 ClassLoader 里查看文件是否落地的检测:可以通过自定义 ClassLoader 携带虚假信息或使用系统类加载器加载恶意类的方式使恶意类的类加载器为空来绕过检测逻辑。

14.png

3. 系统关键位置的检测:有些检测工具可以获取关键位置的信息,并辅助人工排查,例如部分工具获取系统内全部 Filter 型内存马,并进行展示。此时可以通过挖掘非常规内存马的方式来进行规避,在之前我分享的关于 JavaWeb 内存马的 PPT 上层提到过,在 web 请求处理过程中,全部使用职责链设计模式的组件,都能用来作为内存马的挖掘利用方向,因此在各个 web 中间件中,再挖掘一种新的内存马也并非难事。

15.png

4. 很多工具会提供 dump class 的功能,通过将内存中的类字节码 dump 出来进行排查,因此可以通过修改 Java 类在 JVM 中的存在形式 InstanceKlass 数据结构中的相关信息的缓存,如 _cached_class_file 等,来使 dump 出来的 class 是不包含危险代码的类,进行欺骗和隐藏的目的。

5. 一些 RASP 还会使用 redefineClasses 将恶意类、恶意函数的关键方法内容置为空,来达到在运行的系统中清除内存马的目的,此时可以通过修改恶意类的函数修饰符、新增成员变量、方法等方式来使其报错,因为 redefineClasses 不允许类结构和签名的改变。

6. 目前大多数的内存马查杀、防御手段,是通过 Java Agent 技术来实现的,因此防止新的 Agent 注入,也是防检测的重点思路,在第一篇中也提到过,可以通过删除 java pid 文件来阻止 JVM 进程通信,也可以通过阻止后续的 ClassFileTransformer 加载的方式阻止其他 Java Agent 加载来防止检测。

4.3 来无影去无踪

首先内存马已经达到了文件不落地的情况,还能不能做什么再次隐匿自己呢?答案是肯定的。

那就是中间件访问日志的清除,在进行访问请求时,中间件会记录日志,这个日志通常会作为后续审查、应急的依据,如果在内存马访问时,能清除访问日志,岂不是做到无痕浏览?

有了思路,执行也很简单,那就是找到中间件中负责记录日志的组件,将其清空,以 Tomcat 为例。

16.png

4.4 持久化

最后一个问题就是持久化的问题,需要考虑在服务重启甚至操作系统重启后能否重新恢复内存马的注入:

  • 对于 Java 层面,可以使用 Java shutdown hook 进行内存马的落地等操作,如果目标环境是 Tomcat 可以在 Jar 包的 Resource 目录下写入 JSP 文件等;

  • 若目标环境面临可能被 kill -9 的操作,可以启动一个“守护进程” 关注服务器上的 Java 进程;

  • 对于操作系统的重启,可以提前将关键恶意操作注册成为定时任务等方式进行持久化。

由于这部分的动作是作为内存马技术的延伸,并且为了达到持久化的目的可能会有 Jar 包、资源文件的篡改和落地,有点违背使用内存马的初衷,所以这部分不再展开讨论,期待更优雅的思路。

0×05 总结

以上部分简单列举了一些实战使用内存马技术中所使用的一些技术问题和遇到的一些坑的解决方案,在经过对以上技术的研究和解决后,在实战上快速使用内存马基本上就没有什么问题了。

虽然我们讨论的是 JavaWeb 内存马技术,但实际可以看到,对抗的思路和技术早已经不单单在 Java 层,而是延伸到了 native 层、内存层面。这仍然是实战化利用中的九牛一毛。在实际环境的使用中,由于操作系统不同、中间件版本不同、JDK 发行版、版本不同等等差异情况、安全限制、安全防护等等复杂的情况,还会遇到种种难题,因此,多研究,多调试,积累思路,才能在实战化的利用中高效、快速利用内存马。

面对内存马技术,表面上是技术与技术的对抗,实际上还是人与人的对抗,思路与思路的对抗,我在这里抛砖引玉,希望能激发更多巧妙的思路,欢迎大家讨论。

文章中的部分技术将会在后续更新在 Goby 中,敬请期待。


Goby 欢迎表哥/表姐们加入我们的社区大家庭,一起交流技术、生活趣事、奇闻八卦,结交无数白帽好友。

也欢迎投稿到 Goby(Goby 介绍/扫描/口令爆破/漏洞利用/插件开发/ PoC 编写/ IP 库使用场景/ Webshell 等文章均可),审核通过后可奖励 Goby 红队版,快来加入微信群体验吧~~~

  • 文章来自Goby社区成员:su18,转载请注明出处。
  • 微信群:公众号发暗号“加群”,参与积分商城、抽奖等众多有趣的活动

  • 获取版本:https://gobysec.net/sale

最新评论

昵称
邮箱
提交评论