你若安好
便是晴天

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无状态扫描程序开发(一)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址