• 网络扫描程序的详细分析与实现


    1.网络扫描简介

      网络扫描是一种自动化程序,用于检测远程或本地主机的弱点和漏洞。漏洞扫描是入侵防范最基本的工作,攻击者正式利用各种漏洞入侵系统。借助自动化的扫描工作,在攻击者之前发现漏洞问题,并给予相应的修正程序。

      一名攻击者入侵系统,一般分为四个步骤:系统发现,漏洞探测,漏洞利用和痕迹清除。

      本文的重点就是在于系统发现与漏洞探测方面。

    2.端口扫描技术

      端口扫描能够用来查找目标主机已开放的端口,包括TCP和UDP端口。当前针对TCP端口的扫描技术有三种,分别为:全连接扫描,SYN扫描和FIN扫描。针对UDP端口的扫描技术一般是采用ICMP报文中端口不可达的信息来识别UDP端口是否开放。

    • 全连接扫描

      作为最基本的TCP扫描方式,它利用connect()函数,向每一个目标主机的端口发起连接,若端口处于侦听状态,则函数返回成功;否则可以判断出该端口没有开放。

      全连接扫描有两个好处,首先它不许要得到系统权限,任何用户都可以使用,另外一个是,可以采用多线程并发扫描,扫描的速度也不慢。

    • SYN扫描

      SYN扫描通常认为是“半开放”的扫描,这是因为扫描程序不必打开一个完全的TCP连接。扫描程序构造并发送一个SYN数据包,执行TCP三次握手的第一步,若返回一个SYN|ACK数据包,则表示端口处于侦听状态,否则返回一个RST数据包,表示端口没有开放。这种方式需要root权限,并且它不会在主机上留下记录。

    • FIN扫描

      FIN扫描与SYN扫描类似,也是构造数据包,通过识别回应来判断端口状态。发送FIN数据包后,如果返回一个RST数据包,则表示端口没有开放,处于关闭状态,否则,开放端口会忽略这种报文。这种方式难以被发现,但是这种方式可能不准确。

    • UDP的ICMP端口不可达扫描

      UDP扫描的方法并不常见。虽然UDP协议相对比较简单,无论UDP端口是否开放,对于接受到的探测包,他本身默认是不会发送回应信息的。不过UDP扫描有一种方法,就是利用主机ICMP报文的回应信息来识别,当一个关闭的UDP端口发送一个数据包时,会返回一个ICMP_PORT_UNREACH的错误。但由于UDP不可靠,ICMP报文也是不可靠,因此存在数据包中途丢失的可能。

    3.具体实现

      首先介绍全连接扫描模块的设计。

      具体流程:

    1. 判断端口是否处于扫描范围
    2. 初始化套接字,发起连接
    3. 调用getservbyport函数,获取端口映射的TCP服务
    4. 输出结果
    5. 退出程序

    实现代码:

    //全连接扫描
    void scan(char *ip,int minport,int maxport)
    {
        if(maxport<minport){
            printf("error:can't scan int this condition\n");
            return;
        }
        printf("test ip:%s,port:%d,port:%d\n",ip,minport,maxport);
        
        int i=0;
        printf("Now start scanning.....\n");
        for(i=minport;i<maxport;++i){
            struct sockaddr_in clientaddr;
            
            clientaddr.sin_family=AF_INET;
            clientaddr.sin_addr.s_addr=inet_addr(ip);
            clientaddr.sin_port=htons(i);
            
            int sock=socket(AF_INET,SOCK_STREAM,0);
            if(sock<0){
                perror("error:create socket\n");
                return;
            }
            
            int error=connect(sock,(struct sockaddr*)&clientaddr,sizeof(clientaddr));
            if(error<0){
                printf("Port:%5d | Tatus:closed\n",i);
                fflush(stdout);//清空缓存
            }
            else{
                struct servent* sptr;
                if((sptr=getservbyport(htons(i),"tcp"))!=NULL)
                    printf("Port:%5d | Server:%s | Status:open\n",i,sptr->s_name);
                else
                    printf("Port:%5d | Status:open\n",i);
            }
            close(sock);
        }
    }

      可以对上述代码进行改进,可以加入多线程的的方式来解决扫描速度较慢的问题。

      在这里需要定义一个结构体,来为线程传递必要的参数。

    typedef struct threadpara{
        char ip[20];
        int minport;
        int maxport;
    }tp;

      这样的话,我们就可以定义一个线程函数用于连接不同的端口。

    void *conntthread(void *threadp)
    {
        struct threadpara *pThreadpara=(struct threadpara*)threadp;
        
        int i;
        printf("minport:%d maxport:%d thread....\n",pThreadpara->minport,pThreadpara->maxport);
        
        for(i=pThreadpara->minport;i<pThreadpara->maxport;++i){
            struct sockaddr_in clientaddr;
            
            clientaddr.sin_family=AF_INET;
            clientaddr.sin_addr.s_addr=inet_addr(pThreadpara->ip);
            clientaddr.sin_port=htons(i);
            
            int sock=socket(AF_INET,SOCK_STREAM,0);
            if(sock<0){
                perror("error:create socket\n");
                return 0;
            }
            
            int error=connect(sock,(struct sockaddr*)&clientaddr,sizeof(clientaddr));
            if(error<0){
                printf("Port:%5d | Tatus:closed\n",i);
                fflush(stdout);//清空缓存
            }
            else{
                struct servent* sptr;
                if((sptr=getservbyport(htons(i),"tcp"))!=NULL)
                    printf("Port:%5d | Server:%s | Status:open\n",i,sptr->s_name);
                else
                    printf("Port:%5d | Status:open\n",i);
            }
            close(sock);
        }
        pthread_exit(NULL);
    }

      接下来,就是在主扫描程序中添加启动线程的模块,主要代码如下:

    int count=(maxport-minport)/thread_num;
        
        printf("count:%d num:%d ip:%s,port:%d,port:%d\n",count,thread_num,ip,minport,maxport);
        printf("Now start scanning....\n");
        
        int i;
        for(i=0;i<count;++i){
            struct threadpara threadpara;
            strcpy(threadpara.ip,ip);
            threadpara.minport=minport+count*i;
            if(i==thread_num-1)
                threadpara.maxport=maxport;
            else
                threadpara.maxport=threadpara.minport+count;
                
            printf("i:%d maxport:%d,minport:%d\n",i,threadpara.maxport,threadpara.minport);
            
            int temp;
            pthread_t threadid;
            temp=pthread_create(&threadid,NULL,conntthread,(void*)&threadpara);
            if(temp<0)
                printf("create thread error...\n");
            pthread_join(threadid,NULL);
        }

      接下来,再介绍一个SYN半连接扫描的例子。

      扫描模块的设计流程图如下:

      具体流程:

    1. 初始化套接字
    2. 设置信号量以及全局变量flag(控制扫描状态)
    3. 启动接收线程
    4. 构造SYN数据包,开始扫描

      首先,构造并发送syn包模块的代码如下: 

    int sendSyn(int sendSocket,u_long sourceIP,struct sockaddr_in *dest)
    {
        unsigned char netPacket[sizeof(struct tcphdr)];
        struct tcphdr* tcp;
        u_char * pPseudoHead;
        u_char pseudoHead[12+sizeof(struct tcphdr)];
        u_short tcpHeadLen;
        
        memset(netPacket,0,sizeof(struct tcphdr));    
        //构造syn数据包
        tcpHeadLen=htons(sizeof(struct tcphdr));
        tcp=(struct tcphdr*)netPacket;
        tcp->source=htons(10240);
        tcp->dest=dest->sin_port;
        tcp->seq=htonl(12345);
        tcp->ack_seq=0;
        tcp->doff=5;
        tcp->syn=1;
        tcp->window=htons(10052);
        tcp->check=0;
        tcp->urg_ptr=0;
        pPseudoHead=pseudoHead;
        
        memset(pPseudoHead,0,12+sizeof(struct tcphdr));
        memcpy(pPseudoHead,&sourceIP,4);
        
        pPseudoHead+=4;
        
        memcpy(pPseudoHead,&(dest->sin_addr),4);
        pPseudoHead+=5;
        
        memset(pPseudoHead,6,1);
        pPseudoHead++;
        
        memcpy(pPseudoHead,&tcpHeadLen,2);
        pPseudoHead+=2;
        
        memcpy(pPseudoHead,tcp,sizeof(struct tcphdr));
        tcp->check=checksum((u_short*)pPseudoHead,sizeof(struct tcphdr)+12);
        
        int temp=sendto(sendSocket,netPacket,sizeof(struct tcphdr),0,(struct sockaddr*)dest,sizeof(struct sockaddr_in));
        return temp;
    }

      TCP校验和代码:

    unsigned short checksum(unsigned short *addr,int len)//TCP校验和
    {
        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=~sum;
        return(answer);
    }

      接收返回数据包的代码如下:

    void* recv_packet(void* arg)
    {
        struct sockaddr_in *in1;
        char *srcaddr;
        int size;
        u_char readbuff[1600];
        struct sockaddr from;
        int from_len;
        struct tcphdr* tcp;
        struct servent* sptr;
        tcp=(struct tcphdr*)(readbuff+20);
        int fd=*((int*)arg);
        
        while(1){
            if(flag==1)
                return 0;
                
            size=recvfrom(fd,(char*)readbuff,1600,0,&from,(socklen_t *)&from_len);
            if(size<40)
                continue;
            printf("data is ok...ack:seq:%u,destport:%u\n",ntohl(tcp->ack_seq),ntohs(tcp->dest));
            if((ntohl(tcp->ack_seq)!=123456)||(ntohs(tcp->dest)!=10240))
                continue;
            if(tcp->rst&&tcp->ack){
                printf("port:%5d | Status: closed\n",htons(tcp->source));
                continue;
            }
            if(tcp->ack&&tcp->syn){
                in1=(struct sockaddr_in*)&from;
                srcaddr=inet_ntoa(in1->sin_addr);
                printf("SERVER:%s\r",srcaddr);
            }
            if((sptr=getservbyport(tcp->source,"tcp"))!=NULL){
                printf("Port:%d Server:%s | Status: open\n",htons(tcp->source),sptr->s_name);
            }
            else
                printf("Port:%5d | Status:open\n",htons(tcp->source));
            fflush(stdout);
            continue;
        }
    }

      扫描程序的主要程序:

    void synscan(char* ip,int minport,int maxport)
    {
        pthread_t tid;
        struct ifreq if_data;
        int fd;
        u_long addr_p;
        
        struct sockaddr_in clientaddr;
        fd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);//SOCK_RAW原始套接子(数据包式)
        if(fd<0){
            perror("error:create raw socket\n");
            return;
        }
        
        signal(SIGALRM,Alarm);
        
        strcpy(if_data.ifr_name,"eth0");
        if(ioctl(fd,SIOCGIFADDR,&if_data)<0){
            perror("error:ioctl\n");
            return;
        }
        memcpy((void*)&addr_p,(void*)&if_data.ifr_addr.sa_data+2,4);
        
        bzero(&clientaddr,sizeof(clientaddr));
        clientaddr.sin_family=AF_INET;
        clientaddr.sin_addr.s_addr=inet_addr(ip);
        
        printf("Now start scannning...\n");
        int err;
        err = pthread_create(&tid,NULL,recv_packet,(void*)&fd);
        if(err<0)
            perror("error:create thread\n");
        int i=minport;
        for(;i<maxport;++i){
            clientaddr.sin_port=htons(i);
            if(sendSyn(fd,addr_p,&clientaddr)<0){
                perror("error:send syn\n");
            }
            alarm(3);
        }
        pthread_join(tid,NULL);
    }

    最后附上之前的IP地址扫描代码

  • 相关阅读:
    C++读写文件并排序
    我的vim配置---jeffy-vim-v2.2.tar
    vim 代码注释插件
    我的vim配置---jeffy-vim-v2.1.tar
    linux中screen命令的用法
    Install and Enable Telnet server in Ubuntu Linux
    Telnet环境变量
    Telnet窗口尺寸选项
    TELNET终端类型选项
    Telnet技术白皮书
  • 原文地址:https://www.cnblogs.com/coder2012/p/3010087.html
Copyright © 2020-2023  润新知