AXHR_Ghost源码阅读和分析

由于最近写c2感觉自己有闭门造车之势,于是读了一遍AHXR/ghost的源码,发现自己很多想法还不成熟,打算先把周遭的c2读一遍,开一个《安全工具月月读》专题,提升一下自己的眼界。我会尽量把blog写得简单易懂,有问题的地方持续改进

0x01 文件结构

作者把很多函数实现都写在头文件中,这操作让代码混乱,不过可以方便引用,c++中的hpp就是这样

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
├─Encryption (加密逻辑的实现)
│ encrypt.h # 提供加密算法函数

├─ghostdll(动态链接库,将server和zombie共同的结构体和函数提出,减少代码量,方便调用)
│ AssemblyInfo.cpp # 文件属性、信息的添加
│ ghostlib.h # 定义公有结构体,声明公有函数
│ ghostlib.cpp # 全局变量声明、公有函数的实现
│ resource.h # 资源宏定义

├─server (服务端)
│ │ callbacks.h # 回调函数
│ │ config.h # 主要是菜单、日志函数的宏定义
│ │ console.h # 命令控制逻辑
│ │ gui.h # 图形界面类,继承System::Windows::Forms::Form
│ │ info.h # 基本信息获取的实现(获取真实IP等)
│ │ resource.h # 资源宏定义
│ │ server.cpp # 界面主逻辑,调用
│ │
│ └─ini (配置文件的读取,使用第三方库INIReader,本篇不会详细描述)
│ AssemblyInfo.cpp # 同上
│ ini.h
│ ini.cpp
│ INIReader.cpp
│ INIReader.h

└─zombie (客户端,植入被控主机)
info.h # 声明工具函数
info.cpp # 工具函数的实现
resource.h # 资源宏定义
zombie.cpp # 交互主逻辑

0x02 数据结构

数据结构并不复杂,大多数都是直接全局声明的数值和字符串,这里仅介绍结构体和类

struct _clientData是zombie的主要结构体

1
2
3
4
5
struct _clientData {
SOCKET socketRef; // socket文件描述符
struct addrinfo * clientInfo; // 地址信息结构体
char * system_data; // json串,存放被植入系统相关数据
};

class gui是server的界面类,继承了windows系统表格基类

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
public ref class gui : public System::Windows::Forms::Form
{
public:
gui(void)
{
InitializeComponent();
//TODO: Add the constructor code here
}

protected:
/// Clean up any resources being used.
~gui()
{
if (components)
{
delete components;
}
}
public: static System::Windows::Forms::NotifyIcon^ taskbarIcon;
private: System::ComponentModel::IContainer^ components;

private:
// Required designer variable


#pragma region Windows Form Designer generated code
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
void InitializeComponent(void)
{
this->components = (gcnew System::ComponentModel::Container());
System::ComponentModel::ComponentResourceManager^ resources = (gcnew System::ComponentModel::ComponentResourceManager(gui::typeid));
this->taskbarIcon = (gcnew System::Windows::Forms::NotifyIcon(this->components));
this->SuspendLayout();
// taskbarIcon 右下方消息弹窗
this->taskbarIcon->BalloonTipIcon = System::Windows::Forms::ToolTipIcon::Info;
this->taskbarIcon->BalloonTipText = L"New Client";
this->taskbarIcon->BalloonTipTitle = L"ghost";
this->taskbarIcon->Icon = (cli::safe_cast<System::Drawing::Icon^>(resources->GetObject(L"taskbarIcon.Icon")));
this->taskbarIcon->Text = L"ghost";
this->taskbarIcon->Visible = true;

// 右下方任务列表中双击图标 回调函数注册
this->taskbarIcon->MouseDoubleClick += gcnew System::Windows::Forms::MouseEventHandler(this, &gui::taskbarIcon_MouseDoubleClick);

// gui
this->AutoScaleDimensions = System::Drawing::SizeF(6, 13);
this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font;
this->ClientSize = System::Drawing::Size(164, 19);
this->Icon = (cli::safe_cast<System::Drawing::Icon^>(resources->GetObject(L"$this.Icon")));
this->Name = L"gui";
this->ShowIcon = false;
this->ShowInTaskbar = false;
this->Text = L"#ghost (github.com/AHXR)";
this->WindowState = System::Windows::Forms::FormWindowState::Minimized;
this->Load += gcnew System::EventHandler(this, &gui::gui_Load);
this->ResumeLayout(false);

}
#pragma endregion
private: System::Void gui_Load(System::Object^ sender, System::EventArgs^ e) {
}
// 鼠标双击右下角的任务小图标时,记录日志,zombie探活,再刷新界面
private: System::Void taskbarIcon_MouseDoubleClick(System::Object^ sender, System::Windows::Forms::MouseEventArgs^ e) {
if (b_hidden) {
SHOW_CONSOLE();
freopen("conin$", "r", stdin);
freopen("conout$", "w", stdout);
freopen("conout$", "w", stderr);
HANDLE hstdout = GetStdHandle(STD_OUTPUT_HANDLE);
ahxrlogger_handle(hstdout); // 作者自己的logger
WARNING("Refreshing clients... Please wait.");
refreshClients();
SHOW_GHOST();
}
}
};

对上面的GUI编程暂时不是很了解,就是把参数填到对应的入口中,比如程序图标注册、双击回调函数注册,在构造函数那里还能进行一些扩展操作。

0x03 主要函数及关系

0x31 ghostdll

其中采用vector维护全局clientData集合

std::vector < GHOSTLIB > client_data;

0x32 encrypt

其中存在两个函数接口及其实现

1
2
3
4
5
6
std::string encryptCMD(std::string text);
std::string unencryptCMD(std::string text);

