Git链接
篇幅有限,代码就不贴出来了,有兴趣去GitHub看看;
根据socket原始套接字,我们从链路层捕获数据包,然后把数据包层层转换,转换成对应数据包格式的结构体,然后拿到头部信息,再偏移底层协议结构体的字节长度,再获取上一层协议的首部信息。
链路层: 以太网帧格式: "协议类型"字段为0800是IP数据包;0806是ARP包;8035是RARP包;
ARP数据包格式:
网络层 IP数据包 传输层 UDP: TCP:
每种协议对应的结构体都放在/usr/include/Linux/ 下; 或者/usr/include/netinet/这个下边也有,俩差不多;
示例:IP首部结构体,这个结构体的信息与上边图片里的IP数据包的信息一一对应。(到时候就用这个结构体指针将我们捕获的数据包强转,然后获得对应的头部信息)
tcp首部结构体:
其他协议结构体略。 示例: 获得tcp首部信息
void print_tcp(char *buf) { struct tcphdr *pt = (struct tcphdr*)buf; printf("TCP头部[ 源端口:%hu 目的端口: %hu 序号seq : %u ", ntohs(pt->source), ntohs(pt->dest), ntohl(pt->seq)); if ( pt->ack ) printf("确认号ack_seq: %u", ntohl(pt->ack_seq)); if ( pt->fin ) printf(" fin"); if ( pt->syn ) printf(" syn"); if ( pt->ack ) printf(" ack"); printf("]"); printf("\n\n"); }这次我们需要将socket的参数设为
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));参数一指定链路层,参数二原始套接字编程,参数三包含头部信息。
抓取的结果如图:(只抓tcp包,其他类似)
根据用户输入的字符串网段地址(假设c类网络地址),将其转换成IP地址,然后循环255次遍历这个网段的ip,每次给对应的ip地址发送ICMP的ping报文,当然这个包需要我们自己创建,给这个包的数据区填上发送的时间。然后再创建一个select,一边接收对方发回的消息,一边防止超时。当我们发送的ip给我们回复包的时候,我们先判断这个包的源ip跟我们发送的ip是否相等,相等的话再判断是否ICMP的类型值是8应答包,都相等则这个主机正在工作。并且打印出这个主机的一些信息,和这个包发送过来的时间差。如果发送三次ICMP请求包都超时,则我们认为这个主机没有在工作。
这次需要将socket参数设置为
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP)参数一指定IPV4,参数二原始套接字编程,参数三只收发ICMP协议
ICMP报文的类型值是0代表回显请求,8代表回显应答,我的这个程序只用到这俩。同时因为这个ICMP没有涉及到端口号,所以不可以使用write函数,需要用到recvfrom函数发送。
ICMP的报文格式: ICMP的结构体:
根据上边边这俩图片,我们可以设计一共自组ICMP包的函数;
void make_icmp_packet(struct icmp* picmp, int len, int n) { memset(picmp, 0x00, len); gettimeofday((struct timeval*)(picmp->icmp_data), NULL); picmp->icmp_type = ICMP_ECHO;//发送报文,0 picmp->icmp_code = 0; picmp->icmp_cksum = 0; picmp->icmp_id = getpid(); picmp->icmp_seq = n; picmp->icmp_cksum = checksum((u_short*)picmp, len); }思想: 1.将校验和字段清零;2.对每16比特位进行二进制反码累加求和。
//校验和函数 u_short checksum(u_short *data, int len) { u_long sum = 0; for ( ; len > 1; len-=2 ) { // 以16位为单位进行加 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; } while ( sum >> 16 ) // 高16位如果有1,继续计算 sum = (sum&0xffff) + (sum>>16); return (sum == 0xffff) ? sum : ~sum; }开始扫描:
扫描出主机存在且在工作:
查看常用端口的名字: vim /etc/services
做全连接扫描,即客户端发送connect,如果连接成功,则这个端口就是打开的,否则这个端口未打开。
这次只需要普通的TCP连接即可
socket(AF_INET, SOCK_STREAM, 0)示例代码
for (i=start_port; i<=end_port; i++) { if ( tcp_connet(ip, i) == 1 ) { //connet返回1代表连接成功,即端口开放 struct servent *ps = getservbyport(htons(i), "tcp"); //把tcp协议里的i端口相关信息装进对应的结构体中,来获得端口信息 printf("%d, %s", i, (ps==NULL)?"unkown":ps->s_name); //如果是1023后的端口,不是常用端口,没有名字的话打印null } }假设有一台 服务器,它正处于listen状态,此时它会有一个未完成三次握手的队列和已完成三次握手的队列,我们给它发一个请求建立连接的数据包,但是这个包的源ip我们用随机数生成一个,这样它就不会屏蔽我们的数据包,可以不停的给它发请求连接包,直到将它的未完成三次握手队列占满。这样,当真正需要建立连接服务的程序请求与服务器建立连接,那么它会因为未完成三次握手队列已经满了而陷入等待,从而影响服务器的正常工作。
参数一网络层,参数二原始套接字,参数三收发tcp协议
netstat -anpt 我们启动一个自己写的服务器程序,开始监听; 然后再向那个服务器的ip地址和端口号发送请求连接报文; 可以看到以下情况