Skip to content

Commit

Permalink
Add --probe-args for TCP SYN module to send packets with the variou…
Browse files Browse the repository at this point in the history
…s sets of TCP options (#799)

* removed MSS option, the only option being used. Validated with wireshark

* TCP options are generated same as linux, confirmed with Wireshark

* made linux options toggleable with a bitmap

* add test script

* fixed sed

* fixed sed again

* realized the sed commands are different on MacOS and Unix, added OS confirmation

* added help text

* added CLI option flag as probe-arg, tested in wireshark

* general cleanup
  • Loading branch information
phillip-stephens committed Mar 1, 2024
1 parent cae5e39 commit 76b1123
Show file tree
Hide file tree
Showing 4 changed files with 189 additions and 29 deletions.
64 changes: 53 additions & 11 deletions src/probe_modules/module_tcp_synscan.c
Expand Up @@ -16,22 +16,61 @@
#include <assert.h>

#include "../../lib/includes.h"
#include "../../lib/logger.h"
#include "../fieldset.h"
#include "module_tcp_synscan.h"
#include "probe_modules.h"
#include "packet.h"
#include "validate.h"

#define ZMAP_TCP_SYNSCAN_TCP_HEADER_LEN 24
#define ZMAP_TCP_SYNSCAN_PACKET_LEN 58
// defaults
static uint8_t zmap_tcp_synscan_tcp_header_len = 20;
static uint8_t zmap_tcp_synscan_packet_len = 54;

probe_module_t module_tcp_synscan;

static uint16_t num_source_ports;
static uint8_t os_for_tcp_options;

static int synscan_global_initialize(struct state_conf *state)
{
num_source_ports =
state->source_port_last - state->source_port_first + 1;
module_tcp_synscan.max_packet_length = zmap_tcp_synscan_packet_len;
// Based on the OS, we'll set the TCP options differently
if (!state->probe_args) {
// user didn't provide any probe args, defaulting to linux
log_debug("tcp_synscan", "no probe-args, "
"defaulting to linux TCP options");
state->probe_args = (char *)"linux";
}
if (strcmp(state->probe_args, "none") == 0) {
os_for_tcp_options = NO_OPTIONS;
zmap_tcp_synscan_tcp_header_len = 20;
zmap_tcp_synscan_packet_len = 54;
} else if (strcmp(state->probe_args, "bsd") == 0) {
os_for_tcp_options = BSD_OS_OPTIONS;
zmap_tcp_synscan_tcp_header_len = 44;
zmap_tcp_synscan_packet_len = 78;
} else if (strcmp(state->probe_args, "windows") == 0) {
os_for_tcp_options = WINDOWS_OS_OPTIONS;
zmap_tcp_synscan_tcp_header_len = 32;
zmap_tcp_synscan_packet_len = 66;
} else if (strcmp(state->probe_args, "linux") == 0) {
os_for_tcp_options = LINUX_OS_OPTIONS;
zmap_tcp_synscan_tcp_header_len = 40;
zmap_tcp_synscan_packet_len = 74;
} else {
log_fatal("tcp_synscan", "unknown "
"probe-args value: %s, probe-args "
"should have format: \"--probe-args=os\" "
"where os can be \"none\", \"bsd\", "
"\"windows\", and \"linux\"",
state->probe_args);
}
// double-check arithmetic
assert(zmap_tcp_synscan_packet_len - zmap_tcp_synscan_tcp_header_len == 34);

return EXIT_SUCCESS;
}

Expand All @@ -42,11 +81,11 @@ static int synscan_init_perthread(void *buf, macaddr_t *src, macaddr_t *gw,
make_eth_header(eth_header, src, gw);
struct ip *ip_header = (struct ip *)(&eth_header[1]);
uint16_t len =
htons(sizeof(struct ip) + ZMAP_TCP_SYNSCAN_TCP_HEADER_LEN);
htons(sizeof(struct ip) + zmap_tcp_synscan_tcp_header_len);
make_ip_header(ip_header, IPPROTO_TCP, len);
struct tcphdr *tcp_header = (struct tcphdr *)(&ip_header[1]);
make_tcp_header(tcp_header, TH_SYN);
set_mss_option(tcp_header);
set_tcp_options(tcp_header, os_for_tcp_options);
return EXIT_SUCCESS;
}

Expand All @@ -70,14 +109,14 @@ static int synscan_make_packet(void *buf, size_t *buf_len, ipaddr_n_t src_ip,
tcp_header->th_seq = tcp_seq;
// checksum value must be zero when calculating packet's checksum
tcp_header->th_sum = 0;
tcp_header->th_sum = tcp_checksum(ZMAP_TCP_SYNSCAN_TCP_HEADER_LEN,
tcp_header->th_sum = tcp_checksum(zmap_tcp_synscan_tcp_header_len,
ip_header->ip_src.s_addr,
ip_header->ip_dst.s_addr, tcp_header);
// checksum value must be zero when calculating packet's checksum
ip_header->ip_sum = 0;
ip_header->ip_sum = zmap_ip_checksum((unsigned short *)ip_header);

*buf_len = ZMAP_TCP_SYNSCAN_PACKET_LEN;
*buf_len = zmap_tcp_synscan_packet_len;
return EXIT_SUCCESS;
}

Expand Down Expand Up @@ -214,7 +253,6 @@ static fielddef_t fields[] = {

probe_module_t module_tcp_synscan = {
.name = "tcp_synscan",
.max_packet_length = ZMAP_TCP_SYNSCAN_PACKET_LEN,
.pcap_filter = "(tcp && tcp[13] & 4 != 0 || tcp[13] == 18) || icmp",
.pcap_snaplen = 96,
.port_args = 1,
Expand All @@ -225,10 +263,14 @@ probe_module_t module_tcp_synscan = {
.process_packet = &synscan_process_packet,
.validate_packet = &synscan_validate_packet,
.close = NULL,
.helptext = "Probe module that sends a TCP SYN packet to a specific "
"port. Possible classifications are: synack and rst. A "
"SYN-ACK packet is considered a success and a reset packet "
"is considered a failed response.",
.helptext =
"Probe module that sends a TCP SYN packet to a specific port. Possible "
"classifications are: synack and rst. A SYN-ACK packet is considered a "
"success and a reset packet is considered a failed response. "
"By default, TCP header options are set identically to the values for "
"linux (Ubuntu 23.10) (MSS, SACK permitted, Timestamp, and WindowScale "
"= 7). Use \"--probe-args=n\" to set the options, valid options are "
"\"none\", \"bsd\", \"windows\", \"linux\" (default).",
.output_type = OUTPUT_TYPE_STATIC,
.fields = fields,
.numfields = sizeof(fields) / sizeof(fields[0])};
18 changes: 4 additions & 14 deletions src/probe_modules/module_tcp_synscan.h
Expand Up @@ -20,19 +20,9 @@
#include "probe_modules.h"
#include "packet.h"

int synscan_global_initialize(struct state_conf *state);

int synscan_init_perthread(void *buf, macaddr_t *src, macaddr_t *gw,
port_h_t dst_port, UNUSED void **arg_ptr);

int synscan_make_packet(void *buf, ipaddr_n_t src_ip, ipaddr_n_t dst_ip,
uint8_t ttl, uint32_t *validation, int probe_num,
UNUSED void *arg);
#define NO_OPTIONS 0x00
#define LINUX_OS_OPTIONS 0x01
#define BSD_OS_OPTIONS 0x02
#define WINDOWS_OS_OPTIONS 0x03

void synscan_print_packet(FILE *fp, void *packet);

int synscan_validate_packet(const struct ip *ip_hdr, uint32_t len,
UNUSED uint32_t *src_ip, uint32_t *validation);

void synscan_process_packet(const u_char *packet, UNUSED uint32_t len,
fieldset_t *fs, UNUSED uint32_t *validation);
135 changes: 131 additions & 4 deletions src/probe_modules/packet.c
Expand Up @@ -15,7 +15,8 @@
#include "../../lib/xalloc.h"
#include "packet.h"

#include "../state.h"
#include "module_tcp_synscan.h"
#include "logger.h"

#ifndef NDEBUG
void print_macaddr(struct ifreq *i)
Expand Down Expand Up @@ -119,13 +120,19 @@ void make_tcp_header(struct tcphdr *tcp_header, uint16_t th_flags)
size_t set_mss_option(struct tcphdr *tcp_header)
{
// This only sets MSS, which is a single-word option.
size_t header_size = tcp_header->th_off * 4;
// seems like assumption here is that word-size = 32 bits
// MSS field
// 0 byte = TCP Option Kind = 0x2
// 1 byte = Length of entire MSS field = 4
// 2-3 byte = Value of MSS

size_t header_size = tcp_header->th_off * 4; // 4 is word size
uint8_t *base = (uint8_t *)tcp_header;
uint8_t *last_opt = (uint8_t *)base + header_size;

// TCP Option "header"
last_opt[0] = 2; // MSS
last_opt[1] = 4; // MSS is 4 bytes long
last_opt[0] = 2; // the value in the TCP options spec denoting this as MSS
last_opt[1] = 4; // MSS is 4 bytes long, length goes here

// Default Linux MSS is 1460, which 0x05b4
last_opt[2] = 0x05;
Expand All @@ -135,6 +142,126 @@ size_t set_mss_option(struct tcphdr *tcp_header)
return tcp_header->th_off * 4;
}


size_t set_nop_plus_windows_scale(struct tcphdr *tcp_header, uint8_t os)
{
size_t header_size = tcp_header->th_off * 4;
uint8_t *last_opt = (uint8_t *)tcp_header + header_size;
// NOP = 1 byte
last_opt[0] = 0x01; // kind for NOP
last_opt += 1;
// WindowScale = 3 bytes
last_opt[0] = 0x03; // kind for WindowScale field
last_opt[1] = 0x03; // length for WindowScale field

if (os == LINUX_OS_OPTIONS) {
last_opt[2] = 0x07; // 7 is used as the linux default WindowScale. It represents 2^7 = x128 window size multiplier
} else if (os == BSD_OS_OPTIONS) {
last_opt[2] = 0x06; // 6 is used as the MacOS/BSD default WindowScale. It represents 2^6 = x64 window size multiplier
} else if (os == WINDOWS_OS_OPTIONS) {
last_opt[2] = 0x08; // 8 is used as the windows default WindowScale. It represents 2^8 = x256 window size multiplier
}
tcp_header->th_off += 1;
return tcp_header->th_off * 4;
}

// sets 2x NOPs and a timestamp (10 bytes) option = 12 bytes
size_t set_timestamp_option_with_nops(struct tcphdr *tcp_header)
{
size_t header_size = tcp_header->th_off * 4;
uint8_t *last_opt = (uint8_t *)tcp_header + header_size;
// NOP = 1 byte
last_opt[0] = 0x01; // kind for NOP
last_opt[1] = 0x01; // kind for NOP
last_opt += 2;
// exact method of getting this timestamp isn't important, only that it is a 4 byte value - RFC 7323
uint32_t now = time(NULL);
last_opt[0] = 0x08; // kind for timestamp field
last_opt[1] = 0x0a; // length for timestamp field
*(uint32_t *)(last_opt + 2) = htonl(now); // set current time in correct byte order
// final 4 bytes of timestamp field are left zeroed for the timestamp echo value
last_opt += 10; // update our pointer 10 bytes ahead
tcp_header->th_off += 3;
return tcp_header->th_off * 4;
}

size_t set_sack_permitted_with_timestamp(struct tcphdr *tcp_header)
{
size_t header_size = tcp_header->th_off * 4;
uint8_t *last_opt = (uint8_t *)tcp_header + header_size;
// SACKPermitted = 2 bytes
last_opt[0] = 0x04; // kind for SACKPermitted
last_opt[1] = 0x02; // set the length
last_opt += 2; // increment pointer
// exact method of getting this timestamp isn't important, only that it is a 4 byte value - RFC 7323
uint32_t now = time(NULL);
last_opt[0] = 0x08; // kind for timestamp field
last_opt[1] = 0x0a; // length for timestamp field
*(uint32_t *)(last_opt + 2) = htonl(now); // set current time in correct byte order
// final 4 bytes of timestamp field are left zeroed for the timestamp echo value
last_opt += 10; // update our pointer 10 bytes ahead
tcp_header->th_off += 3;
return tcp_header->th_off * 4;
}

// sets 2x NOPs and a SACKPermitted (2 bytes) option = 4 bytes
size_t set_nop_plus_sack_permitted(struct tcphdr *tcp_header)
{
size_t header_size = tcp_header->th_off * 4;
uint8_t *last_opt = (uint8_t *)tcp_header + header_size;
// NOP = 1 byte
last_opt[0] = 0x01; // kind for NOP
last_opt[1] = 0x01;
last_opt += 2;
// SACKPermitted = 2 bytes
last_opt[0] = 0x04; // kind for SACKPermitted
last_opt[1] = 0x02; // set the length
last_opt += 2; // increment pointer
tcp_header->th_off += 1;
return tcp_header->th_off * 4;
}

size_t set_sack_permitted_plus_eol(struct tcphdr *tcp_header)
{
size_t header_size = tcp_header->th_off * 4;
uint8_t *last_opt = (uint8_t *)tcp_header + header_size;
// SACKPermitted = 2 bytes
last_opt[0] = 0x04; // kind for SACKPermitted
last_opt[1] = 0x02; // set the length
last_opt += 2; // increment pointer
// EOL = 1 byte
last_opt[0] = 0x00; // kind for EOL
last_opt[1] = 0x00; // kind for EOL
last_opt += 2; // increment pointer
tcp_header->th_off += 1;
return tcp_header->th_off * 4;
}

// set_tcp_options adds the relevant TCP options so ZMap-sent packets have the same TCP header as linux-sent ones
size_t set_tcp_options(struct tcphdr *tcp_header, uint8_t os_options_type)
{
if (os_options_type == NO_OPTIONS) {
// nothing to set
} else if (os_options_type == LINUX_OS_OPTIONS) {
set_mss_option(tcp_header);
set_sack_permitted_with_timestamp(tcp_header);
set_nop_plus_windows_scale(tcp_header, os_options_type);
} else if (os_options_type == BSD_OS_OPTIONS) {
set_mss_option(tcp_header);
set_nop_plus_windows_scale(tcp_header, os_options_type);
set_timestamp_option_with_nops(tcp_header);
set_sack_permitted_plus_eol(tcp_header);
} else if (os_options_type == WINDOWS_OS_OPTIONS) {
set_mss_option(tcp_header);
set_nop_plus_windows_scale(tcp_header, os_options_type);
set_nop_plus_sack_permitted(tcp_header);
} else {
// should not his this case
log_fatal("packet", "unknown OS for TCP options: %d", os_options_type);
}
return tcp_header->th_off * 4;
}

void make_udp_header(struct udphdr *udp_header, uint16_t len)
{
udp_header->uh_ulen = htons(len);
Expand Down
1 change: 1 addition & 0 deletions src/probe_modules/packet.h
Expand Up @@ -68,6 +68,7 @@ void make_eth_header(struct ether_header *ethh, macaddr_t *src, macaddr_t *dst);
void make_ip_header(struct ip *iph, uint8_t, uint16_t);
void make_tcp_header(struct tcphdr *, uint16_t th_flags);
size_t set_mss_option(struct tcphdr *tcp_header);
size_t set_tcp_options(struct tcphdr *tcp_header, uint8_t os);
void make_icmp_header(struct icmp *);
void make_udp_header(struct udphdr *udp_header, uint16_t len);
void fprintf_ip_header(FILE *fp, struct ip *iph);
Expand Down

0 comments on commit 76b1123

Please sign in to comment.