搜索
您的当前位置:首页正文

端口扫描程序设计

来源:知库网
 .

课 程 设 计

题 目: 端口扫描程序设计

院、 系:计算机科学与技术学院 网络工程系 班 级: 学 号: 姓 名: 同组成员: 指导教师:

.

成 绩:

.

一.系统设计的目标:

扫描器是网络信息收集的一种方法,从功能上可分为漏洞扫描器和端口扫描器。通过此次课程设计,能够掌握漏洞、端口的基础知识,掌握扫描器的基本原理并设计实现端口扫描和漏洞扫描程序。

二.系统原理:

常用的端口扫描技术有以下几种: 2.1 TCP connect()扫描: 2.1.1简介:

这是最基本的TCP扫描,操作系统提供的connect()系统调用可以用来与每一个感兴趣的目标计算机的端口进行连接。如果端口处于侦听状态,那么connect()就能成功。否则,这个端口是不能用的,即没有提供服务。 2.2.2优点:

不需要任何权限。系统中的任何用户都有权利使用这个调用。另一个好处就是速度,如果对每个目标端口以线性的方式,使用单独的connect()调用,那么将会花费相当长的时间,使用者可以通过同时打开多个套接字来加速扫描。使用非阻塞I/O允许你设置一个低的时间用尽周期,同时观察多个套接字。 2.2.3 缺点:

很容易被察觉,并且被防火墙将扫描信息包过滤掉。目标计算机的logs文件会显示一连串的连接和连接出错消息,并且能很快使它关闭。 2.2 TCP SYN扫描: 2.2.1简介:

这种技术通常认为是“半开放”扫描,这是因为扫描程序不必要打开一个完全的TCP连接。扫描程序发送的是一个SYN数据包,好象准备打开一个实际的连接并等待反应一样(参考TCP的三次握手建立一个TCP连接的过程)。一个SYN|ACK的返回信息表示端口处于侦听状态:返回RST表示端口没有处于侦听态。如果收到一个SYN|ACK,则扫描程序必须再发送一个RST信号,来关闭这个连接过程。 2.2.2优点:

一般不会在目标计算机上留下记录。 2.2.3缺点:

必须要有root权限才能建立自己的SYN数据包。

.

2.3 TCP FIN 扫描: 2.3.1简介:

SYN扫描虽然是“半开放”方式扫描,但在某些时候也不能完全隐藏扫描者的动作,防火墙和包过滤器会对管理员指定的端口进行监视,有的程序能检测到这些扫描。相反,FIN数据包在扫描过程中却不会遇到过多问题,这种扫描方法的思想是关闭的端口会用适当的RST来回复FIN数据包。另一方面,打开的端口会忽略对FIN数据包的回复。这种方法和系统的实现有一定的关系,有的系统不管端口是否打开都会回复RST,在这种情况下此种扫描就不适用了。另外这种扫描方法可以非常容易的区分服务器是运行Unix系统还是NT系统。 2.3.2优点:

SYN扫FIN数据包可以不惹任何麻烦的通过。 2.3.3缺点:

这种方法和系统的实现有一定的关系,有些系统不论是打开的或关闭的端口对FIN数据包都要给以回复,这种情况下该方法就不实用了。 2.4 IP段扫描: 2.4.1简介:

这种扫描方式并不是新技术,它并不是直接发送TCP探测数据包,而是将数据包分成两个较小的IP段。这样就将一个TCP头分成好几个数据包。 2.4.2优点:

过滤器就很难探测到。 2.4.3缺点:

一些程序在处理这些小数据包时会有些麻烦。 2.5 TCP 反向 ident扫描: 2.5.1简介:

ident 协议允许(rfc1413)看到通过TCP连接的任何进程的拥有者的用户名,即使这个连接不是由这个进程开始的。例如扫描者可以连接到http端口,然后用identd来发现服务器是否正在以root权限运行。 2.5.2优点:

通过TCP连接的任何进程的拥有者的用户名。 2.5.3缺点:

