通过动态链接库绕过反病毒软件Hook - Break JVM

倾旋
倾旋
技术分享|2022-8-11|最后更新: 2023-6-22|
type
status
date
slug
summary
tags
category
icon
password
URL

0x00 前言

通常情况下获得Java Webshell碰到数字杀毒的场景居多,在这个环境中经常会遇到无法执行命令或命令被拦截的情况,很多小伙伴遇到这个问题就劝退了,我猜测是有一套进程链的检测方式导致了命令无法执行,于是去查看Java的文档,查阅到Java能够加载动态链接库且能够执行动态链接库中的代码,本文演示如何利用Java加载动态链接库的方式实现绕过了数字杀毒的拦截,但在演示之前,需要铺垫一些基础知识,如:猜想的进程链、Windows错误代码、Java加载动态链接库常见的三种办法、Windows动态链接库、土豆提权原理、命名管道技术等。

0x01 猜想的进程链

在获取Webshell以后,一般执行命令都会调用 Runtime.exec ,当然也有其他的命令执行方式,这里不再讨论,执行的命令一般分为两种:
  • 系统自带的PE文件,后面跟上参数
  • CMD或Powershell中内置的命令
例如:dir 命令与forfiles命令,这两个命令都可以列出文件夹内的文件,但要执行 dir 需要启动 cmd.exe 或者 powershell.exe ,执行的过程中进程链就像这样:
 
notion image
在这个过程里,进程的链是java.exe创建了cmd.exe ,那么很容易就能发现问题,每执行一条命了都会创建一个cmd.exe 的进程。从Runtime.exec 执行命令到Windows API CreateProcess 创建cmd.exe这个进程是通过JVM翻译过来的,数字杀毒会Hook CreateProcess API达到监控拦截的目的。
 
而forfiles是一个PE文件,不是CMD内置的命令,所以不需要创建cmd.exe也可以执行,它的进程链会是这样:
 
notion image
达到了同样的目的,但是没有创建cmd.exe ,为了体验上的考量现在的大部分Webshell管理工具执行命令都是要创建cmd.exe的,那么如何让我们的操作都不创建cmd.exe呢?
 
其实只需要改一下原来的小马即可:
 
 
这样虽然不会创建进程,但大部分命令还是会拦截,例如:net.exe net1.exe
 
notion image

0x02 Windows错误代码

经常会遇到一些Windows下的工具刨出Error Code 5,到底代表什么意思?
这个Error Code 5其实是Windows的错误代码,每一个代码都代表了不同的含义。
 
查询错误代码的含义可以通过 net helpmsg 命令:
 
notion image
Visual Studio 有一个工具可以查询错误代码,名为errlookup:
notion image
notion image
有的时候Webshell管理工具并没有直接给出错误代码的含义,而是直接抛出错误代码,这种情况就能使用命令或者工具去查询,了解错误的发生到底是因为什么问题。

0x03 Java加载动态链接库常见的三种办法

Java加载动态链接库常见的有三种办法:
  • System.load / System.loadLibrary
  • Runtime.getRuntime().load
  • com.sun.glass.utils.NativeLibLoader.loadLibrary
第三种有些JDK版本没有这个对象,因此采用反射加载进行运行。
 
大致流程如下:
System.loadRuntime.getRuntime().load0()ClassLoader.loadLibraryNativeLibrary.load native void load(String name, boolean isBuiltin)
 
我实现了一个简单版本的DLL加载JSP代码,确保每一个请求都可以加载一个DLL模块到Java进程中:
 

0x04 Windows 动态链接库

DLL(Dynamic Link Library)文件为动态链接库文件,又称“应用程序拓展”,是软件文件类型。 在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。在Windows平台下,我们使用的应用程序中的功能其实大多都很相似,窗口调用窗口的模块,分配内存调用内存管理的模块,文件操作调用IO模块,这些模块在Windows里的具体表现就是DLL文件。
 
在之前的文章中有简单总结过Dll的一些知识,这里就不做详细介绍了:
在Windows操作系统中,每一个进程加载一个DLL都会默认执行DLLMain函数,利用这个加载的特性我们可以在Java.exe进程中做一些敏感操作,并且这个进程是白名单、签名的。

0x05 实战绕过数字杀毒添加用户

前提条件:
  • 有一个管理员权限的Webshell
  • 编写一个添加用户的DLL
 
首先上传之前写好的专门用于加载DLL的JSP文件,然后编写一个添加用户的DLL文件:
 
在DllMain函数中调用CreateAdminUserInternal实现添加管理员用户audit。将dll文件进行base64编码,发送到加载动态链接库的jsp页面,就可以绕过数字杀毒添加用户了:
notion image
发送之前:
notion image
 
发送之后:
notion image
 
notion image
至此管理员用户添加成功。
当DLL的编译架构与Java进程的位数不同,加载会失败,抛出:Can't load AMD 64-bit .dll on a IA 32-bit platform。
notion image
这个问题只需要调整DLL的编译架构就行:
notion image
同样的我们还可以调用comsvcs.dll导出的MiniDumpW转储lsass.exe进程的内存。
 
