一个简单扫描器的实现

这是很久以前的一个代码了。

网络扫描,扫描器这些名词想必大家并不陌生,踩点啥的必备工具。扫描的方法多种多样,目的也多种多样,本菜鸟就随便找了找资料简单介绍一下:

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 #include #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; i255||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编程基础的人都能看懂,我就不过多做解释了。

你可能感兴趣的:(一个简单扫描器的实现)