SpiderLabs Blog

Spoofing 802.11 Wireless Beacon Management Frames with Manipulated Power Values Resulting in Denial of Service for Wireless Clients

Written by Tom Neaves | Jan 26, 2024 1:00:00 PM

This is another one of those blog posts from me about how I independently carried out some security research into a thing and found something, but I was just too late to the party once again [1]. However, I want to share the journey because I still think there is some value in doing so. I want to take you on that journey and have you experience the excitement and adrenaline rush when an idea, a construct, that abuse/misuse case you had in your head gets to a working proof of concept and is validated as being correct.

On a side note, maybe I need to think about googling prior security research before I start going down rabbit holes… but fun is fun, right?

With that aside, I want to take you on that journey. Buckle up.

So, the story starts in Ubuntu, in dmesg to be exact. Dmesg (diagnostic messages) prints kernel-related messages for those of you not familiar. So, there I was, minding my own business, not at all looking into wireless, actually looking into some Bluetooth research (watch this space!). I had to install some required packages and suddenly Ubuntu crashed on me. I look into dmesg to see what the fuss is all about, no real answer… but I noticed this line that had to do with the wireless interface.

 

 

Hang on a minute… I have to do a double-take. Has my wireless card’s transmit power been limited based on some information provided by the access point? Is the operating system taking something provided in a wireless frame and using that to set the value on my wireless card’s transmit property? I gotta check this out; this is ripe for abuse as I know instinctively that I can spoof all things wireless, especially wireless management frames. If I can spoof the frame coming from the access point then I can create my own values.

I have so many questions… the first one is, can I set this value to 0? The second one, can I set this thing to a minus value? The third one, in what place is this thing being set in the first place? Is it a beacon frame, a probe response, an association or re-association frame, and if so, where in the frame?

The first thing we need to do is play detective here, and we need to start following the breadcrumbs back from the first ones we see. I want to know where Ubuntu is getting the “40 (40 – 0)” values from. These are the first clues.

I download the Ubuntu source code, nearly 4 GB of the stuff. I look through 74,099 files for the string “as advertised by”.

We have a hit, in /net/mac80211/mlme.c. All aboard the bus, next stop, mlme.c.

 

 

We find ourselves inside the ieee80211_handle_pwr_constr() function, as this is the code responsible for that dmesg output.

 

 

So, we need to start filling in the blanks. Where does pwr_level_80211h come from, where does chan_pwr come from, and where does pwr_reduction_80211h come from? We want to know the source of these if we are to manipulate them.

If we scroll up in the function more, we can see that it mentions country_ie which is something I recognise! This is the information element (IE) in a wireless frame relating to the country. It holds details about the country string, channel number, etc. The code makes a call to ieee80211_find_80211h_pwr_constr() shovelling these details from the country_ie in.

 

 

Let’s hop over to ieee80211_find_80211h_pwr_constr() to find out what chan_pwr and pwr_reduction_80211h are because they are used in the calculations on line 1588 above to bring us to the pwr_level_80211h value.

Ok, so in the code below, we can see on line 1531 that chan_pwr takes the value of chans.max_power from the country IE.

 

 

Just to double-check this, let’s have a look on the wireless side; we should expect to see “40” here if we remember from the original output from dmesg… and there it is, “Maximum Transmit Power Level: 40dBm” under the country IE.

 

 

The hex dump of that frame is “28” (hexadecimal), which is “40” in decimal. All matches up.

 

So, one of the blanks has been filled. We know that the left side of the calculation comes from maximum transmit power (which goes into chan_pwr), but the right?

 

 

Now, back to the code. That calculation in the code reveals that pwr_reduction_80211h is the bad boy we’re looking for on the right-hand side, but where does it get its value from?

 

 

We go back to the ieee80211_find_80211h_pwr_constr() function and see that after extracting the chans.max_power value and assigning to chan_pwr that it then moves on to another conditional statement. This is checking whether the wireless frame (what type we don’t yet know exactly) has a maximum transmit power (in the country IE) and a power constraint element, then pwr_reduction gets the pwr_constr_elem value.

 

 

Wait, what, what is a power constraint element?

I leave the code for a bit; I read some things; by ‘things’ I mean wireless standards. So, it turns out that the 802.11h standard has an Information Element known as a “Power Constraint,” with a value set by the access point telling wireless clients by how much they should reduce their power below the regulatory limit per the configured country (the latter the maximum transmit power value in the country IE). That now all makes sense if we go back to our original dmesg output.

 

 

