一個簡單掃描器的實現

這是很久以前的一個代碼了。

網絡掃描,掃描器這些名詞想必大家並不陌生,踩點啥的必備工具。掃描的方法多種多樣,目的也多種多樣,本菜鳥就隨便找了找資料簡單介紹一下:

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編程基礎的人都能看懂,我就不過多做解釋了。

發佈了41 篇原創文章 · 獲贊 42 · 訪問量 35萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章