MembersMgmtCommAttack.py 14 KB

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