Здравствуйте, товарищи. Мы сегодня будем разблокировать интересные возможности
ESP32S3, ESP32S2 и ESP32. Написанное скорее всего будет работать так же на другой модели, но тестировалось
все только на ESP32-S3.
ВВЕДЕНИЕ В ПРОБЛЕМУ
Кратко: Espressif, начиная с определенной версии своего SDK вытащила на свет божий ранее
скрытую функцию
esp_err_t esp_wifi_80211_tx(wifi_interface_t ifx, const void *buffer, int len, bool en_sys_seq)
Да, это то, что вы подумали - функция отправки сырого WiFi кадра. Но, т.к. такой инструмент
может быть потенциально опасным в умелых руках, инженеры их Espressif ограничили типы WiFi пакетов,
которые можно отослать с помощью этой функции до совсем неинтересного набора:
“Attention Currently only support for sending beacon/probe request/probe
response/action and non-QoS data frame”
Будем избавляться от этого ограничения, в среде Arduino. Инструкции, “что делать“, начинаются с “ИСПРАВЛЕНИЯ“. А пока немного теории.
WiFi, которым славятся чипы Espressif, содержит как открытую, так и precompiled часть, причем, как всегда,
самое интересно находится в закрытой части: интересующие нас функции находятся
приемущественно в библиотеке libieee80211.a (устанавливается она вместе ESP32 board support в Arduino IDE).
Вот туда-то мы и полезем (поиск в каталогах вам в помощь).
2025-09-23 06:24 PM 4,848 libcore.a
2025-09-23 06:24 PM 62,174 libespnow.a
2025-09-23 06:24 PM 995,432 libmesh.a
2025-09-23 06:24 PM 1,387,762 libnet80211.a <----------- Это
2025-09-23 06:24 PM 706,754 libpp.a
2025-09-23 06:24 PM 196,186 libsmartconfig.a
2025-09-23 06:24 PM 52,466 libwapi.a
Для начала распакуем библиотеку командой “ar x libieee80211.a” (если у вас linux или windows+cygwin),
должна появится куча файликов, из которых нам нужно выбрать тот, в котором находится наша
esp_wifi_80211_tx()
Это будет файл ieee80211_output.o
Если теперь этот файл загрузить в Ghidra (дизассемблер\декомпайлер), отыскать там esp_wifi_80211_tx
то увидим примерно такое:
(код отредактирован человеком :))
int esp_wifi_80211_tx(int Interface,const void *Buffer,int Length,bool En_Sys_Seq) {
int ret;
char *eb;
.....
.....
.....
// Если ret == 0, то пакет хороший, а если нет - то пакет негодный.
//
if ((ret = ieee80211_raw_frame_sanity_check(Interface,Buffer,Length,En_Sys_Seq)) == 0) {
_mutex_lock(_g_wifi_global_lock);
// Судя по всему - аллокация памяти, копирование пакета
// 1- похоже на "true"
//
eb = ic_ebuf_alloc(Buffer,1,Length);
if (eb == 0) {
_mutex_unlock(_g_wifi_global_lock);
// Память кончилась :(
ret = 0x101;
} else {
.....
.....
.....
ieee80211_post_hmac_tx(eb);
_mutex_unlock(_g_wifi_global_lock);
}
return ret; // должен быть 0 (ESP_OK)
}
В самом начале кода можно увидеть саму проверку - вызов ieee80211_raw_frame_sanity_check().
Как-то нам теперь надо сделать так, чтобы этот sanity_check всегда возвращал 0, т.е. “проверка пройдена успешно”.
ИСПРАВЛЕНИЯ
Ну и сделаем мы это самым прямым и официальным путем - пропросим линкер выкинуть родной код функции проверки и
использовать наш. А наш новый код “проверки” будет выглядеть вот так вот незатейливо:
int ieee80211_raw_frame_sanity_check( … ) {
return 0;
}
Находим файлик “ld_flags” из установки ESP32 Arduino Core (там их несколько, для каждого CPU - свой).
Там, в одну строчку, видимо , для лучшей читаемости записана длинная макаронина опций линкера:
-mlongcalls -nostartfiles -Wl,--cref -Wl,--defsym=IDF_TARGET_ESP32S3=0 -Wl,--no-warn-rwx-segments -Wl,--orphan-handling=warn -fno-rtti -fno-lto -Wl,--gc-sections -Wl,--warn-common -u nvs_sec_provider_include_impl -Wl,--wrap=ieee80211_raw_frame_sanity_check -Wl,--wrap=log_printf -u _Z5setupv -u _Z4loopv -u esp_app_desc -u esp_efuse_startup_include_func -u ld_include_highint_hdl -u start_app -u start_app_other_cores -u __ubsan_include -u esp_system_include_startup_funcs -Wl,--wrap=longjmp -u __assert_func -u esp_security_init_include_impl -Wl,--undefined=FreeRTOS_openocd_params -u app_main -u esp_libc_include_heap_impl -u esp_libc_include_reent_syscalls_impl -u esp_libc_include_syscalls_impl -u esp_libc_include_pthread_impl -u esp_libc_include_assert_impl -u esp_libc_include_getentropy_impl -u esp_libc_include_init_funcs -u esp_libc_init_funcs -u pthread_include_pthread_impl -u pthread_include_pthread_cond_var_impl -u pthread_include_pthread_local_storage_impl -u pthread_include_pthread_rwlock_impl -u pthread_include_pthread_semaphore_impl -u __cxa_guard_dummy -u __cxx_init_dummy -u esp_timer_init_include_func -u uart_vfs_include_dev_init -u include_esp_phy_override -u usb_serial_jtag_vfs_include_dev_init -u usb_serial_jtag_connection_monitor_include -u esp_vfs_include_console_register -u vfs_include_syscalls_impl -u esp_vfs_include_nullfs_register -u esp_system_include_coredump_init
Добавляем туда -Wl,–wrap=ieee80211_raw_frame_sanity_check
Можно - в начало, можно - куда-нибудь в середину, это не имеет значения.
Добавляем в свой Arduino проект один Си файл, назовем его, скажем, itsfine.c:
#include <stdbool.h>
int __wrap_ieee80211_raw_frame_sanity_check(int ifx,const void *buffer,int len,bool auto_seq) {
ifx = ifx;
buffer = buffer;
len = len;
auto_seq = auto_seq;
return 0;
}
Практически все готово. Последний, но важный штрих - сделать оригинальную функцию unresolved external
Добавить
extern int ieee80211_raw_frame_sanity_check(int,const void *,int,bool);
ну или
extern "C" int ieee80211_raw_frame_sanity_check(int,const void *,int,bool);
в файл с кодом, в котором предполагается использовать функцию отсылки кадра esp_wifi_80211_tx(),
например в ваш .ino файл.
Теперь, мы можем свободно пользоваться esp_wifi_80211_tx(), не опасаясь, что пакет будет отвергнут, как не
прошедший проверки.
Как это работает: линкер, при сборке проекта, заменит все вызовы к ieee80211_raw_frame_sanity_check() на вызовы
__wrap_ieee80211_raw_frame_sanity_check. Замена глобальная - т.е. она произойдет и в бинарных закрытых библиотеках:
закрытый код, который раньше вызывал ieee80211_raw_frame_sanity_check, теперь будет вызывать наш враппер. А наш враппер,
как было ужа сказано - незатейлевый - возвращает “все ок” а оригинальный код не вызывает.
Но если сильно нужно , то оригинальная проверка все еще доступна через вызов
__real_ieee80211_raw_frame_sanity_check(int,const void *,int,bool);
Таким образом, проверка всегда будет возвращать 0 “проверка пройдена успешно” и пакет будет отправляться.
Удачных экспериментов с WiFi
Не нарушайте закон, играйтесь только со своими личными сетями.
ЗЫЖ: фрагмент кода, отсылающего мусор
extern int ieee80211_raw_frame_sanity_check(int,const void *,int,bool);
//IEEE802.11 header + data
uint8_t header_and_data[] = {
0x48, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x10, 0x11, 0x11, 0x11, 0x11, 0x11,
0x20, 0x22, 0x22, 0x22, 0x22, 0x22,
0x00, 0x00,
0xDE, 0xAD, 0xBE, 0xEF,
};
void example_wifi_frame_tx() {
int err = esp_wifi_80211_tx(WIFI_IF_STA, header_and_data, sizeof(header_and_data), false);
printf(":-%c\r\n", err == ESP_OK ? ')' : '(');
}

