Utility.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import ipaddress
  2. from random import randint, uniform
  3. from os import urandom
  4. from datetime import datetime
  5. from calendar import timegm
  6. from lea import Lea
  7. platforms = {"win7", "win10", "winxp", "win8.1", "macos", "linux", "win8", "winvista", "winnt", "win2000"}
  8. platform_probability = {"win7": 48.43, "win10": 27.99, "winxp": 6.07, "win8.1": 6.07, "macos": 5.94, "linux": 3.38,
  9. "win8": 1.35, "winvista": 0.46, "winnt": 0.31}
  10. x86_nops = {b'\x90', b'\xfc', b'\xfd', b'\xf8', b'\xf9', b'\xf5', b'\x9b'}
  11. x86_pseudo_nops = {b'\x97', b'\x96', b'\x95', b'\x93', b'\x92', b'\x91', b'\x99', b'\x4d', b'\x48', b'\x47', b'\x4f',
  12. b'\x40', b'\x41', b'\x37', b'\x3f', b'\x27', b'\x2f', b'\x46', b'\x4e', b'\x98', b'\x9f', b'\x4a',
  13. b'\x44', b'\x42', b'\x43', b'\x49', b'\x4b', b'\x45', b'\x4c', b'\x60', b'\x0e', b'\x1e', b'\x50',
  14. b'\x55', b'\x53', b'\x51', b'\x57', b'\x52', b'\x06', b'\x56', b'\x54', b'\x16', b'\x58', b'\x5d',
  15. b'\x5b', b'\x59', b'\x5f', b'\x5a', b'\x5e', b'\xd6'}
  16. forbidden_chars = [b'\x00', b'\x0a', b'\x0d']
  17. def update_timestamp(timestamp, pps, delay=0):
  18. """
  19. Calculates the next timestamp to be used based on the packet per second rate (pps) and the maximum delay.
  20. :return: Timestamp to be used for the next packet.
  21. """
  22. if delay == 0:
  23. # Calculate request timestamp
  24. # To imitate the bursty behavior of traffic
  25. randomdelay = Lea.fromValFreqsDict({1 / pps: 70, 2 / pps: 20, 5 / pps: 7, 10 / pps: 3})
  26. return timestamp + uniform(1 / pps, randomdelay.random())
  27. else:
  28. # Calculate reply timestamp
  29. randomdelay = Lea.fromValFreqsDict({2 * delay: 70, 3 * delay: 20, 5 * delay: 7, 10 * delay: 3})
  30. return timestamp + uniform(1 / pps + delay, 1 / pps + randomdelay.random())
  31. def get_interval_pps(complement_interval_pps, timestamp):
  32. """
  33. Gets the packet rate (pps) for a specific time interval.
  34. :param complement_interval_pps: an array of tuples (the last timestamp in the interval, the packet rate in the
  35. corresponding interval).
  36. :param timestamp: the timestamp at which the packet rate is required.
  37. :return: the corresponding packet rate (pps) .
  38. """
  39. for row in complement_interval_pps:
  40. if timestamp<=row[0]:
  41. return row[1]
  42. return complement_interval_pps[-1][1] # in case the timstamp > capture max timestamp
  43. def get_nth_random_element(*element_list):
  44. """
  45. Returns the n-th element of every list from an arbitrary number of given lists.
  46. For example, list1 contains IP addresses, list 2 contains MAC addresses. Use of this function ensures that
  47. the n-th IP address uses always the n-th MAC address.
  48. :param element_list: An arbitrary number of lists.
  49. :return: A tuple of the n-th element of every list.
  50. """
  51. range_max = min([len(x) for x in element_list])
  52. if range_max > 0: range_max -= 1
  53. n = randint(0, range_max)
  54. return tuple(x[n] for x in element_list)
  55. def index_increment(number: int, max: int):
  56. if number + 1 < max:
  57. return number + 1
  58. else:
  59. return 0
  60. def get_rnd_os():
  61. """
  62. Chooses random platform over an operating system probability distribution
  63. :return: random platform as string
  64. """
  65. os_dist = Lea.fromValFreqsDict(platform_probability)
  66. return os_dist.random()
  67. def check_platform(platform: str):
  68. """
  69. Checks if the given platform is currently supported
  70. if not exits with error
  71. :param platform: the platform, which should be validated
  72. """
  73. if platform not in platforms:
  74. print("\nERROR: Invalid platform: " + platform + "." +
  75. "\n Please select one of the following platforms: ", platforms)
  76. exit(1)
  77. def get_ip_range(start_ip: str, end_ip: str):
  78. """
  79. Generates a list of IPs of a given range. If the start_ip is greater than the end_ip, the reverse range is generated
  80. :param start_ip: the start_ip of the desired IP-range
  81. :param end_ip: the end_ip of the desired IP-range
  82. :return: a list of all IPs in the desired IP-range, including start-/end_ip
  83. """
  84. start = ipaddress.ip_address(start_ip)
  85. end = ipaddress.ip_address(end_ip)
  86. ips = []
  87. if start < end:
  88. while start <= end:
  89. ips.append(start.exploded)
  90. start = start+1
  91. elif start > end:
  92. while start >= end:
  93. ips.append(start.exploded)
  94. start = start-1
  95. else:
  96. ips.append(start_ip)
  97. return ips
  98. def generate_source_port_from_platform(platform: str, previousPort=0):
  99. """
  100. Generates the next source port according to the TCP-port-selection strategy of the given platform
  101. :param platform: the platform for which to generate source ports
  102. :param previousPort: the previously used/generated source port. Must be 0 if no port was generated before
  103. :return: the next source port for the given platform
  104. """
  105. check_platform(platform)
  106. if platform in {"winnt", "winxp", "win2000"}:
  107. if (previousPort == 0) or (previousPort + 1 > 5000):
  108. return randint(1024, 5000)
  109. else:
  110. return previousPort + 1
  111. elif platform == "linux":
  112. return randint(32768, 61000)
  113. else:
  114. if (previousPort == 0) or (previousPort + 1 > 65535):
  115. return randint(49152, 65535)
  116. else:
  117. return previousPort + 1
  118. def get_filetime_format(timestamp):
  119. """
  120. Converts a timestamp into MS FILETIME format
  121. :param timestamp: a timestamp in seconds
  122. :return: MS FILETIME timestamp
  123. """
  124. boot_datetime = datetime.fromtimestamp(timestamp)
  125. if boot_datetime.tzinfo is None or boot_datetime.tzinfo.utcoffset(boot_datetime) is None:
  126. boot_datetime = boot_datetime.replace(tzinfo=boot_datetime.tzname())
  127. boot_filetime = 116444736000000000 + (timegm(boot_datetime.timetuple()) * 10000000)
  128. return boot_filetime + (boot_datetime.microsecond * 10)
  129. def get_rnd_boot_time(timestamp, platform="winxp"):
  130. """
  131. Generates a random boot time based on a given timestamp and operating system
  132. :param timestamp: a timestamp in seconds
  133. :param platform: a platform as string as specified in check_platform above. default is winxp. this param is optional
  134. :return: timestamp of random boot time in seconds since EPOCH
  135. """
  136. check_platform(platform)
  137. if platform is "linux":
  138. uptime_in_days = Lea.fromValFreqsDict({3: 50, 7: 25, 14: 12.5, 31: 6.25, 92: 3.125, 183: 1.5625,
  139. 365: 0.78125, 1461: 0.390625, 2922: 0.390625})
  140. elif platform is "macos":
  141. uptime_in_days = Lea.fromValFreqsDict({7: 50, 14: 25, 31: 12.5, 92: 6.25, 183: 3.125, 365: 3.076171875,
  142. 1461: 0.048828125})
  143. else:
  144. uptime_in_days = Lea.fromValFreqsDict({3: 50, 7: 25, 14: 12.5, 31: 6.25, 92: 3.125, 183: 1.5625,
  145. 365: 0.78125, 1461: 0.78125})
  146. timestamp -= randint(0, uptime_in_days.random()*86400)
  147. return timestamp
  148. def get_rnd_x86_nop(count=1, side_effect_free=False, char_filter=set()):
  149. """
  150. Generates a specified number of x86 single-byte (pseudo-)NOPs
  151. :param count: The number of bytes to generate
  152. :param side_effect_free: Determines whether NOPs with side-effects (to registers or the stack) are allowed
  153. :param char_filter: A set of bytes which are forbidden to generate
  154. :return: Random x86 NOP bytestring
  155. """
  156. result = b''
  157. nops = x86_nops.copy()
  158. if not side_effect_free:
  159. nops |= x86_pseudo_nops.copy()
  160. if not isinstance(char_filter, set):
  161. char_filter = set(char_filter)
  162. nops = list(nops-char_filter)
  163. for i in range(0, count):
  164. result += nops[randint(0, len(nops) - 1)]
  165. return result
  166. def get_rnd_bytes(count=1, ignore=None):
  167. """
  168. Generates a specified number of random bytes while excluding unwanted bytes
  169. :param count: Number of wanted bytes
  170. :param ignore: The bytes, which should be ignored, as an array
  171. :return: Random bytestring
  172. """
  173. if ignore is None:
  174. ignore = []
  175. result = b''
  176. for i in range(0, count):
  177. char = urandom(1)
  178. while char in ignore:
  179. char = urandom(1)
  180. result += char
  181. return result
  182. def check_payload_len(payload_len: int, limit: int):
  183. """
  184. Checks if the len of the payload exceeds a given limit
  185. :param payload_len: The length of the payload
  186. :param limit: The limit of the length of the payload which is allowed
  187. """
  188. if payload_len > limit:
  189. print("\nCustom payload too long: ", payload_len, " bytes. Should be a maximum of ", limit, " bytes.")
  190. exit(1)
  191. def get_bytes_from_file(filepath):
  192. """
  193. Converts the content of a file into its byte representation
  194. The content of the file can either be a string or hexadecimal numbers/bytes (e.g. shellcode)
  195. The file must have the keyword "str" or "hex" in its first line to specify the rest of the content
  196. If the content is hex, whitespaces, backslashes, "x", quotation marks and "+" are removed
  197. Example for a hexadecimal input file:
  198. hex
  199. "abcd ef \xff10\ff 'xaa' x \ ab"
  200. Output: b'\xab\xcd\xef\xff\x10\xff\xaa\xab'
  201. :param filepath: The path of the file from which to get the bytes
  202. :return: The bytes of the file (either a byte representation of a string or the bytes contained in the file)
  203. """
  204. try:
  205. file = open(filepath)
  206. result_bytes = b''
  207. header = file.readline().strip()
  208. content = file.read()
  209. if header == "hex":
  210. content = content.replace(" ", "").replace("\n", "").replace("\\", "").replace("x", "").replace("\"", "")\
  211. .replace("'", "").replace("+", "").replace("\r", "")
  212. try:
  213. result_bytes = bytes.fromhex(content)
  214. except ValueError:
  215. print("\nERROR: Content of file is not all hexadecimal.")
  216. file.close()
  217. exit(1)
  218. elif header == "str":
  219. result_bytes = content.strip().encode()
  220. else:
  221. print("\nERROR: Invalid header found: " + header + ". Try 'hex' or 'str' followed by endline instead.")
  222. file.close()
  223. exit(1)
  224. for forbidden_char in forbidden_chars:
  225. if forbidden_char in result_bytes:
  226. print("\nERROR: Forbidden character found in payload: ", forbidden_char)
  227. file.close()
  228. exit(1)
  229. file.close()
  230. return result_bytes
  231. except FileNotFoundError:
  232. print("\nERROR: File not found: ", filepath)
  233. exit(1)
  234. def handle_most_used_outputs(most_used_x):
  235. """
  236. :param most_used_x: Element or list (e.g. from SQL-query output) which should only be one element
  237. :return: most_used_x if it's not a list. The first element of most_used_x after being sorted if it's a list.
  238. None if that list is empty.
  239. """
  240. if isinstance(most_used_x, list):
  241. if len(most_used_x) == 0:
  242. return None
  243. most_used_x.sort()
  244. return most_used_x[0]
  245. else:
  246. return most_used_x