使用背景

今天继续介绍一下原始套接字的一种用法,就是用来模拟TCP的握手连接过程,不过作为客户端,用户可以直接使用connect函数来进行连接操作,那么为何还要用RawSocket来模拟呢?其实就涉及到一种使用场景,就是扫描端口。听起来就像干坏事
在这里插入图片描述
其实端口扫描是用来发现风险,并且规避风险的重要手段,知己知彼啊
在这里插入图片描述
TCP端口扫描的实现形式

  • SYN扫描

双方并没有建立起一条完整的连接,而是扫描着先向被扫描的目的端口发出一个SYN包,如果从目标端口返回一个syn/ack包,就可以断定该端口处于监听状态(即端口是开放的),如果返回rst包,则表明端口不在监听状态,是关闭的。
优点:速度快,如果不被防火墙过滤的话,基本都能收到应答包
缺点:扫描行为容易被发现,并且它是不可靠的,容易丢包

  • FIN扫描

主动结束的一方发送FIN包,当我们发送一个FIN包给一个非监听的端口时,会有RST应答,反之,发给一个正在监听的端口时,不会有任何回应。
优点:隐蔽性好,速度快
缺点:只能用于Linux系统,Windows系统下无效

就常见的端口服务来说,大概有5000种左右,这个我们可以通过查看/etc/services文件看到目前常见的服务端口
在这里插入图片描述
显然,如果我们需要快速扫描一个设备的开放端口,那么如果扫描每个端口,通过connect函数的方式,将会特别的慢,因为connect默认的超时时间是75秒,那么扫描一台设备,将会耗用大量的时间
在这里插入图片描述

所以我们需要一种快速的方式,即前面引用中描述的,发送syn报文,然后看能否收到syn/ack应答即可。
如果我们需要快速扫描全部端口,建议通过多线程的方式,一个线程向被扫描设备所有端口发送syn消息,然后另一个线程开始接收应答,收到哪个端口的应答,即可认为该端口是开放的。

关键步骤

  • 创建socket(发送线程)
