1609 lines
51 KiB
C
1609 lines
51 KiB
C
|
/*
|
||
|
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 <string.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <ctype.h>
|
||
|
|
||
|
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 <options-list>
|
||
|
* 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 <options-list>
|
||
|
* 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<len; i += 16) {
|
||
|
size_t j;
|
||
|
|
||
|
for (j=i; j<i+16 && j<len; j++) {
|
||
|
char c = ' ';
|
||
|
if (j == offset)
|
||
|
c = '>';
|
||
|
if (j + 1 == offset)
|
||
|
c = '<';
|
||
|
printf("%02x%c", p[j], c);
|
||
|
}
|
||
|
for (;j<i+16; j++)
|
||
|
printf(" ");
|
||
|
printf(" ");
|
||
|
for (j=i; j<i+16 && j<len; j++) {
|
||
|
char c = p[j];
|
||
|
|
||
|
if (j == offset)
|
||
|
c = '#';
|
||
|
|
||
|
if (isprint(c&0xff) && !isspace(c&0xff))
|
||
|
printf("%c", c);
|
||
|
else
|
||
|
printf(".");
|
||
|
}
|
||
|
printf("\n");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* A quick macro to calculate the TCP header length, given a buffer
|
||
|
* and an offset to the start of the TCP header.
|
||
|
***************************************************************************/
|
||
|
static unsigned inline
|
||
|
_tcp_header_length(const unsigned char *buf, size_t offset) {
|
||
|
return (buf[offset + 12] >> 4) * 4;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
* Does a consistency check of the whole packet, including IP header,
|
||
|
* TCP header, and the options in the <options-list> 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 <options> 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 <option-list>
|
||
|
***************************************************************************/
|
||
|
static inline size_t
|
||
|
_opt_begin(struct tcp_hdr_t hdr) {
|
||
|
return hdr.begin + 20; /* start of <options> field */
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
* A quick macro in the for(;;) loop that enumerates all the options
|
||
|
* in the <option-list>. 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 <option-list> 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 <options> 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 <options> 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 <option> */
|
||
|
offset = _find_opt(buf, hdr, in_kind, 0);
|
||
|
if (offset >= hdr.max || buf[offset] != in_kind)
|
||
|
goto fail;
|
||
|
|
||
|
/* We've found it! If we've passed all the checks above, we have
|
||
|
* a well formatted field, so just return it. */
|
||
|
result.kind = in_kind;
|
||
|
result.buf = buf + offset + 2;
|
||
|
result.length = buf[offset+1] - 2;
|
||
|
if (offset + result.length >= hdr.max)
|
||
|
goto fail;
|
||
|
result.is_found = true;
|
||
|
return result;
|
||
|
|
||
|
fail:
|
||
|
result.is_found = false;
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
* Adjusts the IP "total length" and TCP "header length" fields to match
|
||
|
* recent additions/removals of options in the <option-list>
|
||
|
***************************************************************************/
|
||
|
static void
|
||
|
_adjust_length(unsigned char *buf, size_t length, int adjustment, struct tcp_hdr_t hdr) {
|
||
|
size_t ip_offset = hdr.ip_offset;
|
||
|
|
||
|
/* The adjustment should already have been aligned on an even 4 byte
|
||
|
* boundary */
|
||
|
if ((adjustment & 0x3) != 0) {
|
||
|
fprintf(stderr, "[-] templ.tcp: impossible alignment error\n");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/* Adjust the IP header length */
|
||
|
switch (hdr.ip_version) {
|
||
|
case 4: {
|
||
|
unsigned total_length;
|
||
|
total_length = buf[ip_offset+2] << 8 | buf[ip_offset+3] << 0;
|
||
|
total_length += adjustment;
|
||
|
buf[ip_offset+2] = (unsigned char)(total_length>>8);
|
||
|
buf[ip_offset+3] = (unsigned char)(total_length>>0);
|
||
|
total_length = buf[ip_offset+2] << 8 |buf[ip_offset+3] << 0;
|
||
|
if (total_length + 14 != length) {
|
||
|
fprintf(stderr, "[-] IP length mismatch\n");
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case 6: {
|
||
|
unsigned payload_length;
|
||
|
payload_length = buf[ip_offset+4] << 8 | buf[ip_offset+5] << 0;
|
||
|
payload_length += adjustment;
|
||
|
buf[ip_offset+4] = (unsigned char)(payload_length>>8);
|
||
|
buf[ip_offset+5] = (unsigned char)(payload_length>>0);
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Adjust the TCP header length */
|
||
|
{
|
||
|
size_t hdr_length;
|
||
|
size_t offset = hdr.begin + 12;
|
||
|
|
||
|
hdr_length = (buf[offset] >> 4) * 4;
|
||
|
|
||
|
hdr_length += adjustment;
|
||
|
|
||
|
if (hdr_length % 4 != 0) {
|
||
|
fprintf(stderr, "[-] templ.tcp corruptoin\n");
|
||
|
}
|
||
|
|
||
|
buf[offset] = (unsigned char)((buf[offset] & 0x0F) | ((hdr_length/4) << 4));
|
||
|
|
||
|
hdr_length = (buf[offset] >> 4) * 4;
|
||
|
if (hdr.begin + hdr_length > length) {
|
||
|
fprintf(stderr, "[-] templ.tcp corruptoin\n");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
* After adding/removing an option, the <option-list> may no longer be
|
||
|
* aligned on an even 4-byte boundary as required. This function
|
||
|
* adds padding as necessary to align to the boundary.
|
||
|
***************************************************************************/
|
||
|
static void
|
||
|
_add_padding(unsigned char **inout_buf, size_t *inout_length, size_t offset, unsigned pad_count) {
|
||
|
unsigned char *buf = *inout_buf;
|
||
|
size_t length = *inout_length;
|
||
|
|
||
|
length += pad_count;
|
||
|
buf = realloc(buf, length);
|
||
|
|
||
|
/* open space between headers and payload */
|
||
|
safe_memmove(buf, length,
|
||
|
offset + pad_count,
|
||
|
offset,
|
||
|
(length - pad_count) - offset);
|
||
|
|
||
|
/* set padding to zero */
|
||
|
safe_memset(buf, length,
|
||
|
offset, 0, pad_count);
|
||
|
|
||
|
/* Set the out parameters */
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
* Afte changes, there my be more padding bytes than necessary. This
|
||
|
* reduces the number to 3 or less. Also, it changes any trailing NOPs
|
||
|
* to EOL bytes, since there are no more options after that point.
|
||
|
***************************************************************************/
|
||
|
static bool
|
||
|
_normalize_padding(unsigned char **inout_buf, size_t *inout_length) {
|
||
|
unsigned char *buf = *inout_buf;
|
||
|
size_t length = *inout_length;
|
||
|
struct tcp_hdr_t hdr;
|
||
|
size_t offset;
|
||
|
unsigned nop_count = 0;
|
||
|
|
||
|
/* find TCP header */
|
||
|
hdr = _find_tcp_header(buf, length);
|
||
|
if (!hdr.is_found)
|
||
|
goto fail;
|
||
|
|
||
|
|
||
|
/* find the start of the padding field */
|
||
|
offset = _find_opt(buf, hdr, 0x100, &nop_count);
|
||
|
if (offset >= hdr.max && nop_count == 0)
|
||
|
goto success; /* no padding needing to be removed */
|
||
|
|
||
|
/* If NOPs immediately before EOL, include them too */
|
||
|
offset -= nop_count;
|
||
|
|
||
|
{
|
||
|
size_t remove_count = hdr.max - offset;
|
||
|
|
||
|
/* the amount removed must be aligned on 4-byte boundary */
|
||
|
while (remove_count % 4)
|
||
|
remove_count--;
|
||
|
|
||
|
/* If we have nothing left to remove, then exit.
|
||
|
* THIS IS THE NORMAL CASE -- most of the time, we have no
|
||
|
* extra padding to remove. */
|
||
|
if (remove_count == 0)
|
||
|
goto fail; /* likely, normal*/
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset, "before padding removal");
|
||
|
|
||
|
safe_memmove(buf, length,
|
||
|
offset,
|
||
|
offset + remove_count,
|
||
|
length - (offset + remove_count));
|
||
|
hdr.max -= remove_count;
|
||
|
length -= remove_count;
|
||
|
|
||
|
/* normalize all the bytes to zero, in case they aren't already */
|
||
|
safe_memset(buf, length, offset, 0, hdr.max - offset);
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset, "after padding removal");
|
||
|
|
||
|
/* fix the IP and TCP length fields */
|
||
|
_adjust_length(buf, length, 0 - (int)remove_count, hdr);
|
||
|
}
|
||
|
|
||
|
success:
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
return true; /* success */
|
||
|
fail:
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
return false; /* failure */
|
||
|
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
***************************************************************************/
|
||
|
static bool
|
||
|
tcp_remove_opt(
|
||
|
unsigned char **inout_buf, size_t *inout_length, unsigned in_kind
|
||
|
) {
|
||
|
unsigned char *buf = *inout_buf;
|
||
|
size_t length = *inout_length;
|
||
|
struct tcp_hdr_t hdr;
|
||
|
size_t offset;
|
||
|
unsigned nop_count = 0;
|
||
|
|
||
|
/* find the TCP header */
|
||
|
hdr = _find_tcp_header(buf, length);
|
||
|
if (!hdr.is_found)
|
||
|
goto fail;
|
||
|
|
||
|
/* enumerate all the <options> looking for a match */
|
||
|
offset = _find_opt(buf, hdr, in_kind, &nop_count);
|
||
|
if (offset + 2 >= hdr.max)
|
||
|
goto success; /* not found, no matching option type/kind */
|
||
|
|
||
|
|
||
|
{
|
||
|
unsigned opt_len = buf[offset+1];
|
||
|
unsigned remove_length = opt_len;
|
||
|
|
||
|
if (offset + opt_len > hdr.max)
|
||
|
goto fail;
|
||
|
|
||
|
/* Remove any trailing NOPs */
|
||
|
while (offset + remove_length < hdr.max
|
||
|
&& buf[offset + remove_length] == 1)
|
||
|
remove_length++;
|
||
|
|
||
|
/* Remove any leading NOPs */
|
||
|
offset -= nop_count;
|
||
|
remove_length += nop_count;
|
||
|
|
||
|
/* Remove the bytes from the current packet buffer.
|
||
|
* Before this will be the ...IP/TCP headers plus maybe some options.
|
||
|
* After this will be maybe some options, padding, then the TCP payload
|
||
|
* */
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset, "before removal");
|
||
|
|
||
|
safe_memmove(buf, length,
|
||
|
offset,
|
||
|
offset + remove_length,
|
||
|
length - (offset + remove_length));
|
||
|
hdr.max -= remove_length;
|
||
|
length -= remove_length;
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset, "after removal");
|
||
|
|
||
|
|
||
|
/* Now we may need to add back padding */
|
||
|
if (remove_length % 4) {
|
||
|
unsigned add_length = (remove_length % 4);
|
||
|
_add_padding(&buf, &length, hdr.max, add_length);
|
||
|
remove_length -= add_length;
|
||
|
hdr.max += add_length;
|
||
|
}
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset, "padding added");
|
||
|
|
||
|
/* fix the IP and TCP length fields */
|
||
|
_adjust_length(buf, length, 0 - remove_length, hdr);
|
||
|
|
||
|
/* In case we've padded the packet with four 0x00, get rid
|
||
|
* of them */
|
||
|
_normalize_padding(&buf, &length);
|
||
|
}
|
||
|
|
||
|
success:
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
return true;
|
||
|
|
||
|
fail:
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
***************************************************************************/
|
||
|
static int
|
||
|
_insert_field(unsigned char **inout_buf,
|
||
|
size_t *inout_length,
|
||
|
size_t offset_begin,
|
||
|
size_t offset_end,
|
||
|
const unsigned char *new_data,
|
||
|
size_t new_length
|
||
|
) {
|
||
|
unsigned char *buf = *inout_buf;
|
||
|
size_t length = *inout_length;
|
||
|
int adjust = 0;
|
||
|
|
||
|
/* can theoreitcally be negative, but that's ok */
|
||
|
adjust = (int)new_length - ((int)offset_end - (int)offset_begin);
|
||
|
if (adjust > 0) {
|
||
|
length += adjust;
|
||
|
buf = realloc(buf, length);
|
||
|
safe_memmove(buf, length,
|
||
|
offset_begin + new_length,
|
||
|
offset_end,
|
||
|
(length - adjust) - offset_end);
|
||
|
}
|
||
|
if (adjust < 0) {
|
||
|
safe_memmove(buf, length,
|
||
|
offset_begin + new_length,
|
||
|
offset_end,
|
||
|
length - offset_end);
|
||
|
length += adjust;
|
||
|
buf = realloc(buf, length);
|
||
|
}
|
||
|
|
||
|
/**/
|
||
|
memcpy(buf + offset_begin,
|
||
|
new_data,
|
||
|
new_length);
|
||
|
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
|
||
|
return adjust;
|
||
|
}
|
||
|
|
||
|
/** Calculate the total number of padding bytes, both NOPs in the middle
|
||
|
* and EOLs at the end. We call this when there's not enough space for
|
||
|
* another option, and we want to remove all the padding. */
|
||
|
#if 0
|
||
|
static unsigned
|
||
|
_calc_padding(const unsigned char *buf, struct tcp_hdr_t hdr) {
|
||
|
size_t offset;
|
||
|
unsigned result = 0;
|
||
|
|
||
|
/* enumerate through all <option> fields */
|
||
|
for (offset = _opt_begin(hdr);
|
||
|
offset < hdr.max;
|
||
|
offset = _opt_next(hdr, offset, buf)) {
|
||
|
unsigned kind;
|
||
|
|
||
|
/* Get the kind: 0=EOL, 1=NOP, 2=MSS, 3=Wscale, etc. */
|
||
|
kind = buf[offset];
|
||
|
|
||
|
/* If EOL, we end here, and all the remainder bytes are counted
|
||
|
* as padding. */
|
||
|
if (kind == 0) {
|
||
|
result += (hdr.max - offset);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* If a NOP, then this is a padding byte */
|
||
|
if (kind == 1)
|
||
|
result++;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
/***************************************************************************
|
||
|
* Remove all the padding bytes, and return an offset to the beginning
|
||
|
* of the rest of the option field.
|
||
|
***************************************************************************/
|
||
|
static size_t
|
||
|
_squeeze_padding(unsigned char *buf, size_t length, struct tcp_hdr_t hdr, unsigned in_kind) {
|
||
|
size_t offset;
|
||
|
unsigned nop_count = 0;
|
||
|
|
||
|
for (offset = _opt_begin(hdr);
|
||
|
offset < hdr.max;
|
||
|
offset = _opt_next(hdr, offset, buf)) {
|
||
|
unsigned kind;
|
||
|
unsigned len;
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset, "squeeze");
|
||
|
|
||
|
/* Get the kind: 0=EOL, 1=NOP, 2=MSS, 3=Wscale, etc. */
|
||
|
kind = buf[offset];
|
||
|
|
||
|
/* If a NOP padding, simply count it until we reach something
|
||
|
* more interesting */
|
||
|
if (kind == 0x01) {
|
||
|
nop_count++;
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
/* If end of option list, any remaining padding bytes are added */
|
||
|
if (kind == 0x00) {
|
||
|
/* normalize the padding at the end */
|
||
|
offset -= nop_count;
|
||
|
safe_memset(buf, length, offset, 0, hdr.max - offset);
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset, "null");
|
||
|
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
/* If we match an existing field, all those bytes become padding */
|
||
|
if (kind == in_kind) {
|
||
|
len = buf[offset+1];
|
||
|
safe_memset(buf, length, offset, 0x01, len);
|
||
|
nop_count++;
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset, "VVVVV");
|
||
|
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (nop_count == 0)
|
||
|
continue; /*no squeezing needed */
|
||
|
|
||
|
/* move this field backward overwriting NOPs */
|
||
|
len = buf[offset+1];
|
||
|
safe_memmove(buf, length,
|
||
|
offset - nop_count,
|
||
|
offset,
|
||
|
len);
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset - nop_count, "<<<<");
|
||
|
|
||
|
/* now write NOPs where this field used to be */
|
||
|
safe_memset(buf, length,
|
||
|
offset + len - nop_count, 0x01, nop_count);
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset + len - nop_count, "!!!!!");
|
||
|
|
||
|
/* reset the <offset> to the end of this relocated field */
|
||
|
offset -= nop_count;
|
||
|
nop_count = 0;
|
||
|
}
|
||
|
|
||
|
/* if we reach the end, then there were only NOPs at the end and no
|
||
|
* EOL byte, so simply zero them out */
|
||
|
safe_memset(buf, length,
|
||
|
offset - nop_count, 0x00, nop_count);
|
||
|
offset -= nop_count;
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, offset, "");
|
||
|
|
||
|
return offset;
|
||
|
}
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
***************************************************************************/
|
||
|
static bool
|
||
|
tcp_add_opt(unsigned char **inout_buf,
|
||
|
size_t *inout_length,
|
||
|
unsigned opt_kind,
|
||
|
unsigned opt_length,
|
||
|
const unsigned char *opt_data) {
|
||
|
unsigned char *buf = *inout_buf;
|
||
|
size_t length = *inout_length;
|
||
|
struct tcp_hdr_t hdr;
|
||
|
size_t offset;
|
||
|
unsigned nop_count = 0;
|
||
|
int adjust = 0;
|
||
|
|
||
|
|
||
|
/* Check for corruption:
|
||
|
* The maximum size of a TCP header is 60 bytes (0x0F * 4), and the
|
||
|
* rest of the header takes up 20 bytes. The [kind,length] takes up
|
||
|
* another 2 bytes. Thus, the max option length is 38 bytes */
|
||
|
if (opt_length > 38) {
|
||
|
fprintf(stderr, "[-] templ.tcp.add_opt: opt_len too large\n");
|
||
|
goto fail;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* find TCP header */
|
||
|
hdr = _find_tcp_header(buf, length);
|
||
|
if (!hdr.is_found)
|
||
|
goto fail;
|
||
|
|
||
|
/* enumerate all existing options looking match */
|
||
|
offset = _find_opt(buf, hdr, opt_kind, &nop_count);
|
||
|
|
||
|
{
|
||
|
size_t old_begin;
|
||
|
size_t old_end;
|
||
|
unsigned char new_field[64];
|
||
|
size_t new_length;
|
||
|
|
||
|
/* Create a well-formatted field that will be inserted */
|
||
|
new_length = 1 + 1 + opt_length;
|
||
|
new_field[0] = (unsigned char)opt_kind;
|
||
|
new_field[1] = (unsigned char)new_length;
|
||
|
memcpy(new_field + 2, opt_data, opt_length);
|
||
|
|
||
|
/* Calculate the begin/end of the existing field in the packet */
|
||
|
old_begin = offset;
|
||
|
if (old_begin >= hdr.max)
|
||
|
old_end = hdr.max; /* will insert end of header */
|
||
|
else if (buf[offset] == 0x00)
|
||
|
old_end = hdr.max; /* will insert start of padding */
|
||
|
else if (buf[offset] == opt_kind) { /* will replace old field */
|
||
|
size_t len = buf[offset + 1];
|
||
|
old_end = offset + len;
|
||
|
} else {
|
||
|
fprintf(stderr, "[-] not possible i09670t\n");
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* If the existing space is too small, try to expand it by
|
||
|
* using neighboring (leading, trailing) NOPs */
|
||
|
while ((old_end-old_begin) < new_length) {
|
||
|
if (nop_count) {
|
||
|
nop_count--;
|
||
|
old_begin--;
|
||
|
} else if (old_end < hdr.max && buf[old_end] == 0x01) {
|
||
|
old_end++;
|
||
|
} else
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
/* If the existing space is too small, and we are at the end,
|
||
|
* and there's pading, then try to use the padding */
|
||
|
if ((old_end-old_begin) < new_length) {
|
||
|
if (old_end < hdr.max) {
|
||
|
if (buf[old_end] == 0x00) {
|
||
|
/* normalize padding to all zeroes */
|
||
|
safe_memset(buf, length, old_end, 0, hdr.max - old_end);
|
||
|
|
||
|
while ((old_end-old_begin) < new_length) {
|
||
|
if (old_end >= hdr.max)
|
||
|
break;
|
||
|
old_end++;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Make sure we have enough space in the header */
|
||
|
{
|
||
|
static const size_t max_tcp_hdr = (0xF0>>4) * 4; /* 60 */
|
||
|
size_t added = new_length - (old_end - old_begin);
|
||
|
if (hdr.max + added > hdr.begin + max_tcp_hdr) {
|
||
|
//unsigned total_padding = _calc_padding(buf, hdr);
|
||
|
old_begin = _squeeze_padding(buf, length, hdr, opt_kind);
|
||
|
old_end = hdr.max;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Now insert the option field into packet. This may change the
|
||
|
* sizeof the packet. The amount changed is indicated by 'adjust' */
|
||
|
adjust = _insert_field(&buf, &length,
|
||
|
old_begin, old_end,
|
||
|
new_field, new_length);
|
||
|
hdr.max += adjust;
|
||
|
}
|
||
|
|
||
|
if (adjust) {
|
||
|
|
||
|
/* TCP headers have to be aligned to 4 byte boundaries, so we may need
|
||
|
* to add padding of 0 at the end of the header to handle this */
|
||
|
if (adjust % 4 && adjust > 0) {
|
||
|
unsigned add_length = 4 - (adjust % 4);
|
||
|
_add_padding(&buf, &length, hdr.max, add_length);
|
||
|
hdr.max += add_length;
|
||
|
adjust += add_length;
|
||
|
} else if (adjust % 4 && adjust < 0) {
|
||
|
unsigned add_length = 0 - (adjust % 4);
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, hdr.max, "pad before");
|
||
|
_add_padding(&buf, &length, hdr.max, add_length);
|
||
|
hdr.max += add_length;
|
||
|
adjust += add_length;
|
||
|
|
||
|
//_HEXDUMP(buf, hdr, hdr.max, "pad after");
|
||
|
}
|
||
|
|
||
|
/* fix the IP and TCP length fields */
|
||
|
_adjust_length(buf, length, adjust, hdr);
|
||
|
|
||
|
/* In case we've padded the packet with four 0x00, get rid
|
||
|
* of them */
|
||
|
_normalize_padding(&buf, &length);
|
||
|
}
|
||
|
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
return true;
|
||
|
|
||
|
fail:
|
||
|
/* no changes were made */
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
***************************************************************************/
|
||
|
static unsigned
|
||
|
tcp_get_mss(const unsigned char *buf, size_t length, bool *is_found) {
|
||
|
struct tcp_opt_t opt;
|
||
|
unsigned result = 0;
|
||
|
|
||
|
opt = tcp_find_opt(buf, length, 2 /* MSS */);
|
||
|
if (is_found)
|
||
|
*is_found = opt.is_found;
|
||
|
if (!opt.is_found)
|
||
|
return 0xFFFFffff;
|
||
|
|
||
|
if (opt.length != 2) {
|
||
|
/* corrupt */
|
||
|
if (is_found)
|
||
|
*is_found = false;
|
||
|
return 0xFFFFffff;
|
||
|
}
|
||
|
|
||
|
result = opt.buf[0] << 8 | opt.buf[1];
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
***************************************************************************/
|
||
|
static unsigned
|
||
|
tcp_get_wscale(const unsigned char *buf, size_t length, bool *is_found) {
|
||
|
struct tcp_opt_t opt;
|
||
|
unsigned result = 0;
|
||
|
|
||
|
opt = tcp_find_opt(buf, length, 3 /* Wscale */);
|
||
|
if (is_found)
|
||
|
*is_found = opt.is_found;
|
||
|
if (!opt.is_found)
|
||
|
return 0xFFFFffff;
|
||
|
|
||
|
if (opt.length != 1) {
|
||
|
/* corrupt */
|
||
|
if (is_found)
|
||
|
*is_found = false;
|
||
|
return 0xFFFFffff;
|
||
|
}
|
||
|
|
||
|
result = opt.buf[0];
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
***************************************************************************/
|
||
|
static unsigned
|
||
|
tcp_get_sackperm(const unsigned char *buf, size_t length, bool *is_found) {
|
||
|
struct tcp_opt_t opt;
|
||
|
|
||
|
opt = tcp_find_opt(buf, length, 3 /* Wscale */);
|
||
|
if (is_found)
|
||
|
*is_found = opt.is_found;
|
||
|
if (!opt.is_found)
|
||
|
return 0xFFFFffff;
|
||
|
|
||
|
if (opt.length != 1) {
|
||
|
/* corrupt */
|
||
|
if (is_found)
|
||
|
*is_found = false;
|
||
|
return 0xFFFFffff;
|
||
|
}
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
* Called at the end of configuration, to change the TCP header template
|
||
|
* according to configuration. For example, we might add a "sackperm" field,
|
||
|
* or delete an "mss" field, or change the value of "mss".
|
||
|
***************************************************************************/
|
||
|
void
|
||
|
templ_tcp_apply_options(unsigned char **inout_buf, size_t *inout_length,
|
||
|
const struct TemplateOptions *templ_opts) {
|
||
|
unsigned char *buf = *inout_buf;
|
||
|
size_t length = *inout_length;
|
||
|
|
||
|
if (templ_opts == NULL)
|
||
|
return;
|
||
|
|
||
|
/* --tcp-mss <num>
|
||
|
* Sets maximum segment size */
|
||
|
if (templ_opts->tcp.is_mss == Remove) {
|
||
|
tcp_remove_opt(&buf, &length, 2 /* mss */);
|
||
|
} else if (templ_opts->tcp.is_mss == Add) {
|
||
|
unsigned char field[2];
|
||
|
field[0] = (unsigned char)(templ_opts->tcp.mss>>8);
|
||
|
field[1] = (unsigned char)(templ_opts->tcp.mss>>0);
|
||
|
tcp_add_opt(&buf, &length, 2, 2, field);
|
||
|
}
|
||
|
|
||
|
/* --tcp-sackok
|
||
|
* Sets option flag that permits selective acknowledgements */
|
||
|
if (templ_opts->tcp.is_sackok == Remove) {
|
||
|
tcp_remove_opt(&buf, &length, 4 /* sackok */);
|
||
|
} else if (templ_opts->tcp.is_sackok == Add) {
|
||
|
tcp_add_opt(&buf, &length, 4, 0, (const unsigned char*)"");
|
||
|
}
|
||
|
|
||
|
/* --tcp-wscale <num>
|
||
|
* Sets window scale option */
|
||
|
if (templ_opts->tcp.is_wscale == Remove) {
|
||
|
tcp_remove_opt(&buf, &length, 3 /* wscale */);
|
||
|
} else if (templ_opts->tcp.is_wscale == Add) {
|
||
|
unsigned char field[1];
|
||
|
field[0] = (unsigned char)templ_opts->tcp.wscale;
|
||
|
tcp_add_opt(&buf, &length, 3, 1, field);
|
||
|
}
|
||
|
|
||
|
/* --tcp-ts <num>
|
||
|
* Timestamp */
|
||
|
if (templ_opts->tcp.is_tsecho == Remove) {
|
||
|
tcp_remove_opt(&buf, &length, 8 /* ts */);
|
||
|
} else if (templ_opts->tcp.is_tsecho == Add) {
|
||
|
unsigned char field[10] = {0};
|
||
|
field[0] = (unsigned char)(templ_opts->tcp.tsecho>>24);
|
||
|
field[1] = (unsigned char)(templ_opts->tcp.tsecho>>16);
|
||
|
field[2] = (unsigned char)(templ_opts->tcp.tsecho>>8);
|
||
|
field[2] = (unsigned char)(templ_opts->tcp.tsecho>>0);
|
||
|
tcp_add_opt(&buf, &length, 8, 8, field);
|
||
|
}
|
||
|
|
||
|
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
* Used during selftests in order to create a known options field as the
|
||
|
* starting before before changing it somehow, followed by using
|
||
|
* _compare_options() to test whether the change succeeded.
|
||
|
***************************************************************************/
|
||
|
static bool
|
||
|
_replace_options(unsigned char **inout_buf, size_t *inout_length,
|
||
|
const char *new_options, size_t new_length) {
|
||
|
unsigned char *buf = *inout_buf;
|
||
|
size_t length = *inout_length;
|
||
|
struct tcp_hdr_t hdr;
|
||
|
size_t offset;
|
||
|
size_t old_length;
|
||
|
char newnew_options[40] = {0};
|
||
|
int adjust = 0;
|
||
|
|
||
|
/* Maximum length of the options field is 40 bytes */
|
||
|
if (new_length > 40)
|
||
|
goto fail;
|
||
|
|
||
|
/* Pad new options to 4 byte boundary */
|
||
|
memcpy(newnew_options, new_options, new_length);
|
||
|
while (new_length % 4)
|
||
|
new_length++;
|
||
|
|
||
|
/* find TCP header */
|
||
|
hdr = _find_tcp_header(buf, length);
|
||
|
if (!hdr.is_found)
|
||
|
goto fail;
|
||
|
|
||
|
/* Find start of options field */
|
||
|
offset = _opt_begin(hdr);
|
||
|
old_length = hdr.max - offset;
|
||
|
|
||
|
/* Either increase or decrease the old length appropriately */
|
||
|
//_HEXDUMPopt(buf, length, "resize before");
|
||
|
adjust = (int)(new_length - old_length);
|
||
|
if (adjust > 0) {
|
||
|
length += adjust;
|
||
|
buf = realloc(buf, length);
|
||
|
safe_memmove(buf, length,
|
||
|
hdr.max + adjust,
|
||
|
hdr.max,
|
||
|
(length - adjust) - hdr.max);
|
||
|
}
|
||
|
if (adjust < 0) {
|
||
|
safe_memmove( buf, length,
|
||
|
hdr.max + adjust,
|
||
|
hdr.max,
|
||
|
length - hdr.max);
|
||
|
length += adjust;
|
||
|
buf = realloc(buf, length);
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Now that we've resized the options field, overright
|
||
|
* it with then new field */
|
||
|
memcpy(buf + offset, newnew_options, new_length);
|
||
|
|
||
|
/* fix the IP and TCP length fields */
|
||
|
_adjust_length(buf, length, adjust, hdr);
|
||
|
|
||
|
//_HEXDUMPopt(buf, length, "resize after");
|
||
|
|
||
|
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
return true;
|
||
|
fail:
|
||
|
*inout_buf = buf;
|
||
|
*inout_length = length;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/***************************************************************************
|
||
|
***************************************************************************/
|
||
|
enum {
|
||
|
TST_NONE,
|
||
|
TST_PADDING,
|
||
|
TST_ADD,
|
||
|
TST_REMOVE,
|
||
|
};
|
||
|
|
||
|
/***************************************************************************
|
||
|
* This structure specifies test cases for the sefltest function. Each
|
||
|
* test has a pre-condition <options-list>, and option to add/remove, and
|
||
|
* a post-condition <options-list> that should match the result.
|
||
|
***************************************************************************/
|
||
|
struct mytests_t {
|
||
|
struct {
|
||
|
const char *options;
|
||
|
size_t length;
|
||
|
} pre;
|
||
|
struct {
|
||
|
int opcode;
|
||
|
const char *data;
|
||
|
size_t length;
|
||
|
} test;
|
||
|
struct {
|
||
|
const char *options;
|
||
|
size_t length;
|
||
|
} post;
|
||
|
};
|
||
|
|
||
|
/***************************************************************************
|
||
|
* The following tests add/remove options to a test packet. The goal of
|
||
|
* these tests is code-coverage of all the conditions above, testing
|
||
|
* all the boundary cases. Every code path that produces success is tested,
|
||
|
* plus many code paths that produce failures.
|
||
|
***************************************************************************/
|
||
|
static struct mytests_t
|
||
|
tests[] = {
|
||
|
/* A lot of these tests use 2-byte (\4\2) and 3-byte (\3\3\3) options.
|
||
|
* The "\4\2" is "SACK permitted, kind=4, len=2, with no extra data.
|
||
|
* The "\3\3\3" is "Window Scale, kind=3, len=3, data=3.
|
||
|
* The "\2\4\5\6" is "Max Segment Size", kind=2, len=4, data=0x0506
|
||
|
*/
|
||
|
|
||
|
/* Attempt removal of an option that doesn't exist. This is not
|
||
|
* a failure, but a success, though nothing is changed*/
|
||
|
|
||
|
{ {"\3\3\3\0", 4},
|
||
|
{TST_REMOVE, "\x08", 1},
|
||
|
{"\3\3\3\0", 4}
|
||
|
},
|
||
|
|
||
|
|
||
|
/* Test removal of an option. This will also involve removing
|
||
|
the now unnecessary padding */
|
||
|
{ {"\3\3\3\1\1\1\x08\x0a\x1d\xe9\xb2\x98\x00\x00\x00\x00", 16},
|
||
|
{TST_REMOVE, "\x08", 1},
|
||
|
{"\3\3\3\0", 4}
|
||
|
},
|
||
|
|
||
|
/* Test when trying to add a big option that won't fit unless we get
|
||
|
* rid of all the padding */
|
||
|
{ { "\x02\x04\x05\xb4"
|
||
|
"\x01\x03\x03\x06"
|
||
|
"\x01\x01\x08\x0a\x1d\xe9\xb2\x98\x00\x00\x00\x00"
|
||
|
"\x04\x02\x00\x00"
|
||
|
"\0\0\0\0",
|
||
|
28},
|
||
|
{ TST_ADD,
|
||
|
"\7\x14" "AAAAAAAAAAAAAAAAAAAA",
|
||
|
20
|
||
|
},
|
||
|
{ "\x02\x04\x05\xb4"
|
||
|
"\x03\x03\x06"
|
||
|
"\x08\x0a\x1d\xe9\xb2\x98\x00\x00\x00\x00"
|
||
|
"\x04\x02"
|
||
|
"\7\x14" "AAAAAAAAAAAAAAAAAA"
|
||
|
"\0",
|
||
|
40
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/* same as a bove, but field exists*/
|
||
|
{{ "\x02\x04\x05\xb4"
|
||
|
"\x01\x03\x03\x06"
|
||
|
"\x01\x01\x08\x0a\x1d\xe9\xb2\x98\x00\x00\x00\x00"
|
||
|
"\7\4\1\1"
|
||
|
"\x04\x02\x00\x00",
|
||
|
28},
|
||
|
{ TST_ADD,
|
||
|
"\7\x14" "AAAAAAAAAAAAAAAAAAAA",
|
||
|
20
|
||
|
},
|
||
|
{ "\x02\x04\x05\xb4"
|
||
|
"\x03\x03\x06"
|
||
|
"\x08\x0a\x1d\xe9\xb2\x98\x00\x00\x00\x00"
|
||
|
"\x04\x02"
|
||
|
"\7\x14" "AAAAAAAAAAAAAAAAAA"
|
||
|
"\0",
|
||
|
40
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/* Add a new value to full packet */
|
||
|
{{"\3\3\3", 3}, {TST_ADD, "\4\2", 2}, {"\3\3\3\4\2\0\0\0", 8}},
|
||
|
|
||
|
/* Change a 3 byte to 5 byte in middle of packet */
|
||
|
{{"\1\7\3\3\1\1\4\2", 8}, {TST_ADD, "\7\5\5\5\5", 5}, {"\7\5\5\5\5\1\4\2", 8}},
|
||
|
|
||
|
/* Change 3 to 4 byte at start */
|
||
|
{{"\7\3\3\1\2\4\5\6", 8}, {TST_ADD, "\7\4\4\4", 4}, {"\7\4\4\4\2\4\5\6", 8}},
|
||
|
|
||
|
/* Change a 2-byte option */
|
||
|
{{"\4\2", 2}, {TST_ADD, "\4\2", 2}, {"\4\2\0\0", 4}},
|
||
|
|
||
|
/* Change a 3-byte option */
|
||
|
{{"\3\3\2", 3}, {TST_ADD, "\3\3\3", 3}, {"\3\3\3\0", 4}},
|
||
|
|
||
|
/* Change a 4-byte option */
|
||
|
{{"\2\4\1\1", 4}, {TST_ADD, "\2\4\5\6", 4}, {"\2\4\5\6", 4}},
|
||
|
|
||
|
/* Add a 2-byte option to empty packet*/
|
||
|
{{"", 0}, {TST_ADD, "\4\2", 2}, {"\4\2\0\0", 4}},
|
||
|
|
||
|
/* Add a 3-byte option to empty packet*/
|
||
|
{{"", 0}, {TST_ADD, "\3\3\3", 3}, {"\3\3\3\0", 4}},
|
||
|
|
||
|
/* Add a 4-byte option to empty packet*/
|
||
|
{{"", 0}, {TST_ADD, "\2\4\5\6", 4}, {"\2\4\5\6", 4}},
|
||
|
|
||
|
/* Empty packet: padding normalization should make no changes */
|
||
|
{{"", 0}, {TST_PADDING,0,0}, {"", 0}},
|
||
|
|
||
|
/* Empty packet plus 4 bytes of padding, should be removed */
|
||
|
{{"\0", 1}, {TST_PADDING,0,0}, {"", 0}},
|
||
|
|
||
|
/* 8 bytes of padding, should only remove all of them */
|
||
|
{{"\0\0\0\0\0\0\0\0", 8}, {TST_PADDING,0,0}, {"", 0}},
|
||
|
|
||
|
/* some padding is nops, should remove all */
|
||
|
{{"\1\1\0\0\0\0\0\0", 8}, {TST_PADDING,0,0}, {"", 0}},
|
||
|
|
||
|
/* any trailing NOPs should be converted to EOLs */
|
||
|
{{"\3\3\3\1\0\0\0\0", 8}, {TST_PADDING,0,0}, {"\3\3\3\0", 4}},
|
||
|
|
||
|
/* only NOPs should still be removed */
|
||
|
{{"\3\3\3\1\1\1\1\1", 8}, {TST_PADDING,0,0}, {"\3\3\3\0", 4}},
|
||
|
|
||
|
{{0}}
|
||
|
};
|
||
|
|
||
|
|
||
|
/***************************************************************************
|
||
|
* This function runs through the tests in the [tests] array above. It
|
||
|
* first creates a packet accoding to a pre-condition that may have
|
||
|
* options already. We then call a function to manipulate the packet,
|
||
|
* such as adding/changing an option. We then verify that that the
|
||
|
* <option-list> field now matches the post-condition. Along the way,
|
||
|
* we look for any errors are consistency failures.
|
||
|
***************************************************************************/
|
||
|
static int
|
||
|
_selftests_run(void) {
|
||
|
static unsigned char templ[] =
|
||
|
"\0\1\2\3\4\5" /* Ethernet: destination */
|
||
|
"\6\7\x8\x9\xa\xb" /* Ethernet: source */
|
||
|
"\x08\x00" /* Ethernet type: IPv4 */
|
||
|
"\x45" /* IP type */
|
||
|
"\x00"
|
||
|
"\x00\x48" /* total length = 64 bytes */
|
||
|
"\x00\x00" /* identification */
|
||
|
"\x00\x00" /* fragmentation flags */
|
||
|
"\xFF\x06" /* TTL=255, proto=TCP */
|
||
|
"\xFF\xFF" /* checksum */
|
||
|
"\0\0\0\0" /* source address */
|
||
|
"\0\0\0\0" /* destination address */
|
||
|
|
||
|
"\0\0" /* source port */
|
||
|
"\0\0" /* destination port */
|
||
|
"\0\0\0\0" /* sequence number */
|
||
|
"\0\0\0\0" /* ACK number */
|
||
|
"\xB0" /* header length */
|
||
|
"\x02" /* SYN */
|
||
|
"\x04\x01" /* window fixed to 1024 */
|
||
|
"\xFF\xFF" /* checksum */
|
||
|
"\x00\x00" /* urgent pointer */
|
||
|
|
||
|
"\x02\x04\x05\xb4"
|
||
|
"\x01\x03\x03\x06"
|
||
|
"\x01\x01\x08\x0a\x1d\xe9\xb2\x98\x00\x00\x00\x00"
|
||
|
"\x04\x02\x00\x00"
|
||
|
"DeadBeef"
|
||
|
;
|
||
|
size_t i;
|
||
|
|
||
|
/* execute all tests */
|
||
|
for (i=0; tests[i].pre.options; i++) {
|
||
|
unsigned char *buf;
|
||
|
size_t length = sizeof(templ) - 1;
|
||
|
bool success;
|
||
|
struct tcp_hdr_t hdr;
|
||
|
const unsigned char *field;
|
||
|
size_t field_length;
|
||
|
|
||
|
|
||
|
LOG(1, "[+] templ-tcp-hdr: run #%u\n", (unsigned)i);
|
||
|
|
||
|
/* Each tests creates its own copy of the test packet, which it
|
||
|
* will then alter according to the pre-conditions. */
|
||
|
buf = malloc(length);
|
||
|
memcpy(buf, templ, length);
|
||
|
|
||
|
/* Set the pre-condition <option-list> field by replacing what
|
||
|
* was there with a completely new field */
|
||
|
success = _replace_options(&buf, &length,
|
||
|
tests[i].pre.options, tests[i].pre.length);
|
||
|
if (!success)
|
||
|
goto fail; /* this should never happen */
|
||
|
if (_consistancy_check(buf, length, "DeadBeef", 8))
|
||
|
goto fail; /* this shoiuld never happen*/
|
||
|
|
||
|
|
||
|
//_HEXDUMPopt(buf, length, "[PRE]");
|
||
|
|
||
|
/*
|
||
|
* Run the desired test
|
||
|
*/
|
||
|
switch (tests[i].test.opcode) {
|
||
|
case TST_PADDING:
|
||
|
/* We are testing the "normalize padding" function. This
|
||
|
* is called after ever 'add' or 'remove' to make sure that
|
||
|
* the padding at the end is consistent. Mostly, it means
|
||
|
* that when we remove a field, we'll probably have excess
|
||
|
* padding at the end, which needs to be trimmed to the
|
||
|
* minimum amount of padding */
|
||
|
success = _normalize_padding(&buf, &length);
|
||
|
if (!success)
|
||
|
goto fail;
|
||
|
break;
|
||
|
case TST_ADD:
|
||
|
/* We are testing `tcp_add_opt()` function, which is called
|
||
|
* to either 'add' or 'change' an existing option. */
|
||
|
field = (const unsigned char*)tests[i].test.data;
|
||
|
field_length = tests[i].test.length;
|
||
|
if (field_length < 2)
|
||
|
goto fail;
|
||
|
else {
|
||
|
unsigned opt_kind = field[0];
|
||
|
unsigned opt_length = field[1];
|
||
|
const unsigned char *opt_data = field + 2;
|
||
|
|
||
|
if (field_length != opt_length)
|
||
|
goto fail;
|
||
|
|
||
|
/* skip the KIND and LENGTH fields, justa DATA length */
|
||
|
opt_length -= 2;
|
||
|
|
||
|
success = tcp_add_opt(&buf, &length,
|
||
|
opt_kind,
|
||
|
opt_length,
|
||
|
opt_data);
|
||
|
if (!success)
|
||
|
goto fail;
|
||
|
}
|
||
|
break;
|
||
|
case TST_REMOVE:
|
||
|
/* We are testing `tcp_add_opt()` function, which is called
|
||
|
* to either 'add' or 'change' an existing option. */
|
||
|
field = (const unsigned char*)tests[i].test.data;
|
||
|
field_length = tests[i].test.length;
|
||
|
if (field_length != 1)
|
||
|
goto fail;
|
||
|
else {
|
||
|
unsigned opt_kind = field[0];
|
||
|
|
||
|
success = tcp_remove_opt(&buf, &length,
|
||
|
opt_kind);
|
||
|
|
||
|
if (!success)
|
||
|
goto fail;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
return 1; /* fail */
|
||
|
}
|
||
|
|
||
|
//_HEXDUMPopt(buf, length, "[POST]");
|
||
|
|
||
|
if (_consistancy_check(buf, length, "DeadBeef", 8))
|
||
|
goto fail;
|
||
|
|
||
|
/*
|
||
|
* Make sure output matches expected results
|
||
|
*/
|
||
|
{
|
||
|
size_t offset;
|
||
|
int err;
|
||
|
size_t post_length;
|
||
|
|
||
|
/* Find the <options-list> field */
|
||
|
hdr = _find_tcp_header(buf, length);
|
||
|
if (!hdr.is_found)
|
||
|
goto fail;
|
||
|
offset = _opt_begin(hdr);
|
||
|
|
||
|
/* Make sure the length matches the expected length */
|
||
|
post_length = hdr.max - offset;
|
||
|
if (tests[i].post.length != post_length)
|
||
|
goto fail;
|
||
|
|
||
|
/* makre sure the contents of the field match expected */
|
||
|
err = memcmp(tests[i].post.options, buf+offset, (hdr.max-offset));
|
||
|
if (err) {
|
||
|
_HEXDUMPopt(buf, length, "[-] failed expectations");
|
||
|
goto fail;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
free(buf);
|
||
|
}
|
||
|
|
||
|
return 0; /* success */
|
||
|
fail:
|
||
|
fprintf(stderr, "[-] templ.tcp.selftest failed, test #%u\n",
|
||
|
(unsigned)i);
|
||
|
return 1;
|
||
|
};
|
||
|
|
||
|
/***************************************************************************
|
||
|
* These self-tests manipulate a TCP header, adding and removing <option>
|
||
|
* fields in various scenarios. We expose the `tcp_add_option()` function
|
||
|
* to the end-user via the command-line, so we have to anticipate that
|
||
|
* the option they want added is going to be corrupt. For example, the
|
||
|
* end-user might try to add an option that overflows the <option-list>
|
||
|
* field, which is rather small (only 40 bytes long).
|
||
|
***************************************************************************/
|
||
|
int
|
||
|
templ_tcp_selftest(void) {
|
||
|
|
||
|
static unsigned char templ[] =
|
||
|
"\0\1\2\3\4\5" /* Ethernet: destination */
|
||
|
"\6\7\x8\x9\xa\xb" /* Ethernet: source */
|
||
|
"\x08\x00" /* Ethernet type: IPv4 */
|
||
|
"\x45" /* IP type */
|
||
|
"\x00"
|
||
|
"\x00\x48" /* total length = 64 bytes */
|
||
|
"\x00\x00" /* identification */
|
||
|
"\x00\x00" /* fragmentation flags */
|
||
|
"\xFF\x06" /* TTL=255, proto=TCP */
|
||
|
"\xFF\xFF" /* checksum */
|
||
|
"\0\0\0\0" /* source address */
|
||
|
"\0\0\0\0" /* destination address */
|
||
|
|
||
|
"\0\0" /* source port */
|
||
|
"\0\0" /* destination port */
|
||
|
"\0\0\0\0" /* sequence number */
|
||
|
"\0\0\0\0" /* ACK number */
|
||
|
"\xB0" /* header length */
|
||
|
"\x02" /* SYN */
|
||
|
"\x04\x01" /* window fixed to 1024 */
|
||
|
"\xFF\xFF" /* checksum */
|
||
|
"\x00\x00" /* urgent pointer */
|
||
|
|
||
|
"\x02\x04\x05\xb4"
|
||
|
"\x01\x03\x03\x06"
|
||
|
"\x01\x01\x08\x0a\x1d\xe9\xb2\x98\x00\x00\x00\x00"
|
||
|
"\x04\x02\x00\x00"
|
||
|
"DeadBeef"
|
||
|
;
|
||
|
size_t length = sizeof(templ) - 1;
|
||
|
unsigned char *buf;
|
||
|
|
||
|
/* Execute planned selftests */
|
||
|
if (_selftests_run())
|
||
|
return 1;
|
||
|
|
||
|
/* We need to make an allocated copy of the buffer, because the
|
||
|
* size may change from `realloc()` */
|
||
|
buf = malloc(length);
|
||
|
memcpy(buf, templ, length);
|
||
|
|
||
|
/*
|
||
|
* Make sure we start wtih an un-corrupted test packet
|
||
|
*/
|
||
|
if (_consistancy_check(buf, length, "DeadBeef", 8))
|
||
|
goto fail;
|
||
|
|
||
|
if (1460 != tcp_get_mss(buf, length, 0))
|
||
|
goto fail;
|
||
|
if (6 != tcp_get_wscale(buf, length, 0))
|
||
|
goto fail;
|
||
|
if (0 != tcp_get_sackperm(buf, length, 0))
|
||
|
goto fail;
|
||
|
|
||
|
tcp_add_opt(&buf, &length, 2, 2, (const unsigned char*)"\x12\x34");
|
||
|
if (0x1234 != tcp_get_mss(buf, length, 0))
|
||
|
goto fail;
|
||
|
if (_consistancy_check(buf, length, "DeadBeef", 8))
|
||
|
goto fail;
|
||
|
|
||
|
tcp_remove_opt(&buf, &length, 3);
|
||
|
if (0x1234 != tcp_get_mss(buf, length, 0))
|
||
|
goto fail;
|
||
|
if (0xFFFFffff != tcp_get_wscale(buf, length, 0))
|
||
|
goto fail;
|
||
|
if (_consistancy_check(buf, length, "DeadBeef", 8))
|
||
|
goto fail;
|
||
|
|
||
|
|
||
|
free(buf);
|
||
|
return 0; /* success */
|
||
|
fail:
|
||
|
free(buf);
|
||
|
return 1; /* failure */
|
||
|
}
|