这种方法只能在和目标端口建立了一个完整的TCP连接后才能看到。 2.6 FTP 返回攻击 2.6.1简介:

.

FTP协议的一个有趣的特点是它支持代理(proxy)FTP连接,即入侵者可以从自己的计算机self.com和目标主机target.com的FTP server-PI(协议解释器)连接,建立一个控制通信连接。然后请求这个server-PI激活一个有效的server-DTP(数据传输进程)来给Internet上任何地方发送文件。 2.6.2优点:

建立一个控制通信连接。 2.6.3缺点:

能用来发送不能跟踪的邮件和新闻,给许多服务器造成打击,用尽磁盘,企图越过防火墙。

三. 系统功能分析:

作为端口扫描程序,首先需要完成的功能就是对于系统操作系统的服务端口进行扫描,返回扫描结果。对于端口的扫描,包括对于本机系统服务端口,局域网内目标机系统,以及远程IP的系统服务端口进行扫描。

有些时候,用户并不需要去扫描整个系统的所有端口,因为这样的话不仅会浪费大量的时间,而且可能导致难以找到自己需要了解的端口的扫描结果。所以,对于选择性地对端口进行扫描也非常重要。这当然也是扫描程序需要实现的功能之一。

用户在等待扫描的时候,往往希望知道它的工作进度。这样用户可以更好地控制自己的操作。站在用户的角度思考,设置进度是程序需要完成的,这样就能知道程序扫描的进度。

系统必须提供的服务是功能需求的基本,本着站在用户角度思考的原则,做出如上叙述需求,从简列举如下:扫描功能;地址选择功能;端口选择功能;

本程序主要实现了简易的TCP connect()扫描,对TCP扫描支持多线程扫描。

四.系统实现:

4.1步骤:

4.1.1 先输入想要扫描的网段;

4.1.2 然后将输入的网段转化为可排序的ip数组

4.1.3 建立多个线程,每个线程扫描一个ip。每个线程内先建立数据流套接字,然后绑定ip端口进行扫描。将扫描端口保存到g_map_ScanResult。 4.1.4 清理结束后进程,输出结果。 4.1.5 计算所用时间。

.

4.2 程序中主要的函数:

程序 int main(); InitProc(); UserInput(); ScanIp(g_startIp,g_endIp,g_map_ScanResult); CleanProc(); OutPutScanInfo(); DWORD WINAPI ThreadFunc(LPVOID th_para) unsigned long InvertIp(unsigned long srcIp) 简介 主函数 初始化 输入 开始扫描 清理结束后进程 输出结果 扫描线程每一个ip 将ip化为长整型 int GetIpToScan(const string &StartIp, const 将所有ip排序放在一个数组内 string &EndIp, vector &vec_ip) 4.3 实验结果:

.

图4-1 开始界面

图4-2 扫描界面

图4-3 扫描结果

附录: 源程序代码:

#pragma comment(lib,\"ws2_32.lib\") #pragma warning (disable:4786) #include #include #include #include #include #include #include

.

#include //#include \"IpScan.h\" using namespace std; //全局变量: //待扫描的端口 short

g_portsTOscan[]=

{20,21,22,23,25,42,43,47,53,63,67,68,79,80,95,106,107,109,110,113,135,137,138,139,143,

144,161,162,443,445,1024,1080,1433,1434,1755,3306,4000,5010,5190,5631,5632,8000,8080 };

const short PORTSNUM = sizeof(g_portsTOscan) / sizeof(short);//端口个数 //等扫描的IP

vector g_vec_IpToScan; string g_startIp; string g_endIp;

//开启的线程数,目前为1个IP1个线程 long g_runThreadNum;

//socket相关

TIMEVAL g_timeout; //阻塞等待时间

//FD_SET g_mask; //socket模式设置,储存socket信息 const short TIMEOUT = 1; //阻塞等待时间

.

WSADATA g_wsadata; //socket版本信息

//线程中的互斥体 HANDLE g_PortMutex; HANDLE g_ThreadNumMutex;

HANDLE g_ResultMutex; //输入结果的互斥量

