Cobalt Strike C2 反向代理,通过数据包检查与可扩展配置文件关联,抵御蓝队、杀毒软件、EDR、扫描器。
(以前称为 proxy2's malleable_redirector 插件)
让我们提高 C2 重定向器的 IR 弹性门槛,好吗?

红队业务已经看到了 若干 不同 出色 的想法,关于如何对抗事件响应者并误导他们,同时提供有抵抗力的 C2 重定向器网络。
这项工作将许多这些出色想法结合成一个轻量级工具,模仿 Apache2 作为简单 HTTP(S) 反向代理的本质。
结合对 Malleable C2 配置文件的理解、对恶意 IP 地址池的了解以及轻松添加新检查和误路由逻辑的灵活性——结果产生了一个针对 IR 检查的巧妙驱避剂。

如果任何无效入站数据包到达 RedWarden——你可以 redirect、reset 或直接 proxy 将其转走!
此程序充当 HTTP/HTTPS 反向代理,对入站 C2 HTTP 请求施加了几项限制,选择哪些数据包定向到 Teamserver,哪些丢弃,类似于 Apache2 的 mod_rewrite 中强制规定的 .htaccess 文件限制。
RedWarden 旨在解决 C2 重定向器层上的 IR/AV/EDRs/Sandbox 规避问题。它旨在取代为此目的使用的经典 Apache2 + mod_rewrite 设置。
特性:
RedWarden 接收 Malleable C2 配置文件和 teamserver 的 hostname:port 作为输入。然后解析提供的 malleable 配置文件部分以理解契约,并仅让满足契约的入站请求通过,同时误导其他请求。
诸如 http-stager、http-get、http-post 及其对应的 URI、头部、前置/后置模式、User-Agent 等部分,都用于区分合法 Beacon 的请求与无关的互联网噪声或 IR/AV/EDRs 出站数据包。
该程序受益于来自以下位置的出色已知恶意 IP 范围: curi0usJack 和其他人: https://gist.github.com/curi0usJack/971385e8334e189d93a6cb4671238b10
使用 IP 地址黑名单以及通过反向 IP DNS 查询和 HTTP 头部检查进行已知恶意关键字查找,带来了可靠性,从而显著提高了重定向器对想要检查攻击者基础设施的未授权对端的弹性。
无效数据包可能根据三种策略误路由:
此配置在配置文件中强制指定:```yaml
drop_action: redirect
以下示例展示将 `redirect` 目标设为 `https://googole.com` 的结果:

请谨慎使用,注意安全。
### 环境要求
该程序仅能在 Linux 系统上运行,因为它使用 fork 创建多个进程。
此外,系统需预装 `openssl` 命令,因为程序将使用它生成 SSL 证书。
最后,使用以下命令轻松安装所有 Python3 PIP 依赖:```shell
bash $ sudo pip3 install -r requirements.txt
RedWarden 的最小 config.yaml 配置文件可能包含:```yaml port:
profile: jquery-c2.3.14.profile
ssl_cacert: /etc/letsencrypt/live/attacker.com/fullchain.pem ssl_cakey: /etc/letsencrypt/live/attacker.com/privkey.pem
teamserver_url:
drop_action: reset
然后,可以通过提供配置文件的路径来启动程序:```shell
bash$ sudo python3 RedWarden.py -c config.yaml
[INFO] 19:21:42: Loading 1 plugin...
[INFO] 19:21:42: Plugin "malleable_redirector" has been installed.
[INFO] 19:21:42: Preparing SSL certificates and keys for https traffic interception...
[INFO] 19:21:42: Using provided CA key file: ca-cert/ca.key
[INFO] 19:21:42: Using provided CA certificate file: ca-cert/ca.crt
[INFO] 19:21:42: Using provided Certificate key: ca-cert/cert.key
[INFO] 19:21:42: Serving http proxy on: 0.0.0.0, port: 80...
[INFO] 19:21:42: Serving https proxy on: 0.0.0.0, port: 443...
[INFO] 19:21:42: [REQUEST] GET /jquery-3.3.1.min.js
[INFO] 19:21:42: == Valid malleable http-get request inbound.
[INFO] 19:21:42: Plugin redirected request from [code.jquery.com] to [1.2.3.4:8080]
[INFO] 19:21:42: [RESPONSE] HTTP 200 OK, length: 5543
[INFO] 19:21:45: [REQUEST] GET /jquery-3.3.1.min.js
[INFO] 19:21:45: == Valid malleable http-get request inbound.
[INFO] 19:21:45: Plugin redirected request from [code.jquery.com] to [1.2.3.4:8080]
[INFO] 19:21:45: [RESPONSE] HTTP 200 OK, length: 5543
[INFO] 19:21:46: [REQUEST] GET /
[...]
[ERROR] 19:24:46: [DROP, reason:1] inbound User-Agent differs from the one defined in C2 profile.
[...]
[INFO] 19:24:46: [RESPONSE] HTTP 301 Moved Permanently, length: 212
[INFO] 19:24:48: [REQUEST] GET /jquery-3.3.1.min.js
[INFO] 19:24:48: == Valid malleable http-get request inbound.
[INFO] 19:24:48: Plugin redirected request from [code.jquery.com] to [1.2.3.4:8080]
[...]
上述输出中包含一行,指出存在一个未经授权的、不符合我们C2配置文件的入站请求,该请求因提供的User-Agent字符串不兼容而被丢弃:``` [...] [DROP, reason:1] inbound User-Agent differs from the one defined in C2 profile. [...]
## 使用场景
### 对Beacon流量发起方施加IP地理位置限制
你已出色地完成了钓鱼前侦察和开源情报收集工作。现在你掌握了目标所在的位置,并对流量应来源于哪些地域有了一定线索,或者至少能够识别出完全无关的流量。
如何在重定向器上对Beacon请求施加IP地理位置限制?
RedWarden 来帮忙!
假设你只想接受源自波兰(欧洲)的流量。
你的钓鱼前侦察/开源情报结果表明:
- `89.64.64.150` 是你某个目标的合法IP,来自波兰
- `59.99.140.76` 这个IP则并非如此,它以常规的网络噪声数据包到达了你的系统。
你可以使用 RedWarden 的工具 `lib/ipLookupHelper.py` 来收集这两个地址的IP地理元数据:```shell
bash$ python3 ipLookupHelper.py
Usage: ./ipLookupHelper.py <ipaddress> [malleable-redirector-config]
Use this small utility to collect IP Lookup details on your target IPv4 address and verify whether
your 'ip_geolocation_requirements' section of proxy2 malleable-redirector-config.yaml would match that
IP address. If second param is not given - no
前者带来:```shell bash$ python3 ipLookupHelper.py 89.64.64.150 [dbg] Following IP Lookup providers will be used: ['ip_api_com', 'ipapi_co'] [.] Lookup of: 89.64.64.150 [dbg] Calling IP Lookup provider: ipapi_co [dbg] Calling IP Lookup provider: ip_api_com [dbg] New IP lookup entry cached: 89.64.64.150 [.] Output: { "organization": [ "UPC Polska Sp. z o.o.", "UPC.pl", "AS6830 Liberty Global B.V." ], "continent": "Europe", "continent_code": "EU", "country": "Poland", "country_code": "PL", "ip": "89.64.64.150", "city": "Warsaw", "timezone": "Europe/Warsaw", "fulldata": { "status": "success", "country": "Poland", "countryCode": "PL", "region": "14", "regionName": "Mazovia", "city": "Warsaw", "zip": "00-202", "lat": 52.2484, "lon": 21.0026, "timezone": "Europe/Warsaw", "isp": "UPC.pl", "org": "UPC Polska Sp. z o.o.", "as": "AS6830 Liberty Global B.V.", "query": "89.64.64.150" }, "reverse_ip": "89-64-64-150.dynamic.chello.pl" }
后者给出:```shell
bash$ python3 ipLookupHelper.py 59.99.140.76
[dbg] Following IP Lookup providers will be used: ['ip_api_com', 'ipapi_co']
[dbg] Read 1 cached entries from file.
[.] Lookup of: 59.99.140.76
[dbg] Calling IP Lookup provider: ip_api_com
[dbg] New IP lookup entry cached: 59.99.140.76
[.] Output:
{
"organization": [
"",
"BSNL Internet",
"AS9829 National Internet Backbone"
],
"continent": "Asia",
"continent_code": "AS",
"country": "India",
"country_code": "IN",
"ip": "59.99.140.76",
"city": "Palakkad",
"timezone": "Asia/Kolkata",
"fulldata": {
"status": "success",
"country": "India",
"countryCode": "IN",
"region": "KL",
"regionName": "Kerala",
"city": "Palakkad",
"zip": "678001",
"lat": 10.7739,
"lon": 76.6487,
"timezone": "Asia/Kolkata",
"isp": "BSNL Internet",
"org": "",
"as": "AS9829 National Internet Backbone",
"query": "59.99.140.76"
},
"reverse_ip": ""
}
现在你看到前者有 "country": "Poland" 而后者有 "country": "India"。有了这个信息,我们准备以庞大的 YAML 字典形式来设计我们的约束条件:```yaml
ip_geolocation_requirements:
organization:
continent:
continent_code:
country:
- Poland
- PL
- Polska
country_code:
city:
timezone:
那个字典中的每个条目都接受正则表达式,用于匹配入站对等IP地址的已确定IP地理元数据。
我们在 `country` 属性中使用三个条目,以允许请求具有指定的值之一。
在配置中设置好后,你可以使用 `ipLookupHelper` 工具(接受第二个参数)验证另一个 IP 地址是否会通过 RedWarden 的 IP 地理定位鉴别器:

最后一行会告诉你数据包是被阻止还是被接受。
就是这样!明智且安全地配置你的 IP 地理定位约束,仔细检查 RedWarden 日志中任何与 IP 地理相关的 DROP 条目,并让你的 C2 流量保持整洁有序!
### 修复被篡改的 Beacon 请求
如果你碰巧使用诸如 AWS Lambda 或 CloudFlare 之类的中间系统作为你的域名前置/重定向器,你肯定遇到过这样的情况:一些数据包因偏离约定的可塑合约而无法被团队服务器(Teamserver)接受。无论是被篡改或移除的 HTTP 标头、重新排序的 cookie 还是其他任何问题——我敢打赌这浪费了你很多时间。
为了解决 C2 通道设置过程中的问题以及中间系统的篡改行为,RedWarden 提供了修复 Beacon 数据包的功能。
它通过检查可塑配置(Malleable Profile)期望数据包应具有的内容,并根据配置的要求将配置的 HTTP 标头恢复为约定的值来实现修复。
考虑以下简单配置:```
http-get {
set uri "/api/abc";
client {
header "Accept-Encoding" "gzip, deflate";
metadata {
base64url;
netbios;
base64url;
parameter "auth";
}
}
...
你看到这个 Accept-Encoding 了吗?每个 Beacon 请求都必须带有这个 Header 及其特定值。如果你的 Beacon 命中 CloudFlare 系统,并且它们发出的请求中该 Header 被剥离,或者替换为 Accept-Encoding: gzip,会发生什么?Teamserver 会立刻丢弃该请求。
通过在 RedWarden 配置部分设置称为 repair_these_headers 的头部,你可以保护你的连接:```yaml
repair_these_headers:
### 删除有问题的响应头
使用 Cobalt Strike 4.7+ 时,我注意到 Teamserver 会自动删除 Content-Encoding 头,且没有任何通知,从而违反了我们可变的 `http-(get|post).server` 契约。
由于 RedWarden 遵循了该契约,Beacon 要么丢弃响应,要么错误地解压缩它们。
此选项指定应从 Teamserver 响应中删除哪些头,使其在到达 Beacon 进程之前被移除:```yaml
remove_these_response_headers:
- Content-Encoding
RedWarden 现在默认会从 Teamserver 响应中移除 Content-Encoding 标头,以保持与 CS4.7+ 版本的兼容性。
让我们看一下代理产生的输出。
在 verbose: True 选项下,详细程度最多设置为 INFO,以区分接受的请求和被丢弃的请求。
如果请求符合 RedWarden 配置文件中设置的所有条件,则可能会被接受。这种情况会伴随 [ALLOW, ...] 条目日志:```
[INFO] 2021-04-24/17:30:48: [REQUEST] GET /js/scripts.js
[INFO] 2021-04-24/17:30:48: == Valid malleable http-get (variant: default) request inbound.
[INFO] 2021-04-24/17:30:48: [ALLOW, 2021-04-24/19:30:48, 111.222.223.224] "/js/scripts.js" - UA: "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko"
[INFO] 2021-04-24/17:30:48: Connected peer sent 2 valid http-get and 0 valid http-post requests so far, out of 15/5 required to consider him temporarily trusted
[INFO] 2021-04-24/17:30:48: Plugin redirected request from [attacker.com] to [127.0.0.1:5555]
如果请求未通过 RedWarden 对每个请求进行的任何检查,将发出相应的 `[DROP, ...]` 行,其中包含被丢弃的 **原因** 信息。```
[INFO] 2021-04-24/16:48:28: [REQUEST] GET /
[ERROR] 2021-04-24/16:48:29: [DROP, 2021-04-24/18:48:28, reason:1, 128.14.211.186] inbound User-Agent differs from the one defined in C2 profile.
[INFO] 2021-04-24/16:48:29: [DROP, 2021-04-24/18:48:28, 128.14.211.186] "/" - UA: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Safari/537.36"
[ERROR] 2021-04-24/16:48:29: [REDIRECTING invalid request from 128.14.211.186 (zl-dal-us-gp3-wk107.internet-census.org)] GET /
有许多原因决定请求是否可以被丢弃。这些检查中的每一项都可以根据需求或在微调或错误决策修正过程中独立启用或禁用:
摘自 example-config.yaml:```yaml
policy:
allow_proxy_pass: True
allow_dynamic_peer_whitelisting: True
drop_invalid_useragent: True
drop_http_banned_header_names: True
drop_http_banned_header_value: True
drop_dangerous_ip_reverse_lookup: True
drop_ipgeo_metadata_containing_banned_keywords: True
drop_malleable_without_expected_header: True
drop_malleable_without_expected_header_value: True
drop_malleable_without_expected_request_section: True
drop_malleable_without_request_section_in_uri: True
drop_malleable_without_prepend_pattern: True
drop_malleable_without_apppend_pattern: True
drop_malleable_unknown_uris: True
drop_malleable_with_invalid_uri_append: True
默认情况下,所有这些检查都会强制执行。
将 `debug: True` 打开会使控制台缓冲区充斥着大量日志行,描述 RedWarden 在其复杂决策过程中的每一步。
如果你想看到请求和响应的完整内容,请将 `debug` 和 `trace` 都设为 True,然后沉浸在日志的汪洋中吧!
## 常见问题
**- 这个程序可以不使用 Malleable Profile 运行吗?**
可以。但是请求检查逻辑将被关闭,其余功能应该正常工作:IP 地理位置强制、反向查找逻辑、禁止 IP 列表等。
**- 这个程序可以轻松适配其他 C2 框架吗?比如 Mythic、Covenant 等?**
不容易。花费一些精力的话,可以。正如我在下面描述的,这个工具写得不好,会使得适配其他 C2 变得很痛苦。但只要有时间和精力,完全是可行的。
**- 我的数据包被丢弃了。为什么?**
尝试启用 `debug: True` 和 `trace: True` 来收集尽可能多的日志。然后你需要浏览日志并检查发生了什么。数据包是否与你期望的 Malleable 配置文件中的样子完全一致?或者可能在网络上存在某种微妙的篡改,导致 RedWarden 丢弃数据包(也可能导致 Teamserver 丢弃数据包?)。
## 已知问题
- 它*可能*会增加交互式睡眠吞吐量的轻微开销
- ProxyPass 处理逻辑远非完美,而且*真的*有 bug(而且天哪,它真丑!)。
- 奇怪形式的配置文件可能会使 RedWarden 解析器脱轨并报错。解决这个问题的最简单方法是复制 `example-config.yaml` 并在此文件上工作。
## 天哪,为什么这段代码像一坨工程垃圾?
这段代码*简直是一团乱麻*——我承认——而且这有充分的原因:该项目 90% 是在实际红队行动期间开发的。我们都知道,这类行动涉及很多工作,几乎没有时间进行适当的复杂工具开发。更不用说这个程序在项目设置中的关键性了。该工具最初只是一个用 Python2 编写的简单代理脚本,然后演变为带有插件的代理,并接收了 `malleable_redirector` 插件——从那时起,我一直非常努力地保持 `proxy2` 的向后兼容性(可怜的我,我就像微软一样!),以兼容我为其制作的其他插件,并坚持其原始目的。
但是,是时候放手了,重新命名它,并开始修复所有引入的糟糕代码味道。
话虽如此,请在提出 issue、提交 pull request 时对我表示一些同情,尽量提供帮助而不是评判!:-)
谢谢!
## 待办事项
- 研究利用威胁情报源实现恶意目的的可能性——例如,基于 IP 检测安全厂商
- 添加对 MaxMind GeoIP 数据库/API 的支持
- 实现对 JA3 签名的检测和阻止以及模拟(伪装成 nginx/Apache2/自定义设置)。
- 添加一些独特的信标跟踪逻辑,以便代理自行决定是否拒绝阶段化和通信过程
- 引入一天中的时间约束,以实现重定向能力(*仅在办公时间内代理*)
- 在 CONNECT/中继上添加代理身份验证和授权逻辑。
- 添加针对移动用户的重定向
- 添加配置选项,用于定义要注入的自定义 HTTP 头或要删除的头
- 添加配置选项,要求通过 ProxyPass 标准的请求中存在特定的 HTTP 头。
- 交互式界面,允许输入简单字符来控制输出日志的详细程度,类似于 Nmap 的方式
- 将 Malleable 配置文件解析逻辑重写为 [pyMalleableC2](https://github.com/Porchetta-Industries/pyMalleableC2)。当我刚开始编写自己的解析逻辑时,GitHub 上还没有这样的工具包。
- 重构所有代码库
---
### ☕ 表示支持 ☕
这个项目和其他项目都是不眠之夜和**大量辛勤工作**的成果。如果你喜欢我所做的,并且感激我总是回馈社区,
[请我喝杯咖啡](https://github.com/sponsors/mgeeky)(或者更好,请我喝杯啤酒)就只是为了说声谢谢!💪
---
## 作者```
Mariusz Banach / mgeeky, '19-'21
<mb [at] binary-offensive.com>
(https://github.com/mgeeky)