WEASEL是一个小型内存植入程序,使用Python 3,无依赖项。信标客户端会向其控制的DNS区域发送少量主机标识信息。WEASEL服务器可以任务化客户端执行预制或任意命令。
WEASEL是一个阶段1有效载荷,旨在难以检测,并在你那些嘈杂的全功能阶段被捕获时用于重新获得访问权限。
状态
具体使用说明请参见客户端的README和服务器的README。
要启动服务器或客户端,直接执行脚本或将其传递给Python解释器。
确保每个C2域名都有一个指向运行server.py的主机IP地址的NS记录。
WEASEL需要Python 3.6+。
客户端是自包含的,仅使用标准库,因此可以在macOS、Linux等系统上运行。
服务器有一些pip依赖项,包含在服务器的requirements.txt中。服务器应在Linux上运行,但也没有禁止其在macOS或其他类Unix系统上运行。
在这种情况下无需混淆和压缩信标。print语句保留。与上述用法一样,确保servers中的域名的NS记录指向server.py的IP地址。
在服务器主机上:
sudo python3 server.py
在受害者主机上(可以与服务器相同):
python3 beacon.py
你不需要理解这些就能使用WEASEL。
信标通过DNS使用AAAA查询和应答进行通信。它不使用TXT记录,因为TXT记录已知被DNS恶意软件和隧道使用。蓝队通常具有对大型TXT查询告警的DNS隧道检测。
客户端不需要root权限即可运行,不使用原始套接字,也不创建畸形DNS数据包。它使用系统和语言提供的常规接口来发起DNS请求。信息本身在记录中进行了编码+加密。
这个信标旨在保持低速和隐蔽,带宽占用小。它应告知我们它所在的主机,并提供在需要时启动进一步阶段的方法,仅此而已。虽然它支持任意命令,但不应将其用作常规交互式Shell或通信通道。
WEASEL是一个阶段1,你让它持续运行,确保在功能齐全(因此更嘈杂)的阶段被捕获时仍能保持访问。
WEASEL最初针对高正常运行时间服务器,我们在那里有可靠的立足点/利用向量。规避取证是高优先级。因此,它没有原生的持久化功能。
你可以通过将其执行添加到你喜欢的持久化技术中来实现持久化,这留给读者作为练习 :)
一个**请求(来自客户端)**是一个针对类似以下格式名称的单个AAAA查询:
<preamble><data>.<stream>.<session>.domain.tld
前导码为2字节。Preamble[0]是该数据包的序列号。Preamble[1]是该流中的数据包总数。
数据限制为50字节(可配置),包含有效载荷。有效载荷使用自定义字母表进行base32编码。
首先,所有'w'字符替换为'-'。
然后,填充字符从'='替换为'w',以符合DNS字符集:[a-z0-9]和[-]。
我们不直接将'='替换为'-',因为填充总是出现在字符串末尾,以'---'结尾的主机名既可疑又违反DNS RFC。这样,当字符串确实有填充时,它会以'www'结尾,这既不太可疑也符合RFC规范。
一个**响应(来自服务器)**由一个或多个AAAA答案组成。
每个AAAA答案是一个16字节的加密有效载荷,使用socket.inet_ntop表示为IPv6地址。DNS响应中的答案不保持其在传输中的顺序,因此它们像客户端请求一样被排序和重组。
传输有效载荷是一个由^字符分隔的数据元素字符串。
请求和响应遵循以下格式:
<type>|<data>
会话是长期存在的:客户端在第一次执行信标时发起一个会话,该会话应持续到信标在该客户端上活动的整个时间。请注意,由于信标在内存中且不持久,会话数据存储在该Python进程的内存中。任何信标的新调用都将发起一个新会话。
发起会话时,客户端会构建一个包含唯一标识非数据前导码的消息(以向服务器表明这是一个新会话):该前导码是32字节的Diffie-Hellman公钥与16字节随机AES IV的拼接。
服务器收到此消息后,回复自己的32字节公钥。此时,客户端和服务器已建立共享的会话密钥,该密钥将在该会话的生命周期内用于使用AES-128-CTR模式加密数据有效载荷。Diffie-Hellman临时交换确保每个客户端-服务器连接使用唯一的会话密钥,并具有前向保密性。
加密是故意设计得弱小,原因如下:
以下是加密方案的一些已知问题:
p是RFC 3526 Group 5截断为前32字节。这不仅将公钥和私钥限制为32字节,而且Group 5已经被弃用且不推荐使用。我把这个糟糕的决定称为"Group 1"。a,而不是CSPRNG。客户端和服务器之间发送的每条消息都必须被分组为最大50字节的数据包,以便保持在常见DNS隐蔽通道检测的52字节限制以下。特定消息的所有数据包都属于同一个流。消息 == 流。
流由一个2字节的随机十六进制数字标识。回顾一下客户端请求格式:
<preamble><data>.<stream>.<session>.domain.tld
流中每个数据包的2字节前导码包含序列号和该流中数据包总数。这使得服务器能够知道所有数据包是否已到达。
由于这是通过UDP进行的DNS,而UDP不保证数据报到达的顺序。这就是为什么WEASEL必须处理来自许多信标的多个流的排序、重组和跟踪。
每个流重新初始化全局共享的AES-128-CTR密码以加密/解密有效载荷。
有效载荷只能在流完成(所有数据包已到达)后才能被解密。如果不是因为base32,即使缺少数据包,我们也可以解密我们所拥有的消息部分(因为AES-CTR是流密码),但我们无法base32解码部分流。不过,由于DNS客户端的特性,请求会被多次发出(通常2或4次),直到收到响应,因此我们有很好的概率收到流中的所有数据包,因为每个数据包至少会被客户端发送两次。如果我们确实丢失了数据包或流,那也没什么大不了的,信标稍后会再次签到,那时运气可能会更好。
有关如何提供帮助,请参见CONTRIBUTING文件。
WEASEL采用MIT许可证,详见LICENSE文件。
| 类型 | 含义(发送者) | 别名 | 数据 |
|---|
| 0 | 已确认 | ACK | 随机十六进制 |
| 1 | 签到(客户端) | PING | 随机十六进制 |
| 2 | 终止自身(服务器),正在终止自身(客户端) | FIN | |
| 3 | 初始化消息(客户端) | SYN | version|hostname|kernel |
| 4 | 重新连接(服务器) | RST | |
| 5 | 设置回调间隔(服务器) | 秒数 | |
| 6 | 获取网络接口数据 | eth0 1.2.3.4/24\neth1 fe80:::/64\n... | |
| 8 | 评估最多666字节的任意Python3代码(服务器),返回输出前400字节(客户端) | EVAL | python3单行脚本 |
| 9 | 执行最多666字节的任意命令(服务器),返回输出前400字节(客户端) | EXEC | bash命令 |