Около полутора лет назад я решил начать свое путешествие в область реверс-инжиниринга IoT-устройств. Сегодня я хотел бы рассказать о своем опыте и результатах исследования уязвимостей в китайской IP-камере Tenda CP3.
Что я подразумеваю под реверс-инжинирингом IoT-устройств? Прежде всего:
Разборка устройства и идентификация компонентов
Извлечение прошивки
Анализ прошивки и поиск уязвимостей
В процессе анализа нескольких программ мне посчастливилось обнаружить одну уязвимость/бэкдор, которая позволила мне выполнить атаку с удаленным выполнением кода и получить root-доступ с удаленного хоста. Давайте разберемся подробнее.
P.S. Это первая статья для автора, поэтому принимаю виртуальные помидоры в свою сторону в комментариях.
Глава 1: разборка устройства и идентификация компонентов
Скорее всего, это самый простой этап всего проекта, поскольку все, что вам нужно, — это хорошая отвертка и 15 минут свободного времени. В камере есть специальные внутренние двигатели, которые управляют ее движением — благодаря им вы можете управлять устройством удаленно через мобильное приложение для iOS/Android. После отключения нескольких двигателей, отвечающих за движение камеры, плата (PCB) извлекается. Как только все двигатели отключены, мы достаем плату и видим «мозг» устройства. Для наиболее точной идентификации основных элементов я использовал цифровой микроскоп. На плате есть два ключевых компонента, которые стоит выделить:
1) Fulhan FH8626 - SoC камеры Tenda CP3. Этот чип широко используется в IP-камерах и отвечает за обработку видеосигнала.

2) Флеш-чип — cFeon QH64A. Это чип, на котором хранится основная прошивка камеры.

Важное замечание: мне сразу удалось найти UART-пины на плате, причем они были подписаны. Поэтому мне не пришлось идентифицировать их вручную с помощью мультиметра (хотя это и не сложно). После припаивания проводов к UART-пинам я подключил их к известному хакерскому мультитулу Bus Pirate. На этом этапе я использовал Bus Pirate как USB-UART-преобразователь:

Я использовал minicom для установления последовательного соединения с камерой, подключил устройство и сразу увидел логи загрузки uboot и ядра Linux. Я попытался ввести что-то с клавиатуры, но камера потребовала логин и пароль (что вполне ожидаемо). В такой ситуации можно попытаться найти учетные данные в сети. Но мы сделаем это по-хакерски и извлечем прошивку, чтобы получить доступ к файловой системе, где хранятся пароль к root.
Глава 2: снятие прошивки с устройства
Чтобы получить доступ к файловой системе камеры, нужно извлечь всю прошивку из флеш-чипа. Я снова использовал Bus Pirate, но в немного другой конфигурации: подключил SPI-клипce к флеш-чипу, и согласно схеме чипа cFeon, соединил SPI-клипce с Bus Pirate. Вот как это выглядит:

Для снятия прошивки я использовал утилиту flashrom:
sudo flashrom -p buspirate_spi:dev=/dev/ttyUSB0 -r camera_fw.bin
В итоге я извлек всю прошивку устройства. Несколько минут — и файл прошивки размером 8 мегабайт у нас в руках.
Глава 3: анализ прошивки
Чтобы изучить содержимое файла прошивки, можно использовать утилиту binwalk (без флагов на первом этапе) для анализа ее структуры.
binwalk camera_fw.bin
Результат:

На этом этапе нас особенно интересует файловая система SquashFS, так как она обычно содержит файловую систему всего устройства. Чтобы извлечь содержимое всех разделов прошивки, включая SquashFS, можно использовать binwalk с флагом -e. Это распакует прошивку и предоставит доступ к файловой системе устройства:

Я выделил файл shadow, поскольку он содержит хэш пароля root:
root:7h2yflPlPVV5.:18545:0:99999:7:::
Для его расшифровки можно использовать инструмент, такой как hashcat. Однако в данном случае быстрый поиск в Google показал, что кто-то уже взломал этот хеш (респект человеку, который сделал это). Паролем root оказался tdrootfs. Используя этот пароль, открываем minicom, входим как root с паролем tdrootfs, и вуаля — у нас есть полный доступ к устройству:

