你若安好
便是晴天

SYN无状态扫描程序开发(一)

简单流程

  • 构造syn报文,将状态信息存于seq和sport
  • 发送报文
  • 根据带宽自动控制发包速率,提高发包效率且有效避免丢包
  • libpcap嗅探网卡,过滤出目的ip回传的报文(根据ack和dport)
  • 处理报文,判断端口是否open
  • 输出扫描结果

SYN扫描技术介绍

很多著名的扫描器都采用了syn无状态扫描,例如nmap,zmap,masscan。一次完整的tcp连接要经过三次握手,如果我们只需要判断端口是否开放,那么并不需要完成完整的三次握手,只需要完成一次syn握手即可判断端口是否开放。如果对端返回syn-ack包则端口开放,ack-rst包则端口关闭。

  1. 有状态的syn扫描。发送syn包,记录下来随机数seq,源ip,源端口,目的ip,目的端口。通过这些状态从网卡中过滤出对端发送回来的有效包。如果扫描的ip高达几百万,那么就要记录几百万的这些状态,严重降低了扫描效率。
  2. 无状态的syn扫描。记录状态的目的是为了从网卡无数的报文中过滤出需要的有效报文。借鉴zmap,其提供了一种可行的方式,按某种规则生成随机数seq,按取模的方式确定源端口的范围。过滤的时候只需要按这种规则就可以得到有效报文。

理解tcp三次握手

  1. 客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
  2. 服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
  3. 客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。

构造报文

一个报文的长度是54字节,需要这三个重要的结构:ether header + ip header + tcp header。这些结构在C的标准库中都可以找到。先创建一个54字节的缓冲区用于存储构造好的报文。然后依次构造这三个结构。下面是构造报文的函数。需要注意ip和端口的网络序和主机序的区别。

int synscan_make_packet(void *buf, /* 长度为54字节,用于存储报文 */
                        ipaddr_n_t src_ip, /* 本机ip,ipaddr_n_t是#define uint32_t ipadder_n_t, 用于区分网络序和主机序 */
                        ipaddr_n_t dst_ip,/* 目的ip */
                        port_h_t dst_port, /* 目的端口 */
                        uint32_t *validation, /* 生成的密文,用于记录状态 */
                        int probe_num /* 发送报文的序号,例如发送多个相同的syn包,第一个包序号为0,依次类推。用于校验源端口范围。, */) {
    struct ether_header *eth_header = (struct ether_header *) buf; /* buf转ether_header结构 */
    struct ip *ip_header = (struct ip *) (&eth_header[1]); /* buf转ip_header结构 */
    struct tcphdr *tcp_header = (struct tcphdr *) (&ip_header[1]); /* buf转tcp_header结构 */

    // make ether header
    memcpy(eth_header->ether_shost, sconf.hw_mac, ETHER_ADDR_LEN); /* 本机网卡的mac,后面讲解怎么获取 */
    memcpy(eth_header->ether_dhost, sconf.gw_mac, ETHER_ADDR_LEN); /* 网关mac,后面讲解怎么获取 */
    eth_header->ether_type = htons(ETHERTYPE_IP); /* 我们要发送ip包,所以设置ethernet协议为ip*/


    // make ip header
    uint16_t len = htons(sizeof(struct ip) + sizeof(struct tcphdr));
    ip_header->ip_hl = 5;  // Internet Header Length
    ip_header->ip_v = 4;   // IPv4
    ip_header->ip_tos = 0; // Type of Service
    ip_header->ip_len = len; /* ip头和tcp头的总长度 */
    ip_header->ip_id = htons(54321); // identification number
    ip_header->ip_off = 0;       // fragmentation flag
    ip_header->ip_ttl = MAXTTL;      // time to live (TTL)
    ip_header->ip_p = IPPROTO_TCP;      // upper layer protocol => TCP
    ip_header->ip_src.s_addr = src_ip;
    ip_header->ip_dst.s_addr = dst_ip;
    // we set the checksum = 0 for now because that's
    // what it needs to be when we run the IP checksum
    ip_header->ip_sum = 0;
    ip_header->ip_sum = ip_checksum((unsigned short *) ip_header); /* 后面讲解 */

    // make tcp header
    uint32_t tcp_seq = htonl(validation[0]); // 根据srcip dstip计算出来的密文第一部分
    tcp_header->th_seq = tcp_seq; // syn 序列号
    tcp_header->th_ack = 0;
    tcp_header->th_x2 = 0;
    tcp_header->th_off = 5; // data offset
    tcp_header->th_flags = 0;
    tcp_header->th_flags |= TH_SYN; // 发送syn报文
    tcp_header->th_win = htons(65535); // largest possible window
    tcp_header->th_urp = 0;
    tcp_header->th_dport = htons(dst_port);
    tcp_header->th_sport = htons(get_src_port(num_ports, probe_num, validation)); // 通过取模从指定范围内获取源端口。
    tcp_header->th_sum = 0;
    tcp_header->th_sum = tcp_checksum(sizeof(struct tcphdr), ip_header->ip_src.s_addr, ip_header->ip_dst.s_addr,
                                      tcp_header);

    return 0;
}