//保存IP扫描的结果

multimap g_map_ScanResult;

//------------------------------------------------------------------------------------------------------------------- //线程函数,扫描每一个IP

DWORD WINAPI ThreadFunc(LPVOID th_para) {

//获取需要扫描的IP

//char *pStrIp = (char*)th_para;

unsigned long ulScanIp = *(unsigned long*)th_para;

int index = 0; //端口索引 SOCKET link_sock; //SOCKET FD_SET set_flag; //SOCKET描述

.

short select_ret; //select异步返回值 short port; //正在扫描的端口

while (index < PORTSNUM) {

port = g_portsTOscan[index];

//创建数据流套接字

link_sock = socket(AF_INET, SOCK_STREAM, 0); if (link_sock == INVALID_SOCKET) {

//cout << \"创建link_sock socket失败:错误号为: \" << GetLastError() << endl; WaitForSingleObject(g_ThreadNumMutex,INFINITE); g_runThreadNum--;

ReleaseMutex(g_ThreadNumMutex);

//cout << \"***还有_\"<< g_runThreadNum << \"_个扫描线程进行中**\"<< endl; return -1; }

FD_ZERO(&set_flag); //将指定文件描述符清空

FD_SET(link_sock,&set_flag); //用于在文件描述符集合中增加一个新的文件描述符

.

//设置连接地址

SOCKADDR_IN scan_addr; scan_addr.sin_family = AF_INET; scan_addr.sin_addr.s_addr = ulScanIp; scan_addr.sin_port = htons(port);

unsigned long sock_set = 1;

ioctlsocket(link_sock,FIONBIO,&sock_set); //设置套接字为非阻塞模式,第3个参数非0为非阻塞

connect(link_sock,(struct sockaddr *) &scan_addr, sizeof(scan_addr));//连接指定IP端口

select_ret = select(0,NULL,&set_flag,NULL,&g_timeout);//异步返回值

if (select_ret == 0 || select_ret == -1) {

++index; continue; } else {

strstream stream_result;

.

struct in_addr ipaddr; ipaddr.s_addr = ulScanIp; char *pStrIp = inet_ntoa(ipaddr);

stream_result << \"\ 主机地址为:\" << pStrIp << \"\找到开放的端口: \" << port <<'\\0';

string str_result(stream_result.str());

//将扫描结果储存到输出变量中去

WaitForSingleObject(g_ResultMutex,INFINITE);

g_map_ScanResult.insert(make_pair(ulScanIp,str_result));

ReleaseMutex(g_ResultMutex); } ++index; }

//扫描完一个线程 shutdown(link_sock, 0); closesocket(link_sock);

.

WaitForSingleObject(g_ThreadNumMutex,INFINITE); g_runThreadNum--;

ReleaseMutex(g_ThreadNumMutex);

//cout << \"****还有_\"<< g_runThreadNum << \"_个扫描线程进行中****\"<< endl;

return 0; }

//----------------------------------------------------------------------------------------------------------

//将IP转化成能直接递增和递减的地址 unsigned long InvertIp(unsigned long srcIp) {

unsigned char first; unsigned char second; unsigned char third; unsigned char fourth;

first = srcIp & 0x00FF;

second = (srcIp >> 8) & 0x00FF; third = (srcIp >> 16) & 0x00FF; fourth = (srcIp >> 24) & 0x00FF;

.

return (first << 24) | (second << 16) | (third << 8) | fourth; }

//------------------------------------------------------------------------------------------------ //将IP内的IP转化成一个一个unsigned long 类型存在数组中

