- 构造syn报文,将状态信息存于seq和sport
- 发送报文
- 根据带宽自动控制发包速率,提高发包效率且有效避免丢包
- libpcap嗅探网卡,过滤出目的ip回传的报文(根据ack和dport)
- 处理报文,判断端口是否open
- 输出扫描结果
很多著名的扫描器都采用了syn无状态扫描,例如nmap,zmap,masscan。一次完整的tcp连接要经过三次握手,如果我们只需要判断端口是否开放,那么并不需要完成完整的三次握手,只需要完成一次syn握手即可判断端口是否开放。如果对端返回syn-ack包则端口开放,ack-rst包则端口关闭。
- 有状态的syn扫描。发送syn包,记录下来随机数seq,源ip,源端口,目的ip,目的端口。通过这些状态从网卡中过滤出对端发送回来的有效包。如果扫描的ip高达几百万,那么就要记录几百万的这些状态,严重降低了扫描效率。
- 无状态的syn扫描。记录状态的目的是为了从网卡无数的报文中过滤出需要的有效报文。借鉴zmap,其提供了一种可行的方式,按某种规则生成随机数seq,按取模的方式确定源端口的范围。过滤的时候只需要按这种规则就可以得到有效报文。
- 客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
- 服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
- 客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。
一个报文的长度是54字节,需要这三个重要的结构:ether header + ip header + tcp header。这些结构在C的标准库中都可以找到。先创建一个54字节的缓冲区用于存储构造好的报文。然后依次构造这三个结构。下面是构造报文的函数。需要注意ip和端口的网络序和主机序的区别。
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
|
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 *) (ð_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;
}
|
通过ioctl获取网卡的mac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
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;
}
|
- 我们需要知道一个原理,sento发送ip报文,不是向目的ip直接发送过去,而是发送到网关,网关再根据ip header和tcp header中的目的ip和端口发送出去,层层路由出去,路由次数为ttl(默认为255)。所以我们需要获取网关的mac。
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
// 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;
}
|
获取网关mac和本机网卡mac等等都需要iface name。
1
2
3
4
5
6
7
8
9
10
|
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;
}
|
今天累了,明天继续…^_^