sockfd_s = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);  //接收套接字
if(sockfd_s < 0)
{
	TCP_LOG_ERR("create sockfd_s failed\n");
	p2_run--;
	pthread_exit(NULL);
}
int one = 1;
if(setsockopt(sockfd_s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0)
{	 //定义套接字不添加IP首部,代码中手工添加
	TCP_LOG_ERR("setsockopt failed!\n");
	close(sockfd_s);
	p2_run--;
	pthread_exit(NULL);
}

这里的核心操作主要是第二步骤,要自己控制IP头部内容,所以这里不用自动填写,我们自己掌控局面
在这里插入图片描述

  • 构建IP及TCP首部
char pkg_buffer[TCP_BUF_SIZE];
struct ip *ip_header;		//ip首部指针
struct tcphdr *tcp_header;	//tcp首部指针
unsigned int ip_datagram_len;

ip_datagram_len= sizeof(struct ip) + sizeof(struct tcphdr);   //ip数据报长度

// 初始化ip数据报 ip_datagram(IP首部+TCP首部+TCP数据部分)
memset(pkg_buffer,0,TCP_BUF_SIZE);

// 构建IP首部和TCP首部指针
ip_header = (struct ip *)pkg_buffer;
tcp_header = (struct tcphdr *)(pkg_buffer + sizeof(struct ip));//ip首部后面就是tcp报文段了

/*封装ip首部*/
// 版本 4
ip_header->ip_v = IPVERSION;
// 首部长度 4
ip_header->ip_hl = sizeof(struct ip)>>2;
// 服务类型(types of service) 8
ip_header->ip_tos = 0;
// 总长度 16
ip_header->ip_len = htons(ip_datagram_len);
// 标识 16
ip_header->ip_id = 0;
// 标志+偏移 16
ip_header->ip_off = 0;
// 生存时间 8
ip_header->ip_ttl = 0;
// 协议 8
ip_header->ip_p = IPPROTO_TCP;
// 首部检验和 16
ip_header->ip_sum = 0;
//源地址 32
ip_header->ip_src.s_addr =inet_addr(local_ipaddr);
// 目的地址 32
ip_header->ip_dst.s_addr = inet_addr(dest_ipaddr);

 // tcp源端口
tcp_header->source = htons(SEND_TCPMQ[i].sport);
// 目的端口 16
tcp_header->dest = htons(i);
// 序号 32
tcp_header->seq = 0;
// 确认号 32
tcp_header->ack_seq = 0;
// 数据偏移 4
//tcp_header->res1 = 0;
// 保留 4
tcp_header->doff = 5;  // 这里从wireshark来看是指的是数据偏移,resl和doff的位置反了,不知道是头文件有问题还是什么的,应该不是大小端问题。
//res2+urg+ack+psh+rst+syn+fin 8

//tcp_header->res2 = 0;
//tcp_header->urg = 0;
//tcp_header->ack = 0;
//tcp_header->psh = 0;
//tcp_header->rst = 0;
tcp_header->syn = 1;
//tcp_header->fin = 0;
// 窗口 16
//tcp_header->window = 0;
// 检验和 16
tcp_header->check = 0;
// 紧急指针 16
//tcp_header->urg_ptr = 0;

/*计算tcp校验和*/
ip_header->ip_ttl = 0;
tcp_header->check = 0;

// ip首部的校验和,内核会自动计算,可先作为伪首部,存放tcp长度
ip_header->ip_sum = htons(sizeof(struct tcphdr));

// 计算tcp校验和,从伪首部开始
tcp_header->check = check_sum((unsigned short *)pkg_buffer+4,ip_datagram_len-8);

ip_header->ip_ttl = MAXTTL;

  • 发送
if ((n = sendto(sockfd_s, pkg_buffer, ip_datagram_len, 0, (struct sockaddr *)&dst_addr, sizeof(struct sockaddr_in)))< 0)
{
	printf("发送TCP失败n = %d", n);
}
  • 接收部分,采用了select方式,通过配置超时时间,例如两秒,超时没有收到响应,那么认为已经处理完毕
FD_ZERO(&fds);
FD_SET(sockfd_r, &fds);

timeout.tv_sec = 2;
timeout.tv_usec = 0;

memset(buf, 0, 1024);
len = sizeof(client_addr);

ret = select(sockfd_r+1, &fds, NULL, NULL, &timeout);

if(ret == 0)
{
	printf("select time out#######continue\n");
	break;
}
else if(ret == -1)
{
	printf("select error");
	continue;
}
else
{
	if(FD_ISSET(sockfd_r,&fds))
	{
		int n = 0;
		if((n = recvfrom(sockfd_r, buf, 1024, 0, (struct sockaddr*)&client_addr, &len)) < 0)
		{
			continue;
		}
		else
		{
			struct tcphdr *tcp_header= NULL;
			struct	ip * ip = NULL;
			int 	iphdrlen;
			
			ip			= (struct ip *)buf;
			iphdrlen	= ip->ip_hl << 2;
			tcp_header = (struct tcphdr*)(buf+iphdrlen);
			
			unsigned short formport=0;
			
			if (n < (sizeof(struct ip)+sizeof(struct tcphdr)))
			{
				printf("TCP packets length is less than 8\n\r");
			}
			formport=htons(tcp_header->source) ;
			if(SEND_TCPMQ[formport].used)
			{
				if((SEND_TCPMQ[formport].sport)==htons(tcp_header->dest))
				{
				
					if((tcp_header->syn == 1)&&(tcp_header->ack == 1)&&(!SEND_TCPMQ[formport].sended))
					{
						printf("[SYN]faxian[%d]",formport);
						SEND_TCPMQ[formport].sended=true;
														
					}
				}
			}
		}
	}
}

注意这里有一个判断来源端口与发送端口是否一致的过程,因为我们的sokcet并没有绑定任何端口,所以所有数据都会过来,可能类似于抓包的结果,所以需要判断一下这个syn/ack来源是不是我们发送的响应结果。
在这里插入图片描述

下面是完整代码,接收线程

#include <netinet/in.h>     // for sockaddr_in
#include <netinet/tcp.h>    // for tcp
#include <netinet/ip.h>     // for ip

#define TCP_BUF_SIZE           		256
#define TCP_PORTFILE               "/etc/services"
#define TCP_LOG(fmt...)        		printf(fmt);
#define TCP_LOG_ERR(fmt...)   		printf(fmt);

typedef struct _p2_tcp_ser
{
	bool used;
	int sport;
	bool sended;
}TCP_MQ;

static TCP_MQ SEND_TCPMQ[65536];
static char dest_ipaddr[32]={0};
static char local_ipaddr[32]={0};
static int p2_run=2;
static unsigned short check_sum(unsigned short *buffer, int size)
{
    //建议将变量放入寄存器, 提高处理效率.
     int len = size;
    //16bit
     unsigned short *p = buffer;
    //32bit
     unsigned int sum = 0;

    //16bit求和
    while( len >= 2)
    {
        sum += *(p++)&0x0000ffff;
        len -= 2;
    }

    //最后的单字节直接求和
    if( len == 1){
        sum += *((unsigned char *)p);
    }

    //高16bit与低16bit求和, 直到高16bit为0
    while((sum&0xffff0000) != 0){
        sum = (sum>>16) + (sum&0x0000ffff);
    }
    return (unsigned short)(~sum);
}
/* get dst address */
static int tcp_dst_addr(const char *addrHost, struct sockaddr_in * dst_addr,int port)
{
    struct hostent  * host      = NULL;
    unsigned long   inaddr      = 0;
 
    bzero(dst_addr, sizeof(struct sockaddr_in));
    dst_addr->sin_family = AF_INET;
 
    if ((inaddr = inet_addr(addrHost)) == INADDR_NONE)
    {
        if ((host = gethostbyname(addrHost)) == NULL)
        {
            TCP_LOG_ERR("gethostbyname error.\n\r");
            return (-1);
        }
        memcpy((char *) &dst_addr->sin_addr, host->h_addr, host->h_length);
    }
    else 
    {
        memcpy((char *) &dst_addr->sin_addr, (char *) &inaddr, sizeof(dst_addr->sin_addr));
    }
    dst_addr->sin_port = htons(port);
    return 0;
}

static void SEND_TCPMQ_INIT(void)
{
	int i=0;
	FILE *fp = NULL;
	char values[1024]={0};
	
	for (i = 0; i < 65536; ++i)
	{
		SEND_TCPMQ[i].used=false;
		SEND_TCPMQ[i].sended=false;
	}
	
  	if((fp = fopen(TCP_PORTFILE,"r")) == NULL) 
	{
		TCP_LOG_ERR("!!!open[%s] is error!",TCP_PORTFILE);
		return;
	}
	fseek(fp,0,SEEK_SET);

	//开始解析内容		
	while(!feof(fp))
	{
		int oport_int=0;
		unsigned short oport_short=0;
		
		char service[128]={0};
		int scanres=0;
		memset(values,0,1024);
		fgets(values,1024,fp);
		
		if((strlen(values)<2))
		{
			continue;
		}
		clean_newline(values);
		scanres=sscanf(values,"%s\t%d",service,&oport_int);
		if(scanres==2)
		{
			oport_short=(unsigned short)oport_int;
			SEND_TCPMQ[oport_short].used=true;
			SEND_TCPMQ[oport_short].sport=random()%65535;;
		}
	}
	
	fclose(fp);

}
void* p2_tcp_send_main(void* para)
{
	int sockfd_s;
	int i=0;
	
	sockfd_s = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);  //接收套接字
	if(sockfd_s < 0)
	{
		TCP_LOG_ERR("create sockfd_s failed\n");
		p2_run--;
		pthread_exit(NULL);
	}
	int one = 1;
	if(setsockopt(sockfd_s, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) < 0)
	{	 //定义套接字不添加IP首部,代码中手工添加
		TCP_LOG_ERR("setsockopt failed!\n");
		close(sockfd_s);
		p2_run--;
		pthread_exit(NULL);
	}
	for (i = 0; i < 65536; ++i)
	{
		if(SEND_TCPMQ[i].used)
		{
			struct sockaddr_in dst_addr;
			int n=0;
			char pkg_buffer[TCP_BUF_SIZE];
			struct ip *ip_header;		//ip首部指针
			struct tcphdr *tcp_header;	//tcp首部指针
			unsigned int ip_datagram_len;
		
			ip_datagram_len= sizeof(struct ip) + sizeof(struct tcphdr);   //ip数据报长度
		
			// 初始化ip数据报 ip_datagram(IP首部+TCP首部+TCP数据部分)
			memset(pkg_buffer,0,TCP_BUF_SIZE);
		
			// 构建IP首部和TCP首部指针
			ip_header = (struct ip *)pkg_buffer;
			tcp_header = (struct tcphdr *)(pkg_buffer + sizeof(struct ip));//ip首部后面就是tcp报文段了
		
			/*封装ip首部*/
			// 版本 4
			ip_header->ip_v = IPVERSION;
			// 首部长度 4
			ip_header->ip_hl = sizeof(struct ip)>>2;
			// 服务类型(types of service) 8
			ip_header->ip_tos = 0;
			// 总长度 16
			ip_header->ip_len = htons(ip_datagram_len);
			// 标识 16
			ip_header->ip_id = 0;
			// 标志+偏移 16
			ip_header->ip_off = 0;
			// 生存时间 8
			ip_header->ip_ttl = 0;
			// 协议 8
			ip_header->ip_p = IPPROTO_TCP;
			// 首部检验和 16
			ip_header->ip_sum = 0;
			//源地址 32
			ip_header->ip_src.s_addr =inet_addr(local_ipaddr);
			// 目的地址 32
			ip_header->ip_dst.s_addr = inet_addr(dest_ipaddr);
			
			 // tcp源端口
			tcp_header->source = htons(SEND_TCPMQ[i].sport);
			// 目的端口 16
			tcp_header->dest = htons(i);
			// 序号 32
			tcp_header->seq = 0;
			// 确认号 32
			tcp_header->ack_seq = 0;
			// 数据偏移 4
			//tcp_header->res1 = 0;
			// 保留 4
			tcp_header->doff = 5;  // 这里从wireshark来看是指的是数据偏移,resl和doff的位置反了,不知道是头文件有问题还是什么的,应该不是大小端问题。
			//res2+urg+ack+psh+rst+syn+fin 8
			
			//tcp_header->res2 = 0;
			//tcp_header->urg = 0;
			//tcp_header->ack = 0;
			//tcp_header->psh = 0;
			//tcp_header->rst = 0;
			tcp_header->syn = 1;
			//tcp_header->fin = 0;
		

			// 窗口 16
			//tcp_header->window = 0;
			// 检验和 16
			tcp_header->check = 0;
			// 紧急指针 16
			//tcp_header->urg_ptr = 0;
		
			/*计算tcp校验和*/
			ip_header->ip_ttl = 0;
			tcp_header->check = 0;
		
			// ip首部的校验和,内核会自动计算,可先作为伪首部,存放tcp长度
			ip_header->ip_sum = htons(sizeof(struct tcphdr));
		
			// 计算tcp校验和,从伪首部开始
			tcp_header->check = check_sum((unsigned short *)pkg_buffer+4,ip_datagram_len-8);
		
			ip_header->ip_ttl = MAXTTL;

			tcp_dst_addr(dest_ipaddr, &dst_addr,i);
			if ((n = sendto(sockfd_s, pkg_buffer, ip_datagram_len, 0, (struct sockaddr *)&dst_addr, sizeof(struct sockaddr_in)))< 0)
			{
				printf("发送TCP失败n = %d", n);
			}
		}
	}
	close(sockfd_s);
	p2_run--;
	pthread_exit(NULL);
}

