Enabling malicious WiFi frames in the esp32 SDK

Published: Jul 1, 2021 by wildspider

Bypassing frame type restrictions in the ESP32’s WiFi library to allow deauthentication frames among others

NB: This was done in 2021, I haven’t messed with the esp-sdk since then. Shall do soon.


unsupported frame type

If you’re into electronics and cybersecurity then you’ve probably wanted to make your own pentest tools. And when it comes to WiFi (WPA2) attacks, your first idea was likely using the renown esp32 SOC from Espressif. But, alas, its WiFi library contains frame type/subtype restrictions… unless we can bypass them.

Espressif provides tools and libraries under the Apache 2.0 license, some of the internals however are only provided as object files or (compiled) libraries.

In this case we want to enable deauthentication frames, but the same process can be used to enable other restricted types.

A little knowledge of assembly and binary reverse-engineering is needed as well as the Xtensa Instruction Set Architecture (ISA) Reference Manual.

Table of Contents

  1. Setup
  2. Disabling the restriction
  3. Patching the library

Setup

All we need is an ESP32 board and the ESP-IDF toolset (which includes disassembler, compiler, debugger, etc). I use radare2 to disassemble the binary but you can use the provided tools to dig into the binary (esp-idf installs them into the .espressif folder in your home directory by default).

We first compile a basic application with bare minimum configurations that periodically sends deauth frames.

If you want to do this the dynamic way (with a debugger) you would have to setup a debugging interface first, a Rasberry PI with openOCD is enough. The process is simple but disassembling and using the Xtensa ISA reference manual is faster as the restriction is trivial.

Disabling the restriction

We use esp_wifi_80211_tx function to send arbitrary frames.

esp_wifi_80211_tx

Disassembling esp_wifi_80211_tx shows a call to a function with an interesting name : ieee80211_raw_frame_sanity_check

ieee80211_raw_frame_sanity_check

We can also do a string search in the binary to find where the error message is referenced, we find out that the string’s address is loaded into the a13 register inside the ieee80211_raw_frame_sanity_check function:

string search

Disassembling the instruction block where the address is loaded into a13 shows a call to wifi_log (shortly before a retw.n).

instruction blocks

Knowing how arguments are passed we take a look at the values loaded in the a10+ registers:

calln

Xtensa ISA Reference Manual



The a13 register being loaded with the address of our error message means the latter is the 4th argument to wifi_log, which means in this case that whether our error message is displayed or the 802.11 frame is sent is decided in the few branches leading to this block.

l32r

Xtensa ISA Reference Manual



Looking at the visual graph we can see that 2 branches lead to the problematic block (starting at 0x401038a5):

branch

There is no need to dive too much here, to simplify this we make it so this block is never reached.

In this case we would want the conditional bnez.n (narrow branch if not equal to zero) in the 0x40103884 block to be replaced with an instruction that does nothing (like nop, “no operation”) so that it never jumps to 0x401038a5, as for beq (branch if equal) in the 0x4010389f block, we would want it to be replaced by a j (jump) because 0x401038a5 is the next instruction address. The instructions need to have the same size, bnez.n is 2 bytes long therefore to be replaced with nop.n (the 2 bytes long version of nop) while beq is 3 bytes long and so is j.

The opcode for nop.n is 0xf03d, j’s will need to be calculated:

xtensa jump instruction

Xtensa ISA Reference Manual



We need to jump to 0x401083b1 while PC (Program Counter) is pointing to 0x401038a5 (next instruction address) we need an offset of 0xb (an offset of 0 is a jump of 1 byte, 0xb1-0xa5-1 = 0xb), therefore our opcode will be 0x0002c6.

changing instruction

The opcodes are written in big endian



After flashing the ESP32 with our new binary, no error message is displayed anymore and we can check that the frames are indeed being transmitted:

0c0 is supported

wireshark

Frames captured by another device



We can now patch our binaries before flashing them, this can be automated to avoid repeating the process after a new compilation.

Conclusion

We can also just redefine ieee80211_raw_frame_sanity_check as a dummy function as some do but it could remove other useful checks. Patching the library itself is also possible now that we know which instructions to change.

The bypass was easy to find but this is also due to the fact that software and libraries for embedded systems can’t be too complicated due to hardware limitations (storage, processing power, etc).

It isn’t clear why Espressif is putting such restrictions, perhaps to avoid bad publicity.

You can read more about the WiFi library issue.

cybersecurity reverse-engineering embedded-systems iot

Share