Глава 4: анализ рантайма девайса
Получив полный доступ к устройству, можно начать изучать, как оно работает. Начнем с вывода списка всех запущенных процессов, чтобы понять поведение устройства:

Кстати, я рекомендую использовать команду ps с флагом -t, так как она отображает список всех потоков, созданных запущенными процессами. Вы можете задаться вопросом, почему я выделил два конкретных процесса: apollo и noodles. Причина проста: поскольку наша камера взаимодействует с сетью, нам нужно проанализировать все открытые порты, чтобы понять ее сетевую активность. Это можно сделать с помощью:
netstat -natpu
Результат:

Как видно, большинство открытых портов связано с двумя процессами (apollo и noodles), которые я упомянул ранее. С учетом этого я приступил к реверс-инжинирингу этих процессов, чтобы глубже понять их функциональность.
Глава 5: анализ и поиск уязвимостей
Реверс-инжиниринг любого бинарного файла начинается с определения его архитектуры:
➜ abin git:(master) ✗ file noodles
noodles: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-uClibc.so.0, stripped
Выполнение команды file на бинарном файле noodles показало, что это 32-битный исполняемый файл для архитектуры ARM. Кроме того, я выполнил команду strings на этом файле и заметил, что он упакован с помощью UPX-пакера, что указывает на то, что бинарный файл сжат для затруднения анализа его содержимого:
This file is packed with the UPX executable packer http://upx.sf.net
Распаковал его с помощью:
upx -d noodles
Загружаем в Гидру и начинаем анализ программы.
Я проанализировал main и выделил два ключевых участка кода, которые стоит отметить:
1) FUN_000151d0 — эта функция создает сокет, биндит его к порту 1300 и начинает слушать входящие соединения:

2) Вторая часть принимает соединения от других хостов и получает входные данные от пользователя:

Нас особенно интересует часть кода, отвечающая за разбор входных данных пользователя и выполнение определенных функций на основе этих данных. Этот участок критически важен, так как он может показать, как программа обрабатывает команды и взаимодействует с системой:

Из кода видно, что отправка команды ELFEXEC на камеру инициирует выполнение ELF-бинарного файла. Эта функциональность напоминает бэкдор, что, учитывая, что это устройство китайского производства, может быть не совсем удивительным.
Примечание: Выделенная функция FUN_000152a8 отвечает за разбор входных данных пользователя и широко используется в бинарном файле. Для ясности я позже переименовал ее в input_cmd_checker.
Ожидаемый формат команды для взаимодействия с этой функцией следующий:
<ELFEXEC>some_sh1t</ELFEXEC>
После выполнения input_cmd_checker функции, программа запускает уязвимую функцию FUN_00012a04:

Функция FUN_00012274 отвечает за получение бинарного файла с удаленного хоста. Ее реализация проста: устройство создает файл, названный на основе третьего параметра (param3), и записывает в него все данные, полученные через сокет.
Моим открытием была функция FUN_00017ca4 (выделена красным прямоугольником).
Вот как она выглядит:

Эта функция слепо выполняет любой код с помощью функции system.
Сначала камера "скачивает" с хоста исполняемый файл(если триггернуть командой ELFEXEC), делает файл исполняемым:

