资源描述
课 程 设 计 任 务 书
【设计目标】
加深对TCP/IP协议了解,熟悉Socket编程
【设计任务】
实现一个扫描器,必需能够完成以下全部功效:使用TCP connect、TCP SYN和TCP FIN进行端口扫描,使用ICMP echo 扫描实现IP扫描。并把结果统计下来。
【设计要求】
l Windows或Linux环境下,程序在单机上运行。
l 演示:使用端口扫描对一台主机进行扫描,并显示结果(一台主机上有哪些端口是打开)。对一个网段进行IP扫描,显示结果(一个网段内有哪些主机是开机)。
l 友好用户界面
【设计要求】
l 程序源代码,必需有具体注释。
l 项目设计汇报。
网段和端口扫描程序
一、 概述 1
1.1端口扫描 1
1.2端口扫描常见技术介绍 2
1.2.1 connect()扫描 2
1.2.2 SYN扫描 3
1.2.3 XMAS-TREE扫描 3
1.3网段扫描 4
二、需求分析 5
2.1 设计功效 5
2.2 设计要求 5
三、概要设计 5
3.1设计概念和处理步骤 5
3.2 结构设计 7
3.2.1 数据结构及定义 7
3.2.2 接口函数 8
四、细设设计 9
4.1 实现原理 9
4.1.1 connect()函数端口扫描原理 9
4.1.2 ICMP网段扫描原理 11
4.2 函数实现 14
4.2.1 初始化winsock动态链接库 14
4.2.3地址解析 15
4.2.4 计算检验和 15
4.2.5 网段扫描 16
4.2.6 端口扫描 17
五、 总结和体会 19
5.1 程序运行和调试 19
5.2 体会 20
六、 参考文件 21
6.1 参考书目 21
6.2 引用网址 21
一、 概述
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端主动断开连接,连接关闭。
过程图以下:
1.2.2 SYN扫描
SYN扫描前两步和connect()扫描相同,只是最终一步,Client端发送不是ACK确定报文而是RST报文,这么三次握手过程就没有完成,Client和Server也就没有建立TCP连接,因以前述过程不会被Sever端统计到系统日志中,扫描愈加隐蔽。
过程图以下:
1.2.3 XMAS-TREE扫描
经过发送带有以下标志位TCP数据包URG,PSH,FIN来试探主机。在目标端口开放情况下不放回任何信息。
端口开放:发送URG/PSH/FIN,没有响应。
端口关闭:1.发送URG/PSH/FIN,没有响应。2响应RST。
1.3网段扫描
网段扫描是指利用ICMP(因特网控制报文协议)对某一网段全部IP地址发送ICMP报文,测试IP地址所对应主机具体情况(如是否开机等)。所以对目标主机回复ICMP报文进行分析是网段扫描关键。常见报文类型有:
类型(type)
代码(code)
描述
差错
查询
0
0
回显回复
*
3
1
主机不可达
*
3
端口不可达
*
8
0
请求回显
*
二、需求分析
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:(v=vs.85).aspx
用户首先选择要使用功效,程序共有两大功效:端口扫描和IP网段扫描。端口扫描关键功效有,依据用户输入主机名或IP地址和端口范围进行connect()扫描。当用户输入完必需信息并击确定后,程序进行扫描,不过不提议端口区间过于庞大,等候一段时间后用户界面会显示所指定IP地址主机端口使用情况,哪些端口是打开,哪些是关闭。IP网段扫描关键功效有,用户输入某个网段起始IP地址和结束IP地址,确定后,程序依据所输入网段号,对网段类每一个IP地址发送ICMP请求回显报文,假如主机处于开机状态那么将会回送回复报文;假如主机不可达(在同一个网内),则就能够判定该IP所对应主机处于关机状态。当用户选择退出功效时,释放资源,程序关闭。
程序步骤图
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个字节,数据结构定义以下:
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类型数据。
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报文段首部格式。
现在分别介绍各个字段具体含义。(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是表明这是一个请求连接报文,若同意连接则对方应该回送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许可主机或路由器汇报差错情况和提供相关异常情况汇报。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层数据报。函数原型分别为:
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报文。下面对原始套接字做下简单介绍。
原始套接字提供一般TCP和UDP套接字所不提供一下3个功效。
v 有了原始套接字,进程能够读写ICMP和IGMP等分组。举例来说ping程序就是使用原始套接字收发ICMP分组,网段扫描也是基于这个原理。
v 有了原始套接字,进程能够读写内核不处理其协议字段IPv4数据包。
v 有了原始套接字,进程还能够使用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("Failed to WSAStartup(): %d.\n",WSAGetLastError());
exit(EXIT_FAILURE);
}
}
函数调用Winsock2提供WSAStartup()函数,初始化动态链接库。
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("%d\n",WSAGetLastError());
}
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);
}
}
依据用户输入地址字符串转化成对应地址字段,写入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("Create socket failed: %d\n",WSAGetLastError());
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("%s\t%d\tERROR\t\tConnection timed out\n" , hostname , i );
break;
case 10061:
printf("%s\t%d\tERROR\t\tConnection refused\n" , hostname , i );
break;
default:
printf("%s\t%d\tERROR\tCode:\n",WSAGetLastError());
break;
}
//fflush(stdout);
}
else //连接成功
{
printf("%s\t%d\tPORT OPEN\n" , hostname ,i);
//关闭收发服务
if( shutdown( sock ,SD_BOTH ) == SOCKET_ERROR )
{
printf("Failed to shutdown the connect: %d\n",WSAGetLastError());
exit(EXIT_FAILURE);
}
}
closesocket(sock); //关闭Socket,回收资源
//WSACleanup();
}
利用一个for(;;)循环,对确定起始和终止IP地址内全部可能存在主机发送,请求回显ICMP报文,并对返回ICMP报文进行分析,提取出type和code字段,依据ICMP报文格式所定义数值,对各个主机判定,并打印信息。
4.2.6 端口扫描
for(i = startport ; i<= endport ; i++)
{
sock = socket(AF_INET , SOCK_STREAM , IPPROTO_TCP); //创建一个Socket
if(sock==INVALID_SOCKET)
{
printf("Create socket failed: %d\n",WSAGetLastError());
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("%s\t%d\tERROR\t\tConnection timed out\n" , hostname , i );
break;
case 10061:
printf("%s\t%d\tERROR\t\tConnection refused\n" , hostname , i );
break;
default:
printf("%s\t%d\tERROR\tCode:\n",WSAGetLastError());
break;
}
//fflush(stdout);
}
else //连接成功
{
printf("%s\t%d\tPORT OPEN\n" , hostname ,i);
//关闭收发服务
if( shutdown( sock ,SD_BOTH ) == SOCKET_ERROR )
{
printf("Failed to shutdown the connect: %d\n",WSAGetLastError());
exit(EXIT_FAILURE);
}
}
closesocket(sock); //关闭Socket,回收资源
//WSACleanup();
}
函数一样利用一个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.
摘自:(v=vs.85).aspx
五、 总结和体会
5.1 程序调试和运行
程序主界面
IP网段扫描
端口扫描
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 Education,
[2] Wnthony Jones,Jim Ohlund. Network Programming for Microsoft Windows,Second Edition.Microsoft Press,
[3] 谢希仁.计算机网络(第四版).电子工业出版社,
[4] 周鸣争,严楠,丁刚等.计算机网络教程.清华大学出版社,
6.2 引用网址
(v=vs.85).aspx
(v=vs.85).aspx
源代码
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
{
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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <conio.h>
#include <WinSock2.h>
#pragma comment(lib,"ws2_32.lib");
#include "Scanner.h"
#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)
{
printf("Failed to WSAStartup(): %d.\n",WSAGetLastError());
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_REQU
展开阅读全文