/* 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 "util-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; }; /** * Do a memmove() of a chunk of memory within a buffer with bounds checking. */ static void safe_memmove(unsigned char *buf, size_t length, size_t to, size_t from, size_t chunklength) { if (chunklength + to > length) { fprintf(stderr, "+"); fflush(stderr); chunklength = length - to; } if (chunklength + from > length) { fprintf(stderr, "-"); fflush(stderr); chunklength = length - from; } memmove(buf + to, buf + from, chunklength); } /** * Do a memset() of a chunk of memory within a buffer with bounds checking */ static void safe_memset(unsigned char *buf, size_t length, size_t offset, int c, size_t chunklength) { if (chunklength + offset > length) { chunklength = length - offset; fprintf(stderr, "*"); fflush(stderr); } memset(buf + offset, c, chunklength); } /*************************************************************************** * 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 = (unsigned char)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