geneva/censors/censor8b.py

160 lines
6.5 KiB
Python
Raw Normal View History

"""
Censor 8b is a RST censor designed to mimic TCB teardown GFW behavior. It
tracks multiple connections using TCBs, but does not enter a TCB resynchronization
state if a RST or FIN, it simply tears down. It creates new TCBs for connections it is not
yet aware of, but does not check the checksums of incoming packets.
"""
2020-06-24 14:20:51 +02:00
import layers.packet
import netifaces
from censors.censor import Censor
from scapy.all import IP, TCP
class Censor8b(Censor):
def __init__(self, environment_id, forbidden, log_dir, log_level, port, queue_num):
Censor.__init__(self, environment_id, log_dir, log_level, port, queue_num)
self.forbidden = forbidden
self.tcbs = []
self.flagged_ips = []
self.censor_interfaces = netifaces.interfaces()
if(len(self.censor_interfaces) > 1) and 'eth0' in self.censor_interfaces:
self.censor_ip = netifaces.ifaddresses('eth0')[netifaces.AF_INET][0]['addr']
def check_censor(self, packet):
"""
Check if the censor should run against this packet.
Returns true or false.
"""
try:
2020-06-24 14:20:51 +02:00
self.logger.debug("Inbound packet to censor: %s" % layers.packet.Packet._str_packet(packet))
if packet["IP"].src in self.flagged_ips:
self.logger.debug("Content from a flagged IP detected %s..." % packet["IP"].src)
return True
# Only censor TCP packets for now
if "TCP" not in packet:
return False
# Throw away packets that have an invalid dataofs
if packet["TCP"].dataofs < 5:
return False
# If we are in a resynchronization state, or we do not yet have a connection and a new one
# is being created, add or update a TCB
tcb = self.get_matching_tcb(packet)
if (not tcb and packet["TCP"].sprintf('%TCP.flags%') in ["S"]):
# Check if we've been tracking a connection for this ip:port <-> ip:port already,
# so we can just replace that tcb with updated info
tcb = self.get_partial_tcb(packet)
if tcb is None:
2020-06-24 14:20:51 +02:00
self.logger.debug("Making a new TCB for packet %s" % layers.packet.Packet._str_packet(packet))
tcb = {}
tcb["src"] = packet["IP"].src
tcb["dst"] = packet["IP"].dst
tcb["sport"] = packet["TCP"].sport
tcb["dport"] = packet["TCP"].dport
tcb["seq"] = packet["TCP"].seq
# If we're synchronizing on a SYN flag, need to add 1.
if packet["TCP"].sprintf('%TCP.flags%') in ["S"]:
tcb["seq"] += 1
else:
tcb["seq"] += len(self.get_payload(packet))
self.tcbs.append(tcb)
2020-06-24 14:20:51 +02:00
self.logger.debug("Synchronizing a TCB (%s) on packet %s " % (str(tcb), layers.packet.Packet._str_packet(packet)))
return False
# If connection is getting torn down
elif tcb and packet["TCP"].sprintf('%TCP.flags%') in ["R", "RA"]:
self.tcbs.remove(tcb)
2020-06-24 14:20:51 +02:00
self.logger.debug(("Deleting TCB for packet %s" % layers.packet.Packet._str_packet(packet)))
return False
if not tcb:
self.logger.debug("No TCB matches packet.")
return False
# Keep the TCB up to date
tcb["seq"] += len(self.get_payload(packet))
# Check if any forbidden words appear in the packet payload
for keyword in self.forbidden:
if keyword in self.get_payload(packet):
2020-06-24 14:20:51 +02:00
self.logger.debug("Packet triggered censor: %s" % layers.packet.Packet._str_packet(packet))
return True
return False
except Exception:
self.logger.exception("Exception caught by Censor 8b")
return False
def censor(self, scapy_packet):
"""
Adds client and server IPs to flagged IP list.
"""
if scapy_packet["IP"].src not in self.flagged_ips:
self.flagged_ips.append(scapy_packet["IP"].src)
self.logger.debug("Marking IP %s for dropping..." % scapy_packet["IP"].src)
if scapy_packet["IP"].dst not in self.flagged_ips:
self.flagged_ips.append(scapy_packet["IP"].dst)
self.logger.debug("Marking IP %s for dropping..." % scapy_packet["IP"].dst)
client_ip_rst = IP(src=scapy_packet[IP].dst, dst=scapy_packet[IP].src)
client_tcp_rst = TCP(
dport=scapy_packet[TCP].sport,
sport=scapy_packet[TCP].dport,
ack=scapy_packet[TCP].seq+len(str(scapy_packet[TCP].payload)),
seq=scapy_packet[TCP].ack,
flags="R"
)
client_rst = client_ip_rst / client_tcp_rst
server_ip_rst = IP(src=self.censor_ip, dst=scapy_packet[IP].dst)
server_tcp_rst = TCP(
dport=scapy_packet[TCP].dport,
sport=scapy_packet[TCP].sport,
ack=scapy_packet[TCP].ack,
seq=scapy_packet[TCP].seq,
flags="R"
)
server_tcp_rst.show()
server_rst = server_ip_rst / server_tcp_rst
for _ in range(0, 5):
self.mysend(client_rst)
self.mysend(server_rst)
return "accept"
def get_matching_tcb(self, packet):
"""
Checks if the packet matches the stored TCB.
"""
for tcb in self.tcbs:
2020-06-24 14:20:51 +02:00
self.logger.debug("Checking %s against packet %s" % (str(tcb), layers.packet.Packet._str_packet(packet)))
if (packet["IP"].src == tcb["src"] and \
packet["IP"].dst == tcb["dst"] and \
packet["TCP"].sport == tcb["sport"] and \
packet["TCP"].dport == tcb["dport"] and \
packet["TCP"].seq == tcb["seq"]):
return tcb
return None
def get_partial_tcb(self, packet):
"""
Checks if the packet matches an existing connection, regardless if the SEQ/ACK
are correct.
"""
for tcb in self.tcbs:
2020-06-24 14:20:51 +02:00
self.logger.debug("Checking %s against packet %s for partial match" % (str(tcb), layers.packet.Packet._str_packet(packet)))
if (packet["IP"].src == tcb["src"] and \
packet["IP"].dst == tcb["dst"] and \
packet["TCP"].sport == tcb["sport"] and \
packet["TCP"].dport == tcb["dport"]):
return tcb
return None