int GetIpToScan(const string &StartIp, const string &EndIp, vector &vec_ip) {

//判断输入的IP是否合法

unsigned long ulStartIp = inet_addr(StartIp.c_str()); unsigned long ulEndIp = inet_addr(EndIp.c_str());

if(

INADDR_NONE == ulStartIp ||

INADDR_NONE == ulEndIp ) {

cout << \" 请输入合法的IP\" << endl; return -1; }

.

//////////////判断查询的是一个IP还是IP段///////////////////////////////////// if (ulStartIp == ulEndIp && ulStartIp !=0) {

vec_ip.push_back(ulStartIp); return 0; }

if (ulStartIp == 0 && ulEndIp == 0) {

return 0; }

if (ulStartIp == 0) {

vec_ip.push_back(ulEndIp); return 0; }

if (ulEndIp == 0) {

vec_ip.push_back(ulStartIp);

.

return 0; }

////////////////////////////////////////////////////////////////////////////

//将IP转换成可以递增比较的类型 ulStartIp = InvertIp(ulStartIp); ulEndIp = InvertIp(ulEndIp);

//指定前后顺序,ulEndIp较大 unsigned long max_ip; if (ulStartIp > ulEndIp) {

max_ip = ulStartIp; ulStartIp = ulEndIp; ulEndIp = max_ip; }

int ipnums = ulEndIp - ulStartIp;

for(int i = 0;i <= ipnums;++i) {

.

//将每个IP的unsigned long型存到数组中供扫描 vec_ip.push_back(InvertIp(ulStartIp++)); }

return 0; }

//-----------------------------------------------------------------------------------------------------------------

//功能: 输入一个IP段,输出该IP段内的端口开放情况信息

