C语言SYN扫描

本文详细介绍了TCP三次握手的过程,并展示了如何利用SYN扫描技术进行端口状态探测和主机存活检查。通过发送SYN数据包并解析返回的ACK或RST标志,可以判断目标端口是否开放或主机是否在线。代码示例中包含了TCP报头的构造和校验和计算,以及如何使用原始套接字进行扫描。在实际应用中,需要注意防火墙和iptables的规则可能会影响扫描结果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原理

首先需要了解下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数据包时,一般有两种方式,

  1. 伪造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
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值