先说一下大概的流程
- 初始化一个libpcap嗅探器
- 设置数据包过滤器,过滤出ack和rst的tcp包
- 开始捕获数据包
- 处理捕获的数据包,校验ack和dport
具体内容请看以下代码,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
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函数进行处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
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;
}
|
4 handle处理数据包
handle函数中传入的是嗅探到的一个数据包,但是也有可能,这个数据包原来的很大的,这里接收到的不完整,所以需要进行过滤以下。然后提取ip头 tcp头开始校验
- 过滤不完整的数据包
1
2
3
4
5
6
|
// 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;
}
|
- 提取ip头,获取src ip
1
2
3
4
5
|
// 提取ip头
struct ip *ip_hdr = (struct ip *) &packet[pconf.data_link_size];
// 提取src ip
uint32_t src_ip = ip_hdr->ip_src.s_addr;
|
- 提取tcp头获取sport(这里的sport,表示send时候的dport)
1
2
3
|
struct tcphdr *tcp =
(struct tcphdr *) ((char *) ip_hdr + 4 * ip_hdr->ip_hl);
port_h_t sport = ntohs(tcp->th_sport);
|
- 生成密文用于校验
1
2
3
4
|
// 生成密文,用于校验数据包来源
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);
|
- 校验send时候的src port,详细描述请看注释
1
2
3
4
5
6
7
8
9
10
|
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));
}
|
- 校验ack,区分ack包和rst包
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
// 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;
}
}
}
|
到了这里,就是后期的数据处理问题了,无论是输出到文件,还是控制台,都很简单。只需要拿到上面syn-ack包校验通过的sport端口,就是目标机器open的端口,syn-rst包校验通过的sport端口就是close的端口,其他的则是fitler不可达端口
syn无状态扫描技术到此算是结束了,作者基于此开发的全端口扫描,在10m带宽下只需要3~4秒的时间。如果带宽足够的话,应该是可以一小时扫全球了。由于作者水平有限,文中难免存在疏漏,恳请各位读者批评指正!