一、TCP掃描技術
常用的端口掃描技術有很多種,如 TCP connect() 掃描 、TCP SYN 掃描、TCP FIN 掃描 等,網絡上也有很多文章專門介紹,比如 :http://www.antai-genecon.com/suml/zhishiyy/jingong/duankougj.htm 上就介紹了很多我的程序 所使用的最基本的掃描技術:TCP 掃描。
操作系統提供的 connect() 系統調用,用來與每一個感興趣的目標計算機的端口進行連接。如果端口處於偵聽狀態,那麼connect()就能成功。否則,這個端口是不能用的,即沒有提供服務。這個技術的一個最大的優點是,你不需要任何權限。系統中的任何用戶都有權利使用這個調用。另一個好處就是速度。如果對每個目標端口以線性的方式,使用單獨的connect()調用,那麼將會花費相當長的時間,你可以通過同時打開多個套接字,從而加速掃描。使用非阻塞 I/O 允許你設置一個低的時間用盡周期,同時觀察多個套接字。但這種方法的缺點是很容易被發覺,並且被過濾掉。目標計算機的logs文件會顯示一連串的連接和連接是出錯的服務消息,並且能很快的使它關閉。
作者提示:未經許可掃描他人的計算機端口屬非法行為。本程序只是展示一種端口掃描技術,請不得將其用於非法目的,否則後果自負。
二、程序簡介
為了提高掃描速度,本程序采用了多線程技術和非阻塞I/O的技術。程序的主界面是一個對話框,下面是程序框架示意圖:
1、全局變量:
以下是所有全局變量的定義:HWND g_hWnd = NULL; //處理消息的窗口句柄
2、StartScan 線程:
unsigned long g_ulAddr = INADDR_NONE; //掃描的主機地址
DWORD g_dwTimeOut = 1000; //連接超時時間,以ms計
bool g_bTerminate = false; //是否用戶發出結束掃描的標志
short g_nMaxThread = 200; //最大允許的掃描線程數,經試驗不宜大於200
short g_nThreadCount = 0; //當前正在掃描的進程數
這個線程完成的任務是啟動具體的掃描DoScanPort線程,對某一個端口進行掃描。該進程啟動的DoScanPort線程的最大數量在設定的值范圍內,如果線程數已達最大,則會等待某些線程結束後再啟動新的線程。如果用戶發出結束掃描信息,則不再啟動新的線程,而等待已啟動的線程結束後返回。下面是這線程的執行體代碼:DWORD WINAPI StartScan(LPVOID lpParam)
{
tag_PORTS* pScanParam = (tag_PORTS*)lpParam;
DWORD dwThreadId;
unsigned short i;
if (pScanParam->bSepecifiedPort)
{
for(i=0; i<=pScanParam->nCount; i++)
{
if (g_bTerminate)
{
break; //用戶已發出結束掃描命令
}
while(g_nThreadCount >= g_nMaxThread)
{
Sleep(10);
}
if (CreateThread(NULL,
0,
DoScanPort,
(LPVOID)new short(pScanParam->nArrOfPorts[i]),
0,
&dwThreadId) != NULL)
{
g_nThreadCount ++;
}
}
}
else
{
for(i=pScanParam->iStartPort; i<=pScanParam->iEndPort; i++)
{
if (g_bTerminate)
{
break; //用戶已發出結束掃描命令
}
while(g_nThreadCount >= g_nMaxThread)
{
Sleep(10);
}
if (CreateThread(NULL, 0, DoScanPort, (LPVOID)new short(i), 0, &dwThreadId) != NULL)
{
g_nThreadCount ++;
}
}
}
//等待各端口掃描線程結束
while (g_nThreadCount > 0)
{
Sleep(50);
}
::SendMessage(g_hWnd, SCAN_THREAD, STARTSCAN_COMPLETE, 0);
delete pScanParam;
return ERROR_SUCCESS;
}
3、DoScanPort 線程:
這個線程負責具體掃描指定的端口,並將結果SendMessage給主對話框。下面是其代碼:
DWORD WINAPI DoScanPort(LPVOID lpParam)
{
DWORD dwRet;
short nPort = *(short*) lpParam;
delete lpParam;
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock == INVALID_SOCKET)
{
AfxMessageBox("創建套接字失敗!");
dwRet = ERROR_CREATE_SOCKET;
}
else
{
unsigned long flag = 1;
if ((ioctlsocket(sock, FIONBIO, &flag) != 0))
{
AfxMessageBox("未能改為非阻塞模式!");
dwRet = ERROR_MODIFY_FIONBIO;
}
else
{
sockaddr_in severAddr;
severAddr.sin_family = AF_INET;
severAddr.sin_port = htons(nPort);
severAddr.sin_addr.S_un.S_addr = g_ulAddr;
connect(sock, (sockaddr*)&severAddr, sizeof(severAddr));
struct fd_set mask;
FD_ZERO(&mask);
FD_SET(sock, &mask);
struct timeval timeout;
timeout.tv_sec = g_dwTimeOut / 1000;
timeout.tv_usec = g_dwTimeOut % 1000;
switch(select(0, NULL, &mask, NULL, &timeout))
{
case -1:
dwRet = ERROR_SELECT;
break;
case 0:
dwRet = ERROR_SELECT_TIMEOUT;
break;
default:
dwRet = ERROR_SUCCESS;
};
}
closesocket(sock);
}
g_nThreadCount --;
if (dwRet == ERROR_SUCCESS)
{
::SendMessage(g_hWnd, SCAN_THREAD, DOSCAN_FIND_PORT, nPort);
}
else
{
::SendMessage(g_hWnd, SCAN_THREAD, DOSCAN_END_PORT, nPort);
}
return dwRet;
}
三、運行結果
本程序在VC6+WinXp下編寫調試運行正確,在Win98下運行正確。
在我的計算機掃描本機1-5000號端口,超時設置1000ms,200個最大線程數,約需要45秒。當超時設置再短一些時速度可達每秒150個端口的速度。
四、結束語
事實上,速度要想再提高,可能需要其它方法了。如果線程數開得過多,則由於線程的調度開銷過大,速度反而會降低。如果超時設置過短,可能引起掃描的結果不正確(這須視網絡情況決定),並且由於是多線程,超時等待的時間也可能小於因線程調度而等待的時間,則也不能提高速度。
另外我還發現了一個問題,就是掃描本機時,如果以IP地址127.0.0.1時,則139#端口沒有開放;如果設置為本機的實際IP時則139#端口是開放的,這不知是何原因,還望高手指點。
本程序還有一個需要改進的地方,就是不象大多數端口掃描器可掃描指定的IP段,而只設計成掃描指定的某一台主機。
本文配套源碼