scanner_wrapper.py 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import os
  2. import logging
  3. import collections
  4. import threading
  5. import subprocess
  6. import time
  7. from subprocess import PIPE, TimeoutExpired
  8. logger = logging.getLogger("pra_framework")
  9. class ZmapWrapper(object):
  10. def __init__(self, **kwargs):
  11. # there ary many arguments so we take a more efficient approach
  12. timestamp = time.strftime("%Y_%M_%d__%H_%M_%S")
  13. prop_defaults = {
  14. "port_target": 1234,
  15. "filename_output_csv" : None,
  16. "filename_blacklist_target_ip": None,
  17. # TODO: just for online scans
  18. "filename_whitelist_target_ip": None,
  19. "dirname_logs": None,
  20. # scan options
  21. "rate_kbit_per_s": 1000,
  22. "probes_per_target": 1,
  23. "timeout_wait": 0,
  24. # network options
  25. "interface_name": "eth0",
  26. "mac_source": None,
  27. "mac_gw": None,
  28. # probe module, we only use the pra module
  29. "probe_module": "tcp_synscan_pra",
  30. #"probe_module": "tcp_synscan",
  31. # additional options
  32. "marker_encoding": 0,
  33. "markervalue": None,
  34. "markerbits_value": 32,
  35. "markerbits_checksum": 0,
  36. "markerbits_dst_small" : 0,
  37. # disable status outputs, directly exit scanner after finishing (for small address ranges to speed up)
  38. "disable_monitor": 0,
  39. "filename_config": None,
  40. # target adresses as string: "1.2.3.4 1.2.4.0/24 ..."
  41. "target_addresses": None,
  42. "verbosity": 3,
  43. #
  44. "ready_callback": None,
  45. "name_timestamp": timestamp,
  46. # directory which contains "src/zmap wihtout trailing slash"
  47. "base_dir_zmap": "../zmap",
  48. # avoid establishing process communication and don't wait for process to finish
  49. "fast_mode": False,
  50. "scan_timeout": -1
  51. }
  52. for (prop, default) in prop_defaults.items():
  53. setattr(self, prop, kwargs.get(prop, default))
  54. if self.target_addresses is None:
  55. raise Exception("Not address given to scan")
  56. self.filename_zmap_bin = os.path.join(self.base_dir_zmap, "src/zmap")
  57. # use absolute path
  58. #self.filename_zmap_bin = os.path.abspath(self.filename_zmap_bin)
  59. # logger.debug("zmap binary: %s" % self.filename_zmap_bin)
  60. """
  61. if self.filename_blacklist_target_ip is None:
  62. # TODO: change to full blacklist file
  63. self.filename_zmap_blacklist = os.path.join(self.base_dir_zmap, "blacklist.conf")
  64. #self.filename_blacklist_target_ip = os.path.join(self.base_dir_zmap, "blacklist.conf_minimal")
  65. """
  66. self._scan_process = None
  67. self._is_running = False
  68. def start(self):
  69. """
  70. Start a port scan. This call is blocking until the
  71. process is finished. Call stop() to terminate process earlier.
  72. """
  73. if self._is_running:
  74. logger.warning("scanner is allready running")
  75. return
  76. # aggregate scan parameters
  77. cmd = [self.filename_zmap_bin]
  78. # basic arguments
  79. # this is generally not needed when using destination port marker but we set is per default
  80. cmd.extend(["-p", "%d" % self.port_target])
  81. # TODO: activate/deactivate if needed
  82. if self.filename_blacklist_target_ip is not None:
  83. cmd.extend(["-b", self.filename_blacklist_target_ip])
  84. """
  85. if self.filename_whitelist_target_ip is not None:
  86. cmd.extend(["-w", self.filename_whitelist_target_ip])
  87. """
  88. if self.filename_output_csv is not None:
  89. cmd.extend(["-o", self.filename_output_csv])
  90. # data output
  91. cmd.extend(["-O", "csv"])
  92. cmd.extend(["-f", "classification,saddr,daddr,daddr_inner_icmp,sport,dport,success"])
  93. # scan options
  94. cmd.extend(["-B", "%dK" % self.rate_kbit_per_s])
  95. cmd.extend(["-P", "%d" % self.probes_per_target])
  96. if self.filename_output_csv is None:
  97. cmd.extend(["-c", "%d" % self.timeout_wait])
  98. else:
  99. # wait at minimum 2 seconds at end of scan to receive feedback
  100. cmd.extend(["-c", "%d" % 2])
  101. #cmd.extend(["--hello-timeout", self._timeout_syn])
  102. # network options
  103. if self.mac_gw is not None:
  104. cmd.extend(["-G", self.mac_gw])
  105. if self.mac_source is not None:
  106. cmd.extend(["--source-mac", self.mac_source])
  107. cmd.extend(["-i", self.interface_name])
  108. # probe modules
  109. cmd.extend(["-M", self.probe_module])
  110. # additional options
  111. cmd.extend(["--marker_encoding", "%d" % self.marker_encoding])
  112. if self.markervalue is not None:
  113. cmd.extend(["--use_markervalue", "1"])
  114. cmd.extend(["--markervalue", "%d" % self.markervalue])
  115. cmd.extend(["--markerbits_value", "%d" % self.markerbits_value])
  116. cmd.extend(["--markerbits_checksum", "%d" % self.markerbits_checksum])
  117. cmd.extend(["--disable_monitor", "%d" % self.disable_monitor])
  118. if self.filename_config is not None:
  119. cmd.extend(["-C", self.filename_config])
  120. cmd.extend(["-v", "%d" % self.verbosity])
  121. # TODO: activate for zmap internal Logging
  122. if self.dirname_logs is not None:
  123. cmd.extend(["-L", self.dirname_logs])
  124. # TODO: just for testing
  125. # limit scan time
  126. if self.scan_timeout > 0:
  127. cmd.extend(["-t", "%d" % self.scan_timeout])
  128. # two cores are enough to achieve a maximum speed
  129. #cmd.extend(["--cores", "0"])
  130. #cmd.extend(["--cores", "0,1"])
  131. cmd.extend(["--cores", "0,1,2"])
  132. # addresses as list
  133. cmd.extend(self.target_addresses)
  134. logger.debug("starting scanner: " + " ".join(cmd))
  135. self._is_running = True
  136. #return
  137. # start the process
  138. try:
  139. #self._scan_process = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
  140. if not self.fast_mode:
  141. self._scan_process = subprocess.Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE)
  142. else:
  143. self._scan_process = subprocess.Popen(cmd)
  144. except Exception:
  145. logger.warning("could not start process, killing it")
  146. self._scan_process.kill()
  147. #if self.fast_mode:
  148. # return
  149. # TODO: adjust
  150. # if self._scan_process.returncode != 0:
  151. # logger.warning("something went wrong while scanning, check output")
  152. # wait until scan has finished
  153. try:
  154. self._scan_process.wait()
  155. self._check_process_output()
  156. except TimeoutExpired:
  157. # this will update date for status retrieval (time left)
  158. self._check_process_output()
  159. if self._is_running:
  160. self.stop()
  161. if self.ready_callback is not None:
  162. # self._extract_ips()
  163. self.ready_callback(self)
  164. def _check_process_output(self):
  165. try:
  166. outs, errs = self._scan_process.communicate(timeout=1)
  167. #if self.disable_monitor == 0:
  168. # logger.debug("standard output\n:" + outs.decode("ASCII"))
  169. # logger.debug("error output:\n" + errs.decode("ASCII"))
  170. except:
  171. pass
  172. def stop(self):
  173. if not self._is_running:
  174. logger.warning("scanner was not running (anymore?)")
  175. return
  176. self._is_running = False
  177. # avoid ResourceWarning: unclosed file <_io.BufferedReader name=7>
  178. try:
  179. self._scan_process.stdout.close()
  180. self._scan_process.stdin.close()
  181. self._scan_process.stderr.close()
  182. except:
  183. pass
  184. try:
  185. # self._scan_process.terminate()
  186. # do it the hard way, we don't need any resume storing
  187. self._scan_process.kill()
  188. except ProcessLookupError as e:
  189. # logger.warning("could not terminate process")
  190. # logger.warning(e)
  191. # assume process already finished
  192. pass