main_monitor_simulator.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. import os
  2. import logging
  3. import argparse
  4. import time
  5. import socket
  6. import random
  7. import requests
  8. import json
  9. import struct
  10. import multiprocessing
  11. from multiprocessing import Process, Queue
  12. import re
  13. from ipv4 import IPv4Address, IPv4Network
  14. import numpy as np
  15. from pypacker import psocket
  16. from pypacker import ppcap
  17. from pypacker.layer12.ethernet import Ethernet
  18. from pypacker.layer3 import ip
  19. from pypacker.layer4 import tcp
  20. logging.basicConfig(format="%(levelname)s (%(funcName)s): %(message)s")
  21. logger = logging.getLogger("pra_framework")
  22. logger.setLevel(logging.DEBUG)
  23. CURRENT_DIR = os.path.dirname(os.path.realpath(__file__))
  24. FILE_NODES = CURRENT_DIR + "/simulated_nodes.txt"
  25. RESPONSETYPE_NONE = 0
  26. RESPONSETYPE_TCP_RST = 1
  27. RESPONSETYPE_TCP_SYNACK = 2
  28. RESPONSETYPE_ICMP = 4
  29. RESPONSETYPES_TCP = {RESPONSETYPE_TCP_RST, RESPONSETYPE_TCP_SYNACK}
  30. RESPONSE_TYPES_DESCR = {RESPONSETYPE_NONE: "NONE",
  31. RESPONSETYPE_TCP_RST: "RST",
  32. RESPONSETYPE_TCP_SYNACK: "SYNACK",
  33. RESPONSETYPE_ICMP: "ICMP"}
  34. packipv4 = struct.Struct(">I").pack
  35. split_tab = re.compile("\t").split
  36. #cert = ("ssl/simulator/simulator_cert.pem", "ssl/simulator/simulator_key.pem")
  37. requests.packages.urllib3.disable_warnings()
  38. def store_packets(packets, filename="exception_packets.pcap"):
  39. writer = ppcap.Writer(filename=filename)
  40. for bts in packets:
  41. writer.write(bts)
  42. writer.close()
  43. class MonitorSimulator(object):
  44. """
  45. Simulates monitor nodes on a host system which in turn send attack information to a central CIDS.
  46. The simulation can be done using a virtual interface:
  47. modprobe dummy (optional if already loaded)
  48. ip link set name eth10 dev dummy0
  49. ip link show eth10
  50. ifconfig eth10 txqueuelen 10000
  51. Optimazation of /etc/sysctl.conf should be done.
  52. """
  53. def __init__(self,
  54. # total amount of monitors (monitor_ips + random generated)
  55. amount_monitors=10000,
  56. cidr_bits=0,
  57. # total amount of addresses which give scanner feedback
  58. amount_non_monitors=10000,
  59. # monitor IP addresses as ["1.2.3.4", ...]
  60. monitor_ips=[],
  61. seed=123456789,
  62. url_tracing="https://localhost:443",
  63. interface_name="lo",
  64. buffer_size=500000,
  65. # probability that a monitor returns a relevant scanner feedback (RST, SYN/ACK)
  66. prop_mon_scannerfeedback=0.8,
  67. prop_nwdrop=0.0,
  68. file_blacklist=None):
  69. logger.debug("monitors=%d, non monitors=%d, scanner feedback=%f, nw drop=%f" % (amount_monitors,
  70. amount_non_monitors,
  71. prop_mon_scannerfeedback,
  72. prop_nwdrop))
  73. self._amount_monitors = amount_monitors
  74. self._amount_non_monitors = amount_non_monitors
  75. self._prop_mon_scannerfeedback = prop_mon_scannerfeedback
  76. self._prop_nwdrop = prop_nwdrop
  77. self._blacklist_nw_objs = []
  78. self._blacklist_int_sub8 = set()
  79. if file_blacklist is not None:
  80. self._blacklist_nw_objs, self._blacklist_int_sub8 = self.read_blacklist(file_blacklist)
  81. self._rand = random.Random()
  82. self._rand.seed(a=seed, version=1)
  83. self._rand_nwloss = random.Random()
  84. self._rand_nwloss.seed(a=seed, version=1)
  85. logger.debug("TraCINg url=%s, interface=%s" % (url_tracing, interface_name))
  86. logger.debug("Initiating Queue, size: %d" % buffer_size)
  87. self._queue = Queue(maxsize=buffer_size)
  88. # Group response probabilities
  89. self._responsetypes = [
  90. RESPONSETYPE_NONE,
  91. RESPONSETYPE_TCP_RST,
  92. RESPONSETYPE_TCP_SYNACK,
  93. RESPONSETYPE_ICMP]
  94. self._file_identified_monitors = "./output_dir_testing/identified_monitors.csv"
  95. self._listening_ips = {}
  96. # TODO: adjust this for different subnets
  97. # this has to match _cidr_bits_stage1 in main_attack.py
  98. # /0
  99. self._host_addresses_mask = 0xFFFFFFFF
  100. self._host_addresses_mask >>= cidr_bits
  101. logger.debug("Max host IP addresses (host mask): %X" % self._host_addresses_mask)
  102. self._initiate_listening_ips(monitor_ips)
  103. logger.debug("total amount of listening addresses=%d (w/ + w/o feedback)" % len(self._listening_ips))
  104. self._sockets_read = []
  105. self._sockets_write = []
  106. self._is_running = False
  107. # TODO: adjust this on other platforms
  108. self._read_processes_amount = 2
  109. self._read_processes = []
  110. socket_read = psocket.SocketHndl(iface_name=interface_name,
  111. timeout=10,
  112. buffersize_recv=2 ** 29)
  113. self._sockets_read.append(socket_read)
  114. logger.debug("creating %d processes for reading packets" % self._read_processes_amount)
  115. for cnt in range(self._read_processes_amount):
  116. proc = Process(target=self._packet_collect_cycler, args=(cnt + 1,
  117. socket_read,
  118. self._queue,
  119. self._listening_ips))
  120. self._read_processes.append(proc)
  121. requests_session = requests.Session()
  122. #adapter = requests.adapters.HTTPAdapter(pool_connections=10000, pool_maxsize=10000)
  123. #requests_session.mount('https://', adapter)
  124. self._attack_reaction_processes = []
  125. # TODO: adjust this on other platforms
  126. self._attack_reaction_processes_amount = 5
  127. logger.debug("creating %d processes for attack reaction" % self._attack_reaction_processes_amount)
  128. for cnt in range(self._attack_reaction_processes_amount):
  129. socket_write = psocket.SocketHndl(iface_name=interface_name,
  130. timeout=60 * 60 * 24 * 7,
  131. #buffersize_send=2 ** 23)
  132. buffersize_send=2 ** 29)
  133. self._sockets_write.append(socket_write)
  134. attack_reaction_process = Process(
  135. target=self._attack_reaction_cycler,
  136. args=(cnt + 1,
  137. socket_write,
  138. self._queue,
  139. requests_session,
  140. url_tracing,
  141. self._listening_ips))
  142. self._attack_reaction_processes.append(attack_reaction_process)
  143. def start(self):
  144. if self._is_running:
  145. return
  146. logger.debug("starting collection and reaction logic")
  147. self._is_running = True
  148. for process in self._attack_reaction_processes:
  149. process.start()
  150. time.sleep(2)
  151. for proc in self._read_processes:
  152. proc.start()
  153. def stop(self):
  154. if not self._is_running:
  155. return
  156. self._is_running = False
  157. for sock in self._sockets_read + self._sockets_write:
  158. sock.close()
  159. self._queue.close()
  160. for proc in self._read_processes:
  161. # don't wait for process to finish, just terminate
  162. proc.terminate()
  163. for proc in self._attack_reaction_processes:
  164. # don't wait for process to finish, just terminate
  165. proc.terminate()
  166. def _initiate_listening_ips(self, monitor_ips):
  167. """
  168. Initiate IP addresses which trigger alerts and/or give scanner feedback.
  169. monitor_ips -- monitor IPs to be added as "a.b.c.d"
  170. """
  171. # apply reaction on custom defined monitors
  172. if len(monitor_ips) > 0:
  173. logger.debug("listening IP addresses were given explicitly, adding %d" % len(monitor_ips))
  174. for ip_mon in monitor_ips:
  175. self._listening_ips[IPv4Address(ip_str=ip_mon).packed] = [RESPONSETYPE_TCP_RST, True]
  176. #logger.info("creating %d random IPs" % self._amount_monitors)
  177. self._create_random_ips(self._amount_monitors, is_monitor_ip=True)
  178. logger.debug("total amount of monitors=%d" % len(self._listening_ips))
  179. if self._amount_non_monitors > 0:
  180. # target = current amount + non_monitor_ips * probability_nonmonitor
  181. target_amount = len(self._listening_ips) + self._amount_non_monitors
  182. logger.info("creating %d non monitor feedback IPs" % self._amount_non_monitors)
  183. if target_amount > self._host_addresses_mask:
  184. raise Exception("!!!! too many addresses to create! %d >= %d" % (target_amount, self._host_addresses_mask))
  185. self._create_random_ips(target_amount, is_monitor_ip=False)
  186. amount_mon = sum([1 for _, resp_mon in self._listening_ips.items() if resp_mon[1]])
  187. amount_nonmon = sum([1 for _, resp_mon in self._listening_ips.items() if not resp_mon[1]])
  188. logger.info("sanity check: monitors=%d, non monitors=%d" % (amount_mon, amount_nonmon))
  189. self._set_feedback_types()
  190. def check_matches(self):
  191. """
  192. Check if every match in ./output_dir_testing/identified_monitors.csv is
  193. really a monitor.
  194. """
  195. fd = open(self._file_identified_monitors, "r")
  196. monitor_found = 0
  197. monitor_false_positive_no_monitor = 0
  198. monitor_false_positive_unknown = 0
  199. # skip header
  200. fd.readline()
  201. all_ips = set()
  202. for line in fd:
  203. ip_str = split_tab(line)[0]
  204. try:
  205. ips = IPv4Network(nw_ip_str_prefix=ip_str).hosts if "/" in line else [IPv4Address(ip_str=ip_str).packed]
  206. except TypeError:
  207. continue
  208. except Exception as ex:
  209. logger.warning("something went wrong while checking IP address=%r" % ip_str)
  210. print(ex)
  211. break
  212. for ip_bytes in ips:
  213. if ip_bytes in all_ips:
  214. logger.warning("allready counted, duplicate? from file=%s, converted=%r" %
  215. (ip_str, IPv4Address(ip_bytes=ip_bytes)))
  216. all_ips.add(ip_bytes)
  217. try:
  218. assert self._listening_ips[ip_bytes][1] is True
  219. monitor_found += 1
  220. except KeyError:
  221. monitor_false_positive_unknown += 1
  222. if monitor_false_positive_unknown % 100 == 0:
  223. logger.debug("%d: unknown to simulator: %r" % (monitor_false_positive_unknown,
  224. IPv4Address(ip_bytes=ip_bytes)))
  225. except AssertionError:
  226. monitor_false_positive_no_monitor += 1
  227. logger.debug("known to simulator but not a monitor: %r" % IPv4Address(ip_bytes=ip_bytes))
  228. logger.info("correctly identified monitors: %d" % monitor_found)
  229. logger.info("unknown to simulator (false positive): %d" % monitor_false_positive_unknown)
  230. logger.info("found but not monitor (false positive): %d" % monitor_false_positive_no_monitor)
  231. fd.close()
  232. def _set_feedback_types(self):
  233. """
  234. Set the type of feedback given on scanner level.
  235. """
  236. logger.debug("setting feedback types")
  237. randrange = self._rand.randrange
  238. rand_0_1 = self._rand.random
  239. for key, _ in self._listening_ips.items():
  240. #logger.debug(ip_bytes)
  241. # non monitor = give feedback in any case, monitor = feedback based on probability
  242. feedback_prop = self._prop_mon_scannerfeedback if self._listening_ips[key][1] else 1.0
  243. is_feedback = rand_0_1() <= feedback_prop
  244. # feedback: RST or SYN/ACK
  245. self._listening_ips[key][0] = self._responsetypes[randrange(1, 3)] if is_feedback else RESPONSETYPE_NONE
  246. @staticmethod
  247. def read_blacklist(filename):
  248. """
  249. return -- list of blacklisted IP network objects
  250. """
  251. logger.debug("Reading blacklist from: %s" % filename)
  252. addresses_nw_obj = []
  253. addresses_int_sub8 = set()
  254. total_addresses = 0
  255. fd = open(filename, "r")
  256. for line in fd:
  257. try:
  258. if line[0] == "#":
  259. continue
  260. nw = IPv4Network(nw_ip_str_prefix=line)
  261. total_addresses += nw.num_addresses
  262. addresses_nw_obj.append(nw)
  263. addresses_int_sub8.add(nw.ip_int >> 24)
  264. except:
  265. pass
  266. fd.close()
  267. logger.debug("Blacklist address groups=%d, addresses=%d, sub8=%d" %
  268. (len(addresses_nw_obj), total_addresses, len(addresses_int_sub8)))
  269. return addresses_nw_obj, addresses_int_sub8
  270. def _create_random_ips(self, target_amount, is_monitor_ip=True):
  271. """
  272. Create random even distributed IP addresses.
  273. target_amount -- Add addresses until this size is reached
  274. is_monitor_ip -- Add addresses of type monitor if True, add non monitor otherwise
  275. """
  276. logger.debug("creating %d random IP addresses, monitor=%r" % (target_amount, is_monitor_ip))
  277. randrange = self._rand.randrange
  278. #feedback_prop = self._prop_mon_scannerfeedback if is_monitor_ip else 1.0
  279. cnt = 0
  280. while len(self._listening_ips) < target_amount:
  281. cnt += 1
  282. ip_num = randrange(0, self._host_addresses_mask)
  283. ip_bytes = packipv4(ip_num)
  284. # don't overwrite old values
  285. #if ip_bytes in self._listening_ips:
  286. # continue
  287. if (ip_num >> 24) in self._blacklist_int_sub8:
  288. #logger.debug("checking: %r" % ip_bytes)
  289. for ip_nw in self._blacklist_nw_objs:
  290. if ip_num & ip_nw.ip_int == ip_nw.ip_int:
  291. #logger.debug("skipping: %r" % ip_nw)
  292. continue
  293. #logger.debug(ip_bytes)
  294. # feedback: RST or SYN/ACK
  295. #print(ip_bytes)
  296. self._listening_ips[ip_bytes] = [None, is_monitor_ip]
  297. def _create_clustered_monitors(self, target_amount):
  298. """
  299. Create clustered IP addresses by generating sequential addresses using
  300. pareto and exponential distributions.
  301. target_amount -- target amount of IP addresses for _listening_ips
  302. """
  303. logger.info("creating %d random clustered IPs" % self._amount_monitors)
  304. # classical pareto by "scale=m=1"
  305. #s = np.random.pareto(a, 10000) + m
  306. # shape = a = 1.x, scale = 1 (not adjusted), location = 1 (min value 1)
  307. # mean = sum/length = shape * scale / (shape - 1)
  308. # -> a=2.1: mean=~1.9
  309. # -> a=1.1: mean=~10
  310. # -> a=1.01: mean=~100
  311. pareto = np.random.pareto
  312. pareto_shape = 1.01
  313. exponential = np.random.exponential
  314. # mean=scale
  315. exponential_scale = 200
  316. current_ip = 0
  317. ip_max = self._host_addresses_mask - 10000
  318. if ip_max < 0:
  319. logger.warning("address range too small: %d<%d, will not create any monitors" %
  320. (self._host_addresses_mask, 10000))
  321. return
  322. while len(self._listening_ips) < target_amount and current_ip < ip_max:
  323. sequential_add = int(pareto(pareto_shape) + 1)
  324. logger.debug(sequential_add)
  325. for ip_int in range(current_ip, current_ip + sequential_add):
  326. ip_bytes = packipv4(ip_int)
  327. self._listening_ips[ip_bytes] = [None, True]
  328. current_ip += sequential_add + int(exponential(scale=exponential_scale))
  329. if len(self._listening_ips) < target_amount:
  330. logger.warning("could not create enough monitors (to small range?) %d < %d" %
  331. (len(self._listening_ips), target_amount))
  332. def _packet_collect_cycler(self, procnum, sockethndl, queue, ip_whitelist):
  333. """
  334. Collects packets and puts them into the Queue if the destination IP address
  335. matches an address in ip_whitelist.
  336. sockethndl -- A SocketHandler to read bytes from
  337. queue -- A queue to write bytes to if an destination IP addres matches
  338. ip_whitelist -- A dictionary posing a whitelist
  339. """
  340. logger.debug("starting listening process Nr. %d" % procnum)
  341. psock_recv = sockethndl._socket_recv.recv
  342. queue_put = queue.put
  343. cnt = 0
  344. last_cnt = 0
  345. while True:
  346. try:
  347. bts = psock_recv(64)
  348. cnt += 1
  349. # logger.debug("...")
  350. # time.sleep(1)
  351. if not bts[14 + 16: 14 + 16 + 4] in ip_whitelist:
  352. continue
  353. #logger.debug("!!!!!!! got a packet")
  354. queue_put(bts)
  355. except socket.timeout:
  356. # logger.debug("read timeout..")
  357. if last_cnt != cnt:
  358. logger.debug("collector %d: amount of probes to NW=%d" % (procnum, cnt))
  359. last_cnt = cnt
  360. #continue
  361. def _attack_reaction_cycler(self, procnum, socket_write, queue, requests_session, url_tracing, listening_ips):
  362. """
  363. Examines packets in the buffer and reacts on SYN-pings for
  364. specific target IPs. Possible reactions are: sending attack
  365. events to TraCINg and/or feedback on scanner level
  366. """
  367. logger.debug("starting reaction process No. %d" % procnum)
  368. cnt = 0
  369. while True:
  370. bts = queue.get()
  371. try:
  372. packet = Ethernet(bts)
  373. except:
  374. logger.warning("could not parse received packet:\n%r" % bts)
  375. store_packets(bts)
  376. continue
  377. ip_bytes = packet[ip.IP].dst
  378. # this is a monitor, send event to TraCINg
  379. if listening_ips[ip_bytes][1]:
  380. cnt += 1
  381. try:
  382. #if packet.body_handler.dst not in listening_ips:
  383. # continue
  384. p_ip = packet.ip
  385. p_tcp = p_ip.tcp
  386. if cnt % 500 == 0:
  387. logger.debug("%d> %d: monitor was scanned: %s:%d -> %s:%d" %
  388. (procnum, cnt, p_ip.src_s, p_tcp.sport, p_ip.dst_s, p_tcp.dport))
  389. """
  390. Inform TraCINg about an attack.
  391. """
  392. post_data_dict = {
  393. "sensor": {
  394. "name": "monitorsimulation_" + packet[ip.IP].dst_s,
  395. "type": "Honeypot"},
  396. "src": {
  397. "ip": packet[ip.IP].src_s,
  398. "port": "%d" % packet[tcp.TCP].sport},
  399. "dst": {
  400. "ip": packet[ip.IP].dst_s,
  401. "port": "%d" % packet[tcp.TCP].dport},
  402. "type": 11,
  403. "log": "Predefined Log",
  404. "md5sum": "7867de13bf22a7f3e3559044053e33e7",
  405. "date": ("%d" % time.time())
  406. }
  407. #headers = {"Content-type": "application/x-www-form-urlencoded",
  408. # "Accept": "text/plain"}
  409. post_data = json.dumps(post_data_dict)
  410. # logger.debug("POST data:")
  411. # logger.debug(post_data)
  412. requests_session.post(url=url_tracing, data=post_data, verify=False, stream=False)
  413. # response = conn.getresponse()
  414. #logger.debug(response)
  415. #conn = http.client.HTTPSConnection(self._tracing_host, self._tracing_port)
  416. #conn.request("POST", self._tracing_path, body=post_data)
  417. #logger.warning("attack event sent to TraCINg, monitor: %s" % packet[ip.IP].dst_s)
  418. except Exception as ex:
  419. logger.warning("could not inform TraCINg: %r" % ex)
  420. try:
  421. responsetype = listening_ips[ip_bytes][0]
  422. if responsetype == RESPONSETYPE_NONE:
  423. continue
  424. elif responsetype in RESPONSETYPES_TCP:
  425. tcp_packet = packet.body_handler.body_handler
  426. tcp_packet.ack = tcp_packet.seq + 1
  427. tcp_packet.seq = 12345
  428. tcp_packet.flags = tcp.TH_SYN | tcp.TH_ACK if responsetype == RESPONSETYPE_TCP_SYNACK else tcp.TH_RST
  429. packet.reverse_all_address()
  430. #socket_write.send(packet.bin())
  431. socket_write.send(packet.bin(update_auto_fields=False))
  432. elif responsetype == RESPONSETYPE_ICMP:
  433. # ICMP indicates unreachable hosts, ignore
  434. pass
  435. else:
  436. logger.warning("unknown response type for %s: %r" % (packet.ip.dst_s, responsetype))
  437. except Exception as ex:
  438. logger.warning("could not send scanner feedback: %r" % ex)
  439. logger.debug("reaction cycler is terminating")
  440. if __name__ == "__main__":
  441. parser = argparse.ArgumentParser()
  442. parser.add_argument("-i", "--interface", help="Interface to listen on", default="eth10")
  443. parser.add_argument("-m", "--monitors", help="Amount of monitors to be simulated", type=int, default=1000)
  444. parser.add_argument("-r", "--cidrbits", help="CIDR bits of simulated network", type=int, default=0)
  445. parser.add_argument("-f", "--monitorfeedback", help="Probability for monitor feedback", type=float, default=0.9)
  446. parser.add_argument("-n", "--nonmonitors", help="Amount of non-monitors to be simulated", type=int, default=0)
  447. parser.add_argument("-d", "--nwdrop", help="Probability for network drops", type=float, default=0)
  448. parser.add_argument("-b", "--buffersize", help="Buffer to be used for storing received packets", type=int,
  449. default=1000000)
  450. parser.add_argument("-s", "--seed", help="Seed to be used to distribute nodes", type=int, default=123456789)
  451. parser.add_argument("-u", "--url", help="HTTPS URL of TraCINg to send events to", default="https://localhost:443")
  452. args = parser.parse_args()
  453. monitor_ips_init = []
  454. logger.info("amount of CPUs: %d" % multiprocessing.cpu_count())
  455. monitorsimulator = MonitorSimulator(
  456. amount_monitors=args.monitors,
  457. cidr_bits=args.cidrbits,
  458. amount_non_monitors=args.nonmonitors,
  459. monitor_ips=monitor_ips_init,
  460. interface_name=args.interface,
  461. buffer_size=args.buffersize,
  462. url_tracing=args.url,
  463. prop_mon_scannerfeedback=args.monitorfeedback,
  464. prop_nwdrop=args.nwdrop)
  465. input("press enter to continue")
  466. monitorsimulator.start()
  467. print("")
  468. user_input = None
  469. while user_input != "quit":
  470. user_input = input("enter 'quit' to quit, 'check' to compare monitors to those in %s\n" %
  471. monitorsimulator._file_identified_monitors)
  472. if user_input == "check":
  473. monitorsimulator.check_matches()
  474. else:
  475. logger.debug("%d" % monitorsimulator._queue.qsize())
  476. print("")
  477. logger.debug("stopping simulation")
  478. monitorsimulator.stop()