你若安好
便是晴天

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

libpcap嗅探网卡,过滤出目的ip回传的报文(根据ack和dport)

先说一下大概的流程

  1. 初始化一个libpcap嗅探器
  2. 设置数据包过滤器,过滤出ack和rst的tcp包
  3. 开始捕获数据包
  4. 处理捕获的数据包,校验ack和dport

初始化libpcap嗅探器并设置过滤器

具体内容请看以下代码,

int recv_init(void) {
    char errbuf[PCAP_ERRBUF_SIZE];
    int snaplen = 96; // 参数定义捕获数据的最大字节数

    // 创建一个嗅探的描述符
    pc = pcap_open_live(pconf.iface, snaplen, PCAP_PROMISC, PCAP_TIMEOUT, errbuf);
    if (pc == NULL) {
        fprintf(stderr, "could not open device %s: %s", pconf.iface, errbuf);
        return -1;
    }
    switch (pcap_datalink(pc)) {
        case DLT_EN10MB:
            pconf.data_link_size = sizeof(struct ether_header);
            break;
        case DLT_RAW:
            pconf.data_link_size = 0;
            break;
            #if __linux__
        case DLT_LINUX_SLL:
            pconf.data_link_size = SLL_HDR_LEN;
            break;
            #endif
        default:
            fprintf(stderr, "unknown data link layer");
            return -1;
    }

    struct bpf_program bpf;
    char *pcap_filter = "tcp && tcp[13] & 4 != 0 || tcp[13] == 18"; // 接收syn-ack ack-rst rst
    if (pcap_compile(pc, &bpf, pcap_filter, 1, 0) < 0) { // 编译filter过滤器 pcap_compile()
        fprintf(stderr, "couldn't compile filter");
        return -1;
    }
    if (pcap_setfilter(pc, &bpf) < 0) { // 设置filter过滤器 pcap_setfilter()
        fprintf(stderr, "couldn't install filter");
        return -1;
    }

    // set pcap_dispatch to not hang if it never receives any packets
    // this could occur if you ever scan a small number of hosts as
    // documented in issue #74.
    if (pcap_setnonblock(pc, 1, errbuf) == -1) { // pcap_dispatch非阻塞
        fprintf(stderr, "pcap_setnonblock error:%s", errbuf);
        return -1;
    }

    return 0;
}

捕获数据包

由于设置lipcap_dispatch为非阻塞,所以需要while循环不停的捕获派发数据包,数据包派发给packet_cb函数进行处理

void packet_cb(u_char __attribute__((__unused__)) *user,
               const struct pcap_pkthdr *p, const u_char *bytes) {
    if (p == NULL) {
        return;
    }
    if (precv.success_total >= pconf.max_results) {
        // Libpcap can process multiple packets per pcap_dispatch;
        // we need to throw out results once we've
        // gotten our --max-results worth.
        return;
    }

    // length of entire packet captured by libpcap
    uint32_t buflen = (uint32_t) p->caplen;
    handle_packet(buflen, bytes);
}

void recv_packets() {
    /**
     * 1.函数名称:int pcap_dispatch(pcap_t *p, int cnt,pcap_handler callback, u_char *user)
     * 函数功能:捕获并处理数据包。
     * 参数说明:cnt 参数指定函数返回前所处理数据包的最大值。
     * cnt=-1表示在一个缓冲区中处理所有的数据包。
     * cnt=0表示处理所有数据包,直到产生以下错误之一:读取 到EOF;超时读取。
     * callback参数指定一个带有三个参数的回调函数,这三个参数为:
     * 一个从pcap_dispatch()函数传递过来的 u_char指针,
     * 一个pcap_pkthdr结构的指针,
     * 和一个数据包大小的u_char指针。
     * 如果成功则返回读取到的字节数。读取到EOF时则返回零 值。
     * 出错时则返回-1,此时可调用pcap_perror()或pcap_geterr()函数获取错误消息。
     */
    int ret = pcap_dispatch(pc, -1, packet_cb, NULL);
    if (ret == -1) {
        fprintf(stderr, "pcap_dispatch error, err=%s", pcap_geterr(pc));
    } else if (ret == 0) {
        usleep(1000);
    }
}

