В начале 2015 года компания Asus исправила критичнейшую уязвимость в своих роутерах. «Дыра» была в службе по имени infosvr, использующейся утилитами Asus для облегчения настройки роутера путём его автоматического обнаружения в локальной сети. Уязвимость позволяла выполнять любые команды с правами root (ведь infosvr тоже root), что давало злоумышленнику полный контроль над системой.
Но Asus выпустила исправленные прошивки. Теперь это всё в прошлом. Или нет? Хм… А как часто обыватели обновляют прошивки на своих роутерах?
Прошу под кат за подробностями, историей обнаружения, исследованиями, инструкциями и… эксплоитами.
В чём дело?
Служба infosvr слушает 9999 порт (UDP). Когда приходит пакет размером не менее 512 байт она анализирует его и в зависимости от его типа выполняет соответствующие ответные действия. Например, собирает информацию о некоторых настройках роутера и отправляет её программе Device Discovery, которая помогает найти IP-адрес роутера. Но есть ещё и такой тип пакета, который подразумевает выполнение системной команды роутером. Тут-то Asus'овцы и облажались. Из-за ошибки в коде обработки такого пакета выполнить команду можно без всякой авторизации. Это строка if (memcpy(phdr_ex->MacAddress, mac, 6)==0) в следующем листинге (несущественные строки удалены):
Часть функции processPacket(int sockfd, char *pdubuf) из common.c
Вероятно, там вместо memcpy предполагалось memcmp (вот что бывает когда копируете строки кода, с целью чуть-чуть подправить потом), а вместо == предполагалось !=. Но даже если бы этой ошибки не было, всё равно для проникновения достаточно было бы знать MAC-адрес.
После успешной «аутентификации» команда из пакета будет выполнена:
Пройдите по пути Поддержка -> Драйверы и утилиты -> ОС -> Firmware
Ищите в списке прошивку за январь 2015-го
В описании к ней наверняка есть скупая фраза "Fixed infosvr security issue."
???
!!!
Впечатляют масштабы? На самом деле всё было не так уж и плохо (а сейчас, когда «дыру» закрыли — вообще хорошо). Дело в том, что infosvr работает с интерфейсом br0, т.е. с мостом между другими интерфейсами. В случае, если роутер работает в режиме IP Sharing (Режим общего IP), br0 объединяет eth0 и wlan0. Заметьте, eth1 (который WAN) сюда не входит. Т.е. уже радует то, что с внешней сети никто не пролезет. А вот когда роутер в режиме Access point, то br0 объединяет все интерфейсы…
История обнаружения
Как-то мне захотелось порулить своим роутером через UART, то бишь через аппаратную консоль. «Зачем такие трудности? По телнету зайди.» — скажете вы. Не тут-то было! Это же RT-N10E! (Он же RT-N10LX).
Вы видите здесь выключатель телнет? Я тоже - нет
Не буду тут много рассказывать насколько это дерьмовый роутер. Кто сталкивался — знает. А для счастливчиков, кому не приходилось с ним возиться скажу, что роутеры Asus RT-xxxE (или RT-xxxLX) имеют процессор от Realtek, под который нет альтернативных прошивок! Хотя сообщество пытается. На официальных же прошивках я пару лет назад пользовался PPTP. Постоянные зависания, разрывы и прочие радости. PPPoE по началу вообще не поднимался (в какой-то прошивке исправили). Сейчас этот роутер работает просто как мост между WiFi и Ethernet.
Включаем. Видим процесс загрузки. (Кэп: Кстати, через телнет этого не увидишь. Так-то!)
Так загрузка роутера выглядит через /dev/console (/dev/ttyS0)
После окончания загрузки я начал исследовать систему: версии ПО, железо и другую информацию, которую можно добыть при помощи консоли. Но об этом позже.
Интересно посмотреть реакцию на стандартные действия пользователя, как-то: нажатие на кнопку WPS, настройка через веб-морду, использование Asus Utility… Действительно, на консоли появлялась соответствующая информация. Отдельно стоит упомянуть такой факт: при попытке открыть веб-интерфейс httpd (предположительно) пишет в /dev/console (/dev/ttyS0) логин и пароль для входа!
UserID: admin
UserPass: asus-rt
Правда, на телнет (/dev/ttyp0) это не попадает.
Из всего пакета Asus Utility (Device Discovery, Router Setup Wizard и Firmware Restoration) реакцию консоли вызвал только Router Setup Wizard. А именно: при запуске приложения и нажатии на кнопку «Далее» появляются такие строки:
Хм…
Эти команды Router Setup Wizard передаёт? Посмотрел через Wireshark — да, эти команды передаются в UDP пакетах с RSW. А что если их заменить на свои?
Оказывается, я не первый
Перед тем, как начать ковыряться в пакетах, я решил погуглить. И нагуглил кое-что (раз и два). По второй ссылке подробное описание уязвимости и простой эксплоит под Линукс (есть и на python-е).
Велосипед AsusCmd
Мне хотелось бы иметь такую программку под Windows, поскольку Я 95% времени пользуюсь именно ей. Не хочется ради выполнения какой-то команды загружать виртуальную машину с Ubuntu.
Размеры
В процессе написания и отладки эксплоита, а также копания в исходниках infosvr выяснились «параметры» этой «дыры».
А именно её размеры. Они такие:
Размеры буферов приёма и отправки — по 420 байт. И всё бы неплохо, но реально 420 байт роутер может только отправить в ответе. А вот с приёмом дела ещё хуже. Дело в том, что после приёма команды infosvr обрезает её до 256 символов и только потом — исполняет. Но и это ещё не последнее ограничение. При выполнении команды длиной чуть менее 256 символов infosvr падает с грохотом segmentation fault. Команда выполнена, но ни ответа уже не будет, ни возможности исполнить ещё что-то. Опытным путём была найдена предельная безопасная длина пользовательской команды. Это 238 символов. Итак, размеры «дыры»: 238 — команда, 420 — ответ.
Но так рулить роутером не очень удобно. И, если в данном случае ограничение в 238 символов команды не сильно заметно, то ограничение на вывод ответа в 420 символов — это уже печальнее. Но выход есть! Хоть на роутере и сильно ограниченный busybox, но там всё-же есть telnetd! Запускать его лучше на нестандартном порте, т.к. защиты нет никакой: для подключения пароль не нужен.
AsusCmd.exe «telnetd -l/bin/sh -p777»
Ну, теперь другое дело! Можно полноценно командовать со всеми удобствами, как-то история команд и дополнение по Tab. Если телнет перестал отвечать, его можно перезапустить, не перезагружая роутер. Сначала надо прибить связанный с ним sh:
AsusCmd.exe «killall -9 sh»
Учтите, что после выполнения этой команды будет также убит sh, который связан с /dev/console. Теперь можно снова запускать telnetd.
Отлично. Тема исчерпана? Как-бы не так.
Продолжение банкета
Итак, мы можем пользоваться всеми доступными командами. Можем создавать и удалять файлы в каталогах, доступных для записи (ramfs /var). Можно записать несколькими командами длинный текстовый файл. Но не только текстовый! Ведь echo в Linux-е умеет преобразовывать текст в двоичные данные! Это значит, что мы можем заливать на роутер свои программы, которых нам не хватает!
AsusBinWrite
Идея проста:
считываем порцию байт с файла-источника
преобразуем их в текстовый вид, понятный для 'echo -e'
формируем системную команду
отправляем на роутер
повторяем пока не перешлём весь файл
Но это на словах всё легко и просто. На практике мы имеем дело с парой неприятных факторов:
UDP пакеты, которые иногда теряются
Ограничение на полезную длину системной команды (238 символов)
С ограничением на полезную длину команды всё понятно: просто придётся больше пакетов отправить. Плохо в этом случае то, что страдает скорость передачи. Ведь процессору роутера приходится обрабатывать много мелких пакетов. Т.е. скорость передачи прямо пропорциональна скорости процессора роутера. (Проверено с помощью cpuload: во время работы AsusBinWrite CPU роутера грузится на 100%).
С UDP посложнее. Надо как-то контролировать правильность передачи. Примитивный способ — просто дописывать очередные байты в конец файла — не подходит. Стоит одному пакету на пути к роутеру потеряться или продублироваться — и файл уже бракован. Поэтому принято решение сначала записывать отдельные части в разные файлы и проверять их содержимое. А в потом их можно объединить. И ещё: объединять следует не в самом конце, а через какое-то количество переданных частей. Потому, что одна часть занимает места в памяти не, скажем, 40 байт (размер файла-части), а все 4 КБ (страница). А если частей тысячи? Никакой оперативки не хватит (На RT-N10E её лишь 16MB). После успешного объединения (успешность тоже проверяется по признаку увеличения размера целевого файла на роутере) эти части удаляются. Вот так в цикле передаём весь файл до конца. Получился такой-себе протокол надёжной передачи файлов на роутер посредством UDP.
Пример передачи исполняемого файла
Чтобы лучше понять алгоритм работы предлагаю посмотреть, что происходит на /dev/console (/dev/ttyS0) во время передачи файла и сопоставить это с выводом самого AsusBinWrite.
Вывод AsusBinWrite (обратите внимание на случаи потери пакетов)
Примечание к следующему спойлеру
На месте ///*** таких моих комментариев ***/// удалены строки, чтобы не перегружать текст.
А на месте ///--- таких ---/// ничего не удалено. Это просто объяснения.
/dev/console (/dev/ttyS0) - обратите внимание на имя файла, в который перенаправляется вывод на разных этапах
Ну и что дальше?
Как известно, в Linux чтобы запустить файл на исполнение надо установить ему соответствующие разрешения. Проблемка в том, что на роутере не оказалось chmod (помните про сильно урезанный busybox?). Но ничего. Это можно обойти:
Profit! Мы только что загрузили на роутер свой исполняемый файл, и он работает! Хотя погодите… А где мы его взяли, файл этот?
Как получить исполняемые файлы для своего роутера
Прежде всего нужно узнать архитектуру CPU вашего роутера. Лёгкий путь не даёт всей необходимой информации:
Можно, конечно, даташит поискать и в нём найти эту информацию… А если он недоступен? А если есть разные модификации? Самый верный способ — посмотреть на то, что уже работает.
Придётся как-то достать исполняемый файл с роутера, чтобы проанализировать его с помощью команд file и readelf -h.
Я это сделал так:
запустил telnetd и подключил к нему TeraTerm
настроил TeraTerm на лог в файл с опцией «бинарный»
TeraTerm больше не трогаю (чтобы в начало лога ничего лишнего не попало)
Получившийся файл вышел немного больше того, что лежит на роутере (какой-то мусор подмешался). Но ничего: нас интересует только начало файла — ELF Header.
Перекидываем полученный файл на машину с Линуксом. Теперь можно посмотреть заголовок:
Теперь всё ясно: MIPS-1, Big endian.
Собираем тулчейн для нашего роутера
Вот мой скрипт-памятка (выбирайте конфигуратор по вкусу):
На шаге make *config настраиваем тулчейн.
По крайней мере, надо настроить целевую архитектуру. Target options ---> Target Architecture и Target Architecture Variant. Тут есть небольшой подвох: вот так сразу в Target Architecture Variant Mips I вы не найдёте. Чтобы он там появился нужно включить опцию Build options ---> Show options and packages that are deprecated or obsolete.
Можно больше ничего не настраивать. А можно ещё много чего настроить: включить поддержку C++ и других языков, статическую компоновку (у меня ничего хорошего с этого не вышло), выбрать компилируемые под целевую архитектуру приложения, апплеты busybox, версии библиотек и т.п.
После настройки выбираем Exit, соглашаемся с сохранением новой конфигурации и… make. Сборка займёт продолжительное время, в зависимости от того, что вы там в настройках понавыбирали (У меня при настройке только архитектуры целевого процессора сборка на VM Ware заняла немногим более полу часа. Замерял так: ttt=`date`; make; echo $ttt; date). Можно параллельно собирать, но не с помощью -jN. Руководство гласит:
You should never use make -jN with Buildroot: top-level parallel make is currently not supported. Instead, use the BR2_JLEVEL option to tell Buildroot to run the compilation of each individual package with make -jN.
Т.е. нужно настраивать параметр Build options ---> Number of jobs to run simultaneously.
Toolchain готов
Наконец, по прошествии xx минут тулчейн собрался полностью и без ошибок. Можно использовать. GCC для роутера будет по пути ./buildroot-2014.11/output/host/usr/bin/mips-linux-gcc (симлинк на mips-buildroot-linux-uclibc-gcc).
Для начала напишем примитивную программку, скомпилируем, зальём на роутер, выставим разрешение на исполнение (трюк, показанный ранее) и запустим:
Теперь можно писать свои программы для роутера, учитывая ограниченность ресурсов и возможности библиотек, которые есть на роутере.
Но вместе с тулчейном собрались и некоторые бинарники на целевую платформу! Лежат они здесь: ./buildroot-2014.11/output/target/ bin | sbin | usr/bin | usr/sbin. Я перепробовал многие из них (в menuconfig настроил сборку множества дополнительных приложений). Те, которые оказались рабочими и показались полезными сохранил отдельно (может когда-то понадобятся).
Рабочими оказались не все. После загрузки на роутер многие отказывались работать по различным причинам:
чаще всего — can't load library 'какая-то библиотека'
частично работают — can't resolve symbol 'какой-то символ'
Segmentation fault — без комментариев
а все статически слинкованные — Illegal instruction
А если у вас будет ошибка типа Bus error или unexpected word, то скорее всего вы ошиблись с выбором целевой архитектуры (или банально файл повреждён).
Повышаем комфорт
Вы, наверное, заметили, что скорость передачи файла через «дыру» в infosvr, мягко говоря, оставляет желать лучшего. Я хотел решить эту проблему написанием TargetSideAgent, который бы весил немного и поднимал полноценное TCP соединение. Уже и немного кода написал, как ВНЕЗАПНО открыл для себя чудо-утилиту netcat! Эта утилита оказалась рабочей среди приложений, собранных buildroot для целевой платформы. Теперь не надо долго мучиться — netcat передаёт файлы на роутер (да и с роутера тоже) почти мгновенно! Для быстрой передачи теперь можно использовать такой подход:
с помощью AsusBinWrite заливаем на роутер netcat
устанавливаем ему разрешение на исполнение
запускаем на приём: ./netcat -vvlp 8888 > recvfile
а с компа запускаем на передачу: ncat.exe -vv --send-only 192.168.1.1 8888 < Useful\cpuload
Кстати, если у вас почему-то прервалась передача файла с помощью AsusBinWrite, то её можно возобновить, использовав опцию RESUME:
Возобновление прерванной передачи
Теперь скорость приличная и можно легко загружать даже «увесистые» файлы. Но не увлекайтесь. Не забывайте, что /var и /tmp (который на самом деле /var/tmp) — это оперативная память роутера. Если вы полностью её забьёте — роутер перестанет отвечать (или вообще зависнет). В этом случае придётся его перезагрузить вручную. Контролируйте свободную память при помощи free.
Кстати, когда я искал netcat под Windows, то наткнулся на "netcat 21-го века". Он гораздо богаче функционалом, чем обычный netcat. Для нашего случая очень полезной оказалась опция --send-only (разорвать соединение сразу после передачи).
Что ещё можно сделать?
Посмотрите на это 'хозяйство':
Я как-то неосторожно что-то перенаправил в /dev/mtdblock2. Оно записалось. Роутер работал. Потом, когда я решил его перезагрузить — он не загрузился. Посмотрев, что выдаётся на UART я понял, что всё-таки повредил прошивку. Роутер загрузился в режиме восстановления (именно с этим режимом работает утилита Asus Firmware Restoration).
Режим восстановления
Недолго думая я запустил тот таки Firmware Restoration и восстановил прошивку.
Отображение работы утилиты Asus Firmware Restoration
Сам факт повреждения прошивки в работающем роутере, говорит о том, что используя уязвимость можно менять её. Тогда изменения на роутере будут перманентными, а не только до перезагрузки, как было до сих пор. Но на исследование этого у меня нет ни времени, ни желания.
Исходники AsusRouterTools, а также бинарники под Windows можно найти в репозитории на GitHub.
З.Ы. Раз уж разобрались с infosvr, почему бы не использовать его ещё и по назначению? AsusDiscover — это единственное, что продолжит работать после установки прошивки с устраненной уязвимостью.
Купить роутер ASUS по лучшей цене с доставкой по Казахстану, России и странам СНГ можно в магазине Ruba Technology.