Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
botnet-hackpack — 使用 C 语言构建一个基础的命令与控制(C2)僵尸网络 | Kitploit
工具/GitHubGitHub/treehacks/botnet-hackpack
持久化机制恶意软件分析命令与控制学习与教育红队Payload 开发
GitHubtreehacks/botnet-hackpack

botnet-hackpack

使用 C 语言构建一个基础的命令与控制(C2)僵尸网络

查看仓库
40395167年前Kitploit 审核通过

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享

简单 C&C 僵尸网络

本 hackpack 将引导你实现一个基本的框架,它从根本上来刻画僵尸网络的特征。如果你之前用过 C 语言,可能会更容易上手。如果你喜欢这个教程,请务必给这个仓库点个 star!

注意:不要将在本教程中学到的任何东西用于恶意目的。本 hackpack 仅仅是对僵尸网络的案例研究,仅用于教育目的。在本 hackpack 中学到的概念具有广泛的应用场景(基本上任何与网络相关的事情都是如此)。最重要的是,本 hackpack 仅用于本地测试和部署(所以请不要将你基于本 hackpack 构建的任何东西分享给其他黑客)。隐私很重要,请尊重它。

什么是僵尸网络?

在构建僵尸网络之前,理解什么是僵尸网络很重要。僵尸网络是一组能够远程接收命令并在本地执行命令的计算机网络。它们还可以选择将信息中继回网络中的其他节点。它们被用于各种用途,从分布式拒绝服务攻击到广泛部署间谍软件。你可能以前听说过很多僵尸网络。最著名的也许是 Mirai 和 Gameover Zeus,它们分别控制了 3,800 台和 360 万台物联网设备。僵尸网络在执行特定任务的方式上存在很大差异。但为了成功构建我们的僵尸网络,我们需要确保我们的工作网络具备以下特性。

我们的僵尸网络应该:

  1. 包含一个控制网络中所有其他节点的主节点
  2. 在主机上部署伪装的恶意软件/从节点
  3. 将命令从主节点传输到从节点,执行命令,并将输出返回给主节点

这种结构是所谓的命令与控制(C&C)僵尸网络的特征。这类僵尸网络有一个主服务器和许多从服务器。然而,这种僵尸网络风格已经过时,可以通过切断对主域名的访问来轻松摧毁。更新更复杂的僵尸网络采用点对点架构,其中管理员权限分布在网络中的所有节点或部分节点上。这类僵尸网络让安全专家非常头疼,因为没有集中的控制点,而且它们可以增长到数百万个节点。摧毁这样的僵尸网络本身就是一篇有趣的读物。然而,为了本 hackpack 的目的,我们让事情保持简单。我们将为 C&C 僵尸网络实现一个简单的从节点。

alt tag

实现

主节点

本 hackpack 主要涉及实现客户端恶意软件。对于主服务器,我们可以使用一个名为 Netcat 的开源 TCP 服务器。Netcat 与僵尸网络无关。它只是一个方便且成熟的工具,我们可以重新利用它来向客户端发送文本数据包以及从客户端接收文本数据包(主节点本质上就是这样)。我对 netcat 服务器稍作调整,并将其编译成一个名为 "master" 的二进制文件。这里不需要更多工作了!我们的主节点已经准备好使用了。

从节点

让我们继续更有趣的部分:接收并执行远程命令(我们稍后再考虑伪装我们的恶意软件)。这里的目标是让我们的从节点尽可能简单,并遵循上述详细要求。请注意,lib/macros.h 中定义了许多常量,请随意使用它们。所有已实现的函数签名都能在 lib/connect.h 或 lib/utils.h 中找到。

1. 初始化

打开 bot.c 文件。当在我们的服务器上启动一个新节点时,我们应该给它命名,这样主节点才知道要向哪些客户端部署命令。有很多命名约定可以使用。使用 IP 地址可能是最好的,因为它是每个客户端的唯一标识符。然而,为了让普通人更容易阅读,让我们使用计算机的用户名。使用 C 函数 getenv() 并传入参数 "USER",会返回计算机存储在 USER 环境变量中的内容。这是存储用户用户名的地方之一,所以让我们使用它。另外,既然你的从节点正在运行,让我们找到主节点。为此,我们必须知道主节点的 IP 地址。每个网络设备都有一个 IP 地址。它负责标识其他节点和位置寻址。此外,主节点可以有许多服务器在不同端口上运行。所以,我们不仅要连接到主节点,还应该指定正确的端口。这个端口由主节点选择,但可以更改。在本 hackpack 中,我们想在本地测试。所以,我们将使用你的计算机作为我们的网络。每台计算机的本地 IP 地址("localhost" 也解析到该地址)是 "127.0.0.1"。在 master 中,我指定它在端口 9999 上运行。有了这三样东西(主节点 IP 地址、主节点端口和从节点名称),我们就可以在服务器和客户端之间启动一条称为套接字的通信管道。将这三个参数传递给 init_socket() 函数来创建一个套接字。init_function() 不是 C 的内置命令。相反,我们需要实现它。然后,我们需要在栈上分配一些空间来保存传入的消息。让我们在这里非常慷慨,使用大约 10KB 的栈空间。将此栈指针命名为 msg。最后,有一个 printf 语句来表示一切正常。

