1.網絡掃描簡介
網絡掃描是一種自動化程序,用於檢測遠程或本地主機的弱點和漏洞。漏洞掃描是入侵防范最基本的工作,攻擊者正式利用各種漏洞入侵系統。借助自動化的掃描工作,在攻擊者之前發現漏洞問題,並給予相應的修正程序。
一名攻擊者入侵系統,一般分為四個步驟:系統發現,漏洞探測,漏洞利用和痕跡清除。
本文的重點就是在於系統發現與漏洞探測方面。
2.端口掃描技術
端口掃描能夠用來查找目標主機已開放的端口,包括TCP和UDP端口。當前針對TCP端口的掃描技術有三種,分別為:全連接掃描,SYN掃描和FIN掃描。針對UDP端口的掃描技術一般是采用ICMP報文中端口不可達的信息來識別UDP端口是否開放。
- 全連接掃描
作為最基本的TCP掃描方式,它利用connect()函數,向每一個目標主機的端口發起連接,若端口處於偵聽狀態,則函數返回成功;否則可以判斷出該端口沒有開放。
全連接掃描有兩個好處,首先它不許要得到系統權限,任何用戶都可以使用,另外一個是,可以采用多線程並發掃描,掃描的速度也不慢。
- SYN掃描
SYN掃描通常認為是“半開放”的掃描,這是因為掃描程序不必打開一個完全的TCP連接。掃描程序構造並發送一個SYN數據包,執行TCP三次握手的第一步,若返回一個SYN|ACK數據包,則表示端口處於偵聽狀態,否則返回一個RST數據包,表示端口沒有開放。這種方式需要root權限,並且它不會在主機上留下記錄。
- FIN掃描
FIN掃描與SYN掃描類似,也是構造數據包,通過識別回應來判斷端口狀態。發送FIN數據包后,如果返回一個RST數據包,則表示端口沒有開放,處於關閉狀態,否則,開放端口會忽略這種報文。這種方式難以被發現,但是這種方式可能不准確。
- UDP的ICMP端口不可達掃描
UDP掃描的方法並不常見。雖然UDP協議相對比較簡單,無論UDP端口是否開放,對於接受到的探測包,他本身默認是不會發送回應信息的。不過UDP掃描有一種方法,就是利用主機ICMP報文的回應信息來識別,當一個關閉的UDP端口發送一個數據包時,會返回一個ICMP_PORT_UNREACH的錯誤。但由於UDP不可靠,ICMP報文也是不可靠,因此存在數據包中途丟失的可能。
3.具體實現
首先介紹全連接掃描模塊的設計。
具體流程:
- 判斷端口是否處於掃描范圍
- 初始化套接字,發起連接
- 調用getservbyport函數,獲取端口映射的TCP服務
- 輸出結果
- 退出程序
實現代碼:
//全連接掃描 void scan(char *ip,int minport,int maxport) { if(maxport<minport){ printf("error:can't scan int this condition\n"); return; } printf("test ip:%s,port:%d,port:%d\n",ip,minport,maxport); int i=0; printf("Now start scanning.....\n"); for(i=minport;i<maxport;++i){ struct sockaddr_in clientaddr; clientaddr.sin_family=AF_INET; clientaddr.sin_addr.s_addr=inet_addr(ip); clientaddr.sin_port=htons(i); int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0){ perror("error:create socket\n"); return; } int error=connect(sock,(struct sockaddr*)&clientaddr,sizeof(clientaddr)); if(error<0){ printf("Port:%5d | Tatus:closed\n",i); fflush(stdout);//清空緩存 } else{ struct servent* sptr; if((sptr=getservbyport(htons(i),"tcp"))!=NULL) printf("Port:%5d | Server:%s | Status:open\n",i,sptr->s_name); else printf("Port:%5d | Status:open\n",i); } close(sock); } }
可以對上述代碼進行改進,可以加入多線程的的方式來解決掃描速度較慢的問題。
在這里需要定義一個結構體,來為線程傳遞必要的參數。
typedef struct threadpara{ char ip[20]; int minport; int maxport; }tp;
這樣的話,我們就可以定義一個線程函數用於連接不同的端口。
void *conntthread(void *threadp) { struct threadpara *pThreadpara=(struct threadpara*)threadp; int i; printf("minport:%d maxport:%d thread....\n",pThreadpara->minport,pThreadpara->maxport); for(i=pThreadpara->minport;i<pThreadpara->maxport;++i){ struct sockaddr_in clientaddr; clientaddr.sin_family=AF_INET; clientaddr.sin_addr.s_addr=inet_addr(pThreadpara->ip); clientaddr.sin_port=htons(i); int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0){ perror("error:create socket\n"); return 0; } int error=connect(sock,(struct sockaddr*)&clientaddr,sizeof(clientaddr)); if(error<0){ printf("Port:%5d | Tatus:closed\n",i); fflush(stdout);//清空緩存 } else{ struct servent* sptr; if((sptr=getservbyport(htons(i),"tcp"))!=NULL) printf("Port:%5d | Server:%s | Status:open\n",i,sptr->s_name); else printf("Port:%5d | Status:open\n",i); } close(sock); } pthread_exit(NULL); }
接下來,就是在主掃描程序中添加啟動線程的模塊,主要代碼如下:
int count=(maxport-minport)/thread_num; printf("count:%d num:%d ip:%s,port:%d,port:%d\n",count,thread_num,ip,minport,maxport); printf("Now start scanning....\n"); int i; for(i=0;i<count;++i){ struct threadpara threadpara; strcpy(threadpara.ip,ip); threadpara.minport=minport+count*i; if(i==thread_num-1) threadpara.maxport=maxport; else threadpara.maxport=threadpara.minport+count; printf("i:%d maxport:%d,minport:%d\n",i,threadpara.maxport,threadpara.minport); int temp; pthread_t threadid; temp=pthread_create(&threadid,NULL,conntthread,(void*)&threadpara); if(temp<0) printf("create thread error...\n"); pthread_join(threadid,NULL); }
接下來,再介紹一個SYN半連接掃描的例子。
掃描模塊的設計流程圖如下:
具體流程:
- 初始化套接字
- 設置信號量以及全局變量flag(控制掃描狀態)
- 啟動接收線程
- 構造SYN數據包,開始掃描
首先,構造並發送syn包模塊的代碼如下:
int sendSyn(int sendSocket,u_long sourceIP,struct sockaddr_in *dest) { unsigned char netPacket[sizeof(struct tcphdr)]; struct tcphdr* tcp; u_char * pPseudoHead; u_char pseudoHead[12+sizeof(struct tcphdr)]; u_short tcpHeadLen; memset(netPacket,0,sizeof(struct tcphdr)); //構造syn數據包 tcpHeadLen=htons(sizeof(struct tcphdr)); tcp=(struct tcphdr*)netPacket; tcp->source=htons(10240); tcp->dest=dest->sin_port; tcp->seq=htonl(12345); tcp->ack_seq=0; tcp->doff=5; tcp->syn=1; tcp->window=htons(10052); tcp->check=0; tcp->urg_ptr=0; pPseudoHead=pseudoHead; memset(pPseudoHead,0,12+sizeof(struct tcphdr)); memcpy(pPseudoHead,&sourceIP,4); pPseudoHead+=4; memcpy(pPseudoHead,&(dest->sin_addr),4); pPseudoHead+=5; memset(pPseudoHead,6,1); pPseudoHead++; memcpy(pPseudoHead,&tcpHeadLen,2); pPseudoHead+=2; memcpy(pPseudoHead,tcp,sizeof(struct tcphdr)); tcp->check=checksum((u_short*)pPseudoHead,sizeof(struct tcphdr)+12); int temp=sendto(sendSocket,netPacket,sizeof(struct tcphdr),0,(struct sockaddr*)dest,sizeof(struct sockaddr_in)); return temp; }
TCP校驗和代碼:
unsigned short checksum(unsigned short *addr,int len)//TCP校驗和 { int nleft=len; int sum=0; unsigned short *w=addr; unsigned short answer=0; while(nleft>1){ sum+=*w++; nleft-=2; } if(nleft==1){ *(unsigned char*)(&answer)=*(unsigned char*)w; sum+=answer; } sum=(sum>16)+(sum&0xffff); sum+=(sum>>16); answer=~sum; return(answer); }
接收返回數據包的代碼如下:
void* recv_packet(void* arg) { struct sockaddr_in *in1; char *srcaddr; int size; u_char readbuff[1600]; struct sockaddr from; int from_len; struct tcphdr* tcp; struct servent* sptr; tcp=(struct tcphdr*)(readbuff+20); int fd=*((int*)arg); while(1){ if(flag==1) return 0; size=recvfrom(fd,(char*)readbuff,1600,0,&from,(socklen_t *)&from_len); if(size<40) continue; printf("data is ok...ack:seq:%u,destport:%u\n",ntohl(tcp->ack_seq),ntohs(tcp->dest)); if((ntohl(tcp->ack_seq)!=123456)||(ntohs(tcp->dest)!=10240)) continue; if(tcp->rst&&tcp->ack){ printf("port:%5d | Status: closed\n",htons(tcp->source)); continue; } if(tcp->ack&&tcp->syn){ in1=(struct sockaddr_in*)&from; srcaddr=inet_ntoa(in1->sin_addr); printf("SERVER:%s\r",srcaddr); } if((sptr=getservbyport(tcp->source,"tcp"))!=NULL){ printf("Port:%d Server:%s | Status: open\n",htons(tcp->source),sptr->s_name); } else printf("Port:%5d | Status:open\n",htons(tcp->source)); fflush(stdout); continue; } }
掃描程序的主要程序:
void synscan(char* ip,int minport,int maxport) { pthread_t tid; struct ifreq if_data; int fd; u_long addr_p; struct sockaddr_in clientaddr; fd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);//SOCK_RAW原始套接子(數據包式) if(fd<0){ perror("error:create raw socket\n"); return; } signal(SIGALRM,Alarm); strcpy(if_data.ifr_name,"eth0"); if(ioctl(fd,SIOCGIFADDR,&if_data)<0){ perror("error:ioctl\n"); return; } memcpy((void*)&addr_p,(void*)&if_data.ifr_addr.sa_data+2,4); bzero(&clientaddr,sizeof(clientaddr)); clientaddr.sin_family=AF_INET; clientaddr.sin_addr.s_addr=inet_addr(ip); printf("Now start scannning...\n"); int err; err = pthread_create(&tid,NULL,recv_packet,(void*)&fd); if(err<0) perror("error:create thread\n"); int i=minport; for(;i<maxport;++i){ clientaddr.sin_port=htons(i); if(sendSyn(fd,addr_p,&clientaddr)<0){ perror("error:send syn\n"); } alarm(3); } pthread_join(tid,NULL); }
最后附上之前的IP地址掃描代碼!