编译成DLL文件发送过去后,我们可以看到Tomcat.exe或者Java.exe的Debug权限已经被开启:
notion image
C:\Windows\Temp目录下已经生成进程的内存转储文件。
注意:如果Tomcat是以SERVICE账户启动的,那么直接加载DLL会造成Tomcat直接崩溃无法工作,这些敏感操作的失败会引发系统的错误处理程序,最终导致Tomcat进程关闭,在实战中应根据业务的重要程度谨慎操作。
notion image
为了避免类似的风险情况,我增加了权限判断、重复转储判断:
首先判断权限是否是管理员或者SYSTEM权限,然后尝试启用SE_DEBUG权限,最后才进行转储,代码我上传到了Github仓库:https://github.com/Rvn0xsy/j2osWin

0x06 将Java进程进行权限提升

Tomcat 有三种权限运行模式:
notion image
● Local Service ● Network Service ● Users 默认安装好的Tomcat会自动运行在Local Service账户下,意味着权限很低,如果目标安装了数字杀毒,就更加难以实现提权。
 
解决办法:
  1. 利用System.LoadLibrary技术在Tomcat本身进程种执行任意代码
  1. 利用执行任意代码的特点来进行土豆提权
  1. 利用模拟Token创建执行Shellcode的线程,所有的交互通过Webshell与系统管道通信实现
 

0x06.1 EfsRpcOpenFileRaw 提权

土豆提权的原理:在Windows操作系统中,如果当前账户是Local Service/Network Service,那么大部分情况下会有一个令牌模拟的权限,当高权限连接到Service账户开启的服务时,Service账户就可以通过令牌模拟获取客户端的权限来执行任意代码。
 
注意:令牌模拟仅是将当前线程的Token进行临时替换为客户端的令牌,其次,土豆提权仅限于本地操作系统才能工作,域内一般发起请求的都是域账户,或有同一账户体系的可信网络内。
 
土豆提权中有一个关于MS-EFSR RPC接口的利用方式,通过创建一个命名管道,然后调用EfsRpcOpenFileRaw让SYSTEM特权账户连接到命名管道实现提权。@zcgonvh 公开了一个C#的利用代码,并且我还请教了他,这里感谢头像哥的解答。
 
创建命名管道部分实现代码:
触发RPC连接实现代码:
每次调用EfsRpcOpenFileRaw都会触发SYSTEM进程连接命名管道,然后再通过ImpersonateNamedPipeClient模拟SYSTEM进程的权限执行代码,当ImpersonateNamedPipeClient函数调用成功后,当前线程的Token其实已经变成了SYSTEM账户的,特权代码执行完成后还可以用RevertToSelf恢复到原来的线程Token。
 
在我实现成功后遇到数字杀毒会拦截提权的行为,其实很多土豆提权成功后,会复制一份Token去创建进程,一般调用CreateProcessWithToken和CreateProcessAsUser比较多,被拦截的时候会是这样:
notion image
因此常规的办法是不行了,于是请教了头像哥,他的回复与我想的一样,用高权限的Token去跑一个特权线程,利用这个特权线程去执行Shellcode。
 
思路如下:
  • 将Shellcode拷贝到可执行堆内存块中
  • 创建一个暂停的线程
  • 将特权Token设置覆盖掉暂停的线程Token
  • 恢复线程执行
成功执行Shellcode后的样子是这样的:
notion image
上线的User是SYSTEM,但是whoami是Local Service,这是因为当前线程是SYSTEM的,获取用户名的GetUserName API是以当前线程Token作为权限查询的,而创建进程时并不会直接复制模拟的特权Token,这个时候只需要使用CobaltStrike的进程注入到其他SYSTEM进程即可。解决完Local Service提权到SYSTEM被数字杀毒拦截的问题后,那就要思考如何做武器化了,因为在实战中不可能上传一个个DLL文件,我需要把所有的代码写到一个DLL中,通过控制JSP Webshell的参数来达到各种功能的调用。
 

0x07 武器化的思路与实现

所有的代码跑在Tomcat的进程里的,而且只能执行DLLMain,那么怎么通过某种技术可以使得发送一个Web请求就执行DLL中的一些功能呢?
这个问题其实并没有难倒我,最简单的办法是用文件传递参数,每个Web请求过来后往文件中写内容,然后DLLMain里写一个循环读取也可以,但是文件的读写容易被干扰,并且涉及到线程的控制,稍微干扰一下就产生读写问题,容错率不高。
 
最终我采用了管道的形式,在DLLMain被执行时就创建一个命名管道,每个请求会连接管道往里写入16进制的单字节指令。
 
部分代码:
METHOD开头的常量代表了不同的功能:
Java Webshell的改造代码如下:
java.io.RandomAccessFile可以读写命令管道,通过修改Header头WWW-Authenticate来控制不同的功能,每个功能都有一个16进制的编号,剩余的Body内容将会被放到其他内存区域,以供功能函数调用读取,如此以来解决了每个请求都可以执行不同功能的问题,只发送一次DLL就可以将DLL模块打入Tomcat/Java进程的内存中执行,并且利用管道读写的特性也能够实现数据的回显,这个已经在示例代码中体现出来了。
 
SERVICE提权到SYSTEM权限并执行任意代码的流程示例图如下:
notion image
公开的DLL模块代码:https://github.com/Rvn0xsy/j2osWin
 
 
 
©2021-2024 倾旋. All rights reserved.