int p2_tcp_ping_send_process(void)
{
	int ret=0;
	pthread_t main_process;
	pthread_attr_t attr;
	pthread_attr_init (&attr);
	/* 设置线程的属性为分离的 */
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	pthread_create(&main_process, &attr, p2_tcp_send_main, NULL);
	
	if (ret!= 0)
	{
		printf("线程创建失败(%d)", ret);
		pthread_attr_destroy(&attr);
		return -1;
	}
	return 0;
}
void* p2_tcp_revice_main(void* para)
{
	int ret;
	int sockfd_r;
	struct timeval timeout;
	fd_set fds;
	struct sockaddr_in client_addr;
	char buf[1024];
	socklen_t len;
	
	sockfd_r = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);  //接收套接字
	if(sockfd_r < 0)
	{
		printf("create sockfd_r failed\n");
		p2_run--;
		pthread_exit(NULL);
	}
	while(1)
	{
		FD_ZERO(&fds);
		FD_SET(sockfd_r, &fds);

		timeout.tv_sec = 2;
		timeout.tv_usec = 0;

		memset(buf, 0, 1024);
		len = sizeof(client_addr);

		ret = select(sockfd_r+1, &fds, NULL, NULL, &timeout);

		if(ret == 0)
		{
			printf("select time out#######continue\n");
			break;
		}
		else if(ret == -1)
		{
			printf("select error");
			continue;
		}
		else
		{
			if(FD_ISSET(sockfd_r,&fds))
			{
				int n = 0;
				if((n = recvfrom(sockfd_r, buf, 1024, 0, (struct sockaddr*)&client_addr, &len)) < 0)
				{
					continue;
				}
				else
				{
					struct tcphdr *tcp_header= NULL;
					struct	ip * ip = NULL;
					int 	iphdrlen;
					
					ip			= (struct ip *)buf;
					iphdrlen	= ip->ip_hl << 2;
					tcp_header = (struct tcphdr*)(buf+iphdrlen);
					
					unsigned short formport=0;
					
					if (n < (sizeof(struct ip)+sizeof(struct tcphdr)))
					{
						printf("TCP packets length is less than 8\n\r");
					}
					formport=htons(tcp_header->source) ;
					if(SEND_TCPMQ[formport].used)
					{
						if((SEND_TCPMQ[formport].sport)==htons(tcp_header->dest))
						{
						
							if((tcp_header->syn == 1)&&(tcp_header->ack == 1)&&(!SEND_TCPMQ[formport].sended))
							{
								printf("[SYN]faxian[%d]",formport);
								SEND_TCPMQ[formport].sended=true;
																
							}

							
						}
					}
				}
			}
		}
	}
	close(sockfd_r);
	p2_run--;
	pthread_exit(NULL);
}

