Sunday, 31 March 2013

Wi-Fi SSID Sniffer in 10 Lines of Python



One of the things I left out when I put out the SecurityTube Wi-Fi Security Megaprimer was the programming aspects of Wi-Fi Security and Hacking. Around a decade back, when I used to work as a programmer and researcher, most of my code for Wi-Fi fuzzers / sniffers / injectors was in C. However, C requires you to do everything from scratch. I may take up an example of a Wi-Fi Sniffer in C as a separate blog post. In this post, we will be looking at writing a Wi-Fi Sniffer in Python :) 

I've probably used Python for over 6-7 years now and its really been my substitute for C when all I need is quick prototyping. This post should illustrate the power of Python and the great open source library support it has from the community.

[Update] Writing an SSID Sniffer in 11 Lines of Python using Raw Sockets (no 3rd Party Libs) 

This post made it to reddit.com/r/programming and a lot of developers felt it was unfair to use scapy and claim 11 lines :) I agree, so here is an implementation using Raw Sockets: 

http://hackoftheday.securitytube.net/2013/04/wi-fi-ssid-sniffer-in-12-lines-of.html

The code is not as readable as the one on this post but sufficiently proves without a doubt that it can be done :) 
 

Objective: To Code a Wi-Fi SSID Sniffer in 10 Lines of Python

For those in a hurry, here is the code:





Requirements:  You already have a Wi-Fi card capable of sniffing the air using monitor mode. I am using the ALFA AWUS036H.

Now let me break up the whole process of writing the above.

Step 1:  Find a library which can allow us to easily sniff Wi-Fi and hands us the packets. There are many libraries which allow us to do this - scapy is one of the better ones. Install scapy (comes pre-installed in BT5)


Step 2: Scapy can be run in interactive mode or used as a library. The interactive mode is great for quick prototyping and we will use this. Run scapy and type in "conf" to see the current configuration.




 Step 3: Check for the value of "iface". The default interface is typically "eth0" 


Step 4: Let set the interface to "mon0" out monitor mode wireless interface.  Now we use the sniff() function to get packets from mon0. We specify the number of packets we want to sniff. Scapy will block till the time it does not receive that many number of packets. It returns the packets as a Python list. 





Step 5:  We can look at a quick summary of the packets using .summary()




 Step 6:  Scapy has a really cool way to visualize packets real quick using .pdfdump() 




Step 7: This opens up a PDF with the whole packet visualized for you! wow! :) 


 Step 8:  A quick use of Wireshark to look at the Beacon frames tells us that the type = 0 and subtype = 8. Also, the AP MAC address is there in Addr2 and Addr3 (BSSID)





Step 9: Awesome! So now we know that that we need to first filter for 802.11 packets, then check for Packet Type = 0 (Management Frame) and Packet Subtype = 8 (Beacon Frame). The AP MAC can picked up by Address 2 / 3 field in the packet. A closer look at the Scapy class documentation for 802.11 reveals that the SSID is available in the "info" field. Let's put all of this together in code:



 Step 10: We are almost done! Lets now write out the prototype as a standalone Python script. We now import scapy as a library we will use. 



Step 11: Lets write out the whole packet parsing logic into a separate function called PacketHandler as shown below:



Step 12: What we would really want is every time a new packet is received by our code, the PacketHandler function is invoked. Wouldn't it be great if scapy allowed us to define a callback function for every packet? :) Good news is - it does. All you have to do is mention the callback function with an additional argument "prn" to sniff.





Step 13: Full code view till now



Step 14: Verification! Lets run the script.




Step 15: Works Great! However, there is a small problem -- as every AP sends out tons of Beacon Frames we end up seeing the same access point over and over again. We need to do something to ensure that we only see new access points - so we have a comprehensive list of everything in the air. 

The solution is really simple - we define a list called ap_list and add new access points we discover to it (the MAC address). We only display the AP when it is first added. The modified code is below. 


 Step 16: Let's run this now! 



 We see a couple of NULL SSID access points as well :) 

I have this whole blog post explained as a 15 minute video on SecurityTube.net:   http://www.securitytube.net/video/7262  This will help in case you need a more detailed explanation. 

Need more help with Wi-Fi Security or Python programming. Please checkout our paid courses on SecurityTube Training.   


Exercises you can try based on this code sample:

1. Print out more statistics about the Access Point such as the Channel, Rates etc. 

2. Can you detect the Clients which are connected to the Access Point? 

3. Can you write a program to sniff all the Probe Requests made by Wi-Fi Clients? 


Full Source Code of this demo for easy copy/paste:





5 comments:

  1. #!/usr/bin/env python
    from scapy.all import *
    ap_set = set()
    def PacketHandler(pkt):
    if pkt.haslayer(Dot11) and (pkt.type, pkt.subtype) == (0, 0) and pkt.addr2 not in ap_set:
    ap_set.add(pkt.addr2)
    print "AP MAC: {} with SSID {}".format(pkt.addr2, pkt,info)
    sniff(iface="mon0", prn=PacketHandler)

    ReplyDelete
  2. This was a very helpful tutorial to me.

    I'm trying to do an additional step, retrieving the received signal strength from the packet. There's one way I found on the internet, explained here: http://comments.gmane.org/gmane.comp.security.scapy.general/4673.
    Basically, this gets the RSSI value like this: -(256-ord(pkt.notdecoded[-4:-3])), since it's in the notdecoded field of scapy. Yet as stated in the post itself, it's not the most clean or reliable way to do so.

    Do you have any suggestions on its improvement?

    Kind regards
    Raf

    ReplyDelete
    Replies
    1. Thanks Raf!

      Scapy does not parse the Radiotap header and that's why it is in the notdecoded field. Btw, I just posted a raw sockets version of this code as well: http://hackoftheday.securitytube.net/2013/04/wi-fi-ssid-sniffer-in-12-lines-of.html The radiotap header should be easier to get here.

      Delete
  3. Thanks, great post! After I read it I did the same in Ruby: http://www.reddit.com/r/tinycode/comments/1brgki/wifi_ssid_sniffer_in_9_lines_of_ruby_using_raw/

    ReplyDelete
    Replies
    1. Awesome! You did not use "bind" and this can give you a ton of packets and you'd need to check if it is wireless or not.

      Delete