Logging TCP server banners

Hi everyone,

For a network recon framework I am working on, I would like to log
each "TCP server banner" Bro sees.

I call "TCP server banner" the first chunk of data a server sends,
before the client has sent data (if the client sends data before the
server, I don't want to log anything).

Here is what I have done so far (`PassiveRecon` is my module's name):

export {
        redef tcp_content_deliver_all_resp = T;
        
        [...]
}

[...]

event tcp_contents(c: connection, is_orig: bool, seq: count, contents: string)
        {
        if (! is_orig && seq == 1 && c$orig$num_pkts == 2)
                {
                Log::write(PassiveRecon::LOG, [$ts=c$start_time,
                                               $host=c$id$resp_h,
                                               $srvport=c$id$resp_p,
                                               $recon_type=TCP_SERVER_BANNER,
                                               $value=contents]);
                }
        }

Basically, I consider we have a "TCP server banner" when `is_orig` is
false, when `seq` equals 1 and when we have seen exactly two packets
from the client (which should be a SYN and the first ACK).

This solution generally works **but** I sometimes log a data chunk
when I should not, particularly if I have missed part of the
traffic.

As an example, the following Scapy script creates a PCAP file that
would trick my script into logging a "TCP server banner" while the
client has actually sent some data (and we have missed an ACK packet,
left as a comment in the script):

wrpcap("test.cap", [
    Ether() / IP(dst="1.2.3.4", src="5.6.7.8") /
    TCP(dport=80, sport=5678, flags="S", ack=0, seq=555678),
    Ether() / IP(src="1.2.3.4", dst="5.6.7.8") /
    TCP(sport=80, dport=5678, flags="SA", seq=111234, ack=555679),
    # Ether() / IP(dst="1.2.3.4", src="5.6.7.8") /
    # TCP(dport=80, sport=5678, flags="A", ack=111235, seq=555679),
    Ether() / IP(dst="1.2.3.4", src="5.6.7.8") /
    TCP(dport=80, sport=5678, flags="PA", ack=111235, seq=555679) / "DATA",
    Ether() / IP(src="1.2.3.4", dst="5.6.7.8") /
    TCP(sport=80, dport=5678, flags="PA", seq=111235, ack=555683) / "DATA"
])

Is there a way to know that I have not missed any packet from the
client and/or a way to know that the client has not sent any data on
the connection (like an equivalent of the `seq` parameter, but for the
`ack`)?

Also, when `seq` equals 1, am I certain that I have not missed any
packet from the server?

One more question: is there a better, cleaner, etc. way to do what I'm
trying to do?

Thanks a lot,

Pierre

Hi Pierre,

event tcp_contents(c: connection, is_orig: bool, seq: count, contents: string)
         {
         if (! is_orig && seq == 1 && c$orig$num_pkts == 2)
                 {
                 Log::write(PassiveRecon::LOG, [$ts=c$start_time,
                                                $host=c$id$resp_h,
                                                $srvport=c$id$resp_p,
                                                $recon_type=TCP_SERVER_BANNER,
                                                $value=contents]);
                 }
         }

I'd recommend a different approach, namely to keep additional state for each connection to indicate whether you've seen contents from the respective endpoints. That way, when you see contents arrive from the responder you can check directly whether you've previously seen anything from the originator, and if so, ignore the responder's content.

wrpcap("test.cap", [
     Ether() / IP(dst="1.2.3.4", src="5.6.7.8") /
     TCP(dport=80, sport=5678, flags="S", ack=0, seq=555678),
     Ether() / IP(src="1.2.3.4", dst="5.6.7.8") /
     TCP(sport=80, dport=5678, flags="SA", seq=111234, ack=555679),
     # Ether() / IP(dst="1.2.3.4", src="5.6.7.8") /
     # TCP(dport=80, sport=5678, flags="A", ack=111235, seq=555679),
     Ether() / IP(dst="1.2.3.4", src="5.6.7.8") /
     TCP(dport=80, sport=5678, flags="PA", ack=111235, seq=555679) / "DATA",
     Ether() / IP(src="1.2.3.4", dst="5.6.7.8") /
     TCP(sport=80, dport=5678, flags="PA", seq=111235, ack=555683) / "DATA"
])

(With the commented-out line this is missing the pure TCP ACK packet that completes the TCP handshake, so may not be a good test case.)

Best,
Christian