计算机网络课程设计 基于ICMPTCP的网段端口扫描 C语言编写

更新时间:2024-05-02 01:39:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

ronnie

课 程 设 计 任 务 书

【设计目的】

加深对TCP/IP协议的理解,熟悉Socket编程 【设计任务】

实现一个扫描器,必须能够完成以下所有功能:使用TCP connect、TCP SYN和TCP FIN进行端口扫描,使用ICMP echo 扫描实现IP扫描。并把结果记录下来。 【设计要求】

? Windows或Linux环境下,程序在单机上运行。

? 演示:使用端口扫描对一台主机进行扫描,并显示结果(一台主机上有哪些端口是打开的)。对一个网段进行IP扫描,显示结果(一个网段内有哪些主机是开机的)。 ? 友好的用户界面 【设计要求】

? 程序源代码,必须有详细的注释。 ? 项目设计报告。

1

ronnie

网段和端口扫描程序

一、 概述 ..................................................................................................... 3

1.1端口扫描 ........................................................................................... 3 1.2端口扫描常用技术简介 ...................................................................... 3

1.2.1 connect()扫描 ........................................................................... 3 1.2.2 SYN扫描 ................................................................................. 4 1.2.3 XMAS-TREE扫描 .................................................................... 4 1.3网段扫描 ........................................................................................... 5 二、需求分析 ................................................................................................ 6

2.1 设计功能 .......................................................................................... 6 2.2 设计要求 .......................................................................................... 6 三、概要设计 ................................................................................................ 6

3.1设计概念和处理流程 .......................................................................... 6 3.2 结构设计 .......................................................................................... 8

3.2.1 数据结构及定义 ....................................................................... 8 3.2.2 接口函数 ................................................................................. 9

四、细设设计 .............................................................................................. 10

4.1 实现原理 ........................................................................................ 10

4.1.1 connect()函数端口扫描的原理................................................. 10 4.1.2 ICMP网段扫描的原理 ............................................................ 12 4.2 函数实现 ........................................................................................ 15

4.2.1 初始化winsock动态链接库 .................................................... 15 4.2.3地址解析 ................................................................................ 16 4.2.4 计算检验和 ............................................................................ 17 4.2.5 网段扫描 ............................................................................... 17 4.2.6 端口扫描 ............................................................................... 18

五、 总结与体会 ......................................................................................... 20

5.1 程序运行与调试 .............................................................................. 19 5.2 体会 ............................................................................................... 22 六、 参考文献 ............................................................................................ 22

6.1 参考书目 ........................................................................................ 22 6.2 引用网址 ........................................................................................ 23

2

ronnie

一、概述

1.1端口扫描

端口扫描是指利用TCP协议的面向连接的特性,使用本地计算机试图与目的主机的某个端口建立连接,以此来试探目的主机的一些端口的具体状态,如是否打开,若是打开的又运行什么样的程序(利用熟知端口很容易知道)。端口扫描有很多技术实现。其中最常用的有:connect()扫描,TCP SYN扫描,TCP FIN扫描,TCP NULL扫描,XMAS- TREE扫描(圣诞树扫描)等等。在本程序中使用的是connect()扫描。

1.2端口扫描常用技术简介 1.2.1 connect()扫描

此扫描的原理是,本地计算机利用TCP协议的三次握手原理(RFC 793)试图与网络上一台主机或服务器建立TCP连接。如果目的主机或服务器回送SYN/ACK则说明该端口开放,否则该端口关闭。

具体的过程: 1.Client端发送SYN

2.Server端发送SYN/ACK(该端口开放),或Server端发送RST/ACK(该端口关闭),若无应答可能目的主机或服务器不可达。

3.Client端发送ACK,连接建立。 4.Client端主动断开连接,连接关闭。 过程图如下:

3

ronnie

1.2.2 SYN扫描

SYN扫描前两步与connect()扫描相同,只是最后一步,Client端发送的不是ACK确认报文而是RST报文,这样三次握手过程就没有完成,Client与Server也就没有建立TCP连接,因此前述过程不会被Sever端记录到系统日志中,扫描更加隐蔽。 过程图如下:

1.2.3 XMAS-TREE扫描

通过发送带有以下标志位TCP数据包URG,PSH,FIN来试探主机。

4

ronnie

在目标端口开放的情况下不放回任何信息。

端口开放:发送URG/PSH/FIN,没有响应。

端口关闭:1.发送URG/PSH/FIN,没有响应。2响应RST。 1.3网段扫描

网段扫描是指利用ICMP(因特网控制报文协议)对某一网段的所有IP地址发送ICMP报文,测试IP地址所对应的主机具体情况(如是否开机等)。所以对目的主机回答的ICMP报文进行分析是网段

5

ronnie

扫描的关键。常见的报文类型有: 类型(type) 代码(code) 0 3 3 8 0 端口不可达 请求回显 * * 0 1 描述 回显回答 主机不可达 差错 * 查询 *

二、需求分析

2.1 设计功能

实现一个扫描器,使用TCP connect、TCP SYN或TCP SYN进行端口扫描,使用 ICMP echo扫描实现IP扫描,并记录结果。

2.2 设计要求

Windows或Linux环境下,程序应在单机上运行。

使用端口扫描对一台主机进行扫描,并显示结果,在一定的端口范围内,该主机由哪些端口是打开的。对一个网段进行IP扫描,显示结果,该网段的哪些主机是开机的。

友好的界面,便于用户操作,完成全部设计功能。

三、概要设计

3.1设计概念和处理流程

程序使用Windows Sockets API编程,利用winsock2库函数提供的函数实现与主机间的连接,发送ICMP报文。详见

Winsock Referen:

http://msdn.microsoft.com/en-us/library/windows/desktop/ms741416(v=vs.85).aspx

6

ronnie

用户首先选择要使用的功能,程序共有两大功能:端口扫描和IP网段扫描。端口扫描的主要功能有,根据用户输入的主机名或IP地址以及端口范围进行connect()扫描。当用户输入完必要的信息并击确认后,程序进行扫描,不过不建议端口区间过于庞大,等待一段时间后用户界面会显示所指定IP地址主机的端口使用情况,哪些端口是打开的,哪些是关闭的。IP网段扫描的主要功能有,用户输入某个网段起始IP地址和结束IP地址,确认后,程序根据所输入的网段号,对网段类的每一个IP地址发送ICMP请求回显报文,如果主机处于开机状态那么将会回送回答报文;如果主机不可达(在同一个网内),则就能够判断该IP所对应的主机处于关机状态。当用户选择退出功能时,释放资源,程序关闭。

程序流程图

7

ronnie

3.2 结构设计 3.2.1 数据结构及定义 所使用的宏定义,

#define ICMP_ECHO_REQUEST_TYPE 8 //ICMP请求类型 #define ICMP_ECHO_REQUEST_CODE 0 //ICMP请求代码 #define ICMP_ECHO_REPLY_TYPE 0 #define ICMP_ECHO_REPLY_CODE 0 #define ICMP_MINIMUM_HEADER 8

IP数据报首部,首部固定20字节,数据结构定义如下:

