syn无状态扫描程序开发(二)

系列 - syn无状态扫描程序开发

根据srcip和dstip生成一串16字节的校验码,校验码第一个4字节(uint32类型)当做seq,第二个4字节用于从指定sport范围(比如:32768~60000)中选择一个sport。剩下的8字节保留。

  1. 主要使用rijndael算法,参考AES。先初始化一个静态key,然后使用这个key和srcip,dstip生成一串16字节的密文。
 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
#define AES_ROUNDS 10
#define AES_BLOCK_WORDS 4
#define AES_KEY_BYTES 16

static int inited = 0;
static uint32_t aes_input[AES_BLOCK_WORDS];
static uint32_t aes_sched[(AES_ROUNDS + 1) * 4];

void validate_init()
{
	for (int i = 0; i < AES_BLOCK_WORDS; i++) {
		aes_input[i] = 0;
	}
	uint8_t key[AES_KEY_BYTES];
	if (!random_bytes(key, AES_KEY_BYTES)) {
		log_fatal("validate", "couldn't get random bytes");
	}
	if (rijndaelKeySetupEnc(aes_sched, key, AES_KEY_BYTES * 8) !=
	    AES_ROUNDS) {
		log_fatal("validate", "couldn't initialize AES key");
	}
	inited = 1;
}

void validate_gen(const uint32_t src, const uint32_t dst,
		  uint8_t output[VALIDATE_BYTES])
{
	assert(inited);
	aes_input[0] = src;
	aes_input[1] = dst;
	rijndaelEncrypt(aes_sched, AES_ROUNDS, (uint8_t *)aes_input, output);
}
  1. seq值的为validate前四个字节组成的uint32类型值。
1
tcp_header->th_seq = htonl(validation[0]); // 根据srcip dstip计算出来的密文前四个字节
  1. 我们先设定一个sport的范围,然后按取模的方式从这个范围中选出一个sport。
1
2
3
4
5
#define source_port_first 32768 // 源端口起始位置
#define source_port_last 61000 // 源端口结束位置
uint16_t get_src_port(int num_ports, int probe_num/* 对同一个目的端口发送多个syn包时,syn包的序号 */, uint32_t *validation) {
    return source_port_first + ((validation[1] + probe_num) % num_ports);
}
  1. 到这里,完整的报文就构造完成了,接下来就是sendto了,由于sendto不阻塞,发送太快会出现丢包情况,所以需要控制发包速率,请继续往下阅读。

数据构造好了,只需要向原始套接字进行send动作。sockaddr填充网关的mac,数据包发送到网关进行路由。需要注意的是,sendto是网络io操作,虽然不阻塞,但会出现线程安全问题,如果是多线程则需要加线程锁。在这里单线程sendto就已经够用了。

1
2
3
4
int send_packet(int sock, void *buf, int len) {
    ++ssend.sent; // 记录发包数量
    return sendto(sock, buf, len, 0, (struct sockaddr *) &sockaddr, sizeof(struct sockaddr_ll));
}
  1. packet_streams是对同一目的端口发包的数量。while循环根据当前计算出来的速率,添加延时,使发送速率不超过设定的最大值rate。(速率表示每秒发送的数据包数量。)
1
2
3
4
5
6
7
8
#define VALIDATE_BYTES 16
for (int i = 0; i < packet_streams; ++i) {
	uint32_t validation[VALIDATE_BYTES / sizeof(uint32_t)];
	validate_gen((uint32_t) index, sconf.src_ip, dst_ip, (uint8_t *) validation);
	synscan_make_packet(buf, 54, sconf.src_ip, dst_ip, dst_port, validation, i);
	send_packet(sock, buf, length);
	while (ssend.sent/*当前发包数量*/ / (now()/*当前时间*/ - ssend.start/*send开始时间*/) > sconf.rate) usleep(100);
}
  1. 计算系统最大发包速率。rate = bandwidth / pkt_len;,带宽除以一个数据包的总bits数,表示每秒发送的最大数据包个数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// 计算packet长度
size_t pkt_len = 54; // ether header + ip header + tcp header (bytes)
pkt_len *= 8; // bits
// 7 byte MAC preamble, 1 byte Start frame, 4 byte CRC, 12 byte
// inter-frame gap
pkt_len += 8 * 24;
// adjust calculated length if less than the minimum size of an
// ethernet frame
if (pkt_len < 84 * 8) {
	pkt_len = 84 * 8;
}

// 计算带宽,例如带宽为bandwidth = 10m
uint64_t bandwidth *= 1000000;

// 计算速率
uint32_t rate = bandwidth / pkt_len;
if (rate > 0xFFFFFFFFu) { /* 防止溢出 */
	rate = 0;
}

作者太懒,明天继续