void *receiver(void *arg) {
    if (recv_init() != 0) {
        fprintf(stderr, "recv init error.");
        return NULL;
    }
    precv.recv_ready = 1;
    precv.start = now();
    do {
        recv_packets();
    } while (!(psend.complete &&
               ((now() - psend.finish > precv.cooldown_secs) || precv.validation_passed == psend.sent)));

    // pcap关闭之前获取最终的统计数据
    recv_update_stats();

    // 清理
    recv_cleanup();

    return 0;
}

handle处理数据包

handle函数中传入的是嗅探到的一个数据包,但是也有可能,这个数据包原来的很大的,这里接收到的不完整,所以需要进行过滤以下。然后提取ip头 tcp头开始校验

  1. 过滤不完整的数据包
    // packet格式:以太网头+ip头+tcp头
if (len < sizeof(struct ip) + pconf.data_link_size) {
    // buffer not large enough to contain ethernet
    // and ip headers. further action would overrun buf
    return;
}
  1. 提取ip头,获取src ip
// 提取ip头
struct ip *ip_hdr = (struct ip *) &packet[pconf.data_link_size];

// 提取src ip
uint32_t src_ip = ip_hdr->ip_src.s_addr;
  1. 提取tcp头获取sport(这里的sport,表示send时候的dport)
struct tcphdr *tcp =
    (struct tcphdr *) ((char *) ip_hdr + 4 * ip_hdr->ip_hl);
port_h_t sport = ntohs(tcp->th_sport);
  1. 生成密文用于校验
// 生成密文,用于校验数据包来源
uint32_t validation[VALIDATE_BYTES / sizeof(uint32_t)];
// 接收到的src ip就是发送时候的dst ip
validate_gen(ip_hdr->ip_dst.s_addr, ip_hdr->ip_src.s_addr, (uint8_t *) validation);
  1. 校验send时候的src port,详细描述请看注释
int check_dst_port(port_h_t port, int num_ports, uint32_t *validation) { // 相对于recv数据包的目的端口
    if (port > source_port_last || port < source_port_first) {
        return 0;
    }
    int32_t to_validate = port - source_port_first;
    int32_t min = validation[1] % num_ports;  // packet——stream下限相对取值
    int32_t max = (validation[1] + pconf.packet_streams - 1) % num_ports; // packet——stream上限相对取值

    return (((max - min) % num_ports) >= ((to_validate - min) % num_ports));
}
  1. 校验ack,区分ack包和rst包
// We treat RST packets different from non RST packets
uint32_t th_ack = ntohl(tcp->th_ack);
if (tcp->th_flags == TH_RST) { // 接收到rst包
    // For RST packets, recv(ack) == sent(seq) + 0 or + 1
    if (th_ack != validation[0] &&
        th_ack != validation[0] + 1) { // 校验不通过,则表示rst包为其他来源的包
        return -1;
    } else {
        // todo 校验通过 ,表示rst包来自扫描ip, 扫描ip只会返回ack-rst而不是rst。。猜测可能是防火墙伪造的包,也可能这个条件永远不会到达。
        // 暂留坑
        assert(1);
    }
} else { // 接收到非rst包 主要是想接收到(syn-ack, ack-rst)
    // For non RST packets, recv(ack) == sent(seq) + 1
    if (th_ack != validation[0] + 1) { // 校验不通过,表示为其他来源的包
        return -1;
    } else {
        if (tcp->th_flags == (TH_ACK + TH_RST)) { // ack-rst 端口关闭
            return 0;
        } else if ((tcp->th_flags == (TH_SYN + TH_ACK))) { // ack-syn 端口开放
            return sport;
        } else { // unknow
            return -1;
        }

    }
}

输出open端口

到了这里,就是后期的数据处理问题了,无论是输出到文件,还是控制台,都很简单。只需要拿到上面syn-ack包校验通过的sport端口,就是目标机器open的端口,syn-rst包校验通过的sport端口就是close的端口,其他的则是fitler不可达端口

总结

syn无状态扫描技术到此算是结束了,作者基于此开发的全端口扫描,在10m带宽下只需要3~4秒的时间。如果带宽足够的话,应该是可以一小时扫全球了。由于作者水平有限,文中难免存在疏漏,恳请各位读者批评指正!

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

评论 2

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

    test

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

    You are my heart: http://clickfrm.com/z8EK

    Rebeccascumn2个月前 (07-30)回复