MembersMgmtCommAttack.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. from enum import Enum
  2. class MessageType(Enum):
  3. """
  4. Defines possible Message types
  5. """
  6. TIMEOUT = 3
  7. SALITY_NL_REQUEST = 101
  8. SALITY_NL_REPLY = 102
  9. SALITY_HELLO = 103
  10. SALITY_HELLO_REPLY = 104
  11. from random import randint, randrange, choice
  12. from collections import deque
  13. from scipy.stats import gamma
  14. from lea import Lea
  15. from Attack import BaseAttack
  16. from Attack.AttackParameters import Parameter as Param
  17. from Attack.AttackParameters import ParameterTypes
  18. from ID2TLib import FileUtils, PaddingGenerator
  19. from ID2TLib.PacketGenerator import PacketGenerator
  20. from ID2TLib.IPGenerator import IPGenerator
  21. from ID2TLib.PcapAddressOperations import PcapAddressOperations
  22. from ID2TLib.MapInputCSVToIDs import find_interval_with_most_comm, determine_id_roles
  23. from ID2TLib.MacAddressGenerator import MacAddressGenerator
  24. from ID2TLib.PortGenerator import gen_random_server_port
  25. ###### MOVE LOCAL, EXTERNAL IP DECISION INTO MAP CLASS ???? ######
  26. ###### REQUEST/REPLY IDENTIFICATION: NEW DATASTRUCTURE THAT KEEPS REQ AND REPL AND ALL IMPORTANT INFOS (ADDR, ID, PORT). #######
  27. ###### CREATE WHEN DETERMINING INITIATOR AND RESPONDER AND UPDATE LATER WITH ADDRESSES ... ########
  28. class MembersMgmtCommAttack(BaseAttack.BaseAttack):
  29. def __init__(self):
  30. """
  31. Creates a new instance of the Membership Management Communication.
  32. """
  33. # Initialize communication
  34. super(MembersMgmtCommAttack, self).__init__("Membership Management Communication Attack (MembersMgmtCommAttack)",
  35. "Injects Membership Management Communication", "Botnet communication")
  36. # Define allowed parameters and their type
  37. self.supported_params = {
  38. # parameters regarding attack
  39. Param.INJECT_AT_TIMESTAMP: ParameterTypes.TYPE_FLOAT,
  40. Param.INJECT_AFTER_PACKET: ParameterTypes.TYPE_PACKET_POSITION,
  41. Param.PACKETS_PER_SECOND: ParameterTypes.TYPE_FLOAT,
  42. Param.PACKETS_LIMIT: ParameterTypes.TYPE_INTEGER_POSITIVE,
  43. Param.ATTACK_DURATION: ParameterTypes.TYPE_INTEGER_POSITIVE,
  44. # use num_attackers to specify number of communicating devices?
  45. Param.NUMBER_BOTS: ParameterTypes.TYPE_INTEGER_POSITIVE,
  46. # input file containing botnet communication
  47. Param.FILE_CSV: ParameterTypes.TYPE_FILEPATH,
  48. Param.FILE_XML: ParameterTypes.TYPE_FILEPATH,
  49. # the scope of communications
  50. Param.COMM_TYPE: ParameterTypes.TYPE_COMM_TYPE,
  51. # the percentage of IP reuse (if total and other is specified, percentages are multiplied)
  52. Param.IP_REUSE_TOTAL: ParameterTypes.TYPE_PERCENTAGE,
  53. Param.IP_REUSE_LOCAL: ParameterTypes.TYPE_PERCENTAGE,
  54. Param.IP_REUSE_EXTERNAL: ParameterTypes.TYPE_PERCENTAGE,
  55. # the user-selected padding to add to every packet
  56. Param.PACKET_PADDING: ParameterTypes.TYPE_PADDING
  57. }
  58. # create dict with MessageType values for fast name lookup
  59. self.msg_types = {}
  60. for msg_type in MessageType:
  61. self.msg_types[msg_type.value] = msg_type
  62. def init_params(self):
  63. """
  64. Initialize some parameters of this communication-attack using the user supplied command line parameters.
  65. The remaining parameters are implicitly set in the provided data file. Note: the timestamps in the file
  66. have to be sorted in ascending order
  67. :param statistics: Reference to a statistics object.
  68. """
  69. # set class constants
  70. self.DEFAULT_XML_PATH = "resources/MembersMgmtComm_example.xml"
  71. # threshold for ID to be recognized as rather common in communication
  72. self.MOST_COMMON_THRES = 0.08
  73. # probability for initiator ID to be local
  74. self.PROB_INIT_IS_LOCAL = 0.8
  75. # probability for responder ID to be local if comm_type is mixed
  76. self.PROB_RESPND_IS_LOCAL = 0.2
  77. # PARAMETERS: initialize with default values
  78. # (values are overwritten if user specifies them)
  79. # print(self.statistics.get_packet_count())
  80. self.add_param_value(Param.INJECT_AFTER_PACKET, randint(0, self.statistics.get_packet_count()))
  81. # print(self.get_param_value(Param.INJECT_AT_TIMESTAMP))
  82. self.add_param_value(Param.PACKETS_PER_SECOND, 0)
  83. self.add_param_value(Param.FILE_XML, self.DEFAULT_XML_PATH)
  84. self.add_param_value(Param.ATTACK_DURATION, 100)
  85. self.add_param_value(Param.NUMBER_BOTS, 20)
  86. # default locality behavior
  87. self.add_param_value(Param.COMM_TYPE, "mixed")
  88. # TODO: change 1 to something better
  89. self.add_param_value(Param.IP_REUSE_TOTAL, 1)
  90. self.add_param_value(Param.IP_REUSE_LOCAL, 0.5)
  91. self.add_param_value(Param.IP_REUSE_EXTERNAL, 0.5)
  92. # add default additional padding
  93. self.add_param_value(Param.PACKET_PADDING, 0)
  94. def generate_attack_pcap(self):
  95. def add_ids_to_config(ids_to_add, existing_ips, new_ips, bot_configs, idtype="local", router_mac=""):
  96. ids = ids_to_add.copy()
  97. macgen = MacAddressGenerator()
  98. for ip in existing_ips:
  99. random_id = choice(ids)
  100. mac = self.statistics.process_db_query("macAddress(IPAddress=%s)" % ip)
  101. bot_configs[random_id] = {"Type": idtype, "IP": ip, "MAC": mac}
  102. ids.remove(random_id)
  103. for ip in new_ips:
  104. random_id = choice(ids)
  105. if idtype == "local":
  106. mac = macgen.random_mac()
  107. elif idtype == "external":
  108. mac = router_mac
  109. bot_configs[random_id] = {"Type": idtype, "IP": ip, "MAC": mac}
  110. ids.remove(random_id)
  111. def index_increment(number: int, max: int):
  112. if number + 1 < max:
  113. return number + 1
  114. else:
  115. return 0
  116. # parse input CSV or XML
  117. filepath_xml = self.get_param_value(Param.FILE_XML)
  118. filepath_csv = self.get_param_value(Param.FILE_CSV)
  119. # prefer XML input over CSV input
  120. if filepath_csv and filepath_xml == self.DEFAULT_XML_PATH:
  121. filepath_xml = FileUtils.parse_csv_to_xml(filepath_csv)
  122. abstract_packets = FileUtils.parse_xml(filepath_xml)
  123. # find a good communication mapping
  124. duration = self.get_param_value(Param.ATTACK_DURATION)
  125. number_bots = self.get_param_value(Param.NUMBER_BOTS)
  126. comm_interval = find_interval_with_most_comm(abstract_packets, number_bots, duration)[0]
  127. if comm_interval is None:
  128. print("Error: There is no interval in the given CSV/XML that has enough communication")
  129. return 0, None
  130. mapped_ids, id_comms, packet_start_idx, packet_end_idx = comm_interval["IDs"], comm_interval["Comms"], comm_interval["Start"], comm_interval["End"]
  131. # print start and end time of mapped interval
  132. # print(abstract_packets[packet_start_idx]["Time"])
  133. # print(abstract_packets[packet_end_idx]["Time"])
  134. # determine most common communicating IDs
  135. # total_mentions = 0
  136. # most_common_thres = self.MOST_COMMON_THRES
  137. # most_common_ids = {}
  138. # for id_ in mapped_ids:
  139. # total_mentions += mapped_ids[id_]
  140. # for id_ in mapped_ids:
  141. # count = mapped_ids[id_]
  142. # mentions_percentage = count / total_mentions
  143. # if mentions_percentage >= most_common_thres:
  144. # most_common_ids[id_] = mentions_percentage
  145. # determine amount of reused IPs
  146. reuse_percent_total = self.get_param_value(Param.IP_REUSE_TOTAL)
  147. reuse_percent_external = self.get_param_value(Param.IP_REUSE_EXTERNAL)
  148. reuse_percent_local = self.get_param_value(Param.IP_REUSE_LOCAL)
  149. reuse_count_external = int(reuse_percent_total * reuse_percent_external * len(mapped_ids))
  150. reuse_count_local = int(reuse_percent_total * reuse_percent_local * len(mapped_ids))
  151. # create bot IP and MAC configs
  152. ipgen = IPGenerator()
  153. comm_type = self.get_param_value(Param.COMM_TYPE)
  154. pcapops = PcapAddressOperations(self.statistics)
  155. router_mac = pcapops.get_probable_router_mac()
  156. bot_configs = {}
  157. external_ids = set()
  158. local_ids = set()
  159. id_comms = sorted(id_comms)
  160. # print(self.get_param_value(Param.INJECT_AT_TIMESTAMP))
  161. if comm_type == "local":
  162. local_ids = set(mapped_ids.keys())
  163. else:
  164. init_local_or_external = Lea.fromValFreqsDict({"local": self.PROB_INIT_IS_LOCAL*100, "external": (1-self.PROB_INIT_IS_LOCAL)*100})
  165. mixed_respnd_is_local = Lea.fromValFreqsDict({"local": self.PROB_RESPND_IS_LOCAL*100, "external": (1-self.PROB_RESPND_IS_LOCAL)*100})
  166. ids = set(mapped_ids.keys())
  167. init_ids, respnd_ids, both_ids = determine_id_roles(abstract_packets[packet_start_idx:packet_end_idx+1], ids)
  168. # assign IDs in 'both' local everytime for mixed?
  169. initiators = sorted(list(init_ids) + list(both_ids))
  170. for id_ in initiators:
  171. if id_ in local_ids or id_ in external_ids:
  172. continue
  173. pos = init_local_or_external.random()
  174. if pos == "local":
  175. local_ids.add(id_)
  176. for id_comm in id_comms:
  177. ids = id_comm.split("-")
  178. other = ids[0] if id_ == ids[1] else ids[1]
  179. # what if before other was external ...
  180. if other in local_ids or other in external_ids:
  181. continue
  182. if comm_type == "mixed":
  183. other_pos = mixed_respnd_is_local.random()
  184. if other_pos == "local":
  185. local_ids.add(other)
  186. elif other_pos == "external":
  187. external_ids.add(other)
  188. elif comm_type == "external":
  189. if not other in initiators:
  190. external_ids.add(other)
  191. elif pos == "external":
  192. external_ids.add(id_)
  193. for id_comm in id_comms:
  194. ids = id_comm.split("-")
  195. other = ids[0] if id_ == ids[1] else ids[1]
  196. # what if before other was external ...
  197. if other in local_ids or other in external_ids:
  198. continue
  199. if not other in initiators:
  200. local_ids.add(other)
  201. # print(sorted(initiators))
  202. # print(sorted(local_ids))
  203. # print(sorted(external_ids))
  204. # IDs are always added to bot_configs in the same order under a given seed
  205. number_local_ids, number_external_ids = len(local_ids), len(external_ids)
  206. if number_local_ids > 0:
  207. reuse_count_local = int(reuse_percent_total * reuse_percent_local * number_local_ids)
  208. existing_local_ips = sorted(pcapops.get_existing_priv_ips(reuse_count_local))
  209. new_local_ips = sorted(pcapops.get_new_priv_ips(number_local_ids - len(existing_local_ips)))
  210. add_ids_to_config(sorted(local_ids), existing_local_ips, new_local_ips, bot_configs)
  211. if number_external_ids > 0:
  212. reuse_count_external = int(reuse_percent_total * reuse_percent_external * number_external_ids)
  213. existing_external_ips = sorted(pcapops.get_existing_external_ips(reuse_count_external))
  214. remaining = len(external_ids) - len(existing_external_ips)
  215. new_external_ips = sorted([ipgen.random_ip() for _ in range(remaining)])
  216. add_ids_to_config(sorted(external_ids), existing_external_ips, new_external_ips, bot_configs, idtype="external", router_mac=router_mac)
  217. # create bot port configs
  218. for bot in bot_configs:
  219. bot_configs[bot]["Port"] = gen_random_server_port()
  220. # create realistic ttl for every bot
  221. # Gamma distribution parameters derived from MAWI 13.8G dataset
  222. ids = bot_configs.keys()
  223. alpha, loc, beta = (2.3261710235, -0.188306914406, 44.4853123884)
  224. gd = gamma.rvs(alpha, loc=loc, scale=beta, size=len(ids))
  225. for pos, bot in enumerate(bot_configs):
  226. is_invalid = True
  227. pos_max = len(gd)
  228. while is_invalid:
  229. ttl = int(round(gd[pos]))
  230. if 0 < ttl < 256: # validity check
  231. is_invalid = False
  232. else:
  233. pos = index_increment(pos, pos_max)
  234. bot_configs[bot]["TTL"] = ttl
  235. # Setup initial parameters for packet creation
  236. BUFFER_SIZE = 1000
  237. pkt_gen = PacketGenerator()
  238. file_timestamp_prv = float(abstract_packets[packet_start_idx]["Time"])
  239. pcap_timestamp = self.get_param_value(Param.INJECT_AT_TIMESTAMP)
  240. padding = self.get_param_value(Param.PACKET_PADDING)
  241. packets = deque(maxlen=BUFFER_SIZE)
  242. total_pkts = 0
  243. limit_packetcount = self.get_param_value(Param.PACKETS_LIMIT)
  244. limit_duration = duration
  245. duration = 0
  246. path_attack_pcap = None
  247. nl_requests = {}
  248. # create packets to write to pcap file
  249. for abst_packet in abstract_packets[packet_start_idx:packet_end_idx+1]:
  250. # map/retrieve addresses to ids from input file
  251. id_src, id_dst = abst_packet["Src"], abst_packet["Dst"]
  252. if (not id_src in bot_configs) or (not id_dst in bot_configs):
  253. continue
  254. ip_src, ip_dst = bot_configs[id_src]["IP"], bot_configs[id_dst]["IP"]
  255. mac_src, mac_dst = bot_configs[id_src]["MAC"], bot_configs[id_dst]["MAC"]
  256. port_src, port_dst = bot_configs[id_src]["Port"], bot_configs[id_dst]["Port"]
  257. ttl = bot_configs[id_src]["TTL"]
  258. type_src, type_dst = bot_configs[id_src]["Type"], bot_configs[id_dst]["Type"]
  259. if type_src == "external" and type_dst == "external":
  260. continue
  261. if comm_type == "external":
  262. if type_src == "local" and type_dst == "local":
  263. continue
  264. # update timestamps and duration
  265. file_timestamp = float(abst_packet["Time"])
  266. file_time_delta = file_timestamp - file_timestamp_prv
  267. pcap_timestamp += file_time_delta
  268. duration += file_time_delta
  269. file_timestamp_prv = file_timestamp
  270. # if total number of packets has been sent or the attack duration has been exceeded, stop
  271. if ((limit_packetcount is not None and total_pkts >= limit_packetcount) or
  272. (limit_duration is not None and duration >= limit_duration)):
  273. break
  274. # create ip packet and add to packets list
  275. message_type = self.msg_types[int(abst_packet["Type"])]
  276. nl_size = 0
  277. if message_type == MessageType.SALITY_NL_REPLY:
  278. nl_size = randint(1, 25)
  279. elif message_type == MessageType.TIMEOUT:
  280. continue
  281. packet = pkt_gen.generate_mmcom_packet(ip_src=ip_src, ip_dst=ip_dst, ttl=ttl, mac_src=mac_src, mac_dst=mac_dst,
  282. port_src=port_src, port_dst=port_dst, message_type=message_type, neighborlist_entries=nl_size)
  283. PaddingGenerator.add_padding(packet, padding)
  284. packet.time = pcap_timestamp
  285. packets.append(packet)
  286. total_pkts += 1
  287. # Store timestamp of first packet (for attack label)
  288. if total_pkts <= 1:
  289. self.attack_start_utime = packets[0].time
  290. elif total_pkts % BUFFER_SIZE == 0: # every 1000 packets write them to the pcap file (append)
  291. packets = list(packets)
  292. PaddingGenerator.equal_length(packets)
  293. last_packet = packets[-1]
  294. path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
  295. packets = deque(maxlen=BUFFER_SIZE)
  296. if len(packets) > 0:
  297. packets = list(packets)
  298. PaddingGenerator.equal_length(packets)
  299. path_attack_pcap = self.write_attack_pcap(packets, True, path_attack_pcap)
  300. last_packet = packets[-1]
  301. # Store timestamp of last packet
  302. self.attack_end_utime = last_packet.time
  303. # Return packets sorted by packet by timestamp and total number of packets (sent)
  304. return total_pkts , path_attack_pcap