char* name = //Get the client's username and store it in name
int channel = //initiate a channel given SERVER, PORT, and name;
//Allocate stack space of size CMD_LENGTH to hold data of type char. Call the stack pointer msg
printf("%s joining the botnet\n", name);

现在切换到 lib/connect.c。让我们实现 init_channel()。首先,我定义了一个名为 msg 的栈字符缓冲区,长度为 CMD_LENGTH,以及一个特殊的 C 网络结构体 server,它保存了我们与主节点连接的信息。首先,将你传入的 IP 地址从人类可读的格式(带数字和点)转换为网络字节序的二进制格式。这是使用套接字库中一个特殊的 C 函数 inet_addr() 完成的。它只需接收一个 IP 地址,并输出网络可用的二进制形式。在 C 中,我们可以通过填充一个名为 sockaddr_in 的结构体的字段来轻松指定一个网络。我们的该结构体实例名为 server。我们需要填充这个结构体的 3 个字段:server.sin_addr.s_addr(主节点 IP 地址)、server.sin_family(一个指定通信域的单字节值)和 server.sin_port(我们将要连接的主节点上的端口)。sin_family 可以使用套接字库提供的 C 宏。通常,就像本例一样,我们将此字段设置为 AF_INET。这意味着我们的连接通过 IP 地址来标识网络节点,这正是我们想要的。然而,也可以使用 PF_INET,它与 AF_INET 类似,但指定网络可以使用协议内的任何内容来标识特定节点。关于为什么两者都存在,也有很多所谓的历史原因,但那是我真正不知道也不关心的事情。就用 AF_INET 吧。最后,设置服务器端口时,我们必须通过一个名为 htons()(主机到网络短整型)的特殊函数传递 port。这会将数据从主机字节序转换为网络字节序。这个字节序混乱与所谓的“字节序”(Endianness)有关。你可以在这里了解更多。

int init_channel (char *ip, int port, char *name) {
	char msg[CMD_LENGTH];
	struct sockaddr_in server;

	server.sin_addr.s_addr = //convert the ip to network byte order
	server.sin_family = //set the server's communications domain
  server.sin_port = //convert port to network byte order
  
  int channel = //define a SOCK_STREAM socket
  
  if(channel < 0) {
    perror ("socket:");
    exit(1);
  }
  
  int connection_status = //use the defined channel to connect the slave to the master server
  
  if (connection_status < 0) {
    perror ("connect:");
    exit(1);
  }

  //send a greeting message back to master by loading a string into msg (hint: snprintf will come in handy)
  respond (channel, msg);
  return channel;
}

2. 监听消息

一旦从节点连接到主节点,它就需要持续监听消息,并在收到命令时立即执行。所以,让我们使用一个无限 while 循环来接收和解析这些消息。在 bot.c 中,printf 语句下方,添加一个无限 while 循环,按顺序调用两个函数:recieve() 和 parse()。两个函数都将 channel 和 msg 栈缓冲区作为参数。你可以在 lib/utils.h 中找到它们的函数签名。这应该看起来像这样:

Infinite Loop {
  recieve(...);
  parse(...);
}

转到 utils.c 来实现 recieve() 和 respond()。recieve() 从通道中获取消息,respond() 通过通道发送消息。respond() 的参数是套接字地址 s 和我们的栈缓冲区 msg_buf。我们想使用 C 函数 write() 将栈缓冲区中的内容写入通道并返回其状态。write() 需要 3 个参数:套接字地址、消息缓冲区和消息长度。

int respond(int s, char *msg_buf) {
    //write the contents of msg_buf into socket s and return status
}

recieve() 也是一个简单的辅助函数。重置 msg 缓冲区(提示:使用 memset())。现在,调用套接字库函数 read() 来读取消息。read() 需要 3 个参数:套接字地址、消息缓冲区和消息的最大预期长度。

int recieve(int s, char *msg) {
    //reset the msg buffer
    int read_status = //read contents of socket s into msg
    if (read_status) {
      perror("log:");
      exit(1);
    }
    return 0;
}

3. 执行命令

快完成了!我们的僵尸网络目前还很无聊。它只能通过套接字接收和传输消息。让我们让它真正在终端上执行它接收到的内容。我们首先实现函数 parse()。它的功能正如其名:解析命令。我们可以做一些简单的错误检查,看看消息格式是否正确。另外,我们要静默忽略那些接收到的但不是发给它的消息。消息将由主节点格式化为(僵尸网络名称):(要执行的命令)。前者我已经帮你做了。如果两个检查都通过,让我们将命令传递给 execute() 函数。

