diff options
| author | Mosder <xmosder12@gmail.com> | 2026-07-10 22:50:12 +0200 |
|---|---|---|
| committer | Mosder <xmosder12@gmail.com> | 2026-07-10 22:50:12 +0200 |
| commit | 0e9be0d902b44894ed747cabb82ea2120f2e4b45 (patch) | |
| tree | 8bbf583d44e135c1f5ccdcbfe113b855be41386a | |
| parent | 4422f92de05948187eeb0b0067dd947e673f8f1d (diff) | |
feat: added functionallity for TCP stream reassembly
| -rw-r--r-- | Makefile | 9 | ||||
| -rw-r--r-- | analyzer/analyzer.c | 45 | ||||
| -rw-r--r-- | analyzer/parser/fight_results.c | 10 | ||||
| -rw-r--r-- | analyzer/parser/fight_results.h | 9 | ||||
| -rw-r--r-- | analyzer/parser/parser.c | 19 | ||||
| -rw-r--r-- | analyzer/parser/parser.h | 6 | ||||
| -rw-r--r-- | analyzer/utils/pcap.c | 54 | ||||
| -rw-r--r-- | analyzer/utils/pcap.h | 56 | ||||
| -rw-r--r-- | analyzer/utils/structures/heap.h | 3 | ||||
| -rw-r--r-- | analyzer/utils/tcp.c | 172 | ||||
| -rw-r--r-- | analyzer/utils/tcp.h | 68 | ||||
| -rw-r--r-- | analyzer/utils/tcp_seq_math.c | 41 | ||||
| -rw-r--r-- | analyzer/utils/tcp_seq_math.h | 28 |
13 files changed, 375 insertions, 145 deletions
@@ -8,10 +8,15 @@ compile: utils capture analyzer parser gcc *.o main.c -lpcap -o branal rm *.o -utils: +utils: structures gcc -c analyzer/utils/fs.c gcc -c analyzer/utils/time.c - gcc -c analyzer/utils/pcap.c + gcc -c analyzer/utils/tcp.c + gcc -c analyzer/utils/tcp_seq_math.c + +structures: + gcc -c analyzer/utils/structures/hashmap.c + gcc -c analyzer/utils/structures/heap.c capture: gcc -c analyzer/capture.c diff --git a/analyzer/analyzer.c b/analyzer/analyzer.c index 7db377e..2e5caf8 100644 --- a/analyzer/analyzer.c +++ b/analyzer/analyzer.c @@ -1,7 +1,32 @@ #include "analyzer.h" #include "parser/parser.h" -#include "utils/pcap.h" +#include "utils/tcp.h" + +// loop through packets received from pcap_handle and analyze the TCP stream +void analyzer_loop(TCPStream *stream, pcap_t *pcap_handle) { + // read packets from handle + struct pcap_pkthdr *packet_data; + const byte_t *packet; + while (pcap_next_ex(pcap_handle, &packet_data, &packet) == 1) { + // get new segment + TCPSegment segment = get_segment_from_packet(packet, packet_data); + + // handle the new segment + if (handle_segment(stream, segment)) { + // if it was added to the stream - parse it + size_t parsed_len; + size_t n_parsed_data; + ParsedData *parsed_data = parse_stream(stream, &parsed_len, &n_parsed_data); + + // if some bytes were parsed - remove them from the stream + if (parsed_len > 0) + remove_parsed_data(stream, parsed_len); + + // TODO: handle parsed data + } + } +} void analyze_file(char *path) { // open the file @@ -14,19 +39,9 @@ void analyze_file(char *path) { return; } - struct pcap_pkthdr *packet_data; - const byte_t *packet; - const byte_t *payload; - - // read packets from the file - while (pcap_next_ex(handle, &packet_data, &packet) == 1) { - int payload_len; - uint32_t tcp_seq; - payload = get_payload_from_packet(packet, packet_data, &payload_len, &tcp_seq); - // TODO: Stream reassembly using TCP sequence number - int parsed_len; - parse_stream((char *)payload, payload_len, &parsed_len); - } - + // analyze the file + TCPStream stream = new_stream(); + analyzer_loop(&stream, handle); + destroy_stream(stream); pcap_close(handle); } diff --git a/analyzer/parser/fight_results.c b/analyzer/parser/fight_results.c index c6ad7cc..1db79ea 100644 --- a/analyzer/parser/fight_results.c +++ b/analyzer/parser/fight_results.c @@ -3,16 +3,16 @@ #include <stdio.h> #include <string.h> -char *find_fight_results(char *stream, int stream_len, int *length) { - char *start = memmem(stream, stream_len, FIGHT_RESULTS_START, strlen(FIGHT_RESULTS_START)); - char *end = memmem(stream, stream_len, FIGHT_RESULTS_END, strlen(FIGHT_RESULTS_END)); +byte_t *find_fight_results(TCPStream *stream, int *length) { + byte_t *start = memmem(stream->data, stream->len, FIGHT_RESULTS_START, strlen(FIGHT_RESULTS_START)); + byte_t *end = memmem(stream->data, stream->len, FIGHT_RESULTS_END, strlen(FIGHT_RESULTS_END)); *length = start != NULL && end != NULL ? end - start + strlen(FIGHT_RESULTS_END) : -1; return start; } -FightResults parse_fight_results(char *data, int length) { +FightResults parse_fight_results(byte_t *data, size_t length) { // TODO: actually parse that data - printf("len=%d\n%.*s\n----------------\n", length, length, data); + printf("len=%zu\n%.*s\n----------------\n", length, (int)length, data); FightResults tmp; return tmp; } diff --git a/analyzer/parser/fight_results.h b/analyzer/parser/fight_results.h index 270aba8..b275f2b 100644 --- a/analyzer/parser/fight_results.h +++ b/analyzer/parser/fight_results.h @@ -1,6 +1,8 @@ // parser/fight_results.h // utilities for parsing the fight results data +#include "../utils/tcp.h" + // Payload structure: // FIGHT_RESULTS_START EntityResults (SEPARATOR_ENTITY EntityResults)* FIGHT_RESULTS_END // @@ -87,11 +89,10 @@ typedef struct { // find the first fight results data in stream // params: // - stream - stream to find the fight results in -// - stream_len - length of the stream // - length - length of the fight results data or -1 if there's no end yet // returns: -// pointer to the beggining of fight results data or NULL if there's none -extern char *find_fight_results(char *stream, int stream_len, int *length); +// pointer to the beginning of fight results data or NULL if there's none +extern byte_t *find_fight_results(TCPStream *stream, int *length); // parse given fight results // params: @@ -99,4 +100,4 @@ extern char *find_fight_results(char *stream, int stream_len, int *length); // - length - length of fight results data // returns: // FightResults struct containing parsed data -extern FightResults parse_fight_results(char *data, int length); +extern FightResults parse_fight_results(byte_t *data, size_t length); diff --git a/analyzer/parser/parser.c b/analyzer/parser/parser.c index b551d8e..7fb155f 100644 --- a/analyzer/parser/parser.c +++ b/analyzer/parser/parser.c @@ -2,14 +2,19 @@ #include <stdlib.h> -ParsedData *parse_stream(char *stream, int stream_len, int *parsed_len) { +ParsedData *parse_stream(TCPStream *stream, size_t *parsed_len, size_t *n_parsed_data) { // TODO: Need to do parsing of the entire thing (loop) and return all of the parsed data - if (stream_len > 0) { - int length; - char *start = find_fight_results(stream, stream_len, &length); - if (start != NULL) { - parse_fight_results(start, length); - } + int length; + byte_t *start = find_fight_results(stream, &length); + if (start == NULL) { + *parsed_len = stream->len; + } + else if (length == -1) { + *parsed_len = 0; + } + else { + parse_fight_results(start, length); + *parsed_len = start - stream->data + length; } return NULL; } diff --git a/analyzer/parser/parser.h b/analyzer/parser/parser.h index 18b4b83..088bb5d 100644 --- a/analyzer/parser/parser.h +++ b/analyzer/parser/parser.h @@ -3,6 +3,8 @@ #include "fight_results.h" +#include "../utils/tcp.h" + // "generic" type for parsed data typedef struct { enum { FIGHT_RESULTS } data_type; @@ -14,8 +16,8 @@ typedef struct { // parse a stream of TCP data // params: // - stream - the stream to parse -// - stream_len - length of the stream to parse // - parsed_len - pointer to inform how much of the stream was parsed (not all if e.g. start of data is there, but the end isn't yet) +// - n_parsed_data - pointer to the count of parsed data that was retrieved from the stream // returns: // pointer to the array of parsed data -extern ParsedData *parse_stream(char *stream, int stream_len, int *parsed_len); +extern ParsedData *parse_stream(TCPStream *stream, size_t *parsed_len, size_t *n_parsed_data); diff --git a/analyzer/utils/pcap.c b/analyzer/utils/pcap.c deleted file mode 100644 index d38cbff..0000000 --- a/analyzer/utils/pcap.c +++ /dev/null @@ -1,54 +0,0 @@ -#include "pcap.h" - -#include <netinet/if_ether.h> -#include <netinet/ip.h> -#include <netinet/tcp.h> - -const byte_t *get_payload_from_packet(const byte_t *packet, const struct pcap_pkthdr *packet_data, int *payload_len, uint32_t *tcp_seq) { - // get Ethernet header - const struct ether_header *eth_header = (const struct ether_header *)packet; - int eth_header_len = sizeof(struct ether_header); - - // check if it's IP - if (ntohs(eth_header->ether_type) != ETHERTYPE_IP) { - *payload_len = -1; - return NULL; - } - - // get IP header - const struct ip *ip_header = (const struct ip *)(packet + eth_header_len); - int ip_header_len = ip_header->ip_hl * 4; - - // check if it's TCP - if (ip_header->ip_p != IPPROTO_TCP) { - *payload_len = -1; - return NULL; - } - - // get TCP header - const struct tcphdr *tcp_header = (const struct tcphdr *)(packet + eth_header_len + ip_header_len); - int tcp_header_len = tcp_header->th_off * 4; - - // get TCP sequence number - *tcp_seq = ntohl(tcp_header->seq); - - // get payload - int total_header_len = eth_header_len + ip_header_len + tcp_header_len; - *payload_len = packet_data->caplen - total_header_len; - return packet + total_header_len; -} - -PendingPayload new_pending(const byte_t *payload, int payload_len) { - PendingPayload tmp; - return tmp; -} - -byte_t *free_parsed_data(byte_t *stream, uint32_t stream_seq, int stream_len, int parsed_len) { - // TODO: impement - return NULL; -} - -int add_segment_to_stream(byte_t *stream, uint32_t stream_seq, int stream_len, const byte_t *payload, uint32_t payload_seq, int payload_len, HashMap *pending) { - // TODO: impement - return 0; -} diff --git a/analyzer/utils/pcap.h b/analyzer/utils/pcap.h deleted file mode 100644 index 10f01f1..0000000 --- a/analyzer/utils/pcap.h +++ /dev/null @@ -1,56 +0,0 @@ -// utils/pcap.h -// helper functions for usage of libpcap and for handling TCP segments in general - -#include <pcap.h> - -#include "hashmap.h" - -typedef unsigned char byte_t; - -// get payload from a TCP/IP packet -// params: -// - packet - captured packet -// - packet_data - metadata about packet -// - payload_len - pointer to save the payload length to or -1 if not a correct packet -// - tcp_seq - pointer to save the TCP sequence number -// returns: -// pointer to the payload or NULL if not a correct packet -extern const byte_t *get_payload_from_packet(const byte_t *packet, const struct pcap_pkthdr *packet_data, int *payload_len, uint32_t *tcp_seq); - -// struct to persistently store pending payloads -typedef struct { - byte_t *payload; - int payload_len; -} PendingPayload; - -// store persistently and return a PendingPayload, the payload is malloced and needs to be freed -// params: -// - payload - payload to store -// - payload_len - length of the payload to store -// returns: -// PendingPayload struct with stored payload -extern PendingPayload new_pending(const byte_t *payload, int payload_len); - -// remove parsed part of the stream from memory and return only unparsed part -// params: -// - stream - stream to free a part of -// - stream_seq - pointer to the current TCP sequence number at the beginning of the stream, updated to the new one in the function -// - stream_len - pointer to the current stream length, updated to the new length in the function -// - parsed_len - count of n first bytes of the stream that were parsed -// returns: -// pointer to the new, reduced stream -extern byte_t *free_parsed_data(byte_t *stream, uint32_t stream_seq, int stream_len, int parsed_len); - -// add TCP segment to stream -// params: -// - stream - current stream -// - stream_seq - TCP sequence number at the beginning of the stream -// - stream_len - current length of the stream -// - payload - payload to add to stream -// - payload_seq - payload TCP sequence number -// - payload_len - length of the payload -// - pending - hashmap (seq -> PendingPayload) of pending payloads to be added to stream -// returns: -// 1 if the payload was added to the stream, 0 if it was discarded or saved to pending -extern int -add_segment_to_stream(byte_t *stream, uint32_t stream_seq, int stream_len, const byte_t *payload, uint32_t payload_seq, int payload_len, HashMap *pending); diff --git a/analyzer/utils/structures/heap.h b/analyzer/utils/structures/heap.h index e960fb7..eb3430b 100644 --- a/analyzer/utils/structures/heap.h +++ b/analyzer/utils/structures/heap.h @@ -6,6 +6,8 @@ #define INIT_HEAP_DEPTH 7 #define INIT_HEAP_CAPACITY (1 << INIT_HEAP_DEPTH) - 1 +#ifndef HEAP_DEFINED +#define HEAP_DEFINED typedef struct { void **data; size_t n_elements; @@ -14,6 +16,7 @@ typedef struct { int (*compare)(void *element1, void *element2); void (*element_cleanup)(void *element); } Heap; +#endif // create a new heap // params: diff --git a/analyzer/utils/tcp.c b/analyzer/utils/tcp.c new file mode 100644 index 0000000..56df842 --- /dev/null +++ b/analyzer/utils/tcp.c @@ -0,0 +1,172 @@ +#include "tcp.h" + +#include <netinet/if_ether.h> +#include <netinet/ip.h> +#include <netinet/tcp.h> +#include <stdlib.h> +#include <string.h> + +#include "tcp_seq_math.h" + +// initalize default TCP segment +TCPSegment new_segment() { + TCPSegment segment; + segment.payload = NULL; + segment.len = -1; + segment.seq = 0; + return segment; +} + +// segments compare function to use with heap +int segments_compare(void *segment1, void *segment2) { + TCPSegment *seg1 = segment1; + TCPSegment *seg2 = segment2; + return seq_before(seg1->seq, seg2->seq); +} + +// segment cleanup function to use with heap +void segment_cleanup(void *segment) { + TCPSegment *seg = segment; + free(seg->payload); +} + +TCPSegment get_segment_from_packet(const byte_t *packet, const struct pcap_pkthdr *packet_data) { + TCPSegment segment = new_segment(); + + // get Ethernet header + const struct ether_header *eth_header = (const struct ether_header *)packet; + int eth_header_len = sizeof(struct ether_header); + + // check if it's IP + if (ntohs(eth_header->ether_type) != ETHERTYPE_IP) + return segment; + + // get IP header + const struct ip *ip_header = (const struct ip *)(packet + eth_header_len); + int ip_header_len = ip_header->ip_hl * 4; + + // check if it's TCP + if (ip_header->ip_p != IPPROTO_TCP) + return segment; + + // get TCP header + const struct tcphdr *tcp_header = (const struct tcphdr *)(packet + eth_header_len + ip_header_len); + int tcp_header_len = tcp_header->th_off * 4; + + // get TCP sequence number + segment.seq = ntohl(tcp_header->seq); + + // get payload + int total_header_len = eth_header_len + ip_header_len + tcp_header_len; + segment.len = packet_data->caplen - total_header_len; + segment.payload = malloc(segment.len); + memcpy(segment.payload, packet + total_header_len, segment.len); + return segment; +} + +TCPStream new_stream() { + TCPStream stream; + stream.data = NULL; // NULL to differentiate between first and other segments + stream.len = 0; + stream.capacity = INIT_STREAM_CAPACITY; + stream.seq = 0; + stream.pending = heap_new(sizeof(TCPSegment), segments_compare, segment_cleanup); + return stream; +} + +void destroy_stream(TCPStream stream) { + free(stream.data); + heap_free(stream.pending); +} + +void remove_parsed_data(TCPStream *stream, size_t parsed_len) { + memmove(stream->data, stream->data + parsed_len, stream->len - parsed_len); + stream->len -= parsed_len; + stream->seq += parsed_len; +} + +// get the index of the first relevant byte in segment (its tcp sequence number is the one stream is expecting) +// return the index of the first relevant byte in payload or -1 if segment isn't directly connected to stream +int get_first_relevant_byte_index(TCPStream stream, TCPSegment segment) { + // expected tcp sequence numbers for stream and segment + uint32_t stream_expected = stream.seq + stream.len; + uint32_t segment_expected = segment.seq + segment.len; + if (seq_in_range_ie(stream_expected, segment.seq, segment_expected)) + return stream_expected - segment.seq; + return -1; +} + +// check if the segment is in acceptable range to the stream +int in_acceptable_range(TCPStream stream, TCPSegment segment) { + // expected tcp sequence number for stream and max acceptable sequence number for segment + uint32_t stream_expected = stream.seq + stream.len; + uint32_t max_acceptable_seq = stream_expected + MAX_SEQ_OFFSET; + return seq_in_range_ei(segment.seq, stream_expected, max_acceptable_seq); +} + +// expand stream capacity exponentially to fit new_len +void expand_stream_capacity(TCPStream *stream, size_t new_len) { + while (stream->capacity < new_len) + stream->capacity <<= 1; + stream->data = realloc(stream->data, stream->capacity); +} + +// add segment directly to stream +void add_segment(TCPStream *stream, TCPSegment segment, size_t data_start) { + size_t new_stream_len = stream->len + segment.len - data_start; + if (new_stream_len > stream->capacity) + expand_stream_capacity(stream, new_stream_len); + memcpy(stream->data + stream->len, segment.payload + data_start, segment.len - data_start); + stream->len = new_stream_len; +} + +// add pending segments to the stream +void add_pending_segments(TCPStream *stream) { + int top_changed = 1; + TCPSegment *top; + while (top_changed && (top = heap_top_get(stream->pending))) { + top_changed = 0; + + // add pending segment it it became directly connected + int data_start = get_first_relevant_byte_index(*stream, *top); + if (data_start != -1) { + add_segment(stream, *top, data_start); + top_changed = 1; + } + + // remove top if it was just added or no longer is in acceptable range (e.g. was contained within another one pending) + if (top_changed || !in_acceptable_range(*stream, *top)) { + heap_top_remove(stream->pending); + } + } +} + +int handle_segment(TCPStream *stream, TCPSegment segment) { + // add the segment if it's the first one + if (stream->data == NULL) { + stream->data = malloc(stream->capacity); + stream->seq = segment.seq; + add_segment(stream, segment, 0); + free(segment.payload); + return 1; + } + + // add segment directly to the stream if it's directly connected + int data_start = get_first_relevant_byte_index(*stream, segment); + if (data_start != -1) { + add_segment(stream, segment, data_start); + free(segment.payload); + add_pending_segments(stream); + return 1; + } + + // add to pending if segment is in acceptable range: + if (in_acceptable_range(*stream, segment)) { + heap_insert(stream->pending, &segment); + return 0; + } + + // if neither, discard entirely + free(segment.payload); + return 0; +} diff --git a/analyzer/utils/tcp.h b/analyzer/utils/tcp.h new file mode 100644 index 0000000..37ab4bc --- /dev/null +++ b/analyzer/utils/tcp.h @@ -0,0 +1,68 @@ +// utils/tcp.h +// functions for handling TCP streams/segments + +#include <pcap.h> + +#include "structures/heap.h" + +typedef unsigned char byte_t; + +// struct to persistently store TCP segments +// payload field is malloced and needs to be freed +#ifndef TCP_SEGMENT_DEFINED +#define TCP_SEGMENT_DEFINED +typedef struct { + byte_t *payload; + size_t len; + uint32_t seq; +} TCPSegment; +#endif + +// get TCP segment from captured packet +// params: +// - packet - captured packet +// - packet_data - metadata about packet +// returns: +// TCPSegment from packet, payload is NULL and len is -1 if the received packet was incorrect +extern TCPSegment get_segment_from_packet(const byte_t *packet, const struct pcap_pkthdr *packet_data); + +// struct that stores all the information about stream +#ifndef TCP_STREAM_DEFINED +#define TCP_STREAM_DEFINED +typedef struct { + byte_t *data; // bytes in the stream + size_t len; // length of the stream + size_t capacity; // capacity of the data buffer + uint32_t seq; // TCP sequence number of the first byte in the stream + Heap *pending; // min heap of segments yet to be added to stream (sorted by TCP sequence number) +} TCPStream; +#endif + +#define INIT_STREAM_CAPACITY 256 + +// initialize new stream +// returns: +// newly created TCPStream structure with initial values +extern TCPStream new_stream(); + +// cleanup memory allocations within stream +// params: +// stream - stream to destroy +extern void destroy_stream(TCPStream stream); + +// remove parsed data from the stream +// params: +// - stream - pointer to the stream to remove a part of +// - parsed_len - count of n first bytes of the stream that were parsed +extern void remove_parsed_data(TCPStream *stream, size_t parsed_len); + +// max difference between expected TCP seq and received TCP seq to still accept it into pending +#define MAX_SEQ_OFFSET 100000 + +// add TCP segment to stream, pending or completely discard it +// params: +// - stream - pointer to the current stream +// - segment - segment to handle +// returns: +// 1 if the segment was added to the stream, 0 if it was discarded or saved to pending +extern int handle_segment(TCPStream *stream, TCPSegment segment); diff --git a/analyzer/utils/tcp_seq_math.c b/analyzer/utils/tcp_seq_math.c new file mode 100644 index 0000000..272cb5a --- /dev/null +++ b/analyzer/utils/tcp_seq_math.c @@ -0,0 +1,41 @@ +#include "tcp_seq_math.h" + +int seq_after(uint32_t a, uint32_t b) { + return (int32_t)(a - b) > 0; +} + +int seq_before(uint32_t a, uint32_t b) { + return (int32_t)(a - b) < 0; +} + +int seq_after_eq(uint32_t a, uint32_t b) { + return (int32_t)(a - b) >= 0; +} + +int seq_before_eq(uint32_t a, uint32_t b) { + return (int32_t)(a - b) <= 0; +} + +int seq_in_range_ii(uint32_t value, uint32_t range_start, uint32_t range_end) { + int after_eq = seq_after_eq(value, range_start); + int before_eq = seq_before_eq(value, range_end); + return after_eq && before_eq; +} + +int seq_in_range_ee(uint32_t value, uint32_t range_start, uint32_t range_end) { + int after = seq_after(value, range_start); + int before = seq_before(value, range_end); + return after && before; +} + +int seq_in_range_ie(uint32_t value, uint32_t range_start, uint32_t range_end) { + int after_eq = seq_after_eq(value, range_start); + int before = seq_before(value, range_end); + return after_eq && before; +} + +int seq_in_range_ei(uint32_t value, uint32_t range_start, uint32_t range_end) { + int after = seq_after(value, range_start); + int before_eq = seq_before_eq(value, range_end); + return after && before_eq; +} diff --git a/analyzer/utils/tcp_seq_math.h b/analyzer/utils/tcp_seq_math.h new file mode 100644 index 0000000..c4da5f9 --- /dev/null +++ b/analyzer/utils/tcp_seq_math.h @@ -0,0 +1,28 @@ +// utils/tcp_seq_math.h +// helper functions for math regarding tcp sequence numbers which correctly handle overflow + +#include <stdint.h> + +// check if a is after b +extern int seq_after(uint32_t a, uint32_t b); + +// check if a is before b +extern int seq_before(uint32_t a, uint32_t b); + +// check if a is after or equal to b +extern int seq_after_eq(uint32_t a, uint32_t b); + +// check if a is before or equal to b +extern int seq_before_eq(uint32_t a, uint32_t b); + +// check if value is in [range_start; range_end] +extern int seq_in_range_ii(uint32_t value, uint32_t range_start, uint32_t range_end); + +// check if value is in (range_start; range_end) +extern int seq_in_range_ee(uint32_t value, uint32_t range_start, uint32_t range_end); + +// check if value is in [range_start; range_end) +extern int seq_in_range_ie(uint32_t value, uint32_t range_start, uint32_t range_end); + +// check if value is in (range_start; range_end] +extern int seq_in_range_ei(uint32_t value, uint32_t range_start, uint32_t range_end); |
