123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616 |
- import os
- import sys
- from collections import deque
- from datetime import datetime
- from random import randint, randrange, choice, uniform
- import ID2TLib.Botnet.libbotnetcomm as lb
- from lea import Lea
- from scapy.layers.inet import IP, IPOption_Security
- import ID2TLib.Botnet.Message as Bmsg
- import ID2TLib.Utility as Util
- from Attack import BaseAttack
- from Attack.AttackParameters import Parameter as Param
- from Attack.AttackParameters import ParameterTypes
- from ID2TLib import Generator
- from ID2TLib.Botnet.CommunicationProcessor import CommunicationProcessor
- from ID2TLib.Botnet.MessageMapping import MessageMapping
- from ID2TLib.PcapAddressOperations import PcapAddressOperations
- from ID2TLib.Ports import PortSelectors
- class MembersMgmtCommAttack(BaseAttack.BaseAttack):
- def __init__(self):
- """
- Creates a new instance of the Membership Management Communication.
- """
-
- super(MembersMgmtCommAttack, self).__init__(
- "Membership Management Communication Attack (MembersMgmtCommAttack)",
- "Injects Membership Management Communication", "Botnet communication")
-
- self.supported_params = {
-
- Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
- Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
- Param.PACKETS_LIMIT: ParameterTypes.TYPE_INTEGER_POSITIVE,
- Param.ATTACK_DURATION: ParameterTypes.TYPE_INTEGER_POSITIVE,
-
- Param.NUMBER_INITIATOR_BOTS: ParameterTypes.TYPE_INTEGER_POSITIVE,
-
- Param.FILE_CSV: ParameterTypes.TYPE_FILEPATH,
- Param.FILE_XML: ParameterTypes.TYPE_FILEPATH,
-
- Param.IP_REUSE_TOTAL: ParameterTypes.TYPE_PERCENTAGE,
- Param.IP_REUSE_LOCAL: ParameterTypes.TYPE_PERCENTAGE,
- Param.IP_REUSE_EXTERNAL: ParameterTypes.TYPE_PERCENTAGE,
-
- Param.PACKET_PADDING: ParameterTypes.TYPE_PADDING,
-
- Param.NAT_PRESENT: ParameterTypes.TYPE_BOOLEAN,
-
-
- Param.TTL_FROM_CAIDA: ParameterTypes.TYPE_BOOLEAN,
-
-
- Param.MULTIPORT: ParameterTypes.TYPE_BOOLEAN,
-
- Param.INTERVAL_SELECT_STRATEGY: ParameterTypes.TYPE_INTERVAL_SELECT_STRAT,
- Param.INTERVAL_SELECT_START: ParameterTypes.TYPE_INTEGER_POSITIVE,
- Param.INTERVAL_SELECT_END: ParameterTypes.TYPE_INTEGER_POSITIVE,
-
-
- Param.HIDDEN_MARK: ParameterTypes.TYPE_BOOLEAN
- }
-
- self.msg_types = {}
- for msg_type in Bmsg.MessageType:
- self.msg_types[msg_type.value] = msg_type
- self.DEFAULT_XML_PATH = None
- def init_params(self):
- """
- Initialize some parameters of this communication-attack using the user supplied command line parameters.
- The remaining parameters are implicitly set in the provided data file. Note: the timestamps in the file
- have to be sorted in ascending order
- """
-
- self.DEFAULT_XML_PATH = Util.RESOURCE_DIR + "Botnet/MembersMgmtComm_example.xml"
-
-
- self.add_param_value(Param.INJECT_AFTER_PACKET, 1 + randint(0, self.statistics.get_packet_count() // 5))
- self.add_param_value(Param.FILE_XML, self.DEFAULT_XML_PATH)
-
- duration = int(float(self.statistics.get_capture_duration()))
- self.add_param_value(Param.ATTACK_DURATION, duration)
- self.add_param_value(Param.NUMBER_INITIATOR_BOTS, 1)
-
- self.add_param_value(Param.NAT_PRESENT, True)
-
- self.add_param_value(Param.IP_REUSE_TOTAL, 1)
- self.add_param_value(Param.IP_REUSE_LOCAL, 0.5)
- self.add_param_value(Param.IP_REUSE_EXTERNAL, 0.5)
-
- self.add_param_value(Param.PACKET_PADDING, 20)
-
- self.add_param_value(Param.TTL_FROM_CAIDA, False)
-
- self.add_param_value(Param.MULTIPORT, False)
-
- self.add_param_value(Param.INTERVAL_SELECT_STRATEGY, "optimal")
- self.add_param_value(Param.HIDDEN_MARK, False)
- def generate_attack_pcap(self):
- """
- Injects the packets of this attack into a PCAP and stores it as a temporary file.
- :return: a tuple of the number packets injected, the path to the temporary attack PCAP
- and a list of additionally created files
- """
-
- messages = self._create_messages()
- if not messages:
- return 0, None
-
- buffer_size = 1000
- pkt_gen = Generator.PacketGenerator()
- padding = self.get_param_value(Param.PACKET_PADDING)
- packets = deque(maxlen=buffer_size)
- total_pkts = 0
- limit_packetcount = self.get_param_value(Param.PACKETS_LIMIT)
- limit_duration = self.get_param_value(Param.ATTACK_DURATION)
- path_attack_pcap = None
- over_thousand = False
- msg_packet_mapping = MessageMapping(messages, self.statistics.get_pcap_timestamp_start())
- mark_packets = self.get_param_value(Param.HIDDEN_MARK)
-
- for msg in messages:
-
- ip_src, ip_dst = msg.src["IP"], msg.dst["IP"]
- mac_src, mac_dst = msg.src["MAC"], msg.dst["MAC"]
- if msg.type.is_request():
- port_src, port_dst = int(msg.src["SrcPort"]), int(msg.dst["DstPort"])
- else:
- port_src, port_dst = int(msg.src["DstPort"]), int(msg.dst["SrcPort"])
- ttl = int(msg.src["TTL"])
-
- duration = msg.time - messages[0].time
-
- if ((limit_packetcount is not None and total_pkts >= limit_packetcount) or
- (limit_duration is not None and duration >= limit_duration)):
- break
-
- nl_size = 0
- if msg.type == Bmsg.MessageType.SALITY_NL_REPLY:
- nl_size = randint(1, 25)
-
- packet = pkt_gen.generate_mmcom_packet(ip_src=ip_src, ip_dst=ip_dst, ttl=ttl, mac_src=mac_src,
- mac_dst=mac_dst,
- port_src=port_src, port_dst=port_dst, message_type=msg.type,
- neighborlist_entries=nl_size)
- Generator.add_padding(packet, padding, True, True)
- packet.time = msg.time
- if mark_packets and isinstance(packet.payload, IP):
- ip_data = packet.payload
- hidden_opt = IPOption_Security()
- hidden_opt.option = 2
- hidden_opt.security = 16
- ip_data.options = hidden_opt
- packets.append(packet)
- msg_packet_mapping.map_message(msg, packet)
- total_pkts += 1
-
- if total_pkts <= 1:
- self.attack_start_utime = packets[0].time
- elif total_pkts % buffer_size == 0:
- if over_thousand:
- packets = list(packets)
- Generator.equal_length(packets, length=max_len, padding=padding, force_len=True)
- last_packet = packets[-1]
- path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
- packets = deque(maxlen=buffer_size)
- else:
- packets = list(packets)
- Generator.equal_length(packets, padding=padding)
- last_packet = packets[-1]
- max_len = len(last_packet)
- over_thousand = True
- path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
- packets = deque(maxlen=buffer_size)
-
- if len(packets) > 0:
- if over_thousand:
- packets = list(packets)
- Generator.equal_length(packets, length=max_len, padding=padding, force_len=True)
- path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
- last_packet = packets[-1]
- else:
- packets = list(packets)
- Generator.equal_length(packets, padding=padding)
- path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
- last_packet = packets[-1]
-
- current_ts = datetime.now().strftime("%Y%m%d-%H%M%S")
- mapping_filename = "mapping_" + current_ts + ".xml"
- msg_packet_mapping.write_to_file(mapping_filename)
-
- self.attack_end_utime = last_packet.time
-
- return total_pkts, path_attack_pcap, [mapping_filename]
- def generate_attack_packets(self):
- pass
- def _create_messages(self):
- """
- Creates the messages that are to be injected into the PCAP.
- :return: the final messages as a list
- """
- def add_ids_to_config(ids_to_add: list, existing_ips: list, new_ips: list, bot_configs: dict,
- idtype: str = "local", router_mac: str = ""):
- """
- Creates IP and MAC configurations for the given IDs and adds them to the existing configurations object.
- :param ids_to_add: all sorted IDs that have to be configured and added
- :param existing_ips: the existing IPs in the PCAP file that should be assigned to some, or all, IDs
- :param new_ips: the newly generated IPs that should be assigned to some, or all, IDs
- :param bot_configs: the existing configurations for the bots
- :param idtype: the locality type of the IDs
- :param router_mac: the MAC address of the router in the PCAP
- """
- ids = ids_to_add.copy()
-
-
- macgen = Generator.MacAddressGenerator()
-
- for ip in existing_ips:
- random_id = choice(ids)
- mac = self.statistics.process_db_query("macAddress(IPAddress=%s)" % ip)
- bot_configs[random_id] = {"Type": idtype, "IP": ip, "MAC": mac}
- ids.remove(random_id)
-
- for ip in new_ips:
- random_id = choice(ids)
- if idtype == "local":
- mac = macgen.random_mac()
- elif idtype == "external":
- mac = router_mac
- bot_configs[random_id] = {"Type": idtype, "IP": ip, "MAC": mac}
- ids.remove(random_id)
- def assign_realistic_ttls(bot_configs: dict):
- """
- Assigns a realisitic ttl to each bot from @param: bot_configs. Uses statistics and distribution to be able
- to calculate a realisitc ttl.
- :param bot_configs: List that contains all bots that should be assigned with realistic ttls.
- """
- ids = sorted(bot_configs.keys())
- for pos, bot in enumerate(ids):
- bot_type = bot_configs[bot]["Type"]
- if bot_type == "local":
- bot_configs[bot]["TTL"] = 128
-
- else:
- bot_ttl_dist = self.statistics.get_ttl_distribution(bot_configs[bot]["IP"])
- if len(bot_ttl_dist) > 0:
- source_ttl_prob_dict = Lea.fromValFreqsDict(bot_ttl_dist)
- bot_configs[bot]["TTL"] = source_ttl_prob_dict.random()
- else:
- most_used_ttl = self.statistics.process_db_query("most_used(ttlValue)")
- if isinstance(most_used_ttl, list):
- bot_configs[bot]["TTL"] = choice(self.statistics.process_db_query("most_used(ttlValue)"))
- else:
- bot_configs[bot]["TTL"] = self.statistics.process_db_query("most_used(ttlValue)")
- def assign_realistic_timestamps(messages: list, external_ids: set, local_ids: set, avg_delay_local: float,
- avg_delay_external: float, zero_reference: float):
- """
- Assigns realistic timestamps to a set of messages
- :param messages: the set of messages to be updated
- :param external_ids: the set of bot ids, that are outside the network, i.e. external
- :param local_ids: the set of bot ids, that are inside the network, i.e. local
- :param avg_delay_local: the avg_delay between the dispatch and the reception of a packet between local
- computers
- :param avg_delay_external: the avg_delay between the dispatch and the reception of a packet between a local
- and an external computer
- :param zero_reference: the timestamp which is regarded as the beginning of the pcap_file and therefore
- handled like a timestamp that resembles 0
- """
- updated_msgs = []
-
-
-
- last_response = {}
- for m in messages:
- last_response[(m.src, m.dst)] = -1
-
- for req_msg in messages:
- if req_msg in updated_msgs:
-
- continue
-
-
- if last_response[(req_msg.src, req_msg.dst)] == -1 or last_response[(req_msg.src, req_msg.dst)] < (
- zero_reference + req_msg.time - 0.05):
-
- req_msg.time = zero_reference + req_msg.time + uniform(-0.05, 0.05)
- updated_msgs.append(req_msg)
- else:
- req_msg.time = last_response[(req_msg.src, req_msg.dst)] + 0.06 + uniform(-0.05, 0.05)
-
- if req_msg.refer_msg_id != -1:
- respns_msg = messages[req_msg.refer_msg_id]
-
-
- if req_msg.src in external_ids or req_msg.dst in external_ids:
-
- respns_msg.time = req_msg.time + avg_delay_external + uniform(-0.1 * avg_delay_external,
- 0.1 * avg_delay_external)
- else:
-
- respns_msg.time = req_msg.time + avg_delay_local + uniform(-0.1 * avg_delay_local,
- 0.1 * avg_delay_local)
- updated_msgs.append(respns_msg)
- last_response[(req_msg.src, req_msg.dst)] = respns_msg.time
- def assign_ttls_from_caida(bot_configs):
- """
- Assign realistic TTL values to bots with respect to their IP, based on the CAIDA dataset.
- If there exists an entry for a bot's IP, the TTL is chosen based on a distribution over all used TTLs by
- this IP.
- If there is no such entry, the TTL is chosen based on a distribution over all used TTLs and their
- respective frequency.
- :param bot_configs: the existing bot configurations
- """
- def get_ip_ttl_distrib():
- """
- Parses the CSV file containing a mapping between IP and their used TTLs.
- :return: returns a dict with the IPs as keys and dicts for their TTL distribution as values
- """
- ip_based_distrib = {}
- with open(Util.RESOURCE_DIR + "CaidaTTL_perIP.csv", "r") as file:
-
- next(file)
- for line in file:
- ip_addr, ttl, freq = line.split(",")
- if ip_addr not in ip_based_distrib:
-
- ip_based_distrib[ip_addr] = {}
- ip_based_distrib[ip_addr][ttl] = int(freq)
- return ip_based_distrib
- def get_total_ttl_distrib():
- """
- Parses the CSV file containing an overview of all used TTLs and their respective frequency.
- :return: returns a dict with the TTLs as keys and their frequencies as keys
- """
- total_ttl_distrib = {}
- with open(Util.RESOURCE_DIR + "CaidaTTL_total.csv", "r") as file:
-
- next(file)
- for line in file:
- ttl, freq, _ = line.split(",")
- total_ttl_distrib[ttl] = int(freq)
- return total_ttl_distrib
-
- ip_ttl_distrib = get_ip_ttl_distrib()
-
- total_ttl_prob_dict = Lea.fromValFreqsDict(get_total_ttl_distrib())
-
- for bot_id in sorted(bot_configs):
- bot_type = bot_configs[bot_id]["Type"]
- bot_ip = bot_configs[bot_id]["IP"]
- if bot_type == "local":
- bot_configs[bot_id]["TTL"] = 128
-
- elif bot_ip in ip_ttl_distrib:
- ip_ttl_freqs = ip_ttl_distrib[bot_ip]
-
- source_ttl_prob_dict = Lea.fromValFreqsDict(ip_ttl_freqs)
- bot_configs[bot_id]["TTL"] = source_ttl_prob_dict.random()
-
- else:
- bot_configs[bot_id]["TTL"] = total_ttl_prob_dict.random()
-
- filepath_xml = self.get_param_value(Param.FILE_XML)
- filepath_csv = self.get_param_value(Param.FILE_CSV)
-
- cpp_comm_proc = lb.botnet_comm_processor()
-
-
- print_updates = False
- if filepath_csv and filepath_xml == self.DEFAULT_XML_PATH:
- filename = os.path.splitext(os.path.basename(filepath_csv))[0]
- filesize = os.path.getsize(filepath_csv) / 2**20
- if filesize > 10:
- print("\nParsing input CSV file...", end=" ")
- sys.stdout.flush()
- print_updates = True
- cpp_comm_proc.parse_csv(filepath_csv)
- if print_updates:
- print("done.")
- print("Writing corresponding XML file...", end=" ")
- sys.stdout.flush()
- filepath_xml = cpp_comm_proc.write_xml(Util.OUT_DIR, filename)
- if print_updates:
- print("done.")
- else:
- filesize = os.path.getsize(filepath_xml) / 2**20
- if filesize > 10:
- print("Parsing input XML file...", end=" ")
- sys.stdout.flush()
- print_updates = True
- cpp_comm_proc.parse_xml(filepath_xml)
- if print_updates:
- print("done.")
-
- nat = self.get_param_value(Param.NAT_PRESENT)
- comm_proc = CommunicationProcessor(self.msg_types, nat)
- duration = self.get_param_value(Param.ATTACK_DURATION)
- number_init_bots = self.get_param_value(Param.NUMBER_INITIATOR_BOTS)
- strategy = self.get_param_value(Param.INTERVAL_SELECT_STRATEGY)
- start_idx = self.get_param_value(Param.INTERVAL_SELECT_START)
- end_idx = self.get_param_value(Param.INTERVAL_SELECT_END)
- potential_long_find_time = (
- strategy == "optimal" and (filesize > 4 and self.statistics.get_packet_count() > 1000))
- if print_updates or potential_long_find_time:
- if not print_updates:
- print()
- print("Selecting communication interval from input CSV/XML file...", end=" ")
- sys.stdout.flush()
- if potential_long_find_time:
- print("\nWarning: Because of the large input files and the (chosen) interval selection strategy")
- print("'optimal', this may take a while. Consider using selection strategy 'random' or 'custom'...",
- end=" ")
- sys.stdout.flush()
- print_updates = True
- comm_interval = comm_proc.get_comm_interval(cpp_comm_proc, strategy, number_init_bots, duration, start_idx,
- end_idx)
- if not comm_interval:
- print("Error: An interval that satisfies the input cannot be found.")
- return []
- if print_updates:
- print("done.")
-
- mapped_ids = comm_interval["IDs"]
- packet_start_idx = comm_interval["Start"]
- packet_end_idx = comm_interval["End"]
- while len(mapped_ids) > number_init_bots:
- rm_idx = randrange(0, len(mapped_ids))
- del mapped_ids[rm_idx]
- if print_updates:
- print("Generating attack packets...", end=" ")
- sys.stdout.flush()
-
- abstract_packets = cpp_comm_proc.get_messages(packet_start_idx, packet_end_idx)
- comm_proc.set_mapping(abstract_packets, mapped_ids)
-
- messages = comm_proc.det_id_roles_and_msgs()
-
- local_ids, external_ids = comm_proc.det_ext_and_local_ids()
-
- reuse_percent_total = self.get_param_value(Param.IP_REUSE_TOTAL)
- reuse_percent_external = self.get_param_value(Param.IP_REUSE_EXTERNAL)
- reuse_percent_local = self.get_param_value(Param.IP_REUSE_LOCAL)
-
- ipgen = Generator.IPGenerator()
- pcapops = PcapAddressOperations(self.statistics)
- router_mac = pcapops.get_probable_router_mac()
- bot_configs = {}
-
-
- number_local_ids, number_external_ids = len(local_ids), len(external_ids)
-
- if number_local_ids > 0:
- reuse_count_local = int(reuse_percent_total * reuse_percent_local * number_local_ids)
- existing_local_ips = sorted(pcapops.get_existing_local_ips(reuse_count_local))
- new_local_ips = sorted(pcapops.get_new_local_ips(number_local_ids - len(existing_local_ips)))
- add_ids_to_config(sorted(local_ids), existing_local_ips, new_local_ips, bot_configs)
-
- if number_external_ids > 0:
- reuse_count_external = int(reuse_percent_total * reuse_percent_external * number_external_ids)
- existing_external_ips = sorted(pcapops.get_existing_external_ips(reuse_count_external))
- remaining = len(external_ids) - len(existing_external_ips)
- for external_ip in existing_external_ips:
- ipgen.add_to_blacklist(external_ip)
- new_external_ips = sorted([ipgen.random_ip() for _ in range(remaining)])
- add_ids_to_config(sorted(external_ids), existing_external_ips, new_external_ips, bot_configs,
- idtype="external", router_mac=router_mac)
-
-
-
- zero_reference = self.get_param_value(Param.INJECT_AT_TIMESTAMP) - messages[0].time
-
- avg_delay_local, avg_delay_external = self.statistics.get_avg_delay_local_ext()
-
- assign_realistic_timestamps(messages, external_ids, local_ids, avg_delay_local, avg_delay_external,
- zero_reference)
- port_selector = PortSelectors.LINUX.clone()
- reserved_ports = set(int(line.strip()) for line in open(Util.RESOURCE_DIR + "reserved_ports.txt").readlines())
- def filter_reserved(get_port):
- port = get_port()
- while port in reserved_ports:
- port = get_port()
- return port
-
- use_multiple_ports = self.get_param_value(Param.MULTIPORT)
- for bot in sorted(bot_configs):
- bot_configs[bot]["SrcPort"] = filter_reserved(port_selector.select_port_udp)
- if not use_multiple_ports:
- bot_configs[bot]["DstPort"] = filter_reserved(Generator.gen_random_server_port)
- else:
- bot_configs[bot]["DstPort"] = filter_reserved(port_selector.select_port_udp)
-
- if self.get_param_value(Param.TTL_FROM_CAIDA):
- assign_ttls_from_caida(bot_configs)
- else:
- assign_realistic_ttls(bot_configs)
-
-
- final_messages = []
- messages = sorted(messages, key=lambda m: m.time)
- new_id = 0
- for msg in messages:
- type_src, type_dst = bot_configs[msg.src]["Type"], bot_configs[msg.dst]["Type"]
- id_src, id_dst = msg.src, msg.dst
-
- if type_src == "external" and type_dst == "external":
- continue
- msg.src, msg.dst = bot_configs[id_src], bot_configs[id_dst]
- msg.src["ID"], msg.dst["ID"] = id_src, id_dst
- msg.msg_id = new_id
- new_id += 1
-
- final_messages.append(msg)
- return final_messages
|