int ScanIp(const string &start_Ip, const string &endIp, multimap &ouputMap) {

//分解IP段内的IP到全局数组中去

GetIpToScan(start_Ip,endIp,g_vec_IpToScan);

int scanNum = g_vec_IpToScan.size();

//线程总数

g_runThreadNum = scanNum;

cout<.

cout<<\"********************************************************************************\";

cout << \" 共有 \" << scanNum <<\" 个IP要扫描 \" << endl;

//对每个IP开一个线程

for (int i = 0; i < scanNum; ++i) {

CreateThread(NULL,0,ThreadFunc,&g_vec_IpToScan[i],0,NULL); //要是不间隔时间的话,同时创建socket会出现10093错误 Sleep(50); }

return 0; }

//-------------------------------------------------------------------------------------------------------

//输出扫描结果 int OutPutScanInfo() {

cout <<\" 扫描到 \" << g_map_ScanResult.size() << \" 条记录

\" << endl;

.

multimap::iterator iter = g_map_ScanResult.begin();

ofstream out(\"out.txt\");

cout <<\" 显示总 \" << g_map_ScanResult.size() << \" 条记录: \" << endl;

cout<for (; iter!=g_map_ScanResult.end(); ++iter) {

out << iter->second << endl; cout << iter->second << endl; }

return 0; }

//----------------------------------------------------------------------------------------------------

void UserInput() {

cout<<\" \"<cout<<\" *********************** \"<cout<<\" * *

.

\"<cout<<\" * 端口扫描器 * \"<cout<<\" * * \"<cout<<\" *********************** \"<cout<<\" \"<cout<<\"********************************************************************************\"<cout<<\"* 可扫描一个或多个IP,输入同一网段的两个IP。 *\"<cout<<\"* 格式为192.168.1.1 192.168.1.254.

*\"<cout<<\"* 只扫描一个IP时,输入两个相同IP. *\"<cout<<\"* 扫描多个IP时,输入两个不相同IP. *\"<cout<<\"********************************************************************************\"<<

.

endl;

cout<cout<cout<<\"********************************************************************************\";

cout<<\" 现在请输入需要扫描的IP \";

cout<<\" 起始IP为:\";

cin >> g_startIp;

cout<<\" 结束IP为:\"; cin >> g_endIp;

cout<<\"********************************************************************************\"<< endl; }

//------------------------------------------------------------------------------------------------------------ //初始化相关信息 void InitProc() {

//初始化socket相关信息

.

int ws_result;

ws_result = WSAStartup(MAKEWORD(2,2),&g_wsadata);

if (ws_result != 0) {

cout << \"socket WSAStartup初始化失败\" << endl; }

//设置阻塞函数的超时时间 g_timeout.tv_sec = TIMEOUT;

g_PortMutex = CreateMutex(NULL, FALSE, \"port mutex\"); g_ThreadNumMutex= CreateMutex(NULL, FALSE, \"threadnums mutex\"); g_ResultMutex = CreateMutex(NULL, FALSE, \"result mutex\"); }

//------------------------------------------------------------------------------------------------------------------- //清理

void CleanProc() {

////////////////线程都执行完后清理socket相关信息////// while (1) {

.

WaitForSingleObject(g_ThreadNumMutex,INFINITE); if (g_runThreadNum == 0) {

break; }

ReleaseMutex(g_ThreadNumMutex); Sleep(100); }

//清理socket相关信息 WSACleanup(); }

int main() //主函数 {

clock_t start_time; clock_t end_time; double cost_time = 0; start_time = clock(); InitProc();//初始化 UserInput();//输入

ScanIp(g_startIp,g_endIp,g_map_ScanResult);//开始扫描 CleanProc();//清理结束后进程 OutPutScanInfo();//输出结果

.

//计算程序运行时间 end_time = clock();

cost_time = (double) (end_time - start_time) / CLOCKS_PER_SEC;

cout<cout <<\" 时间为: \" << cost_time << \" 秒\" << endl; cout<<\"********************************************************************************\";

cout<return 0; }

五.总结:

5.1提出问题:

5.1.1确定选题后,综合考虑选择哪种技术去实现端口扫描,写出原因。 5.1.2面对大量端口需要扫描,怎么样做到既保持程序效率又保持程序的稳定性。 5.1.3在程序设计完成后,怎样经过调试确定最大连接线程数。 5.2解决问题:

5.2.1首先综合考虑各种技术及背景,发现用connect()函数去连接这个方法更加可靠

且易于实现和调试,在出现问题后可相对容易的对其解决。而且在本学期学过网络安全和tcp/ip的课程,对connect()函数的连接过程有些许掌握,更有利于软件的开发。

5.2.2在面对几千甚至上万个端口需要扫描时,需要考虑到并发处理端口,我们想到了

多线程,在对其多线程设计中,要注意控制线程的数量,如果线程数量过多,会造成程序的崩溃,若太少,则程序运行太慢。所以用多线程并发技术并加以最大线程数量来控制。

5.2.3程序在运行中,与程序性能有关的首先是本机硬件条件和对方应答所需时间,不

同端口号处理响应请求时间也不一样而且跟网络环境也有一定关系。一般来说,机

.

器处理器速度越快,则处理每个端口的速度越快。所以程序应该在实际运行中,通过不断修改最大线程数并运行来找到一个合适的最大线程数量,可以将范围从1000设置到10000,再到60000,不断进行调试达到最佳。

六.心得体会:

本设计经过近2个星期的努力,基本满足了一个端口扫描程序的基本要求。完成后的程序实现了ip端口扫描功能,能对单个ip扫描或多个ip。

系统设计期间,学习到很多课堂上没有的知识,还积累了很多实践经验,增强了动手能力和解决实际问题的能力。通过这次的课程设计,对网络编程有了更深入的了解,进一步熟悉了TCP和UDP协议的内容,掌握了ip扫描端口的基本原理。对编程思想有了进一步的体会,养成了一些良好的编程习惯。系统虽然完成,但还有很多不足之处,希望自己能不断学习和实践,争取以后做得更好。

此软件扫描功能单一:不支持TCP SYN扫描、TCP FIN 扫描功能,对UDP扫描不支持多线程功能,而且在扫描主机前没有发送ICMP报文去判断主机是否在线,还需要进一步完善。

七.致谢:

这次课程设计郭老师指导了很多,从算法原理到计算机原理知识都一并拓展,我受益颇多,感谢郭老师在这次课程设计中的指导和栽培,祝老师工作顺利、身体健康。

八.参考文献:

[1] 梁亚声等.计算机网络安全教程.第2版.北京:机械工业出版社,2008 [2] 刘文涛.网络安全编程技术与实例.北京:机械工业出版社2008

因篇幅问题不能全部显示,请点此查看更多更全内容

Top