typedef struct ip_hdr { unsigned char iphVerLen; // 版本号和头长度(各占4位) unsigned char ipTOS; // 服务类型 unsigned short ipLength; // 封包总长度,即整个IP报的长度

unsigned short ipID; // 封包标识,惟一标识发送的每一个数据报

unsigned short ipFlags; // 标志 unsigned char ipTTL; // 生存时间,就是TTL unsigned char ipProtocol; // 协议,可能是TCP、UDP、ICMP等

unsigned short ipChecksum; // 校验和 unsigned long ipSource; // 源IP地址 unsigned long ipDestination; // 目标IP地址 }IP_HDR,*PIP_HDR;

IP数据报格式

ICMP报文首部格式,8个字节,数据结构定义如下:

8

ronnie

typedef struct icmp_hdr { unsigned char icmp_type; //类型 unsigned char icmp_code; //代码 unsigned short icmp_checksum; //检验和 unsigned short icmp_id; //唯一请求ID,通常使用进程PID

unsigned short icmp_sequence; //序列号 unsigned long icmp_timestamp; //时间戳 } ICMP_HDR,*PICMP_HDR;

3.2.2 接口函数

Void InitializeWinsock(),初始化Winsock动态链接库。

Void Resolove(char hostname[]),解析主机名(或IP地址),该函数是公共接口函数,端口扫描和IP网段扫描都使用该函数解析地址。函数首先判断用户输入的IP是十进制点分地址还是域名地址,然后分别调用inet_addr()和gethostbyname()库函数处理成unsigned long型数据。

Void ResoloveIPAddr(char starthost[],char endhost[],int *start,int *end),此函数用户IP网段扫描,将用户输入的起始IP地址和结束IP地址转换为int类型数据。

9

ronnie

Char * Assemble(char startehost[],int cur),将当前IP地址转化为字符串类型的IP地址。

void SegmentScan(char starthost[],char endhost[]),通过发送ICMP,检测某个网段上主机的具体状态。函数的两个参数starthost[]、endhost[]分别代表用户给定的起始IP地址和结尾IP地址。该函数内部调用了InitializeWinsock()、Resolove()、ResoloveIPAddr()、Assemble()、以及库函数sendto()和recvfrom()。通过这些函数的调用实现了对指定网络区间的ICMP扫描。

connectScan(char startport[],char endport[],hostname),该函数把用户输入的主机名(或IP地址)写入sin_addr.s_addr,起始端口写入sin_port中。函数调用了InitializeWinsock()、Resolove()以及库函数connect()、shutdown()。实现了利用connect()系统函数对指定主机端口区间扫描。

四、细设设计

4.1 实现原理

4.1.1 connect()函数端口扫描的原理 首先要明白TCP报文段的首部格式。

10

ronnie

现在分别介绍各个字段的具体含义。(1)源端口和目的端口 各占两个字节。端口是运输层与应用层的服务。(2)序号 占4个字节。TCP把传送的数据流中的每一个字节都编上一个序号。(3)确认号 占4个字节。是指期望对方的下一个报文段的数据的第一个字节的序号。(5)数据偏移 占4位。它指出TCP报文段的数据起始处距离TCP报文段的起始距离有多远。(6)保留。(7)紧急比特URG 当URG=1时,表明紧急指针字段有效。告诉系统有紧急数据,应尽快发送。(8)确认比特ACK 只有当ACK=1时确认号字段才有效。(9)推送比特PSH 将数据流尽快的推送给应用进程。而不是等到缓冲区满后才将数据送至应用程序。(10)复位比特RST 当RST=1时,表明TCP出现错误,必须释放连接,然后重新建立运输连接。复位比特还用来拒绝一个非法报文或拒绝打开一个连接。(11)同步比特SYN 当SYN=1而ACK=0是表明这是一个请求连接报文,若同意连接则

11

ronnie

对方应该回送SYN=1,ACK=1。(12)终止比特 FIN。用于释放一个连接。(13)检验和 占2个字节。

设客户进程运行在本地计算机上。它首先向其TCP发出主动打开(active open)命令,表明要与某个IP地址的某个端口建立运输连接。 首先发送SYN=1,SEQ=X的报文给目标主机的某端口,当本地计算机收到SYN=1,ACK=1的报文时,表明对方主机同意连接,这是还需发送一个ACK以确认连接。这样,经过三次握手,一条TCP连接链路就建立完成。

在Winsock2环境下,系统类库提供了connect()接口函数。一下是函数原型:

connect function

int connect( _In_ SOCKET s,

_In_ const struct sockaddr *name, _In_ int namelen );

s表示一个为连接的socket,name表示一个指向sockaddr的结构

体指针,namelen是sockaddr中name域的比特长度。利用此函数,可以非常方便的连接一个套接字。 4.1.2 ICMP网段扫描的原理

因特网控制报文协议ICMP(Internet Control Message Protocol)【RFC792】。ICMP允许主机或路由器报告差错情况和提供有关异常情

12

ronnie

况的报告。ICMP报文作为IP层数据报的数据,加上数据报的首部,组成数据发送出去。ICMP报文的种类有两种,即ICMP差错报告报文和ICMP询问报文。ICMP报文的前4个字节是统一格式,共有三个字段:类型,代码和检验和。对网段的主机扫描就是利用了这三个字段。根据主机发回的不同类型和代码对主机的状态进行判断。 ICMP的差错报文共有5种:

(1)终点不可达 终点不可达分为:网络不可达,主机不可达,协议不可达,端口不可达。

(2)源站抑制 当路由器或主机由于拥塞而丢弃数据包时,就向源站发生源站抑制报文。

(3)时间超时 当路由器手打生存时间为零的数据包时,除丢弃该数据报外,还向源站发送时间超过报文。 (4)参数问题

(5)改变路由(重定向)

对一个网段扫描时,主要利用(1)和(3)两种报文。 ICMP的询问报文主要有4种: (1)回送请求和回答(Ping)

(2)时间戳和回答(常用来记录一个主机到另一个主机的延迟) (3)掩码请求和回答 (4)路由询问和回答

同样,主要利用(1)和(2)两种报文。

Winsock2提供sendto()和recvfrom()两个函数分别来发送IP层数

13

ronnie

据报。函数原型分别为:

sendto function

int sendto(

_In_ SOCKET s, _In_ const char *buf, _In_ int len, _In_ int flags,

_In_ const struct sockaddr *to, _In_ int tolen );

recvfrom function

int recvfrom(

_In_ SOCKET s, _Out_ char *buf, _In_ int len, _In_ int flags,

_Out_ struct sockaddr *from, _Inout_opt_ int *fromlen );

结合原始套接字可以很方便做到将一个ICMP数据包封装到IP数据包中,以及从一个IP数据报中解析出包含ICMP报文。下面对原

14

ronnie

始套接字做下简单的介绍。

原始套接字提供普通的TCP和UDP套接字所不提供的一下3个功能。

? 有了原始套接字,进程可以读写ICMP和IGMP等分组。举例来说ping程序就是使用原始套接字收发ICMP分组,网段扫描也是基于这个原理。

? 有了原始套接字,进程可以读写内核不处理其协议字段的IPv4数据包。

? 有了原始套接字,进程还可以使用IP_HDRINCL套接字选项自行构造IPv4首部。这一点是非常强大的。

在利用Winsock编写原始套接字时要注意程序应该取得管理员权限,因为大多数操作系统对原始套接字的创建有较为严格的限制。如果没有取得管理员权限,那么在创建原始套接字的时候会报出10013--Permission denied.的错误,从而无法创建套接字。 4.2 函数实现

4.2.1 初始化winsock动态链接库

void InitializeWinsock()

{ int status; WSADATA wsa; if (status=WSAStartup(MAKEWORD(2,2),&wsa)!=0) { printf(\ exit(EXIT_FAILURE); } }

函数调用Winsock2提供的WSAStartup()函数,初始化动态链接库。

15

ronnie

4.2.2 初始化ICMP首部

void InitIcmpHeader(ICMP_HDR* icmp_hdr) { char buff[sizeof(ICMP_HDR) + 32]; //ICMP报文类型,类型为8,代码为0 icmp_hdr->icmp_type = ICMP_ECHO_REQUEST_TYPE; // 请求回显 icmp_hdr->icmp_code = ICMP_ECHO_REQUEST_CODE; icmp_hdr->icmp_id = (USHORT)GetCurrentProcessId(); icmp_hdr->icmp_checksum = 0; icmp_hdr->icmp_sequence = 0; icmp_hdr->icmp_timestamp= GetTickCount(); //往ICMP头部填充信息 memset(&buff[sizeof(ICMP_HDR)], 'E', 32); }

函数为ICMP报文首部中各个字段赋值,达到初始化的目的。其中比较重要的语句有

icmp_hdr->icmp_type = ICMP_ECHO_REQUEST_TYPE; // 请求回显 icmp_hdr->icmp_code = ICMP_ECHO_REQUEST_CODE;

这样赋值后,就把ICMP报文设置成ICMP询问报文,回送请求和回答。

4.2.3地址解析

void Resolove(char hostname[]) { if(isdigit(hostname[0])) //判断主机名是否为数字 { //printf(\执行inet_addr()...\\n\ dest.sin_addr.s_addr = inet_addr(hostname); //将主机地址写入s_addr //printf(\ } else if( (host=gethostbyname(hostname)) != 0)//判断所给主机名是否与host中的一致 { //printf(\执行gethostbyname()...\\n\ strncpy((char *)&dest.sin_addr , (char *)host->h_addr_list[0] , sizeof dest.sin_addr);

//printf(\完成。\\n\ } else { printf(\解析主机失败。\\n\ exit(EXIT_FAILURE); }

16

ronnie

}

根据用户输入地址字符串转化成相应的地址字段,写入dest.sin_addr中。如果用户输入的点分十进制。则就会调用相应的inet_addr()函数,否则调用gethostbyname(),在写入地址字段。 4.2.4 计算检验和

unsigned short checksum(unsigned short *buffer, int size) { unsigned long cksum=0; while (size > 1) { cksum += *buffer++; size -= sizeof(USHORT); } if (size) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); }

根据TCP/IP协议,IP数据报在传输过程前必须计算检验和,对收到的数据也要计算检验和。该函数实现了首部的检验和计算。 4.2.5 网段扫描 主要语句如下:

for(i = startport ; i<= endport ; i++) { sock = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP); //创建一个Socket if(sock==INVALID_SOCKET) { printf(\ exit(EXIT_FAILURE); } //主机字节序转换为网络字节序

17

ronnie

}

dest.sin_port = htons(i);

//用此Socket连接目的主机,核心代码

status = connect(sock , (struct sockaddr *)&dest , sizeof dest);

if(status == SOCKET_ERROR) //连接失败 { switch(WSAGetLastError()) { case 10060: printf(\ break; case 10061: printf(\ break; default: printf(\ break; } //fflush(stdout); }

else //连接成功 { printf(\ //关闭收发服务 if( shutdown( sock ,SD_BOTH ) == SOCKET_ERROR ) { printf(\ exit(EXIT_FAILURE); } }

closesocket(sock); //关闭Socket,回收资源 //WSACleanup();

利用一个for(;;)循环,对确定的起始和终止IP地址的内的所有可能存在的主机发送,请求回显的ICMP报文,并对返回的ICMP报文进行分析,提取出type和code字段,根据ICMP报文格式所定义的数值,对各个主机判断,并打印信息。 4.2.6 端口扫描

18

ronnie

for(i = startport ; i<= endport ; i++) { sock = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP); //创建一个Socket if(sock==INVALID_SOCKET) { printf(\ exit(EXIT_FAILURE); } //主机字节序转换为网络字节序 dest.sin_port = htons(i); //用此Socket连接目的主机,核心代码 status = connect(sock , (struct sockaddr *)&dest , sizeof dest); if(status == SOCKET_ERROR) //连接失败 { switch(WSAGetLastError()) { case 10060: printf(\ break; case 10061: printf(\ break; default: printf(\ break; } //fflush(stdout); } else //连接成功 { printf(\ //关闭收发服务 if( shutdown( sock ,SD_BOTH ) == SOCKET_ERROR ) { printf(\ exit(EXIT_FAILURE); } } closesocket(sock); //关闭Socket,回收资源 //WSACleanup(); }

19

ronnie

函数同样利用一个for(;;)循环对给定的主机的端口区间扫描。调用WSAGetLastError()库函数判断端口是否连接,根据相应的ERROR CODE判断端口的具体情况。以下是主要的ERROR CODE:

WSAETIMEDOUT 10060

Connection timed out.

A connection attempt failed because the connected party did not properly respond after a period of time, or the established connection failed because the connected host has failed to respond.

WSAECONNREFUSED 10061

Connection refused.

No connection could be made because the target computer actively refused it. This usually results from trying to connect to a service that is inactive on the foreign host—that is, one with no server application running.

WSAELOOP 10062

Cannot translate name. Cannot translate a name.

摘自:http://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx

五、总结与体会

5.1 程序调试与运行 程序主界面

20

ronnie

IP网段扫描

端口扫描

21

ronnie

5.2 体会

通过本次的课程设计加深了本人对TCP/IP协议特别是网络层协议,IP首部格式,ICMP首部格式的理解。对Windows网络编程也有初步的了解,掌握了基本的套接字程序的编写。理解原始套接字的运行机制,加深一些协议字段的理解。编写过程遇到的困难也不少,但是依靠钻研精神,逐步对网络编程步骤产生了整体的印象,这对以后的学习也是一笔宝贵的财富。以前学习总是停留在纸面上理论上,虽然貌似一些概念是清楚了,但是这样的记忆是不长久的。只有通过理论联系实际,编写一些代码,实现一些功能,才能加强记忆,学以致用。

六、参考文献

6.1 参考书目

[1] W.Richard Stevens,Bill Fenner,Andrew M.Rudoff. Unix Network Programming Volume1:The Sockets Networking API,Third Edition. Pearson

22

ronnie

Education,2010 [2] Wnthony Jones,Jim Ohlund. Network Programming for Microsoft Windows,Second Edition.Microsoft Press,2002

[3] 谢希仁.计算机网络(第四版).电子工业出版社,2003

[4] 周鸣争,严楠,丁刚等.计算机网络教程.清华大学出版社,2011

6.2 引用网址

http://msdn.microsoft.com/en-us/library/windows/desktop/ms741416(v=vs.85).aspx

http://msdn.microsoft.com/en-us/library/windows/desktop/ms740668(v=vs.85).aspx

http://www.ietf.org/rfc.html

http://datatracker.ietf.org/doc/rfc792/

http://baike.http://www.wodefanwen.com//view/700108.htm

http://baike.http://www.wodefanwen.com//view/30564.htm

http://zh.wikipedia.org/zh-cn/RFC

源代码

Scanner,h //IP首部

typedef struct ip_hdr { unsigned char iphVerLen; // 版本号和头长度(各占4位) unsigned char ipTOS; // 服务类型 unsigned short ipLength; // 封包总长度,即整个IP报的长度 unsigned short ipID; // 封包标识,惟一标识发送的每一个数据报 unsigned short ipFlags; // 标志 unsigned char ipTTL; // 生存时间,就是TTL unsigned char ipProtocol; // 协议,可能是TCP、UDP、ICMP等 unsigned short ipChecksum; // 校验和 unsigned long ipSource; // 源IP地址 unsigned long ipDestination; // 目标IP地址 }IP_HDR,*PIP_HDR;

//ICMP首部

typedef struct icmp_hdr

23

ronnie

{ unsigned char icmp_type; //类型 unsigned char icmp_code; //代码 unsigned short icmp_checksum; //检验和 unsigned short icmp_id; //唯一请求ID,通常使用进程PID unsigned short icmp_sequence; //序列号 unsigned long icmp_timestamp; //时间戳 } ICMP_HDR,*PICMP_HDR;

Scanner.c #include #include #include #include #include

#pragma comment(lib,\

#include \

#define ICMP_ECHO_REQUEST_TYPE 8 //ICMP请求类型 #define ICMP_ECHO_REQUEST_CODE 0 //ICMP请求代码 #define ICMP_ECHO_REPLY_TYPE 0 #define ICMP_ECHO_REPLY_CODE 0 #define ICMP_MINIMUM_HEADER 8

struct hostent *host; //记录主机信息 struct sockaddr_in dest; //存储目的主机信息 struct sockaddr_in from; //用于回显

//注释:常见的ICMP报文类型

// 类型 代码 描述

// 0 0 回显回答 // 3 1 主机不可达 // 3 3 端口不可达 // 8 0 请求回显

//初始化winsock动态链接库 void InitializeWinsock() { int status; WSADATA wsa; if (status=WSAStartup(MAKEWORD(2,2),&wsa)!=0) {

24

ronnie

printf(\ exit(EXIT_FAILURE); } }

//初始化ICMP首部

void InitIcmpHeader(ICMP_HDR* icmp_hdr) { char buff[sizeof(ICMP_HDR) + 32]; //ICMP报文类型,类型为8,代码为0 icmp_hdr->icmp_type = ICMP_ECHO_REQUEST_TYPE; // 请求回显 icmp_hdr->icmp_code = ICMP_ECHO_REQUEST_CODE; icmp_hdr->icmp_id = (USHORT)GetCurrentProcessId(); icmp_hdr->icmp_checksum = 0; icmp_hdr->icmp_sequence = 0; icmp_hdr->icmp_timestamp= GetTickCount(); //往ICMP头部填充信息 memset(&buff[sizeof(ICMP_HDR)], 'E', 32); }

//地址解析,解析IP地址或域名 void Resolove(char hostname[]) { if(isdigit(hostname[0])) //判断主机名是否为数字 { //printf(\执行inet_addr()...\\n\ dest.sin_addr.s_addr = inet_addr(hostname); //将主机地址写入s_addr //printf(\ } else if( (host=gethostbyname(hostname)) != 0)//判断所给主机名是否与host中的一致 { //printf(\执行gethostbyname()...\\n\ strncpy((char *)&dest.sin_addr , (char *)host->h_addr_list[0] , sizeof dest.sin_addr); //printf(\完成。\\n\ } else { printf(\解析主机失败。\\n\ exit(EXIT_FAILURE); } }

//提取目的地址低三位,用于网段扫描

void ResoloveIPAddr(char starthost[],char endhost[],int *start,int *end)

25

ronnie

{ int cd1=0;int cd2=0;int ci1=0;int ci2=0; char c1[4],c2[4]; while (*starthost!='\\0'&&*endhost!='\\0') { if (*starthost!='\\0') { if (cd1<3) { if(*starthost=='.') ++cd1; } else { c1[ci1]=*starthost; ++ci1; } ++starthost; } if(*endhost!='\\0') { if (cd2<3) { if(*endhost=='.') ++cd2; } else { c2[ci2]=*endhost; ++ci2; } ++endhost; } } c1[3]=c2[3]='\\0'; *start=atoi(c1); *end=atoi(c2); }

//将当前IP低三位与网段号组装,用于发送ICMP的目的主机IP地址 char* Assemble(char starthost[],int cur) { int i=0,cd=0;char appendix[4],*tmp=starthost; itoa(cur,appendix,10);

26

ronnie

//printf(\ while (cd<3) { if (*tmp=='.') ++cd; ++tmp; } *tmp='\\0'; strcat(starthost,appendix); return starthost; }

//计算检验和

unsigned short checksum(unsigned short *buffer, int size) { unsigned long cksum=0; while (size > 1) { cksum += *buffer++; size -= sizeof(USHORT); } if (size) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); }

//利用TCP connect()方法对目的主机的指定端口范围扫描 void ConnectScan(int startport,int endport,char hostname[]) { int i,status; SOCKET sock=INVALID_SOCKET; strncpy((char *)&dest,\ dest.sin_family = AF_INET; InitializeWinsock(); Resolove(hostname); printf(\

27

ronnie

printf(\for(i = startport ; i<= endport ; i++) { sock = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP); //创建一个Socket if(sock==INVALID_SOCKET) { printf(\ exit(EXIT_FAILURE); } }

//主机字节序转换为网络字节序 dest.sin_port = htons(i);

//用此Socket连接目的主机,核心代码

status = connect(sock , (struct sockaddr *)&dest , sizeof dest);

if(status == SOCKET_ERROR) //连接失败 { switch(WSAGetLastError()) { case 10060: printf(\ break; case 10061: printf(\ break; default: printf(\ break; } //fflush(stdout); }

else //连接成功 { printf(\ //关闭收发服务 if( shutdown( sock ,SD_BOTH ) == SOCKET_ERROR ) { printf(\ exit(EXIT_FAILURE); } }

closesocket(sock); //关闭Socket,回收资源

28

ronnie

}

//利用ICMP进行网段扫描

void SegmentScan(char starthost[],char endhost[]) { int status, //记录状态信息 tick, //获取当前系统启动时间的毫秒数 start, //起始网络号 end, //结尾网络号 i; //用于for循环等 WSADATA wsa; //char starthost[50],endhost[50]; SOCKET sock=INVALID_SOCKET; char recvBuf[1024*5]; //设置接收缓冲区,这里设为5K ICMP_HDR* icmp_hdr,*recv_icmp; //发送和接收的ICMP报文首部定义 unsigned short nSeq = 0; int nLen = sizeof(from); //创建ICMP数据包 char buff[sizeof(ICMP_HDR) + 32]; icmp_hdr = (ICMP_HDR*)buff; //加载动态链接库 InitializeWinsock(); //创建原始套接字 //AF_INET表示地址族为IPV4, //SOCK_RAW表示创建的为原始套接字,注意若在UNIX/LINUX环境下 //应该获得root权限,在Windows环境下使用管理员权限运行程序 //协议参数填写ICMP协议 sock=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP); if (sock == INVALID_SOCKET) { if (WSAGetLastError()==10013) { //错误代码为10013,原因是权限不够 printf(\ exit(EXIT_FAILURE); } }

29

ronnie

//填写目的主机相关信息,不需要填写端口,因为ICMP是网络层协议 dest.sin_family=AF_INET; ResoloveIPAddr(starthost,endhost,&start,&end); printf(\ printf(\ for (i=start;i<=end;i++) { //printf(\ Resolove(Assemble(starthost,i)); //printf(\ //填充ICMP包 InitIcmpHeader(icmp_hdr); //icmp_hdr->icmp_checksum=0; //icmp_hdr->icmp_timestamp = GetTickCount(); icmp_hdr->icmp_sequence=i; icmp_hdr->icmp_checksum=checksum((unsigned short*)buff, sizeof(ICMP_HDR) + 32); // 开始发送和接收ICMP封包 status=sendto(sock, buff, sizeof(ICMP_HDR) + 32, 0, (SOCKADDR *)&dest, sizeof(dest)); if(status==SOCKET_ERROR) { printf(\ exit(EXIT_FAILURE); } //接受回显回答 status=recvfrom(sock,recvBuf,1024*5,0,(SOCKADDR*)&from,&nLen); if(status==SOCKET_ERROR) { if(WSAGetLastError()==WSAETIMEDOUT) { printf(\ exit(EXIT_FAILURE); } printf(\ exit(EXIT_FAILURE); } // 接收到的数据中包含IP头,IP头大小为20个字节,所以加20得到ICMP头 recv_icmp=(ICMP_HDR*)(recvBuf+20); tick=GetTickCount(); //判断回显报是否符合长度 if (status

30

ronnie

printf(\ } //icmp_type为0是回显回答 if(recv_icmp->icmp_type!=0) { //主机未响应是不能调用回显地址,只能用测试时的主机地址starthost printf(\

Nonresponse\\ttype:%d\\tcode:%d\\n\ //exit(EXIT_FAILURE); } /*if(recv_icmp->icmp_id != GetCurrentProcessId()) { printf(\ exit(EXIT_FAILURE); }*/ else{ printf(\Response\\t\\ttype:%d\\tcode:%d\\t\\t%d ms\\n\cmp_timestamp); } } printf(\ closesocket(sock); }

void main() { /*char starthost[100],endhost[100]; //起始结尾IP地址 int startport,endport; //起始和结束端口,确定端口区间 char hostname[100]; //主机地址或域名 /*printf(\ gets(hostname); printf(\ scanf(\ ConnectScan(startport,endport,hostname);*/ /*printf(\ gets(starthost);gets(endhost); SegmentScan(starthost,endhost);*/ int choice; char host[100],starthost[100],endhost[100]; //起始结尾IP地址 int startport,endport; //起始和结

31

ronnie

束端口,确定端口区间 while (TRUE) { printf(\ printf(\ A simple scanner \\n\ printf(\ printf(\ \\n\ printf(\ \\n\ printf(\ \\n\ printf(\ \\n\ printf(\ choice=getch(); switch(choice) { case '1': printf(\ gets(starthost); gets(endhost); fflush(stdin); SegmentScan(starthost,endhost); break; case '2': printf(\ gets(host); printf(\ scanf(\ fflush(stdin); ConnectScan(startport,endport,host); break; case '3': system(\ break; case '4': WSACleanup(); exit(0); break; default: system(\ break; } } }

32

本文来源:https://www.bwwdw.com/article/ls4g.html

Top