Очевидно, что это некий сенсорный экран, но не было никаких указаний на то, чем он управляет. Арендодатель понятия не имел, что это. На устройстве не было ни кнопок, ни надписей, лишь крошечный жёлтый огонёк, дающий понять, что у него есть питание.
У меня возник миллион вопросов, но я был слишком занят переездом, поэтому забыл про него и вспомнил только неделю назад. Я просматривал большую папку с инструкциями по разным приборам в моей квартире, и из неё выскользнула вот такая брошюра:
Ничего себе, это же тот сенсорный экран! Оказалось, что это часть системы мониторинга энергии, сообщающая о текущем энергопотреблении и способная отображать исторические данные. Это показалось мне очень удобным — любопытно было бы понаблюдать за паттернами энергопотребления в квартире.
В брошюре также упоминалась вторая часть, так называемый «менеджер энергии», напрямую подключённый к электросчётчику для получения информации об энергопотреблении. Я подошёл к общему шкафу электросчётчиков дома, и он действительно обнаружился в его углу. Раньше я даже никогда не замечал эти ящики, но, по крайней мере, на них тот же бренд, что и в брошюре — некая компания с креативным названием «NETTHINGS».
Пока всё довольно просто: у нас есть два устройства, одно — «сервер», собирающий данные, другое — «клиент», считывающий эти данные. Расстояние между ними очень мало — всего несколько метров и две-три стены, вполне разумная схема для кабельного соединения. И в этот момент я заметил странный стикер в углу брошюры. На нём были напечатаны две строки с метками «SSID» и «Pwd». Я в ужасе замер. Они бы не осмелились. Расстояние здесь буквально три метра. Это встроенные устройства, зачем нужны такие сложности…
И, разумеется, оказалось, что два устройства общаются через WiFi. Для меня это необычно, потому что я не разработчик приложений для встроенных устройств. Однако один мой друг, работавший над функциями умного дома одного голосового помощника, сообщил мне, что на самом деле это довольно популярная схема в мире IoT. Наверно, буква E в аббревиатуре IoT обозначает «экономия».
Дальше мне нужно было каким-то образом включить этот странный сенсорный экран. При внимательном изучении я заметил сбоку небольшое отверстие, которое походило на место, куда можно вставить скрепку, чтобы выполнить сброс устройства:
Я на несколько секунд прижал спрятанную кнопку с проводом, после чего увидел логотип запуска Android! Да, это оказался планшет с Android, причём с довольно старой версией. Там были установлены Google Talk, Flash и куча других интересных штук:
Насколько я понимаю, это Android 5, но не уверен точно. Одно приложение обратило на себя моё внимание: NetThings. После запуска оно переходит к экрану, где можно выбрать сеть WiFi. К сожалению, указанной в брошюре сети в списке не нашлось. Я проверил список дважды, попытался обновить его, проверил сети WiFi в других своих устройствах, попытался напрямую подключиться к WiFi при помощи указанных данных, но ничего не получилось.
Когда я зашёл в комнату со счётчиками, то заметил очевидное: на ящиках всех остальных квартир горели огоньки, но мой почему-то был отключён:
Я живу в Великобритании, и это страна электрических предохранителей. Видимо, здесь считается, что чем больше предохранителей, тем лучше: предохранители есть повсюду, даже у некоторых розеток. Предохранители некоторых приборов поставляются в отдельных коробках с небольшим выдвигающимся ящичком, позволяющим заменить предохранитель. Выглядит это так:
Как видите, в моём ящичке предохранителя по какой-то причине нет. Нет предохранителя — нет электрического соединения, нет питания для менеджера энергии и нет точки доступа WiFi. Благодаря конструкции ящика для предохранителя, его легко заменить, но я не знал, какой ток должен быть допустим в сети. К счастью, в том же шкафу было несколько работающих менеджеров энергии, поэтому я открыл их ящички (отключив на несколько секунд их питание), чтобы посмотреть, что за предохранители там установлены. Оказалось, что мне нужен предохранитель на 3 А, поэтому я заказал такой с Amazon и на следующий день установил его.
Честно говоря, всё это довольно сильно меня пугало, потому что я был очень близко к электросети. После установки предохранителя я несколько раз в день проверял его температуру, ища признаки того, что хуже не станет. Он уже проработал около недели, но я всё равно не рекомендую проводить самостоятельно такие эксперименты.
После установки предохранителя менеджер энергии начал мерцать зелёными светодиодами, и на всех устройствах отобразилась сеть WiFi. Я выбрал сеть в планшете с Android, и приложение перешло на следующий экран:
При касании экрана приложение переходит к этому меню, предлагая пользователю выбрать, какой ресурс он хочет отслеживать. На самом деле здесь ничего не работает, кроме «Mains Electricity» (электропитания сети), потому что это единственный счётчик, к которому подключён менеджер энергии.
Нажав на «Mains Electricity», мы попадаем на самый разочаровывающий экран в истории дизайна UX:
Хм, с чего бы начать? Во-первых, что это за цветовой индикатор справа? Что означает позиция по вертикали? Если он зелёный, то это значит, что я потребляю маленький объём электроэнергии или просто обычный? А «маленький» — это сколько? Если он поднимается до самого верха, то это относительно моего исторического максимального потребления? За какой период времени?
Из пяти чисел, показанных справа от индикатора, правильное только одно — количество потреблённых кВт (kW). Все остальные числа зависят от поставщика электричества и совершенно точно поменялись с момента установки этого монитора.
А самое лучшее я оставил напоследок. Сумму денег, а также примерную оценку генерации CO2 на кВт нельзя настраивать. Согласно брошюре, их можно указать при первой установке, но там нет никакой информации о том, как сбросить систему к настраиваемому состоянию.
Кроме того, в брошюре говорится следующее:
Дата и время всегда правильны и не требуют никакой настройки
Понятия не имею, зачем они это написали. Разумеется, с 2015 года (предполагаемой даты установки) часы на планшете с Android сдвинулись почти на 15 минут.
В целом всё это весьма разочаровывало. Однако у меня нашлось дома несколько микроконтроллеров Raspberry Pico. Если я смогу подключиться к сети WiFi менеджера энергии и получить данные с сервера, то просто извлеку потребление кВт из API, умножу на правильную стоимость и буду отображать её на каком-нибудь инстансе Grafana.
Главная проблема заключается в том, что я не знаю IP-адреса сервера. Я уже был готов запустить сканирование IP-адресов с ноутбука, но заметил, что в брошюре говорится о возможности контроля энергопотребления через PC. Вместе с инструкциями были указаны IP-адрес и порт. Открыв их в браузере, мы видим знакомый экран:
Оказалось, что интерфейс на планшете с Android — это просто webview. Это упрощает нашу работу, потому что можно просто перейти в web inspector и просмотреть все вызовы API. Взглянув на URL, мы видим…
...Socket.IO! Ого, честно говоря, не ожидал этого. Клиенту в буквальном смысле требуется получить от сервера всего пять чисел, поэтому Socket.IO в этом случае — абсолютно лишняя трата ресурсов. Кроме того, для своей функциональности клиентский код выглядит очень сложным. В нём не менее шести модулей RequireJS, и все они, разумеется, загружаются динамически через разные запросы. Там есть Handlebars, Backbone.js, Underscore.js… я как будто снова попал в старшую школу. Именно этими технологиями я очень интересовался, когда только начинал заниматься веб-разработкой.
Но постойте-ка, использование Socket.IO означает, что во встроенном устройстве внутри шкафа со счётчиками работает JavaScript? Наверно, это самая странная платформа граничных вычислений из тех, которые я встречал в своей жизни. Я хочу развернуть на ней хоть что-нибудь!
Полностью забыв свою идею о получении данных с сервера при помощи Raspberry Pico, я надел шляпу хакера и начал изучать систему. IoT-устройства имеют ужасную репутацию с точки зрения безопасности, поэтому я ожидал, что это будет лёгкая прогулка, которая займёт максимум два часа. Как же я ошибался!
Попытка прямого подключения по SSH командой ssh root@172.16.0.254
сразу завершается с ошибкой «Connection refused». Это может означать многое, так что давайте изучим доступные порты:
$ sudo nmap -p- -sV -O 172.16.0.254
... частично обрезано ...
Nmap scan report for 172.16.0.254
Host is up (0.011s latency).
Not shown: 65530 closed tcp ports (reset)
PORT STATE SERVICE VERSION
53/tcp open domain dnsmasq 2.63rc6
80/tcp open http Node.js (Express middleware)
1534/tcp open micromuse-lm?
3000/tcp open http Node.js (Express middleware)
41142/tcp open ssh OpenSSH 6.2 (protocol 2.0)
А вот это уже интересно. Как и ожидалось, мы видим запущенный сервер Node.js. Также тут есть dnsmasq
— DHCP-сервер (логично, ведь устройство имеет точку доступа WiFi) и скрытый SSH-сервер на порту 41142.
SSH-соединение больше не отклоняется, но оказалось, что рут защищён паролем. Не подошла ни одна из простых комбинаций имени и пароля типа admin/admin
или root/root
, то есть мы, по сути, вернулись к началу. Однако nmap
обнаружил на порту 1534 нераспознанный сервис под названием micromuse-lm
. Первым результатом поиска в Google стал следующий пост с форума:
Не знаю, кто ты такой, @ljohnson
, но пусть твоя жизнь будет счастливой и процветающей. В этом посте было не особо много информации, но он подсказал мне нужные ключевые слова для дальнейших поисков. Самое главное здесь — это tcf-agent
. Конкретное описание происходящего здесь разбросано тонким слоем по множеству веб-сайтов, каждый из которых подразумевает знание терминологии. Каждый из этих веб-сайтов отдаёт тебе крошечный кусочек пазла, и ты должен соединить их самостоятельно. Спустя несколько часов и кучу матерных слов я узнал о tcf-agent
следующее:
TCF расшифровывается как Target Communications Framework. Это текстовый протокол, позволяющий читать файловую систему, запускать новые процессы, отправлять сигналы процессам и делать с целевой системой многое другое. tcf-agent
— это сервер, реализующий этот протокол, или, иными словами, вторая по серьёзности уязвимость безопасности после беспарольного SSH-рута. Я не понимаю, зачем разработчики пошли на такие хлопоты с паролями SSH, но оставили работающий tcf-agent
.
Похоже, TCF тесно связан с экосистемой Eclipse. В руководстве Getting Started в качестве основного способа взаимодействия с tcf-agent
рекомендуется множество разных плагинов для Eclipse. Я попробовал установить эти плагины на новую версию Eclipse, но это оказалось абсолютно невозможно. Повсюду возникали проблемы с зависимостями, а когда пытаешься установить недостающие зависимости, Eclipse не позволяет этого сделать, потому что они конфликтуют с какими-то другими зависимостями. Творится настоящий хаос, в точности, как я и предполагал.
К счастью, у проекта TCF есть Python SDK. Как и со всем остальным в этом исследовании, мне пришлось пройти по трём ссылкам на разные веб-сайты, чтобы найти его. Во-первых, мы встречаемся с этой милой страницей:
На ней была ссылка, ведущая сюда, где есть очень мелкий текст, в котором говорится, что репозиторий был перемещён в этот репозиторий Gitlab. Фух. У репозитория даже есть несколько свежих коммитов, что меня немного удивило, ведь в целом TCF ощущается заброшенным проектом.
Как бы то ни было, в репозитории есть довольно современный Python 3 (!) SDK, даже со встроенной документацией. Она неидеальна, некоторые документы устарели, иногда методы довольно странные, но из кода достаточно легко можно понять, что он делает. Сильно помогла в этом процессе спецификация протокола (здесь и здесь).
Если вкратце, то tcf-agent
предоставляет набор сервисов, раскрывающих различные части системы. Например, есть сервис FileSystem
для всех взаимодействий с файловой системой, сервис Processes
для запуска/остановки/отладки процессов и так далее. Вот пример получения информации текущего пользователя:
import tcf
from tcf.util.sync import CommandControl
tcf.protocol.startEventQueue()
cmd = CommandControl(tcf.connect('TCP:172.16.0.254:1534'))
error, user = cmd.FileSystem.user()
print(user)
И вот так мы узнали, что tcf-agent
на самом деле работает под пользователем root
. Снова задаюсь вопросом, зачем было заморачиваться с паролями SSH, если оставляешь отладочный сервер с рут-доступом. Я этого никогда не пойму.
Сервисы FileSystem
и Processes
имеют другие функции, приблизительно соответствующие системным вызовам. При помощи этого API можно достаточно легко воспроизвести альтернативы популярным командам наподобие ls
, cat
, ps
и так далее. Это сейчас я говорю «достаточно легко», но на самом деле мне понадобилось четыре часа на угадывание формата протокола, попытки найти документацию, устранение багов в SDK, и всё это на чрезвычайно нестабильном WiFi-соединении со встроенного устройства. Весёлое занятие. Результаты можно найти в этом репозитории Github.
Теперь, когда у нас есть основные инструменты, приступим к хакингу! Первым делом я попытался взломать пароль рута, потому что всё ещё думал, что он тривиальный. Я воспользовался взломщиком паролей John the Ripper:
# `cat.py` получает содержимое файлов из менеджера энергии
# при помощи сервиса TCF `FileSystem`.
$ ./cat.py /etc/passwd > passwd.txt
$ ./cat.py /etc/shadow > shadow.txt
$ unshadow passwd.txt shadow.txt > passwords.txt
$ john passwords.txt
Я оставил взломщик работать примерно на семь часов, но ему не удалось найти совпадений. John сообщил, что завершит свой брутфорс в 2035 году, поэтому я решил попробовать другой подход.
Немного погуглив, я выяснил, что можно просто оставить пароль рута пустым, модифицировав /etc/shadow
. Сделав это, я отключил устройство, сняв установленный ранее предохранитель, и включил обратно, вставив его обратно спустя какое-то время. К сожалению, SSH по-прежнему отказывал моим попыткам логина.
Больше из отчаяния, чем из-за чего-то ещё, я решил посмотреть на sshd-конфигурацию хоста и наконец нашёл виновника. В sshd_config
находилась строка PermitRootLogin no
— весьма разумная мера защиты, если ты не предоставляешь полный дисковый доступ всем, кто есть в сети.
Я заменил эту строку на PermitRootLogin yes
и увидел результат, за который боролся:
Мы вошли! Ух, ну и приключение было, правда? Давайте осмотримся!
root@nt-core:~# uname -srm
Linux 3.10.28 armv5tejl
Мы видим, что в системе работает Linux 3.10, то есть довольно новый релиз (середина 2013 года), учитывая, что устройство было разработано и установлено примерно в 2014-2015 годах. Внутри установлен чип ARM, что вполне нормально для встроенного устройства:
root@nt-core:~# cat /proc/cpuinfo
processor : 0
model name : ARM926EJ-S rev 5 (v5l)
BogoMIPS : 226.09
Features : swp half fastmult edsp java
CPU implementer : 0x41
CPU architecture: 5TEJ
CPU variant : 0x0
CPU part : 0x926
CPU revision : 5
Hardware : Freescale MXS (Device Tree)
Revision : 0000
Serial : 0000000000000000
Это CPU семейства ARM9. В Википедии говорится, что он был выпущен в 2001, а также упомянуто, что его использовали в качестве сопроцессора в Nintendo Wii.
Ещё более удивительным для меня показалось то, что этот процессор поддерживает непосредственное исполнение байт-кода Java. Да, вы не ошиблись, java
в списке функций CPU действительно означает тот самый Java. Расширение ARM для этой функции называется Jazelle. Похоже, в 2000-х это было трендом, потому что с этой функцией я встречаюсь не в первый раз.
root@nt-core:~# cat /proc/meminfo
MemTotal: 118172 kB
...остальное я вырезал...
Кроме того, устройство имеет 118 МБ ОЗУ. Я не специалист по Linux для встроенных систем, но после работы с Raspberry Pi Pico и ей подобными это кажется достаточно большим объёмом. В то же время, это выглядит логично, ведь на хосте работает приложение Node.js, а JavaScript использует память не очень эффективно.
Само приложение представляет собой значительных размеров кодовую базу. Несмотря на то, что создавшая устройство и ПО для него компания уже не существует, я не хочу рисковать, публикуя исходный код. Я почти уверен, что никому не будет до этого дела, но всё равно не хочу связываться. Поэтому вместо этого мы просто изучим списки файлов.
Структура папок верхнего уровня выглядит так:
root@nt-core:/srv/server# ls -la
drwxr-xr-x 13 nodejs nogroup 4096 Oct 23 10:47 .
drwxr-xr-x 3 root root 4096 Oct 14 12:13 ..
-rw-r--r-- 1 nodejs nogroup 8125 Oct 14 12:13 Gruntfile.js
-rw-r--r-- 1 nodejs nogroup 1916 Oct 14 12:13 app.js
drwxr-xr-x 2 nodejs nogroup 4096 Oct 14 12:13 app_data
drwxr-xr-x 10 nodejs nogroup 4096 Oct 14 12:13 bin
-rw-r--r-- 1 nodejs nogroup 1278 Oct 14 12:13 bower.json
drwxr-xr-x 2 nodejs nogroup 4096 Oct 23 10:40 info
-rw-r--r-- 1 nodejs nogroup 16 Oct 14 12:13 jira_version
-rw-r--r-- 1 nodejs nogroup 2673 Oct 14 12:13 karma.conf.js
drwxr-xr-x 2 nodejs dialout 4096 Oct 23 10:49 logs
drwxr-xr-x 4 nodejs nogroup 4096 Oct 14 12:13 modules
drwxr-xr-x 17 nodejs nogroup 4096 Oct 14 12:13 node_modules
-rw-r--r-- 1 root root 0 Oct 23 10:40 nodejs.log
-rw-r--r-- 1 nodejs nogroup 76023 Oct 14 12:13 npm-shrinkwrap.json
-rw-r--r-- 1 nodejs nogroup 2216 Oct 14 12:13 package.json
drwxr-xr-x 6 nodejs nogroup 4096 Oct 14 12:13 production
drwxr-xr-x 6 nodejs nogroup 4096 Oct 14 12:13 public
drwxr-xr-x 4 nodejs nogroup 4096 Oct 14 12:13 routes
drwxr-xr-x 3 nodejs nogroup 4096 Oct 14 12:13 scripts
drwxr-xr-x 4 nodejs nogroup 4096 Oct 14 12:13 views
Насколько я понял, приложение состоит из двух частей. Первая отвечает за считывание данных энергопотребления с подключённого к устройству счётчика. Эта часть называется «Pulse app», а её двоичный файл расположен в папке bin
:
root@nt-core:/srv/server# ls -la bin
drwxr-xr-x 10 nodejs nogroup 4096 Oct 14 12:13 .
drwxr-xr-x 13 nodejs nogroup 4096 Oct 23 10:47 ..
drwxrw---- 2 nodejs dialout 4096 Nov 16 00:12 aggregate
drwxrw---- 2 nodejs dialout 4096 Oct 23 11:03 cfg
-rwxr-xr-x 1 nodejs dialout 52418 Oct 14 12:13 ct-read-daemon
drwxrw---- 2 nodejs dialout 4096 Nov 16 01:00 daily
-rwxr-xr-x 1 nodejs nogroup 1155 Oct 14 12:13 get_sys_versions.sh
drwxrw---- 2 nodejs dialout 118784 Nov 16 03:00 hourly
drwxrw---- 2 nodejs dialout 4096 Nov 1 01:00 monthly
drwxr-xr-x 2 nodejs nogroup 4096 Oct 14 12:13 output
-rwxr-xr-x 1 nodejs dialout 34543 Oct 14 12:13 pulse-app
-rwxr-xr-x 1 nodejs dialout 117029 Oct 14 12:13 pulse.ko
-rwxr-xr-x 1 nodejs nogroup 758 Oct 14 12:13 reset_nrg_mgr.sh
drwxrw---- 2 nodejs dialout 4096 Nov 16 01:00 weekly
drwxrw---- 2 nodejs dialout 4096 Oct 23 11:00 yearly
Изучив отладочные символы, я сначала решил, это обычное приложение на C, что казалось подходящим для взаимодействия с низкоуровневыми контактами GPIO. Однако меня заинтересовал файл pulse.ko
. Расширение .ko
обычно обозначает Kernel Object, то есть можно предположить, что на самом деле это модуль ядра. Но я ничего не знаю о модулях ядра, так что могу ошибаться.
«Pulse app» считывает данные с контактов GPIO и сохраняет результаты в файлы CSV. Эти файлы CSV разбиты по месяцам, дням и часам в папках с соответствующими именами, тоже находящихся в папке bin
. Такое разделение не является случайностью. Режим исторических данных в веб-UI менеджера энергии поддерживает отображение данных только по месяцу, дню и часу.
Кроме «Pulse app» существует и вторая часть приложения. Приложение Node.js считывает файлы CSV, заполненные данными об энергопотреблении, и отображает их пользователю в веб-UI. Оно использует Node.js 0.10.26, Express.js 4.13.3 и Socket.io 1.3.6.
Исследуя зависимости, я заметил пакет mqtt
. Это меня заинтриговало, потому что пока я не видел взаимодействия с брокером сообщений. Ещё немного поизучав исходники, я решил, что это незавершённая интеграция с облаком, обещанная компанией Netthings в брошюре. В исходниках даже упоминаются жёстко прописанные в коде IP-адреса, используемые для подключения к брокеру сообщений. Неудивительно, что ни одного из них уже не существует. Я даже не уверен, как бы всё это работало, ведь у устройства нет доступа к Интернету.
В конечном итоге, это было очень интересное расследование! Я назвал его «своим проектом по городской археологии».
P.S. В качестве завершающего штриха я решил оставить небольшую записку в папке home. Странно осознавать, что, вероятно, я единственный человек, который прочитает её. Но возможно, в далёком будущем в этой квартире будет жить другой разработчик ПО, который найдёт её. Только время даст ответ…
P.P.S. Пользователь HackerNews M6WIQ сообщил о ещё одной статье, посвящённой последствиям инженерных решений NetThings. Отличное чтиво!
P.P.P.S. Автор статьи по ссылке выше ответил мне в Mastodon и спросил, по-прежнему ли устройство использует его IP-адрес NTP-сервера. Правда оказалась гораздо хуже.
P.P.P.P.S. Марк Беван любезно предложил мне воспользоваться своими ресурсами GPU для брутфорса паролей пользователей Linux в менеджере энергии. Оказалось, что их хэшировали алгоритмом, которому уже не менее тридцати лет, не представляющим никакой сложности для современной вычислительной мощи. Для пользователей root
и gecko_user
использовались пароли Newt@rd$
, а для аккаунта prod_test
— пароль NetTh@ng
, что довольно иронично.
Больше интересных статей читайте на моём сайте.
Автор:
ru_vds