int parse (int s, char *msg, char* name) {
  char *target = msg;
  
  //check whether the msg was targetted for this client. If no, then silently drop the packet by returning 0
  
  char *cmd = strchr(msg, ':');
  if (cmd == NULL) {
    printf("Incorrect formatting. Reference: TARGET: command");
    return -1;
  }
 
  //adjust the cmd pointer to the start of the actual command
  //adjust the terminated character to the end of the command
  //print a local statement detailing what command was recieved
  
  execute (s, cmd);
  return 0;
}

现在是酷炫的部分。execute() 应该将收到的任何命令通过管道传给终端,并将任何输出通过套接字写回主节点。创建一个栈缓冲区来存储每行输入。然后使用 C 函数 popen() 运行输入并将输出存储在文件 f 中(此时有很多方法可以处理这个问题。你可以自定义你的僵尸网络,利用主节点的输入做一些非常酷的事情,并与僵尸网络中的其他节点执行一些自主协作/更新。请随意发挥你的创造力。我们现在只会坚持我们简单直接的目标)。逐行解析 f,并将所有内容通过套接字发送出去。关闭 f,你就完成了!

int execute (int s, char *cmd) {
  FILE *f = //use popen to run the command locally
  if (!f) return -1;
  while (!feof (f)) {
    //parse through f line by line and send any output back to master
  }
  fclose(f);
  return 0;
}

使用以下终端命令编译你的新僵尸网络:

gcc -lcurl lib/connect.c lib/utils.c bot.c -o bin/slave

在一个终端窗口中运行 bin/master,在其他窗口中运行 bin/slave。输入 (从节点用户名):(远程终端命令) 格式的命令。恭喜!你刚刚构建了一个僵尸网络!

4. 伪装你的恶意软件

在伪装和部署恶意软件方面,你可以做一些非常酷的事情。事实上,这本身就是一整个领域。一个例子是将恶意软件伪装成图片。让我们使用一张熊猫图片。我在 utils.c 中添加了一个简单函数,它会获取一张熊猫图片并在预览中显示。这给用户一种正在打开熊猫图片的印象,而实际上用户正在运行你的恶意软件。要添加此功能,请在 bot.c 中包含以下代码行:

char* open_cmd = alias_img();
system(open_cmd);
free(open_cmd);

接下来,右键单击任何图像并选择“显示简介”。对 bin/slave 也执行同样的操作。将图像缩略图拖到 bin/slave 的可执行文件缩略图上。这应该会改变它在桌面上的外观。但是,我们仍然缺少典型的 .png 文件后缀。将你的可执行文件重命名为

panda⒈png

现在,这看起来像一个 png 文件。然而,我们使用了 Unicode 字符 "1." 而不是 "." 来掩盖这仍然是一个 Unix 可执行文件的事实。你还可以使用许多更可信的 Unix 技巧,例如使用“从左到右覆盖”字符来掩盖可执行文件名。在更极端的情况下,你可以在图片和文件宏中嵌入代码,以便在打开宿主文件时并发运行(有点像特洛伊木马……)。然而,因为 TreeHacks 没有网络罪犯,我们不应该太在意这些技术 ;)。

5. 扩展

既然你有了一个完全可用的僵尸网络,有很多扩展可以挑战自己。我们的僵尸网络仍然相当无趣。除非用户每次都点击它,否则它做不了什么。这里有一些建议:

1. 实现持久化

如果僵尸网络能设法在计算机上存留,即使计算机关机后也能再次运行,那么它们对攻击者来说就真正成为可靠的恶意活动来源。尝试琢磨如何让你的从节点在每次启动时都能重新启动。这样,一旦用户点击了恶意软件,他的/她的计算机就被感染了,直到他/她清理干净。实现这一目标的一个建议是将你的可执行进程变成一个守护进程。然后,生成一个配置文件,将你的可执行文件添加到应在启动时执行的守护进程列表中(云存储应用、团队消息平台等已经在这样做)。在这里了解更多相关信息。

2. 实现点对点网络

实现点对点网络无非是重新安排网络设计。然而,P2P 网络的关键在于管理员/攻击者可以通过网络上的任何节点实现主控权。因此,攻击者应该拥有某种主密钥和加密登录方式,使其能够主控任何节点。在这里阅读更多关于点对点网络如何工作的内容。

3. 添加多个层次的误导

你实现的主从结构不是非常安全。通过杀死主节点,从节点可以很容易地被释放。最理想的情况是切换到 P2P 设计。然而,你也可以略微提高主节点的安全性,将其命令随机通过一系列攻击者控制的机器人(bots)进行转发,然后再部署到僵尸网络上。这使得专家更难定位指挥中心,也更难追踪攻击者节点与客户端节点之间的僵尸网络调用。

4. 探索合适的网络协议
下载工具