int p2_tcp_ping_revice_process(void)
{
	int ret=0;
	pthread_t main_process;
	pthread_attr_t attr;
	pthread_attr_init (&attr);
	/* 设置线程的属性为分离的 */
	pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
	pthread_create(&main_process, &attr, p2_tcp_revice_main, NULL);
	
	if (ret!= 0)
	{
		printf("线程创建失败(%d)", ret);
		pthread_attr_destroy(&attr);
		return -1;
	}
	return 0;
}

void p2_tcp_ping(char* remote_ip,char* local_ip)
{
	p2_run=2;
	strcpy(dest_ipaddr,remote_ip);
	strcpy(local_ipaddr,local_ip);
	
	//初始化数组,随机生成每个请求的源端口
	SEND_TCPMQ_INIT();
	//启动接收线程
	p2_tcp_ping_revice_process();
	//启动发送线程
	p2_tcp_ping_send_process();
	while(p2_run!=0)
	{
		usleep(100);
	}
}

主函数void p2_tcp_ping(char* remote_ip,char* local_ip),需要传递远端IP和本地IP,进行端口扫描。
如果我们不想拘泥于常见端口,那么可以不通过/etc/services函数的指示,直接扫描每个端口,一共65535个,时间基本相差无几。

前面的引用中还提到了一种方式,发送FIN包,如果收到RST的回应,则端口关闭,否则端口开放。通过抓包也发现了这种情况,但是Socket并没有接收到数据,原因还在查。
在这里插入图片描述
最近升级了win11,大家有没有发现打开文件夹延迟巨大,感觉都有3-4秒的延迟,这对于我们这些思维敏捷的少年来说,简直是酷刑
在这里插入图片描述
还好网上找到了解决方法,
在这里插入图片描述
在这里插入图片描述
亲测,果然好用,放弃了退回版本的想法啊。毕竟11还是颜值更高。
在这里插入图片描述

Logo

更多推荐