MembersMgmtCommAttack.py 16 KB

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