И потом запускает функцию FUN_00017ca4, которая запускает файл.
Можно предположить, что разработчики добавили такую возможность в качестве тестирования продукта: закидывают с хоста какой-то тестовый файл и сразу запускают его. Но что нам мешает передать камере вредноносный файл и тогда "функция для тестов" становится бэкдором?
Глава 6: создание эксплоита
Изначально я рассматривал возможность разработки эксплойта типа "reverse shell", при котором цель (в данном случае камера) инициирует обратное соединение с хостом. Однако для этого на целевом устройстве требуется утилита netcat, которой на камере нет. К счастью, на камере есть telnetd, что позволяет нам открыть любой выбранный порт. Это дает возможность создать эксплойт типа "bind shell". Вот алгоритм эксплоита:
Подключаемся к 1300 порту на камере;
Отправляем полезную нагрузку, содержащую команду ELFEXEC, чтобы получить доступ к уязвимым функциям в бинарном файле;
После успешного выполнения шага 2 камера "загружает" бинарный файл с хоста. В данном случае содержимое бинарного файла будет следующим:
telnetd -p 8888 -l /bin/sh &
Данная команда открывает 8888 порт, предоставляя доступ к рут-шеллу.
Когда эксплойт выполнен, он отправляет полезную нагрузку, и камера запускает команду telnetd. Наконец, мы можем подключиться с нашего хоста к порту 8888 на камере, чтобы получить доступ к оболочке:

Глава 7: другой бэкдор
В процессе исследования этой камеры я наткнулся на научную статью (ссылка: https://arxiv.org/html/2406.15103v2), написанную итальянскими студентами, которые анализировали поведение этой камеры и искали уязвимости. Одна из частей этой работы особенно меня заинтересовала:
The latter command (YGMP_CMD), however, is far more interesting in the scope of our work, as it allows unauthenticated remote code execution on the camera by sending a formatted XML payload. In particular, the payload accepts 3 different tags for parsing: TARGET, MAC, and CMD. Although the content of both TARGET and MAC are apparently not used except in some printing functions, the content of the CMD tag is compared to the reboot string. If the strcmp returns 0, the FUN_00016ea8 function is called with the argument /app/bin/cmd reset; otherwise, the content of the CMD tag is passed directly to the same function.
Вкратце: одна из программ камеры создает UDP-сокет, привязывает его к определенному порту и принимает через этот порт различные пользовательские команды. Отправляя определенную полезную нагрузку на этот порт, можно выполнить атаку с удаленным выполнением кода, поскольку программа не проверяет, какая команда была получена.
Вот как это работает под капотом:
input_cmd_checker(acStack_828,&DAT_00029cc8,&local_c3c,0x400);
if (((char)local_c3c == '\0') || (iVar4 = strcmp((char *)&local_c3c,acStack_e1c), iVar4 == 0)) {
input_cmd_checker(acStack_828,&DAT_00029c9c,&local_c3c,0x400);
/* [BAD 2] fprintf */
fprintf(stderr,"cmd<%s>\n",&local_c3c);
iVar4 = strcmp((char *)&local_c3c,"reboot");
if ((iVar4 == 0) && (iVar4 = FUN_0001b040("/app/bin/cmd"), iVar4 != 0)) {
func_with_system("/app/bin/cmd reset");
}
func_with_system(&local_c3c);
}
Как видно из последних строк, если отправленная нами команда — это "reboot", программа перезагрузит камеру, но если это не так... программа просто выполнит эту команду. Не бэкдор ли это? Кхм. Мы подключаемся к этому порту, отправляем полезную нагрузку с "вредоносным кодом":
<YGMP_CMD><TARGET>ip_was_here</TARGET><MAC>10:20:30:40:50:60</MAC><CMD>echo pwned_by_cr0cus > /dev/kmsg </CMD></YGMP_CMD>
Где echo pwned_by_cr0cus > /dev/kmsg — это наша вредоносная полезная нагрузка, которая оставляет след в кольцевом буфере ядра. Результат показан на скриншоте. Фактически, разработчики оставили последнюю строку, чтобы можно было выполнить любой произвольный код на камере. Другими словами, эта уязвимость позволяет легко выполнить произвольный код:

Аутро
Вот как китайская камера была хакнута мной. Спасибо за прочтение и всего хорошего!
P.S. у автора статьи есть телеграм-канал, в котором я пишу о низкоуровневой безопасности, хардвере, CTF и прочих аспектах безопасности. Приходите
0xKyle
Побольше бы таких статей, было интересно