From 4f8925702566154002b3dbeff3a52321367c56a9 Mon Sep 17 00:00:00 2001 From: Robert Graham Date: Tue, 7 Nov 2023 22:00:00 -0500 Subject: [PATCH] Can set TCP header options on command line, --tcpmss, --tcpsackon, --tcp-tsecho, and so forth. The --tcp-mss is now enabled by default, use to remove it form the header. --- src/main-conf.c | 413 +++++- src/main.c | 3 +- src/masscan.h | 7 +- src/templ-opts.h | 66 + src/templ-pkt.c | 49 +- src/templ-pkt.h | 6 +- src/templ-tcp-hdr.c | 1571 ++++++++++++++++++++++ src/templ-tcp-hdr.h | 25 + xcode4/masscan.xcodeproj/project.pbxproj | 18 +- 9 files changed, 2113 insertions(+), 45 deletions(-) create mode 100644 src/templ-opts.h create mode 100644 src/templ-tcp-hdr.c create mode 100644 src/templ-tcp-hdr.h diff --git a/src/main-conf.c b/src/main-conf.c index 338094bb..14cb7de1 100644 --- a/src/main-conf.c +++ b/src/main-conf.c @@ -27,6 +27,7 @@ #include "massip.h" #include "massip-parse.h" #include "massip-port.h" +#include "templ-opts.h" #include #include @@ -575,6 +576,75 @@ parseInt(const char *str) return result; } +/** + * a stricter function for determining if something is boolean. + */ +static bool +isBoolean(const char *str) { + size_t length = str?strlen(str):0; + + if (length == 0) + return false; + + /* "0" or "1" is boolean */ + if (isdigit(str[0])) { + if (strtoul(str,0,0) == 0) + return true; + else if (strtoul(str,0,0) == 1) + return true; + else + return false; + } + + switch (str[0]) { + case 'e': + case 'E': + if (memcasecmp("enable", str, length)==0) + return true; + if (memcasecmp("enabled", str, length)==0) + return true; + return false; + case 'd': + case 'D': + if (memcasecmp("disable", str, length)==0) + return true; + if (memcasecmp("disabled", str, length)==0) + return true; + return false; + + case 't': + case 'T': + if (memcasecmp("true", str, length)==0) + return true; + return false; + case 'f': + case 'F': + if (memcasecmp("false", str, length)==0) + return true; + return false; + + case 'o': + case 'O': + if (memcasecmp("on", str, length)==0) + return true; + if (memcasecmp("off", str, length)==0) + return true; + return false; + case 'Y': + case 'y': + if (memcasecmp("yes", str, length)==0) + return true; + return false; + case 'n': + case 'N': + if (memcasecmp("no", str, length)==0) + return true; + return false; + default: + return false; + } +} + static unsigned parseBoolean(const char *str) { @@ -587,23 +657,32 @@ parseBoolean(const char *str) return 1; } switch (str[0]) { - case 't': + case 'e': /* enable */ + case 'E': + return 1; + case 'd': /* disable */ + case 'D': + return 0; + + case 't': /* true */ case 'T': return 1; - case 'f': + case 'f': /* false */ case 'F': return 0; - case 'o': + + case 'o': /* on or off */ case 'O': if (str[1] == 'f' || str[1] == 'F') return 0; else return 1; break; - case 'Y': + + case 'Y': /* yes */ case 'y': return 1; - case 'n': + case 'n': /* no */ case 'N': return 0; } @@ -1424,6 +1503,213 @@ static int SET_nobanners(struct Masscan *masscan, const char *name, const char * return CONF_OK; } +static int SET_tcpmss(struct Masscan *masscan, const char *name, const char *value) +{ + /* h/t @IvreRocks */ + static const unsigned default_mss = 1460; + + if (masscan->echo) { + if (masscan->templ_opts) { + switch (masscan->templ_opts->tcp.is_mss) { + case Default: + break; + case Add: + if (masscan->templ_opts->tcp.mss == default_mss) + fprintf(masscan->echo, "tcp-mss = %s\n", "enable"); + else + fprintf(masscan->echo, "tcp-mss = %u\n", + masscan->templ_opts->tcp.mss); + break; + case Remove: + fprintf(masscan->echo, "tcp-mss = %s\n", "disable"); + break; + default: + break; + } + } + return 0; + } + + if (masscan->templ_opts == NULL) + masscan->templ_opts = calloc(1, sizeof(*masscan->templ_opts)); + + if (value == 0 || value[0] == '\0') { + /* no following parameter, so interpret this to mean "enable" */ + masscan->templ_opts->tcp.is_mss = Add; + masscan->templ_opts->tcp.mss = default_mss; /* 1460 */ + } else if (isBoolean(value)) { + /* looking for "enable" or "disable", but any boolean works, + * like "true/false" or "off/on" */ + if (parseBoolean(value)) { + masscan->templ_opts->tcp.is_mss = Add; + masscan->templ_opts->tcp.mss = default_mss; /* 1460 */ + } else + masscan->templ_opts->tcp.is_mss = Remove; + } else if (isInteger(value)) { + /* A specific number was specified */ + uint64_t num = parseInt(value); + if (num >= 0x10000) + goto fail; + masscan->templ_opts->tcp.is_mss = Add; + masscan->templ_opts->tcp.mss = (unsigned)num; + } else + goto fail; + + return CONF_OK; +fail: + fprintf(stderr, "[-] %s: bad value: %s\n", name, value); + return CONF_ERR; +} + +static int SET_tcp_wscale(struct Masscan *masscan, const char *name, const char *value) +{ + static const unsigned default_value = 3; + + if (masscan->echo) { + if (masscan->templ_opts) { + switch (masscan->templ_opts->tcp.is_wscale) { + case Default: + break; + case Add: + if (masscan->templ_opts->tcp.wscale == default_value) + fprintf(masscan->echo, "tcp-wscale = %s\n", "enable"); + else + fprintf(masscan->echo, "tcp-wscale = %u\n", + masscan->templ_opts->tcp.wscale); + break; + case Remove: + fprintf(masscan->echo, "tcp-wscale = %s\n", "disable"); + break; + default: + break; + } + } + return 0; + } + + if (masscan->templ_opts == NULL) + masscan->templ_opts = calloc(1, sizeof(*masscan->templ_opts)); + + if (value == 0 || value[0] == '\0') { + masscan->templ_opts->tcp.is_wscale = Add; + masscan->templ_opts->tcp.wscale = default_value; + } else if (isBoolean(value)) { + if (parseBoolean(value)) { + masscan->templ_opts->tcp.is_wscale = Add; + masscan->templ_opts->tcp.wscale = default_value; + } else + masscan->templ_opts->tcp.is_wscale = Remove; + } else if (isInteger(value)) { + uint64_t num = parseInt(value); + if (num >= 255) + goto fail; + masscan->templ_opts->tcp.is_wscale = Add; + masscan->templ_opts->tcp.wscale = (unsigned)num; + } else + goto fail; + + return CONF_OK; +fail: + fprintf(stderr, "[-] %s: bad value: %s\n", name, value); + return CONF_ERR; +} + +static int SET_tcp_tsecho(struct Masscan *masscan, const char *name, const char *value) +{ + static const unsigned default_value = 0x12345678; + + if (masscan->echo) { + if (masscan->templ_opts) { + switch (masscan->templ_opts->tcp.is_tsecho) { + case Default: + break; + case Add: + if (masscan->templ_opts->tcp.tsecho == default_value) + fprintf(masscan->echo, "tcp-tsecho = %s\n", "enable"); + else + fprintf(masscan->echo, "tcp-tsecho = %u\n", + masscan->templ_opts->tcp.tsecho); + break; + case Remove: + fprintf(masscan->echo, "tcp-tsecho = %s\n", "disable"); + break; + default: + break; + } + } + return 0; + } + + if (masscan->templ_opts == NULL) + masscan->templ_opts = calloc(1, sizeof(*masscan->templ_opts)); + + if (value == 0 || value[0] == '\0') { + masscan->templ_opts->tcp.is_tsecho = Add; + masscan->templ_opts->tcp.tsecho = default_value; + } else if (isBoolean(value)) { + if (parseBoolean(value)) { + masscan->templ_opts->tcp.is_tsecho = Add; + masscan->templ_opts->tcp.tsecho = default_value; + } else + masscan->templ_opts->tcp.is_tsecho = Remove; + } else if (isInteger(value)) { + uint64_t num = parseInt(value); + if (num >= 255) + goto fail; + masscan->templ_opts->tcp.is_tsecho = Add; + masscan->templ_opts->tcp.tsecho = (unsigned)num; + } else + goto fail; + + return CONF_OK; +fail: + fprintf(stderr, "[-] %s: bad value: %s\n", name, value); + return CONF_ERR; +} + +static int SET_tcp_sackok(struct Masscan *masscan, const char *name, const char *value) +{ + if (masscan->echo) { + if (masscan->templ_opts) { + switch (masscan->templ_opts->tcp.is_sackok) { + case Default: + break; + case Add: + fprintf(masscan->echo, "tcp-sackok = %s\n", "enable"); + break; + case Remove: + fprintf(masscan->echo, "tcp-sackok = %s\n", "disable"); + break; + default: + break; + } + } + return 0; + } + + if (masscan->templ_opts == NULL) + masscan->templ_opts = calloc(1, sizeof(*masscan->templ_opts)); + + if (value == 0 || value[0] == '\0') { + masscan->templ_opts->tcp.is_sackok = Add; + } else if (isBoolean(value)) { + if (parseBoolean(value)) { + masscan->templ_opts->tcp.is_sackok = Add; + } else + masscan->templ_opts->tcp.is_sackok = Remove; + } else if (isInteger(value)) { + if (parseInt(value) != 0) + masscan->templ_opts->tcp.is_sackok = Add; + } else + goto fail; + + return CONF_OK; +fail: + fprintf(stderr, "[-] %s: bad value: %s\n", name, value); + return CONF_ERR; +} + + static int SET_noreset(struct Masscan *masscan, const char *name, const char *value) { UNUSEDPARM(name); @@ -1964,6 +2250,39 @@ static int SET_output_stylesheet(struct Masscan *masscan, const char *name, cons return CONF_OK; } +static int SET_topports(struct Masscan *masscan, const char *name, const char *value) +{ + unsigned default_value = 20; + + if (masscan->echo) { + /* don't echo: this instead triggers filling the `--port` + * list, so the ports themselves will be echoed, not this + * parameter */ + return 0; + } + + if (value == 0 || value[0] == '\0') { + /* can be specified by itself on the command-line, alone + * without a following parameter */ + /* ex: `--top-ports` */ + masscan->top_ports = default_value; + } else if (isBoolean(value)) { + /* ex: `--top-ports enable` */ + if (parseBoolean(value)) + masscan->top_ports = default_value; + else + masscan->top_ports = 0; + } else if (isInteger(value)) { + /* ex: `--top-ports 5` */ + uint64_t num = parseInt(value); + masscan->top_ports = (unsigned)num; + } else { + fprintf(stderr, "[-] %s: bad value: %s\n", name, value); + return CONF_ERR; + } + return CONF_OK; +} + struct ConfigParameter { @@ -1972,7 +2291,7 @@ struct ConfigParameter { unsigned flags; const char *alts[6]; }; -enum {F_NONE, F_BOOL}; +enum {F_NONE, F_BOOL=1, F_NUMABLE=2}; struct ConfigParameter config_parameters[] = { {"resume-index", SET_resume_index, 0, {0}}, {"resume-count", SET_resume_count, 0, {0}}, @@ -1983,6 +2302,10 @@ struct ConfigParameter config_parameters[] = { {"shard", SET_shard, 0, {"shards",0}}, {"banners", SET_banners, F_BOOL, {"banner",0}}, {"nobanners", SET_nobanners, F_BOOL, {"nobanner",0}}, + {"tcpmss", SET_tcpmss, F_NUMABLE, {0}}, + {"tcp-wscale", SET_tcp_wscale, F_NUMABLE, {0}}, + {"tcp-tsecho", SET_tcp_tsecho, F_NUMABLE, {0}}, + {"tcp-sackok", SET_tcp_sackok, F_BOOL, {0}}, {"retries", SET_retries, 0, {"retry", "max-retries", "max-retry", 0}}, {"noreset", SET_noreset, F_BOOL, {0}}, {"nmap-payloads", SET_nmap_payloads, 0, {"nmap-payload",0}}, @@ -2021,6 +2344,7 @@ struct ConfigParameter config_parameters[] = { {"stylesheet", SET_output_stylesheet, 0, {0}}, {"script", SET_script, 0, {0}}, {"SPACE", SET_space, 0, {0}}, + {"top-ports", SET_topports, F_NUMABLE, {"top-port",0}}, {0} }; @@ -2636,6 +2960,25 @@ masscan_set_parameter(struct Masscan *masscan, } } +static bool +is_numable(const char *name) { + size_t i; + + for (i=0; config_parameters[i].name; i++) { + if (EQUALS(config_parameters[i].name, name)) { + return (config_parameters[i].flags & F_NUMABLE) == F_NUMABLE; + } else { + size_t j; + for (j=0; config_parameters[i].alts[j]; j++) { + if (EQUALS(config_parameters[i].alts[j], name)) { + return (config_parameters[i].flags & F_NUMABLE) == F_NUMABLE; + } + } + } + } + return false; +} + /*************************************************************************** * Command-line parsing code assumes every --parm is followed by a value. * This is a list of the parameters that don't follow the default. @@ -2797,26 +3140,52 @@ masscan_command_line(struct Masscan *masscan, int argc, char *argv[]) /* * --name=value * --name:value - * -- name value + * --name value */ if (argv[i][0] == '-' && argv[i][1] == '-') { - if (strcmp(argv[i], "--help") == 0) { + const char *argname = argv[i] + 2; + + if (EQUALS("help", argname)) { masscan_help(); - } else if (EQUALS("top-ports", argv[i]+2)) { - /* special handling here since the following parameter - * is optional */ - const char *value = "20"; - unsigned n; - - /* Only consume the next parameter if it's a number, - * otherwise default to 10000 */ - if (i+1 < argc && isInteger(argv[i+1])) { - value = argv[++i]; + exit(1); + } else if (is_numable(argname)) { + /* May exist by itself like a bool or take an additional + * numeric argument */ + char name2[64]; + const char *name = argname; + unsigned name_length; + const char *value; + + /* Look for: + * --name=value + * --name:value */ + value = strchr(argname, '='); + if (value == NULL) + value = strchr(&argv[i][2], ':'); + if (value) { + name_length = (unsigned)(value - name); + } else { + /* The next parameter contains the name */ + if (i+1 < argc) { + value = argv[i+1]; + if (isInteger(value) || isBoolean(value)) + i++; + else + value = ""; + } else + value = ""; + name_length = (unsigned)strlen(argname); } - n = (unsigned)parseInt(value); - LOG(2, "top-ports = %u\n", n); - masscan->top_ports = n; - + + /* create a copy of the name */ + if (name_length > sizeof(name2) - 1) { + fprintf(stderr, "%.*s: name too long\n", name_length, name); + name_length = sizeof(name2) - 1; + } + memcpy(name2, name, name_length); + name2[name_length] = '\0'; + + masscan_set_parameter(masscan, name2, value); } else if (EQUALS("readscan", argv[i]+2)) { /* Read in a binary file instead of scanning the network*/ masscan->op = Operation_ReadScan; diff --git a/src/main.c b/src/main.c index 5c739690..7f65aad1 100644 --- a/src/main.c +++ b/src/main.c @@ -1302,7 +1302,8 @@ main_scan(struct Masscan *masscan) masscan->payloads.udp, masscan->payloads.oproto, stack_if_datalink(masscan->nic[index].adapter), - masscan->seed); + masscan->seed, + masscan->templ_opts); /* * Set the "source port" of everything we transmit. diff --git a/src/masscan.h b/src/masscan.h index ccaea410..5eb51709 100644 --- a/src/masscan.h +++ b/src/masscan.h @@ -16,6 +16,7 @@ struct Adapter; struct TemplateSet; struct Banner1; +struct TemplateOptions; /** * This is the "operation" to be performed by masscan, which is almost always @@ -204,7 +205,11 @@ struct Masscan unsigned is_hello_http:1; /* --hello=http, use HTTP on all ports */ unsigned is_scripting:1; /* whether scripting is needed */ unsigned is_capture_servername:1; /* --capture servername */ - + + /** Packet template options, such as whether we should add a TCP MSS + * value, or remove it from the packet */ + struct TemplateOptions *templ_opts; + /** * Wait forever for responses, instead of the default 10 seconds */ diff --git a/src/templ-opts.h b/src/templ-opts.h new file mode 100644 index 00000000..9b3ac1d2 --- /dev/null +++ b/src/templ-opts.h @@ -0,0 +1,66 @@ +#ifndef TEMPL_OPTS_H +#define TEMPL_OPTS_H +#include "massip-addr.h" + +/** + * This tells us whether we should add, remove, or leave default + * a field in the packet headers. + * FIXME: not all of these are supported + */ +typedef enum {Default, Add, Remove} addremove_t; + +struct TemplateOptions { + struct { + addremove_t is_badsum:4; /* intentionally bad checksum */ + addremove_t is_tsecho:4; /* enable timestamp echo */ + addremove_t is_tsreply:4; /* enable timestamp echo */ + addremove_t is_flags:4; + addremove_t is_ackno:4; + addremove_t is_seqno:4; + addremove_t is_win:4; + addremove_t is_mss:4; + addremove_t is_sackok:4; + addremove_t is_wscale:4; + unsigned flags; + unsigned ackno; + unsigned seqno; + unsigned win; + unsigned mss; + unsigned sackok; + unsigned wscale; + unsigned tsecho; + unsigned tsreply; + } tcp; + + struct { + addremove_t is_badsum:4; /* intentionally bad checksum */ + } udp; + + struct { + addremove_t is_sender_mac:4; + addremove_t is_sender_ip:4; + addremove_t is_target_mac:4; + addremove_t is_target_ip:4; + macaddress_t sender_mac; + ipaddress sender_ip; + macaddress_t target_mac; + ipaddress target_ip; + } arp; + + struct { + addremove_t is_badsum:4; /* intentionally bad checksum */ + addremove_t is_tos:4; + addremove_t is_ipid:4; + addremove_t is_df:4; + addremove_t is_mf:4; + addremove_t is_ttl:4; + + unsigned tos; + unsigned ipid; + unsigned ttl; + + } ipv4; +}; + +#endif + diff --git a/src/templ-pkt.c b/src/templ-pkt.c index 6f9e0d7a..24e375a4 100644 --- a/src/templ-pkt.c +++ b/src/templ-pkt.c @@ -8,6 +8,8 @@ appropriate changes. */ #include "templ-pkt.h" +#include "templ-tcp-hdr.h" +#include "templ-opts.h" #include "massip-port.h" #include "proto-preprocess.h" #include "proto-sctp.h" @@ -31,7 +33,7 @@ static unsigned char default_tcp_template[] = "\x08\x00" /* Ethernet type: IPv4 */ "\x45" /* IP type */ "\x00" - "\x00\x28" /* total length = 40 bytes */ + "\x00\x2c" /* total length = 40 bytes */ "\x00\x00" /* identification */ "\x00\x00" /* fragmentation flags */ "\xFF\x06" /* TTL=255, proto=TCP */ @@ -43,12 +45,12 @@ static unsigned char default_tcp_template[] = "\0\0" /* destination port */ "\0\0\0\0" /* sequence number */ "\0\0\0\0" /* ACK number */ - "\x50" /* header length */ + "\x60" /* header length */ "\x02" /* SYN */ - "\x04\x0" /* window fixed to 1024 */ + "\x04\x01" /* window fixed to 1024 */ "\xFF\xFF" /* checksum */ "\x00\x00" /* urgent pointer */ - "\x02\x04\x05\xb4" /* added options [mss 1460] */ + "\x02\x04\x05\xb4" /* opt [mss 1460] h/t @IvreRocks */ ; static unsigned char default_udp_template[] = @@ -1227,7 +1229,7 @@ _template_init( unsigned char *px; struct PreprocessedInfo parsed; unsigned x; - + /* * Create the new template structure: * - zero it out @@ -1378,24 +1380,32 @@ template_packet_init( struct PayloadsUDP *udp_payloads, struct PayloadsUDP *oproto_payloads, int data_link, - uint64_t entropy) + uint64_t entropy, + const struct TemplateOptions *templ_opts) { + unsigned char *buf; + size_t length; templset->count = 0; templset->entropy = entropy; - /* [SCTP] */ - _template_init(&templset->pkts[Proto_SCTP], + + /* [TCP] */ + length = sizeof(default_tcp_template)-1; + buf = malloc(length); + memcpy(buf, default_tcp_template, length); + templ_tcp_apply_options(&buf, &length, templ_opts); /* mss, sack, wscale */ + _template_init(&templset->pkts[Proto_TCP], source_mac, router_mac_ipv4, router_mac_ipv6, - default_sctp_template, - sizeof(default_sctp_template)-1, + buf, length, data_link); templset->count++; + free(buf); - /* [TCP] */ - _template_init(&templset->pkts[Proto_TCP], + /* [SCTP] */ + _template_init(&templset->pkts[Proto_SCTP], source_mac, router_mac_ipv4, router_mac_ipv6, - default_tcp_template, - sizeof(default_tcp_template)-1, + default_sctp_template, + sizeof(default_sctp_template)-1, data_link); templset->count++; @@ -1515,6 +1525,14 @@ template_selftest(void) { struct TemplateSet tmplset[1]; int failures = 0; + struct TemplateOptions templ_opts = {{0}}; + + /* Test the module that edits TCP headers */ + if (templ_tcp_selftest()) { + fprintf(stderr, "[-] templ-tcp-hdr: selftest failed\n"); + return 1; + } + memset(tmplset, 0, sizeof(tmplset[0])); template_packet_init( @@ -1525,7 +1543,8 @@ template_selftest(void) 0, /* UDP payloads = empty */ 0, /* Oproto payloads = empty */ 1, /* Ethernet */ - 0 /* no entropy */ + 0, /* no entropy */ + &templ_opts ); failures += tmplset->pkts[Proto_TCP].proto != Proto_TCP; failures += tmplset->pkts[Proto_UDP].proto != Proto_UDP; diff --git a/src/templ-pkt.h b/src/templ-pkt.h index a5eaa5c2..199fd711 100644 --- a/src/templ-pkt.h +++ b/src/templ-pkt.h @@ -5,6 +5,7 @@ #include "massip-addr.h" struct PayloadsUDP; struct MassVulnCheck; +struct TemplateOptions; /** * Does a regression test of this module. @@ -83,6 +84,8 @@ struct TemplateSet struct TemplateSet templ_copy(const struct TemplateSet *templ); + + /** * Initialize the "template" packets. As we spew out probes, we simply make * minor adjustments to the template, such as changing the target IP @@ -115,7 +118,8 @@ template_packet_init( struct PayloadsUDP *udp_payloads, struct PayloadsUDP *oproto_payloads, int data_link, - uint64_t entropy); + uint64_t entropy, + const struct TemplateOptions *templ_opts); /** * Sets the target/destination IP address of the packet, the destination port diff --git a/src/templ-tcp-hdr.c b/src/templ-tcp-hdr.c new file mode 100644 index 00000000..41225939 --- /dev/null +++ b/src/templ-tcp-hdr.c @@ -0,0 +1,1571 @@ +/* + This module edits an existing TCP packet, adding and removing + options, setting the values of certain fields. + + From RFC793: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Source Port | Destination Port | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Sequence Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Acknowledgment Number | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Data | |U|A|P|R|S|F| | + | Offset| Reserved |R|C|S|S|Y|I| Window | + | | |G|K|H|T|N|N| | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Checksum | Urgent Pointer | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Options | Padding | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | data | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + TCP Window Scale Option (WSopt): + Kind: 3 Length: 3 bytes + +---------+---------+---------+ + | Kind=3 |Length=3 |shift.cnt| + +---------+---------+---------+ + + TCP Timestamps Option (TSopt): + Kind: 8 + Length: 10 bytes + +-------+-------+---------------------+---------------------+ + |Kind=8 | 10 | TS Value (TSval) |TS Echo Reply (TSecr)| + +-------+-------+---------------------+---------------------+ + 1 1 4 4 + + TCP Sack-Permitted Option: + Kind: 4 + +---------+---------+ + | Kind=4 | Length=2| + +---------+---------+ + + + TCP SACK Option: + Kind: 5 + Length: Variable + + +--------+--------+ + | Kind=5 | Length | + +--------+--------+--------+--------+ + | Left Edge of 1st Block | + +--------+--------+--------+--------+ + | Right Edge of 1st Block | + +--------+--------+--------+--------+ + | | + / . . . / + | | + +--------+--------+--------+--------+ + | Left Edge of nth Block | + +--------+--------+--------+--------+ + | Right Edge of nth Block | + +--------+--------+--------+--------+ + + */ +#include "templ-tcp-hdr.h" +#include "templ-opts.h" +#include "logger.h" +#include "proto-preprocess.h" +#include +#include +#include + +struct tcp_opt_t { + const unsigned char *buf; + size_t length; + unsigned kind; + bool is_found; +}; + +struct tcp_hdr_t { + size_t begin; + size_t max; + size_t ip_offset; + unsigned char ip_version; + bool is_found; +}; + +/*************************************************************************** + * A typical hexdump function, but dumps specifically the + * section of a TCP header. An added feature is that it marks the byte + * at "offset". This makes debugging easier, so I can see the + * as I'm stepping through code. You'll see this commented-out throughout + * the code. + ***************************************************************************/ +static void +_HEXDUMP(const void *v, struct tcp_hdr_t hdr, size_t offset, const char *name) +{ + const unsigned char *p = ((const unsigned char *)v) + hdr.begin + 20; + size_t i; + size_t len = hdr.max - hdr.begin + 8 - 20; + + printf("%s:\n", name); + offset -= hdr.begin + 20; + + for (i=0; i> 4) * 4; +} + +/*************************************************************************** + * Does a consistency check of the whole packet, including IP header, + * TCP header, and the options in the field. This is used + * in the self-test feature after test cases, to make sure the packet + * hasn't bee corrupted. + ***************************************************************************/ +static int +_consistancy_check(const unsigned char *buf, size_t length, + const void *payload, size_t payload_length) { + struct PreprocessedInfo parsed; + unsigned is_success; + + /* Parse the packet */ + is_success = preprocess_frame(buf, + (unsigned)length, + 1 /*enet*/, + &parsed); + if (!is_success || parsed.found != FOUND_TCP) { + fprintf(stderr, "[-] check: TCP header not found\n"); + goto fail; + } + + /* Check the lengths */ + switch (parsed.ip_version) { + case 4: + if (parsed.ip_length + 14 != length) { + fprintf(stderr, "[-] check: IP length bad\n"); + goto fail; + } + break; + case 6: + break; + default: + fprintf(stderr, "[-] check: IPv?\n"); + goto fail; + } + + /* Validate TCP header options */ + { + size_t offset = parsed.transport_offset; + size_t max = offset + _tcp_header_length(buf, offset); + + /* Get the start of the section of the header. This is defined + * as 20 bytes into the TCP header. */ + offset += 20; + + /* Enumerate any existing options one-by-one. */ + while (offset < max) { + unsigned kind; + unsigned len; + + /* Get the option type (aka. "kind") */ + kind = buf[offset++]; + + if (kind == 0x00) { + /* EOL - end of options list + * According to the spec, processing should stop here, even if + * there are additional options after this point. */ + break; + } else if (kind == 0x01) { + /* NOP - No-operation + * This is a single byte option, used to pad other options to + * even 4 byte boundaries. Padding is optional. */ + continue; + } + + /* If we've reached the end of */ + if (offset > max) + goto fail; + if (offset == max) + break; + len = buf[offset++]; + + /* Check for corruption, the lenth field is inclusive, so should + * equal at least two. It's maximum length should be bfore the end + * of the packet */ + if (len < 2 || len > (max-offset+2)) { + goto fail; + } + + offset += len - 2; + } + } + + /* Check the payload */ + if (parsed.app_length != payload_length) + goto fail; + if (memcmp(buf + parsed.app_offset, payload, payload_length) != 0) + goto fail; + + return 0; +fail: + return 1; +} + +/*************************************************************************** + * Find the TCP header in the packet. We can't be sure what's in the + * current template because it could've been provided by the user, so + * we instead parse it as if we've received it from the network wire. + ***************************************************************************/ +static struct tcp_hdr_t +_find_tcp_header(const unsigned char *buf, size_t length) { + struct tcp_hdr_t hdr = {0}; + struct PreprocessedInfo parsed; + unsigned is_success; + + /* + * Parse the packet, telling us where the TCP header is. This works + * for both IPv4 and IPv6, we care only about the TCP header portion. + */ + is_success = preprocess_frame(buf, /* the packet, including Ethernet hdr */ + (unsigned)length, + 1 /*enet*/, + &parsed); + if (!is_success || parsed.found != FOUND_TCP) { + /* We were unable to parse a well-formatted TCP packet. This + * might've been UDP or something. */ + goto fail; + } + + hdr.begin = parsed.transport_offset; + hdr.max = hdr.begin + _tcp_header_length(buf, hdr.begin); + hdr.ip_offset = parsed.ip_offset; + hdr.ip_version = parsed.ip_version; + hdr.is_found = true; + return hdr; + +fail: + hdr.is_found = false; + return hdr; +} + +/*************************************************************************** + * A quick macro at the start of for(;;) loops that enumerate all the + * options in the + ***************************************************************************/ +static inline size_t +_opt_begin(struct tcp_hdr_t hdr) { + return hdr.begin + 20; /* start of field */ +} + +/*************************************************************************** + * A quick macro in the for(;;) loop that enumerates all the options + * in the . It has three possibilities based on the KIND: + * 0x00 - we've reached the end of the options-list + * 0x01 - padding NOP byte, which we skipo + * 0x?? - some option, the following byte is the length. We skip + * that `len` bytes. + ***************************************************************************/ +static inline size_t +_opt_next(struct tcp_hdr_t hdr, size_t offset, const unsigned char *buf) { + unsigned kind = buf[offset]; + if (kind == 0x00) { + return hdr.max; + } else if (kind == 0x01) { + return offset + 1; + } else if (offset + 2 > hdr.max) { + return hdr.max; /* corruption */ + } else { + unsigned len = buf[offset+1]; + if (len < 2 || offset + len > hdr.max) + return hdr.max; /* corruption */ + else + return offset + len; + } +} + +/*************************************************************************** + ***************************************************************************/ +static void +_HEXDUMPopt(const unsigned char *buf, size_t length, const char *name) { + struct tcp_hdr_t hdr; + + hdr = _find_tcp_header(buf, length); + if (!hdr.is_found) { + fprintf(stderr, "[-] templ.tcp.hdr: failure\n"); + } + _HEXDUMP(buf, hdr, _opt_begin(hdr), name); +} + +/*************************************************************************** + * Search throgh the until we find the specified option, + * 'kind', or reach the end of the list. An impossible 'kind', like 0x100, + * will force finding the end of the list before padding starts. + ***************************************************************************/ +static size_t +_find_opt(const unsigned char *buf, struct tcp_hdr_t hdr, unsigned in_kind, + unsigned *nop_count) { + size_t offset; + + /* This field is optional, if used, set it to zero */ + if (nop_count) + *nop_count = 0; + + /* enumerate all looking for a match */ + for (offset = _opt_begin(hdr); + offset < hdr.max; + offset = _opt_next(hdr, offset, buf)) { + unsigned kind; + + /* get the option type/kind */ + kind = buf[offset]; + + /* Stop search if we hit an EOL marker */ + if (kind == 0x00) + break; + + /* Stop search when we find our option */ + if (kind == in_kind) + break; + + /* Count the number of NOPs leading up to where we end */ + if (nop_count) { + if (kind == 0x01) + (*nop_count)++; + else + (*nop_count) = 0; + } + } + return offset; +} + +/*************************************************************************** + * Search the TCP header's field for the specified kind/type. + * Typical kinds of options are MSS, window scale, SACK, timestamp. + ***************************************************************************/ +static struct tcp_opt_t +tcp_find_opt(const unsigned char *buf, size_t length, unsigned in_kind) { + struct tcp_opt_t result = {0}; + struct tcp_hdr_t hdr; + size_t offset; + + /* Get the TCP header in the packet */ + hdr = _find_tcp_header(buf, length); + if (!hdr.is_found) + goto fail; + + /* Search for a matchin