原理
首先需要了解下TCP连接过程,经典的三次握手,如下:
1、扫描端向目标端发送SYN请求建立连接 2、目标端收到请求后,回复ACK同意连接并同意发送SYN请求建立连接 3、扫描端收到后,发送ACK同意,此时三次握手完成,以此来判断端口是否存活
SYN扫描过程为:
1. 扫描端向目标发起SYN请求建立连接。 2. 目标同意SYN请求建立连接。 3. 扫描端接收后,发送RST断开连接。
扫描端发送SYN数据包后需要通过原始套接字调用recvfrom获取数据包状态。
端口状态的判断:
收到服务器返回数据以及说明如下:
1. ACK + SYN : 表示端口开启 2. ACK ,无SYN : 一般也表示开启 3. ACK + RST : 端口关闭,但是主机存活 4. 无数据 : 防火墙过滤活IP无法访问活服务器不存活
构造数据包注意点:
在构造TCP数据包时,一般有两种方式,
-
伪造IP + TCP报文
这种需要设置socket为原始套接字,并且设置选项为IP_HDRINCL,这样在调用sendto时就不会自动填充IP数据包首部。
int onVal; setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &oneVal, sizeof(oneVal));
2. 伪造TCP报文
由上可知,sendto函数会自动伪造IP首部,因此构造数据时,不用构造IP头,只需要构造tcp报文即可。
实现
直接上代码吧:
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <pthread.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <time.h>
#include <net/if.h>
#include <sys/ioctl.h>
//定义TCP伪报头
struct pseudo_header { //Needed for checksum calculation
unsigned int source_address;
unsigned int dest_address;
unsigned char placeholder;
unsigned char protocol;
unsigned short tcp_length;
struct tcphdr tcp;
};
/**
* 计算校验和
*/
unsigned short checksum(unsigned short *addr,int len){
int nleft=len;
int sum=0;
unsigned short * w=addr;
unsigned short answer=0;
while (nleft>1)
{
sum+=*w++;
nleft-=2;
}
if (nleft==1)
{
*(unsigned char *)(&answer)=*(unsigned char *)w;
sum+=answer;
}
sum=(sum>>16)+(sum & 0xffff);
sum+=(sum>>16);
answer=(short)~sum;
return(answer);
}
void get_local_ip(char* localip)
{
int sock;
struct sockaddr_in sin;
struct ifreq ifr;
sock = socket(AF_INET,SOCK_DGRAM,0);
if(sock == -1){
exit(0);
}
strncpy(ifr.ifr_name,"eth0",IFNAMSIZ);
ifr.ifr_name[IFNAMSIZ-1] = 0;
if(ioctl(sock,SIOCGIFADDR,&ifr)<0){
close(sock);
exit(0);
}
strcpy(localip,inet_ntoa(((struct sockaddr_in*)&ifr.ifr_addr)->sin_addr));
close(sock);
return;
}
void prepare_header(char* datagram,struct sockaddr_in dest,int src_port, int dst_port,int rst,int syn)
{
struct tcphdr* tcph = (struct tcphdr*)datagram; //TCP header
struct pseudo_header psh;
char source_ip[INET6_ADDRSTRLEN];
get_local_ip(source_ip);
//TCP Header
tcph->source = htons(src_port);
tcph->dest = htons(dst_port);
tcph->seq = htonl(134235232);
tcph->ack_seq = 0;
tcph->doff = sizeof(struct tcphdr) / 4;
tcph->fin = 0;
tcph->syn = syn;
tcph->rst = rst;
tcph->psh = 0;
tcph->ack = 0;
tcph->urg = 0;
tcph->window = htons(14600);
tcph->check = 0;
tcph->urg_ptr = 0;
// 构造psh计算checksum
psh.source_address = inet_addr(source_ip);
psh.dest_address = dest.sin_addr.s_addr;
psh.placeholder = 0;
psh.protocol = IPPROTO_TCP;
psh.tcp_length = htons(sizeof(struct tcphdr));
memcpy(&psh.tcp,tcph,sizeof(struct tcphdr));
tcph->check = checksum((unsigned short*)&psh, sizeof(struct pseudo_header));
}
int syn_scan(char *target_ip,int target_port){
struct sockaddr_in target;
int sockfd ;
target.sin_family = AF_INET ;
target.sin_addr.s_addr = inet_addr(target_ip) ;
sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
char dataheader [4096];
memset(dataheader, 0, 4096);
srand((unsigned)time(NULL));
int source_port = rand()%(32767-20000+1) + 20000;
prepare_header(dataheader,target,source_port,target_port,0,1);
//
int ret = sendto(sockfd, dataheader, sizeof(struct tcphdr),0,(struct sockaddr*)&target,sizeof(target)) ;
if (ret < 0){
return;
}
//接收的返回的数据包
struct tcphdr* tcph ;
struct sockaddr_in tmp;
char msg[1024] ;
int len = sizeof(tmp);
int count ,size;
while (1){
memset(msg,0,1024);
size = recvfrom(sockfd,msg,sizeof(msg),0,(struct sockaddr*)&tmp,&len) ;
if (size == -1) return ;
// recvfrom 接收到原始套接字包含ip + tcp
tcph = (struct tcphdr*)( msg+ sizeof(struct ip)) ;
if (tcph->syn == 1 && tcph->ack == 1 && tcph->dest == htons(source_port)) {
printf("IP : %s, Port %d open\n",inet_ntoa(tmp.sin_addr), ntohs(tcph->source));
break;
}
}
close(sockfd);
}
int main(){
// 发送请求数据包
char * target_ip = "8.8.8.8";
int target_port = 53;
syn_scan(target_ip,target_port);
}
效果如下:
[root@VM-0-17-centos scan]# gcc cscan.c -o main [root@VM-0-17-centos scan]# ./main IP : 8.8.8.8, Port 53 open
tcpdump抓包为:
[root@VM-0-17-centos ~]# tcpdump host 8.8.8.8 -vv tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes 20:11:41.322280 IP (tos 0x0, ttl 64, id 47479, offset 0, flags [DF], proto TCP (6), length 40) VM-0-17-centos.26350 > dns.google.domain: Flags [S], cksum 0xb9c8 (correct), seq 1105024978, win 14600, length 0 20:11:41.389558 IP (tos 0xa0, ttl 100, id 61621, offset 0, flags [none], proto TCP (6), length 44) dns.google.domain > VM-0-17-centos.26350: Flags [S.], cksum 0x93bd (correct), seq 913248537, ack 1105024979, win 65535, options [mss 1394], length 0 20:11:41.389606 IP (tos 0xa0, ttl 64, id 47538, offset 0, flags [DF], proto TCP (6), length 40) VM-0-17-centos.26350 > dns.google.domain: Flags [R], cksum 0xf2cd (correct), seq 1105024979, win 0, length 0
这里有一个疑点,代码本身并没有发送RST数据包关闭连接,抓包数据中却有RST数据包的发送,查阅了一些资料,RST是内核主动发起,可以通过iptables进行关闭。
iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 10.0.0.1 -j DROP
本文详细介绍了TCP三次握手的过程,并展示了如何利用SYN扫描技术进行端口状态探测和主机存活检查。通过发送SYN数据包并解析返回的ACK或RST标志,可以判断目标端口是否开放或主机是否在线。代码示例中包含了TCP报头的构造和校验和计算,以及如何使用原始套接字进行扫描。在实际应用中,需要注意防火墙和iptables的规则可能会影响扫描结果。
5489

被折叠的 条评论
为什么被折叠?



