DokeyC2
已完成第一阶段demo开发, 目前实现了客户端到服务端之间的通信, 向服务端发送命令、获取监听器信息、获取被控端信息、向所有客户端推送信息; 被控端到服务端之间的通信, 向服务端获取命令执行、向服务端发送心跳包保持活跃, 向服务端发送系统信息等
运行展示
DockeyC2通信架构
通信协议
C2服务端
到客户端
选择使用HTTPS通信,使用TLS(传输层安全协议加密通信内容);服务端和客户端之前长连接机制来保证连接复用,在多次请求/响应周期中复用连接,避免频繁建立和断开连接的开销,提高系统性能和响应速度。C2服务端
到被控端
目前支持HTTP协议通信,因为这块后续需要根据监听器(HTTP、HTTPS、DNS)来选择并创建不同的Beacon,所以目前只实现了HTTP逻辑。
服务端(Server)
服务端主要功能概述
监听连接
- 监听
4433
端口,并使用线程处理每个验证通过的客户端,可以多个客户端连接到服务端 - 可以通过客户端在服务端创建监听器,被控端(Beacon)通过监听器连接至服务端,支持多个被控端连接到该监听器并正确处理请求
命令分发与执行
- 在服务端通过工厂模式来模块化处理客户端、被控端的请求,不同的请求对应不同的处理逻辑
- 客户端通过HTTPS请求将命令传递到服务端,服务端根据
beacon_id
将命令存储到对应命令队列中,并等待被控端通过请求来获取需要执行的命令 - 被控端通过
/CommandRetrieval
接口请求需要执行的命令,当获取命令后根据command_id
将命令状态置为正在执行
状态,当执行完并返回结果后根据command_id
将命令状态置为已执行
状态;保证获取命令时不会获取已执行的命令;此处将命令执行完成将命令状态置为已完成
状态,暂未实现将命令删除或转存,后续可能需要获取命令执行历史记录
信息存储
- 客户端连接存储:使用
std::unordered_map <std::string, std::shared_ptr<ClientSession>>
来存储客户端session,以client_id
为键,值为std::shared_ptr<ClientSession>
;首先std::unordered_map
提供了高效查找,可以通过std::mutex
来实现线程安全;其次使用std::shared_ptr<ClientSession>
可以动态管理内存,简化对象的共享和传递,避免重复传递对象;另外std::unordered_map
支持快速扩展,可以轻松地添加或删除客户端会话,确保性能稳定 - 被控端连接存储:使用
std::unordered_map<std::string, std::shared_ptr<Beacon>>
来存储被控端session,以beacon_id
为键,值为std::shared_ptr<Beacon>
,使用特性和客户端连接存储同理 - 监听器存储:使用
std::unordered_map <std::string, std::shared_ptr<IListener>>
来存储监听器信息,使用特性和客户端连接存储同理
服务端架构与代码结构
服务端结构概述
- C2服务端:作为指挥中心,负责接收来自客户端的命令、验证客户端身份、管理客户端连接以及向被控端下发指令。
- C2客户端:用于与服务端通信的用户界面,通常负责显示当前连接的状态、管理连接、发送命令、接收执行结果等。
- 被控端(Beacon):被控制的目标机器,通过 C2 服务端下发的命令执行操作,并将结果反馈给服务端。
通信流程
客户端连接与认证
- 建立连接:C2客户端发起 HTTPS 请求,尝试与服务端建立连接。
- 身份验证:
- 客户端在请求中附带加密的身份验证信息(如用户名/密码或密钥)。
- 服务端接收到请求后,解密并验证身份。
- 如果身份验证通过,服务端生成一个会话令牌(目前为client_id)并发送给客户端,表示身份验证成功。客户端保存此令牌并在后续的请求中使用。
- 如果身份验证失败,服务端返回错误响应,客户端需要重新进行身份验证。
命令交互
- 客户端发送命令:
+客户端通过 HTTPS 向服务端发送执行命令请求(如执行 Beacon 任务、查询 Beacon 状态等)。请求中会附带会话令牌作为身份验证。 - 服务端处理命令:
- 服务端接收到请求后,验证会话令牌的有效性。
- 如果令牌有效,服务端根据请求内容查找对应的 Beacon,并将命令下发给目标 Beacon。
- 服务端等待 Beacon 返回执行结果。
Beacon执行与反馈
- Beacon 接收命令:
- Beacon 定期向服务端发送心跳包或 Beacon 信息,表明自己在线,并接收下发的命令。
- Beacon 执行命令:
- eacon 根据服务端下发的命令执行相应操作,并将结果返回给服务端(例如执行 Shell 命令、获取系统信息等)。
- 服务端反馈结果:
- 服务端接收到 Beacon 返回的结果后,整理并通过 HTTPS 返回给客户端。
- 客户端收到执行结果并显示给用户,更新客户端的状态。
代码结构
1 | DokeyC2 |
服务端与客户端、Beacon通信数据验证
服务端与客户端(HTTPS)
客户端发送的请求以及服务端的回应都会使用 SSL/TLS 进行加密,保证数据传输的安全性。
服务端与被控端(HTTP)
当前支持HTTP监听器, 后续添加HTTPS、DNS类型监听器
客户端(Client)
客户端使用 MFC 单文档开发,界面参考 cobalt strike ;自实现 C2 上版本使用
MFC基于对话框
进行客户端界面开发,在视觉效果上总觉得差强人意,在视图、数据的展示会存在很多隐藏的bug,所以选用MFC单文档进行开发,也一边学习 MFC 单文档开发模式
MFC单文档概述
MFC(Microsoft Foundation Class)是一个基于 C++ 的类库,提供了丰富的界面和功能来帮助开发 Windows 应用程序。MFC 支持多种应用程序架构,其中 单文档界面(SDI) 是最常用的一种。
- 单文档界面 (SDI):在单文档界面应用程序中,通常只有一个主窗口(即主框架窗口),并且该窗口的内容或数据通过一个文档对象来管理。SDI 应用程序的特点是每次只能打开一个文档实例。
- 特点:简单、直观、适用于需要操作单一数据集的应用,用户界面清晰。
MFC单文档应用程序的基本结构
在 MFC 单文档应用程序中,主要有以下几个关键组成部分:
- 文档类(CDocument):负责应用程序的数据管理和存储。文档类通常用于存储应用程序中的核心数据(如网络连接信息、命令队列等),并提供对这些数据的操作接口。
- 视图类(CView):负责显示文档中的内容,并与用户进行交互。视图类的任务是将文档中的数据可视化,提供用户与应用程序交互的界面。
- 框架类(CFrameWnd):负责窗口的布局、菜单、工具栏等界面的控制。框架窗口是应用程序的主要容器,包含视图和文档的显示界面。
C2客户端设计
网络连接与通信
客户端与 C2 服务端通过 HTTPS 建立连接并保持会话。使用 Boost.Beast 库来实现与服务端的通信。
- 建立连接:客户端在启动时通过 HTTPS 长连接与服务端进行通信,进行身份验证等。
- 命令发送与结果接收:客户端可以通过发送命令请求(如执行 Beacon 操作、查询 Beacon 状态等),等待服务端返回结果并显示。
关键代码
在 HttpsClient.h 中实现了对服务端的连接并进行响应解析,并且在 StartReceiveResponse 函数中对响应进行持续读取解析,并通过自定义消息(WM_HTTPS_RESPONSE)将响应发送到主框架处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43void StartReceiveResponse()
{
// 清空响应正文
response_.body().clear();
// 重置响应头部和状态码
response_.clear();
// 清空缓冲区
buffer_.consume(buffer_.size());
boost::beast::http::async_read(*stream_, buffer_, response_,
[this](boost::system::error_code ec, std::size_t /*length*/) {
if (ec) {
HandleError(ec, "读取服务器响应失败");
return;
}
std::vector<char> body_data(response_.body().begin(), response_.body().end());
try {
Command2Server deserializedServer;
std::stringstream ss(std::string(body_data.begin(), body_data.end()));
boost::archive::binary_iarchive ia(ss);
ia >> deserializedServer;
if (pMainWnd_) {
Command2Server* pData = new Command2Server(deserializedServer);
pMainWnd_->PostMessage(WM_HTTPS_RESPONSE, reinterpret_cast<WPARAM>(pData), 0);
//手动处理消息循环, 让WM_HTTPS_RESPONSE先被处理
MSG msg;
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
catch (const std::exception& e) {
CString msg;
msg.Format(_T("解析响应失败: %S"), e.what());
AfxOutputDebugString(msg);
AfxMessageBox(msg);
}
// 你可以继续读取响应数据
StartReceiveResponse();
});
}在 MainFrm.cpp 中对响应进行细化处理,根据不同的命令类型解析出数据并传递到文档类(CDocument),通过文档类(CDocument)来通知视图类(CView)进行数据更新
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56LRESULT CMainFrame::OnHttpsResponse(WPARAM wParam, LPARAM lParam)
{
try
{
Command2Server* pData = reinterpret_cast<Command2Server*>(wParam);
// 获取当前活动视图
CView* pView = GetActiveView();
if (pData && pView)
{
// 处理 pData 数据
CString cstrMessage(pData->message.c_str()); // std::string 转 CString
// 获取与当前视图关联的文档
CDokeyC2Demov43ClientDoc* pDoc = dynamic_cast<CDokeyC2Demov43ClientDoc*>(pView->GetDocument());
AfxOutputDebugString(cstrMessage);
pDoc->AddLog(cstrMessage);
if (pData->command == "INITIALIZE_CONNECTION")
{
client_id_ = pData->client_id;
pDoc->UpdateClientID(pData->client_id);
}
else if(pData->command == "CREATE_LISTENER" || pData->command == "GET_LISTENERS")
{
// 反序列化 data 中的 ListenerInfo
std::stringstream listenerDataStream(pData->data);
boost::archive::binary_iarchive iaListener(listenerDataStream);
ListenerInfo newListener;
iaListener >> newListener; // 从 data 中恢复 ListenerInfo
pDoc->UpdateListenerInfo(newListener);
}
else if (pData->command == "PUSH_BEACON_INFO" || pData->command == "GET_BEACONS")
{
std::stringstream beaconSystemInfoDataStream(pData->data);
boost::archive::binary_iarchive iaBeaconSystemInfos(beaconSystemInfoDataStream);
BeaconsInfo newBeacons;
iaBeaconSystemInfos >> newBeacons;
pDoc->UpdateBeaconInfo(newBeacons);
}
else if (pData->command == "COMMAND_RESULT_REPORT")
{
pDoc->UpdateCommandResult(*pData);
}
else if (pData->command == "UPDATE_DELAY")
{
pDoc->UpdateBeaconUpdateDelay(*pData);
}
}
delete pData;
}
catch (const std::exception& e)
{
CString msg;
msg.Format(_T("接收响应出现错误: %s"), CString(e.what()));
AfxMessageBox(msg);
}
return LRESULT();
}
用户界面(UI)设计
在主界面上,使用 SDI 架构的主窗口展示核心功能和信息。主界面通常包括以下部分:
- 列表控件:显示当前所有连接的 Beacon 信息,包括 Beacon ID、状态、IP 地址、用户名、计算机名、延迟等。
- 标签页控件:通过动态创建标签来展现不同标签页内容,包括日志标签页、监听器信息标签页、Beacon 命令执行/执行结果展示标签页。
- 日志视图:显示客户端与服务端的交互日志,帮助用户了解当前操作的执行情况。使用 CRichEdit 控件展示日志信息。属于动态标签页。
- 监听器信息视图:属于动态标签页,显示通过客户端创建的监听器信息。
- Beacon 命令操作视图:属于动态标签页,包括输入命令CRichEdit控件、执行结果展示CRichEdit控件;分别用于输入命令、展示执行结果。
界面详情
被控端(Beacon)
Beacon主要功能
定期通信(心跳和报告)
Beacon 定期与 C2 服务端保持连接,以确保与控制中心的联系始终畅通。这种通信通常以 心跳包 形式进行,目的是:
- 确保 Beacon 仍然处于在线状态。
- 使得 C2 服务端能够实时监控 Beacon 的状态。
获取命令
Beacon 通过 HTTP/HTTPS/DNS 连接到 C2 服务端并通过接口来获取命令指挥 Beacon 执行不同的任务。命令可能包括:
- 执行系统命令或脚本。
- 获取目标系统的信息(如硬件信息、操作系统版本等)。
执行命令
Beacon 收到服务端的指令后,执行相应的操作。根据命令类型,Beacon 可以:
- 执行系统命令、shell 命令或自定义脚本。
- 获取并返回系统的状态信息或日志。
- 执行恶意操作(如删除文件、修改配置、植入后门等)。
反馈执行结果
执行完命令后,Beacon 会将结果反馈给 C2 服务端。这些结果可能包括:
- 执行成功或失败的状态。
- 执行过程中的错误信息或输出结果。
- 执行时间、命令日志等。
TODO
后期实现功能清单
在客户端、服务端实现自动生成Beacon功能
重构Beacon代码结构, 使其结构化, 具备自动生成
服务端、客户端实现创建监听器保存至文件, 运行服务端自动加载并开启监听
支持更多监听器类型(HTTPS、DNS)