這是很久以前的一個代碼了。
網路掃描,掃描器這些名詞想必大家並不陌生,踩點啥的必備工具。掃描的方法多種多樣,目的也多種多樣,本菜鳥就隨便找了找資料簡單介紹一下:
1 主機存活掃描技術
主機掃描的目的是確定在目標網路上的主機是否可達。這是資訊收集的初級階段,其效果直接影響到後續的掃描。Ping就是最原始的主機存活掃描技術,利用icmp的echo欄位,發出的請求如果收到回應的話代表主機存活。
常用的傳統掃描手段有:
1. ICMP Echo掃描 精度相對較高。通過簡單地向目標主機發送ICMP Echo Request 資料包,並等待回複的ICMP Echo Reply 包,如Ping。
2. ICMP Sweep 掃描:sweep這個詞的動作很像機槍掃射,icmp進行掃射式的掃描,就是並發性掃描,使用ICMP Echo Request一次探測多個目標主機。通常這種探測包會並行發送,以提高探測效率,適用於大範圍的評估。
3. Broadcast ICMP掃描:廣播型icmp掃描,利用了一些主機在icmp實現上的差異,設定ICMP請求包的目標地址為廣播位址或網路地址,則可以探測廣播域或整個網路範圍內的主機,子網內所有存活主機都會給以回應。但這種情況只適合於UNIX/Linux系統。
4. Non-Echo ICMP掃描:在ICMP協議中不光光只有ICMP ECHO的ICMP查詢資訊類型,在ICMP掃描 技術中也用到Non-ECHO ICMP技術(不僅僅能探測主機,也可以探測網路裝置如路由)。利用了ICMP的服務類型(Timestamp和Timestamp Reply 、Information Request和Information Reply 、Address Mask Request 和Address Mask Reply)。
2. 連接埠掃描技術
在完成主機存活性判斷之後,就應該去判定主機開放通道的狀態,連接埠就是在主機上面開放的通道,0-1024為知名連接埠,連接埠總數是65535。連接埠實際上就是從網路層映射到進程的通道。通過這個關係就可以掌握什麼樣的進程使用了什麼樣的通訊,在這個過程裡面,能夠通過進程取得的資訊,就為尋找後門、瞭解系統狀態提供了有力的支撐。常見流行的連接埠掃描技術通常有:
(1) TCP掃描:
利用三向交握過程與目標主機建立完整或不完整的TCP串連。
TCP connect()掃描: tcp的前序裡,有6個串連標記,分別是urg、ack、psh、rst、syn、fin。通過這些串連標記不同的組合方式,可以獲得不同的返回報文。例如,發送一個syn置位的報文,如果syn置位瞄準的連接埠是開放的,syn置位的報文到達的連接埠開放的時候,他就會返回syn+ack,代表其能夠提供相應的服務。我收到syn+ack後,返回給對方一個ack。這個過程就是著名的三向交握。這種掃描的速度和精度都是令人滿意的。
Reverse-ident掃描:這種技術利用了Ident協議(RFC1413),tcp連接埠113.很多主機都會啟動並執行協議,用於鑒別TCP串連的使用者。
identd 的操作原理是尋找特定 TCP/IP 串連並返回擁有此串連的進程的使用者名稱。它也可以返回主機的其他資訊。但這種掃描方式只能在tcp全串連之後才有效,並且實際上很多主機都會關閉ident服務。
Tcp syn掃描:向目標主機的特定連接埠發送一個SYN包,如果連接埠沒開放就不會返回syn+ack,這時會給你一個rst,停止建立串連。由於串連沒有完全建立,所以稱為半開放掃描。但由於syn flood作為一種ddos攻擊手段被大量採用,因此很多防火牆都會對syn報文進行過濾,所以這種方法並不能總是有用。
其他還有fin、NULL、Xmas等掃描方式。
(2 ) UDP掃描
由於現在防火牆裝置的流行,tcp連接埠的管理狀態越來越嚴格,不會輕易開放,並且通訊監視嚴格。為了避免這種監視,達到評估的目的,就出現了秘密掃描。這種掃描方式的特點是利用UDP連接埠關閉時返回的ICMP資訊,不包含標準的TCP三向交握協議的任何部分,隱蔽性好,但這種掃描使用的資料包在通過網路時容易被丟棄從而產生錯誤的探測資訊。
但是,UDP掃描方式的缺陷很明顯,速度慢、精度低。UDP的掃描方法比較單一,基礎原理是:當你發送一個報文給udp連接埠,該連接埠是關閉狀態時,連接埠會返回給一個icmp資訊,所有的判定都是基於這個原理。如果關閉的話,什麼資訊都不發。
Traceroute掃描:tracert 向30000以上的高連接埠(一般認為,主機的30000以上高連接埠利用率非常低,任何主機都不會輕易開放這種高連接埠,預設都是關閉的)。如果對方連接埠關閉,會返回給icmp資訊,根據這個往返時間,計算跳數、路徑資訊,瞭解延時情況。這是tracerote原理,也是從這個原理上演變出來udp掃描技術。
使用udp掃描要注意的是1、udp狀態、精度比較差,因為udp是不連線導向的,所以整個精度會比較低。2、udp掃描速度比較慢,tcp掃描開放1秒的延時,在udp裡可能就需要2秒,這是由於不同作業系統在實現icmp協議的時候為了避免廣播風暴都會有峰值速率的限制(因為icmp資訊本身並不是傳輸載荷資訊,不會有人拿他去傳輸一些有價值資訊。作業系統在實現的時候是不希望icmp報文過多的。為了避免產生廣播風暴,作業系統對icmp報文規定了峰值速率,不同作業系統的速率不同)利用udp作為掃描的基礎協議,就會對精度、延時產生較大影響。
當前在滲透測試過程中對於連接埠的掃描是非常靈活的,06年的黑帽大會上,就有人利用了開發了工具探測網內哪台主機開啟了80連接埠,這樣的技術在當前的互連網上利用的非常普遍。
本段代碼實現的是最簡單的方法TCP connect掃描的方法,這種掃描原理上不夠安全,因為他要與被掃描的機器建立串連就需要三向交握你在掃描目標機器的時候同時也將自己的機器資訊暴露給目標機器,很容易被防火牆記錄,並且最大的一個缺點就是因為要經過三次確認發送,時間比較長。下面我們看看代碼:
#include <stdio.h>#include <winsock.h>#pragma comment(lib,"Ws2_32.lib")USHORT checksum(USHORT* buff, int size){unsigned long cksum = 0;while(size>1){cksum += *buff++;size -= sizeof(USHORT);}// 是奇數if(size){cksum += *(UCHAR*)buff;}// 將32位的chsum高16位和低16位相加,然後取反cksum = (cksum >> 16) + (cksum & 0xffff);cksum += (cksum >> 16); // ??? return (USHORT)(~cksum);}typedef struct icmp_hdr{ unsigned char icmp_type; // 訊息類型 unsigned char icmp_code; // 代碼 unsigned short icmp_checksum; // 校正和// 下面是回顯頭 unsigned short icmp_id; // 用來惟一標識此請求的ID號,通常設定為進程ID unsigned short icmp_sequence; // 序號 unsigned long icmp_timestamp; // 時間戳記} ICMP_HDR, *PICMP_HDR;int SetTimeout(SOCKET s, int nTime, BOOL bRecv){int ret = ::setsockopt(s, SOL_SOCKET, bRecv ? SO_RCVTIMEO : SO_SNDTIMEO, (char*)&nTime, sizeof(nTime));return ret != SOCKET_ERROR;}int Computer(char szDestIP[30]) //掃描主機是否存活{WSADATA wsaData;WORD wVersionRequested=MAKEWORD(1,1);if (WSAStartup(wVersionRequested , &wsaData)){printf("Winsock Initialization failed.\n");exit(1);}SOCKET sRaw=::socket(AF_INET,SOCK_RAW,IPPROTO_ICMP);SetTimeout(sRaw,1000,TRUE);SOCKADDR_IN dest;dest.sin_family=AF_INET;dest.sin_port=htons(0);dest.sin_addr.S_un.S_addr=inet_addr(szDestIP);char buff[sizeof(ICMP_HDR)+32];ICMP_HDR * pIcmp=(ICMP_HDR *)buff;pIcmp->icmp_type=8;pIcmp->icmp_code=0;pIcmp->icmp_id=(USHORT)::GetCurrentProcessId();pIcmp->icmp_checksum=0;pIcmp->icmp_sequence=0;memset(&buff[sizeof(ICMP_HDR)],'E',32);USHORT nSeq=0;char revBuf[1024];SOCKADDR_IN from;int nLen=sizeof(from);static int nCount=0;int nRet;/*if (nCount++==4){break;}*/pIcmp->icmp_checksum=0;pIcmp->icmp_timestamp=::GetTickCount();pIcmp->icmp_sequence=nSeq++;pIcmp->icmp_checksum=checksum((USHORT *)buff,sizeof(ICMP_HDR)+32);nRet=::sendto(sRaw,buff,sizeof(ICMP_HDR)+32,0,(SOCKADDR *)&dest,sizeof(dest));if (nRet==SOCKET_ERROR){printf("sendto() failed:%d\n",::WSAGetLastError());return -1;}nRet=::recvfrom(sRaw,revBuf,1024,0,(sockaddr *)&from,&nLen);if (nRet==SOCKET_ERROR){printf("%s 主機沒有存活!\n",szDestIP);return -1;}printf("%s 主機存活!\n",szDestIP);closesocket(nRet);WSACleanup();return 0;}void Port(char adr[20]) //掃描存活主機連接埠{int mysocket,m,n;int pcount = 0; struct sockaddr_in my_addr;WSADATA wsaData;WORD wVersionRequested=MAKEWORD(1,1);printf("請輸入要掃描的連接埠範圍(例如1-1024):");scanf("%d-%d",&m,&n);if (WSAStartup(wVersionRequested , &wsaData)){printf("Winsock Initialization failed.\n");exit(1);}for(int i=m; i<n; i++){if((mysocket = socket(AF_INET, SOCK_STREAM,0)) == INVALID_SOCKET)exit(1);my_addr.sin_family = AF_INET;my_addr.sin_port = htons(i);my_addr.sin_addr.s_addr = inet_addr(adr);if(connect(mysocket, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == SOCKET_ERROR){printf("Port %d - 關閉\n", i);closesocket(mysocket);}else{pcount++;printf("Port %d - 開啟\n", i);}}printf("%d ports open on host - %s\n", pcount, adr);closesocket(mysocket);WSACleanup();}void change(int a,int b,int c,int d,char IP[20]) //IP轉換{char IPPort[4][4]={'\0'};char temp[2]={'.','\0'};itoa(a,IPPort[0],10); itoa(b,IPPort[1],10); itoa(c,IPPort[2],10); itoa(d,IPPort[3],10); strcat(IP,IPPort[0]);strcat(IP,temp);strcat(IP,IPPort[1]);strcat(IP,temp);strcat(IP,IPPort[2]);strcat(IP,temp);strcat(IP,IPPort[3]);}void main(){int a[4],b[4];loop1:printf("請輸入起始IP:");scanf("%d.%d.%d.%d",&a[0],&a[1],&a[2],&a[3]);if (a[0]>255||a[1]>255||a[2]>255||a[3]>255){printf("輸入的起始地址有誤!請重新輸入!\n");goto loop1;}loop2:printf("請輸入結束IP:");scanf("%d.%d.%d.%d",&b[0],&b[1],&b[2],&b[3]);if (b[0]>255||b[1]>255||b[2]>255||b[3]>255){printf("輸入的結束有誤!請重新輸入!\n");goto loop2;}while(!(a[0]==b[0]&&a[1]==b[1]&&a[2]==b[2]&&a[3]==(b[3]+1))){char IP[20]={'\0'};change(a[0],a[1],a[2],a[3],IP);if((Computer(IP))==0){Port(IP);}a[3]++;if (a[3]>=255){a[3]=0;a[2]++;}if (a[2]>=255){a[2]=0;a[1]++;}if (a[1]>=255){a[1]=0;a[0]++;}if (a[0]>=255){printf("地址溢出!\n"); break;}}}
代碼很簡單,有一點socket編程基礎的人都能看懂,我就不過多做解釋了。