主机扫描概述
使用“ICMP响应-请求”,“ICMP响应-应答”,“TCP连接请求和TCP协议的RST标志”、“UDP协议的包和ICMP不能到达的报文”可以探索目的主机是否存在于互连的网络中
在进行主机扫描时,按照顺序改变接收端的IP地址,发送ICMP响应-请求,对返回的ICMP响应-应答进行列表,就能得到在此计算机网络中互联的主机或者路由器的IP地址。
提醒:对于公网上的计算机进行扫描可能会存在犯罪行为,慎重
程序原理
向探测的目的IP地址发送ICMP响应-请求包,设置倒计时,如果在此倒计时中收到了来自目的IP地址的ICMP响应-应答包,则推断该主机存在,否则认为此IP主机不存在,增加IP地址,循环扫描后面的IP地址。
scannhost会使用raw IP,需要root权限
使用方法
./scanhost first_ip last_ip
程序代码
#include<sys/time.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<netdb.h>
#include<netinet/in_systm.h>
#include<unistd.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<netinet/ip_icmp.h>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<sys/select.h>
using namespace std;
#define CHKADDRESS(_saddr_)\
{\
u_char *p=(u_char*)&(_saddr_);\
if((p[0]==10)||(p[0]==168&&16<=p[1]&&p[1]<=31)||(p[0]==192&&p[1]==168))\
;\
else{\
fprintf(stderr,"ip address error!\n");\
exit(EXIT_FAILURE);\
}\
}
enum{CMD,START_IP,LAST_IP};
#define BUFSIZE 4096
#define PACKET_LEN 72
//创建icmp请求并发送
void make_icmp8_packet(struct icmp* icmp,int len,int n);
//设置tv结构体,检查是否超时
void tvsub(struct timeval* out,struct timeval* in);
u_short checksum(u_short *data,int len);
int main(int argc,char** argv)
{
//发送端IP地址
struct sockaddr_in send_ip;
//套接字描述符
int sockfd;
//发送缓冲区
char send_buf[BUFSIZE];
//接受缓冲区
char recv_buf[BUFSIZE];
//扫描的ip地址的初始值
int start_ip;
//扫描的ip地址的结束值
int last_ip;
//扫描的ip地址数
int dstip;
//时间信息
struct timeval tv;
//循环变量
int i;
//select的文件描述符
fd_set readfd;
//ip报头的结构体
struct ip* ip;
struct icmp* icmp;
//报文长度
int hlen;
if(argc!=3)
{
fprintf(stderr,"usage:%s start_ip last_ip",argv[CMD]);
exit(EXIT_FAILURE);
}
//设定扫描的ip范围
//先转换为ip地址,然后转换为主机long
start_ip=ntohl(inet_addr(argv[START_IP]));
last_ip=ntohl(inet_addr(argv[LAST_IP]));
memset((char*)&send_ip,0,sizeof(struct sockaddr_in));
send_ip.sin_family=AF_INET;
//raw套接字
if((sockfd=socket(AF_INET,SOCK_RAW,IPPROTO_ICMP))<0)
{
perror("socket");
exit(EXIT_FAILURE);
}
//扫描主机的主要子程序
for(dstip=start_ip;dstip<last_ip;dstip++)
{
send_ip.sin_addr.s_addr=htonl(dstip);
//检查发送端的ip地址
CHKADDRESS(send_ip.sin_addr);
//发送三次icmp请求
for(i=0;i<3;i++)
{
printf("scan'%s(%d)'\n",inet_ntoa(send_ip.sin_addr),i+1);
fflush(stdout);
//生成icmp响应请求包
make_icmp8_packet((struct icmp*)send_buf,PACKET_LEN,i);
if(sendto(sockfd,(char*)&send_buf,PACKET_LEN,0,(struct sockaddr*)&send_ip,sizeof(send_ip))<0)
{
perror("sendto");
exit(EXIT_FAILURE);
}
//设置select的超时值
tv.tv_sec=0;
tv.tv_usec=200*1000;
while(1)
{
//设置使用select的检查描述符
FD_ZERO(&readfd);
FD_SET(sockfd,&readfd);
if(select(sockfd+1,&readfd,NULL,NULL,&tv)<0)
break;
//接受错误
if(recvfrom(sockfd,recv_buf,BUFSIZE,0,NULL,NULL)<0)
{
perror("recvfrom");
exit(EXIT_FAILURE);
}
//ip报头中ip长度字段
ip=(struct ip*)recv_buf;
hlen=ip->ip_hl<<2;
//源地址为发送的ip地址
if(ip->ip_src.s_addr==send_ip.sin_addr.s_addr)
{
struct icmp* icmp;
//icmp
icmp=(struct icmp*)(recv_buf+hlen);
//icmp请求响应
if(icmp->icmp_type==ICMP_ECHOREPLY)
{
//输出主机的ip地址
printf("%15s",inet_ntoa(*(struct in_addr*)&(ip->ip_src.s_addr)));
gettimeofday(&tv,(struct timezone*)0);
tvsub(&tv,(struct timeval*)(icmp->icmp_data));
/*
__time_t tv_sec; /* Seconds.
__suseconds_t tv_usec; /* Microseconds.
将秒和微秒转化为毫秒
*/
printf(":RTT:%8.4fms\n",tv.tv_sec*1000.0+tv.tv_usec/1000.0);
goto exit_loop;
}
}
}
}
exit_loop:;
}
//关闭
close(sockfd);
return EXIT_SUCCESS;
}
/*
function:生成icmp包
返回值:void
*/
void make_icmp8_packet(struct icmp* icmp,int len,int n)
{
memset((char*)icmp,0,len);
//数据部分记录时间
gettimeofday((struct timeval*)(icmp->icmp_data),(struct timezone *)0);
//生成
icmp->icmp_type=ICMP_ECHO;
icmp->icmp_code=0;
icmp->icmp_id=0;
icmp->icmp_seq=n;
//计算检查和
icmp->icmp_cksum=0;
icmp->icmp_cksum=checksum((u_short*)icmp,len);
}
/*
struct timeval的减法运算,计算结果存储到out中
*/
void tvsub(struct timeval* out,struct timeval* in)
{
if((out->tv_usec-=in->tv_usec)<0)
{
out->tv_sec--;
out->tv_usec+=1000000;
}
//计算时间差
out->tv_sec-=in->tv_sec;
}
/*
function:计算检查和
big_endian little_endian 都适用
*/
u_short checksum(u_short *data,int len)
{
u_long sum=0;
//short为两个字节:: 16bit
//每次两个字节
for(;len>1;len-=2)
{
sum+=*data++;
//右移,将超出的位置
if(sum&0x80000000)
sum=(sum&0xffff)+(sum>>16);
}
//奇数个字节的情况
if(len==1)
{
u_short i=0;
*(u_char*)(&i)=*(u_char*)data;
sum+=i;
}
//对于溢出的位进行处理,sum占16位
//在前十六位为1得情况下反复进行处理
while(sum>>16)
{
sum=(sum&0xffff)+(sum>>16);
}
//在补码为0的情况下,并不使用一个补码,而是原封不动的将该值作为返回值
return (sum==0xffff)?sum:~sum;
}