char c_original[ENCRYPT_CMD_LEN] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890,./<>?;:\'\"[]\\{}|=-+_)(*&^%$#@!~` ";

char c_encrypt [ENCRYPT_CMD_LEN] = "yr(GRMJ1#\'Fme]Kc3<0`{QCBa,$pDx2[h;g8_./unU|+fELqYN~}7l>=dzX?WkjTVH%@b6s9viIo4:v5Aw&O*tP!\\S^)Z\" -";

原理很简单,密码和明文下标相对应转换,通信全程用这种方法加解密

0x33 zombie

  1. 先说info.h中的三个工具函数的功能
1
2
3
4
5
6
7
8
9
// 获取被控主机反病毒软件的名称
// 参考http://www.rohitab.com/discuss/topic/42792-wmi-get-antivirus-name-c/?p=10106373
std::string getAntivirus();

// 对https://api.ipify.org发起请求,网站作用是返回请求者的IP,在返回结果中找到被植入机器的真实IP
std::string real_ip();

// 用进程名找到进程id,后续用于找到taskmgr并杀死,意在令用户无法通过taskmgr杀掉该进程
DWORD FindProcessId(const std::wstring& processName);
  1. zombie.cpp中除main外的函数功能
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
/* 用于接收server返回的信息。作为例程,在
onClientConnect中以新线程的方式被调用 */

DWORD WINAPI t_ping(LPVOID lpParams);

/* 用FindProcessId找到taskmgr的进程号,OpenProcess打开进程、
TerminateProcess杀死进程。作为例程,在zombie的main函数中以新线程方式被调用 */

DWORD WINAPI t_payloads(LPVOID lpParams);

/* 当连接成功时,获取本地PC信息并组装如下json串,
{
"ID": "",
"USER": "",
"IP": "",
"PORT": "",
"AV": "",
"VERSION": "",
"OS": ""
}
发送给server,然后开启一个线程接收server返回信息(直接丢弃)
*/

void onClientConnect();

/* client接收data,解密后,判断收到的命令是:"探活"、"命令执行"、
"是否允许任务管理器杀死client"、"下载并执行程序",以此执行相应操作 */

void onClientRecData(char* data);
  1. zombie.cpp中main函数逻辑

zombieMain


几个要点:

  • wget构建
  • 将zombie程序移动到系统目录下
  • 杀死taskmgr的策略
  • 注册表接口编程

0x34 server

server端的主要逻辑在console.h中

大体的逻辑结构:先设置端口、超时时间,保存刚才的配置,然后在一个while(1)循环内读取命令or选项

代码略丑就不放出来,单纯通过堆砌while和SwitchCase完成逻辑,下面是zombie和server的执行效果

1617548323790

1617548221882

下面用简单的伪代码演示server逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
while(1)
switch(input)
1) 显示所有zombie的ID、IP、Port
输入想要继续操作的zombie_id,查看USER、VERSION、AV
switch(input_a)
0) 回主界面
1) 执行命令
2) 下载和执行程序
3) 杀死taskmgr开关

2) 配置保存,保存刚才设置的端口和web页面中查询的IP
保存完后下次会读取配置

3) 刷新,如果zombie断连则数据会从全局容器中删除

4) 最小化
选择后我们便可以关闭console,图标会保留在右下角的任务窗口中,双击任务窗口console又会显示

5) 退出

0x04 功能设计要点

  • 命令传输、执行
  • 文件下载、执行
  • 操作注册表,自启动
  • 心跳探活
  • 通信加密
  • GUI
  • 定时杀死任务管理器
  • 真实IP获取
  • 获取Antivirus信息
  • zombie数据保存

0x05 关键语法及技术

  1. 宏的使用
    • #pragma once:让文件编译一次,防止多次引入,有别于#ifndef [8]
    • #pragma comment (lib, "lib_name"):链接对应的库,这样就不用再手动设置工程settings
    • #pragma region(endregion):pragma region (endregion) 是一个Visio Studio Code Editor中的命令,来定义可以扩展和收缩的代码区域的开头和结尾,可以用来收缩或者展开一段代码。
  2. cin.ignore():吃掉一个字符,比如最后的\n,防止干扰下一次输入
  3. 这里的^并非c#扩展,而是c++/cli中的语法,c++/cli是标准c++的超集,该语法拥有垃圾回收特性
  4. gcnew(garbage collection new),带有gc特性的new,配合上述3使用
  5. 注册表函数接口(后续开一篇blog写)

0x06 总结

  1. 虽然头文件会被包含进cpp文件,但函数实现写在头文件里似乎不是一个好的习惯
  2. 代码逻辑略为混乱,尤其在命令执行模式和下载执行模式那一块,如果软件定位是一项目一重写那也未尝不可,但如果不是则这么写后期的维护工作量会很大
  3. 加密是必要的,可以采用不同的对称加密方式进行加密(AES、DES等)
  4. 通过注册表操作属于持久化的一种形式,持久化是后渗透的另一个大项
  5. 多线程编程思想,不阻塞主逻辑,在t_ping接收server返回消息和b_payload杀死taskmgr中体现
  6. 良好的开关设计思想,体现在杀死taskmgr的逻辑中
  7. 隐藏方式(不弹窗)执行命令
  8. json和ini都运用到库,有效提高开发效率
  9. 参数的合理判断,多处对于输入参数的判断采用string输入后转int判断的方式,增强容错性
  10. 配置存储,下一次运行直接读取先前保存的配置

0x07 参考资料

[1] awesome rat

[2] AHXR/ghost

[3] AHXR/ahxrlogger

[4] AHXR/ahxrwinsocket

[5] MITRE ATT&CK

[6] Why is implementation code in header files?

[7] windows中常见后门持久化方法总结

[8] #pragma once用法总结