获取本机网卡的mac(hw_mac)

通过ioctl获取网卡的mac

int get_iface_hw_addr(char *iface/* 网卡名称,通常是eth0 */, unsigned char *hw_mac/*用于存储mac的字符串数组,6字节*/) {
    int s;
    struct ifreq buffer;

    // Load the hwaddr from a dummy socket
    s = socket(PF_INET, SOCK_DGRAM, 0);
    if (s < 0) {
        logger_error("Unable to open socket: %s",
                     strerror(errno));
        return EXIT_FAILURE;
    }
    memset(&buffer, 0, sizeof(buffer));
    strncpy(buffer.ifr_name, iface, IFNAMSIZ);
    ioctl(s, SIOCGIFHWADDR, &buffer);
    close(s);
    memcpy(hw_mac, buffer.ifr_hwaddr.sa_data, 6);
    return EXIT_SUCCESS;
}

获取网关mac

  • 我们需要知道一个原理,sento发送ip报文,不是向目的ip直接发送过去,而是发送到网关,网关再根据ip header和tcp header中的目的ip和端口发送出去,层层路由出去,路由次数为ttl(默认为255)。所以我们需要获取网关的mac。
// gw and iface[IF_NAMESIZE] MUST be allocated
int _get_default_gw(struct in_addr *gw, char *iface) {
    struct rtmsg req;
    unsigned int nl_len;
    char buf[8192];
    struct nlmsghdr *nlhdr;

    if (!gw || !iface) {
        return -1;
    }

    // Send RTM_GETROUTE request
    memset(&req, 0, sizeof(req));
    int sock = send_nl_req(RTM_GETROUTE, 0, &req, sizeof(req));

    // Read responses
    nl_len = read_nl_sock(sock, buf, sizeof(buf));
    if (nl_len <= 0) {
        return -1;
    }

    // Parse responses
    nlhdr = (struct nlmsghdr *) buf;
    while (NLMSG_OK(nlhdr, nl_len)) {
        struct rtattr *rt_attr;
        struct rtmsg *rt_msg;
        int rt_len;
        int has_gw = 0;

        rt_msg = (struct rtmsg *) NLMSG_DATA(nlhdr);

        if ((rt_msg->rtm_family != AF_INET) ||
            (rt_msg->rtm_table != RT_TABLE_MAIN)) {
            return -1;
        }

        rt_attr = (struct rtattr *) RTM_RTA(rt_msg);
        rt_len = RTM_PAYLOAD(nlhdr);
        while (RTA_OK(rt_attr, rt_len)) {
            switch (rt_attr->rta_type) {
                case RTA_OIF:
                    if_indextoname(*(int *) RTA_DATA(rt_attr),
                                   iface);
                    break;
                case RTA_GATEWAY:
                    gw->s_addr = *(unsigned int *) RTA_DATA(rt_attr);
                    has_gw = 1;
                    break;
            }
            rt_attr = RTA_NEXT(rt_attr, rt_len);
        }

        if (has_gw) {
            return 0;
        }
        nlhdr = NLMSG_NEXT(nlhdr, nl_len);
    }
    return -1;
}

int get_default_gw(struct in_addr *gw, char *iface) {
    char _iface[IF_NAMESIZE];
    memset(_iface, 0, IF_NAMESIZE);

    _get_default_gw(gw, _iface);
    if (strcmp(iface, _iface) != 0) {
        logger_error(
                "interface specified (%s) does not match "
                "the interface of the default gateway (%s). You will need "
                "to manually specify the MAC address of your gateway.",
                iface, _iface);
    }
    return EXIT_SUCCESS;
}

通过pcap获取网卡名称

获取网关mac和本机网卡mac等等都需要iface name。

char *get_default_iface(void) {
    char errbuf[PCAP_ERRBUF_SIZE];
    char *iface = pcap_lookupdev(errbuf);
    if (iface == NULL) {
        logger_error("could not detect default network interface "
                     "(e.g. eth0). Try running as root or setting"
                     " interface.");
    }
    return iface;
}

构造报文还差最重要的一步,构造seq和sport,否则无法从网卡中过滤出目的ip回传的报文

今天累了,明天继续…^_^

原创作品未经允许不得转载:WTF博客 » SYN无状态扫描程序开发(一)

评论 4

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #1

    On the other hand, we denounce with righteous indignation and dislike men who are so beguiled and demoralized by the charms of pleasure of the moment Nonah Stephan Rutan

    episodes5个月前 (02-02)回复
  2. #2

    Hi Linda and thanks for weighing in on your project. We just finished gutting ours last Friday and will be taking up to Colin Hyde for the body off restoration, pre-wire and pre-plumb, then back here to Kentucky for the finish work. We are going to be posting another post soon on the demolition process. Please be sure to visit our site often and make comments. Dina Son Ponce

    yabanci5个月前 (02-02)回复
  3. #3

    agree 100 % with the matching 401k and profit sharing Bessie Laughton Florence

    mp34个月前 (02-09)回复
  4. #4

    What cant be cured must be endured.