It was now time for me to exit the code, put on my overalls and visit my wireless workshop to knock up some wireless packets as a proof of concept. What I did next was waste some/a lot of time reconstructing whole wireless beacon management frames, probe responses frames, and association and re-association frames from scratch using Scapy.

I won’t lie; it was a bit/a lot painful; I think I aged at least 5 years in the process. In many instances, Scapy didn’t have support for building these elements with preset things like with Dot11EltRSN, Dot11EltCountry, etc., so I had to manually work out the hex and insert raw bytes using Dot11Elt, build, and then see if it looked right through trial and error. It was my belief that it should have everything in, right down to RSN goodness to be believable by a client to then act on power levels. My plan was to build these frames, insert them into the airwaves, and watch in dmesg on the ‘victim’ computer to see if anything changed. I’d then know what type of management frame is needed.

What I should have done is follow the functions and work backwards and arrive at the caller function ieee80211_rx_mgmt_beacon() which would have given me a big hint that they are beacon frames! I did this, but only after the pain was already caused by the other route.

I also deviate from my ‘plan A’ of constructing a management beacon frame from scratch. Instead, I went for ‘plan B’, deciding to pluck one of these from the air (visualise me holding up a net on a stick in the air) from my test access point and that I would instead hack that up as a baseline, tweaking only the bytes I need.

I open up the baseline beacon management frame, and if you remember from earlier, “28” hexadecimal is “40” in decimal, as shown below.

 

I change this value to “00” because we want this value to be “0”.

 

I fold the hacked up beacon management frame and throw it into the air. I glance over at the ‘victim’ computer…

 

Bingo! “Limiting TX power to 0 (0 – 0)” is now shown in dmesg! We were able to fake a beacon management frame to tell the client to reduce their wireless card’s transmit power to 0 (“Tx-Power=0 dBm” below).

 

 

I was about to have the celebratory tea and biscuit when I saw this…

 

 

The real access point sends out a beacon frame with the true values (the 40 dBm) and the client’s wireless card resets its transmit power back. So, if this attack is to be sustained then clearly the fake beacon frames need to keep being sent and likely the volume increased, just like any good DoS attack. I’m also curious about the actual impact this quick TX flicking has on the connectivity of the client. On the ‘victim’ computer, I ping the gateway (the access point) and then send some/lots of fake beacons with my “0” maximum transmit power edition.

 As you can see in the image below, there is clearly an impact. The ~2ms responses are the baseline, before I start sending any modified beacons. Notice that when the fake beacons are surfing the airwaves, making the TX flip to “0” (and then back again with the real beacons) the response times significantly increase, up to 234 ms! When I stop the beacons, these responses then drop back to the baseline of ~2ms – proof we are affecting them!

 

 

Tea and biscuit time.

I shift my attention to the power constraint element because I want to see if I can control that, as the code says I should be able to. In the baseline beacon frame, the element looks like the one below, with a value of “0” because it isn’t reducing anything from the maximum transmit power (the regulatory bit) on the left. On the right-hand side this was always “0” if you remember.

 

 

So, if I get my tweezers out (aka vi and xxd) and do some editing, this is the before, with “00” (0) for this value.

 

 

…and the below image is the after. I have added in hexadecimal “32” which is “50” in decimal where “00” was. My thinking is that if the baseline is “40 – 0” then if I make it “40 – 50” I’ll be answering the minus question I had earlier aiming for -10 if it all works out.

 

 

I fire this beacon frame off into the air once again. I have put the maximum transmit power from the first part of this blog post back untouched (to “40”), just so we don’t complicate things by trying to hack all the things at once. 

We get the result we want, “0 (40 – 50)” by using this other method; however, no minus number is reported in the TX. This is fine; it answers the question about how, in reality, it may handle this minus input, maybe a crash, etc. Again, we still get clobbered by the original access point beacon too, so we’d need to do the same approach as with the maximum transmit power if we want to keep the client affected.

 

 

So, there you have it. Never trust your inputs and act on them with settings things, especially when you’re not verifying those inputs with any kind of signature.

It was a little bit of a shock when I came across Mathy’s paper [1] after I got this all figured out, but as Monty Python says, Always Look on the Bright Side of Life. I had fun, I absolutely loved bringing this proof of concept to life, but I think I may move my prior research/literature review to the start of my methodology, before any proof of concept!

Thanks for reading, I hope you enjoyed the journey.

[1] “Protecting Wi-Fi Beacons from Outside Forgeries”, Mathy Vanhoef, Prasant Adhikari, Christina Popper, WiSec 2020.