Skip to content

Commit

Permalink
Support for IPv6 (#16)
Browse files Browse the repository at this point in the history
* Support for IPv6

* README update for IPv6 flows

Co-authored-by: counterthreatunit <counterthreatunit@users.noreply.github.com>
  • Loading branch information
bhaan and counterthreatunit committed Dec 30, 2019
1 parent 49ba2c1 commit 14473db
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 27 deletions.
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ You can declare a flow using the following syntax:

flow [flow name] [proto] [src]:[srcport] [directionality] [dst]:[dstport] ([flow options]);


*src* and *dst* can be IPv4 addresses, IPv6 addresses, or resolvable domain names. For IPv6, the address(es) must be enclosed in square brackets ('[' and ']').

The following flow declaration would describe a flow going from a computer to google.com:

flow my_connection tcp mydesktop.corp.acme.com:44123 > google.com:80 (tcp.initialize;);
Expand All @@ -77,6 +80,10 @@ The following flow declaration would describe a flow going from a computer to a

flow dns_request udp mydesktop.corp.acme.com:11234 > 8.8.8.8:53;

The following flow declaration would describe a flow using IPv6 addresses:

flow default tcp [2600:1337:2800:1:248:1893:25c8:d1]:31337 > [2600:1337:2800::f1]:80 (tcp.initialize;);

For the interim, directionality should always be specified as to server: >

If a DNS record is specified in the flow declaration (instead of an explicit IP address) then Flowsynth will resolve the DNS entry at the time of the flow's declaration. The first A record returned for DNS entry will be used as the IP address throughout the session. The DNS query and response is not included in the output.
Expand Down Expand Up @@ -175,4 +182,4 @@ The *tcp.flags.rst* attribute tells Flowsynth to force the packet to be a RST pa

+ David Wharton
+ @2xyo

+ @bhaan
3 changes: 3 additions & 0 deletions examples/ipv6-http-get.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
flow default tcp [2606:2800:220:1:248:1893:25c8:1946]:44123 > [2607:f8b0:4004:800::200e]:80 (tcp.initialize;);
default > (content:"GET / HTTP/1.1\x0d\x0aHost:google.com\x0d\x0aUser-Agent: DogBot\x0d\x0a\x0d\x0a";);
default < (content:"HTTP/1.1 200 OK\x0d\x0aContent-Length: 300\x0d\x0a\x0d\x0aWelcome to Google.com!\x0d\x0a\x0d\x0a";);
95 changes: 69 additions & 26 deletions src/flowsynth.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
logging.getLogger("scapy.interactive").setLevel(logging.ERROR)
logging.getLogger("scapy.loading").setLevel(logging.ERROR)
from scapy.all import Ether, IP, TCP, UDP, RandMAC, hexdump, wrpcap
from scapy.all import Ether, IP, IPv6, TCP, UDP, RandMAC, hexdump, wrpcap

#global variables
APP_VERSION_STRING = "1.0.6"
Expand Down Expand Up @@ -93,6 +93,11 @@ class FSLexer:
instructions = []
dnscache = {}

ipv4regex = r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"

# https://stackoverflow.com/a/17871737
ipv6regex = r"^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$"

def __init__(self, synfiledata):

#init
Expand All @@ -115,8 +120,7 @@ def __init__(self, synfiledata):

def resolve_dns(self, shost):
"""Perform DNS lookups once per file, and cache the results. tested."""
rdns = r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"
if (re.match(rdns, shost) == None):
if (re.match(self.ipv4regex, shost) == None and re.match(self.ipv6regex, shost) == None):
if shost in self.dnscache:
logging.debug("Host %s in DNSCACHE, returned %s", shost, self.dnscache[shost])
shost = self.dnscache[shost]
Expand All @@ -142,52 +146,79 @@ def lex_flow(self, tokens):
#need to read the following mandatory values:
try:
flow_name = tokens[0]
flow_proto = tokens[1]
l4_proto = tokens[1]
tokens = tokens[2:]
except IndexError:
raise SynSyntaxError("Corrupt flowdecl")

flow_src = ""
tok_ctr = 0
for token in tokens:
if (token == ':'):
break
else:
if tokens[0] == '[':
l3_proto = Flow.PROTO_IPV6
tokens = tokens[1:]
for token in tokens:
tok_ctr = tok_ctr + 1
if (token == ']'):
break
flow_src = "%s%s" % (flow_src, token)
else:
l3_proto = Flow.PROTO_IPV4
for token in tokens:
if (token == ':'):
break
flow_src = "%s%s" % (flow_src, token)
tok_ctr = tok_ctr + 1

tokens = tokens[tok_ctr+1:]
try:
flow_src_port = tokens[0]
except IndexError:
raise SynSyntaxError("No flow source port specified")
tokens = tokens[1:]

directionality = tokens[1]
directionality = tokens[0]
if (directionality != ">" and directionality != "<"):
raise SynSyntaxError("Unexpected flow directionality: %s" % directionality)
tokens = tokens[1:]

tokens = tokens[2:]
flow_dst = ""
tok_ctr = 0
for token in tokens:
if (token == ':'):
break
else:
if tokens[0] == '[':
if l3_proto != Flow.PROTO_IPV6:
raise SynSyntaxError("Inconsistent layer 3 protocols")
tokens = tokens[1:]
for token in tokens:
tok_ctr = tok_ctr + 1
if (token == ']'):
break
flow_dst = "%s%s" % (flow_dst, token)
else:
if l3_proto != Flow.PROTO_IPV4:
raise SynSyntaxError("Inconsistent layer 3 protocols")
for token in tokens:
if (token == ':'):
break
flow_dst = "%s%s" % (flow_dst, token)
tok_ctr = tok_ctr + 1

tokens = tokens[tok_ctr+1:]
flow_dst_port = tokens[0]
try:
flow_dst_port = tokens[0]
except IndexError:
raise SynSyntaxError("No flow destination port specified")
tokens = tokens[1:]

if (flow_proto.lower() == 'udp'):
flow_proto = Flow.PROTO_UDP
if (l4_proto.lower() == 'udp'):
l4_proto = Flow.PROTO_UDP
else:
flow_proto = Flow.PROTO_TCP
l4_proto = Flow.PROTO_TCP

#start to build our flow decl
flowdecl = {}
flowdecl['type'] = 'flow'
flowdecl['name'] = flow_name
flowdecl['proto'] = flow_proto
flowdecl['l3_proto'] = l3_proto
flowdecl['l4_proto'] = l4_proto
flowdecl['src_host'] = self.resolve_dns(flow_src)
flowdecl['src_port'] = flow_src_port
flowdecl['dst_host'] = self.resolve_dns(flow_dst)
Expand Down Expand Up @@ -339,7 +370,11 @@ def lex_event(self, tokens):
class Flow:
"""a class for modeling a specific flow"""

#consts for different protocols
#consts for different L3 protocols
PROTO_IPV4 = 0
PROTO_IPV6 = 1

#consts for different L4 protocols
PROTO_TCP = 0
PROTO_UDP = 1

Expand All @@ -349,7 +384,8 @@ class Flow:
FLOW_BIDIRECTIONAL = 2

#specific values for the flow
proto = 0
l3_proto = 0
l4_proto = 0
flow = 0
name = ""
src_mac = ""
Expand All @@ -376,7 +412,8 @@ def __init__(self, flowdecl = None):
parser_bailout("Flowdecl must be a dictionary.")
try:
self.name = flowdecl['name']
self.proto = flowdecl['proto']
self.l3_proto = flowdecl['l3_proto']
self.l4_proto = flowdecl['l4_proto']
self.src_host = flowdecl['src_host']
self.src_port = flowdecl['src_port']
self.flow = flowdecl['flow']
Expand Down Expand Up @@ -517,7 +554,7 @@ def render(self, eventid):

if (hasPayload == True):
#we have a payload and we are using TCP; observe the MSS
if (len(total_payload) > self.tcp_mss and self.proto == Flow.PROTO_TCP):
if (len(total_payload) > self.tcp_mss and self.l4_proto == Flow.PROTO_TCP):
payload = total_payload[:self.tcp_mss]
total_payload = total_payload[self.tcp_mss:]
else:
Expand Down Expand Up @@ -571,9 +608,12 @@ def render(self, eventid):
pkt = None
logging.debug("SRC host: %s", src_host)
logging.debug("DST host: %s", dst_host)
lyr_ip = IP(src = src_host, dst = dst_host)
if self.l3_proto == Flow.PROTO_IPV4:
lyr_ip = IP(src = src_host, dst = dst_host)
else:
lyr_ip = IPv6(src = src_host, dst = dst_host)
lyr_eth = Ether(src = src_mac, dst = dst_mac)
if (self.proto == Flow.PROTO_UDP):
if (self.l4_proto == Flow.PROTO_UDP):
#generate udp packet
lyr_udp = UDP(sport = src_port, dport = dst_port) / payload
pkt = lyr_eth / lyr_ip / lyr_udp
Expand Down Expand Up @@ -651,7 +691,10 @@ def render(self, eventid):
if 'tcp.noack' not in event['attributes']:
logging.debug('INFERRED ACK: S:%s A:%s', tcp_seq, tcp_ack)
lyr_eth = Ether(src = dst_mac, dst=src_mac)
lyr_ip = IP(src=dst_host, dst=src_host)
if self.l3_proto == Flow.PROTO_IPV4:
lyr_ip = IP(src = dst_host, dst = src_host)
else:
lyr_ip = IPv6(src = dst_host, dst = src_host)
lyr_tcp = TCP(sport = dst_port, dport = src_port, flags='A', seq=tcp_seq, ack=tcp_ack)
pkt = lyr_eth / lyr_ip / lyr_tcp
pkts.append(pkt)
Expand Down

0 comments on commit 14473db

Please sign in to comment.