
Как можно догадаться, на этот раз речь пойдёт об очередном поделии китайского IoT-гения, а именно о цифровой камере для третьего "глаза" микроскопа (можно ли назвать камеру для микроскопа IoT-ом?). Не знаю, может у них там сверху наказ такой: в каждую камеру по бэкдору (и чем больше, тем лучше), но это в самом деле треш.
За какую камеру не возьмись: будь она для видеонаблюдения, либо, как в моём случае, для вывода картинки с микроскопа на монитор/смартфон - по факту вы получаете не только девайс, выполняющий свои основные функции, но и как "премиум-фичу" - следящее (конечно же, за Вами) устройство. Об одном таком девайсе я и хочу рассказать.
Зачем
На рабочих проектах, да и в качестве хобби, я люблю покопаться в кишочках различных железок. И, как правило, я преследую при этом следующие цели:
-
найти недокументированный функционал и выяснить, можно ли его эксплутировать
-
предостеречь текущих, либо будущих владельцев этих девайсов от небезопасного использования
-
выработать с вендором исправления безопасности (и исправить баги по возможности). Бывает, конечно, что вендор не идёт на контакт, тогда приходится публиковать инструкцию по ручному закрытию таких дыр = теоретической эксплуатации нехорошими дядями)
Так вот, ковыряние железок часто подразумевает подпаивание к различным мелким дорожкам/ножкам компонентов, отладочным площадкам, а с моим орлиным зрением без микроскопа там зачастую делать нечего. Поэтому я и купил себе в домашнюю лабу (по-факту, филиал работы на лоджии) микроскоп.
Хорошо, конечно, да маловато будет - фотку платы для красивого отчёта/статейки не сделаешь, а искать объективом телефона "дырочку" окуляра можно, но презентабельность не та. Так я и купил камеру.
О самой камере
Модель устройства: SCAM4K8MPB (да, с неймингом вообще топ! Надеюсь, "S" здесь не про Security). Выглядит буквально так, как на превьюшках на сайте:


Имеет массивный алюминиевый корпус, внутри сенсор Sony IMX585
. Получать изображение с камеры можно через совершенно разные интерфейсы:
-
через USB WiFi-свисток и подключение к создаваемой камерой WiFi-точке доступа (софт от вендора умеет находить камеру и получать с неё изображение)
-
через USB WiFi-свисток и подключение к вашей точке доступа (точно также, через софт вендора)
-
через USB-Video (по-факту, подключается камера как устройство)
-
HDMI (через него к тому же становится доступным внутренний GUI камеры, где можно тыкать мышкой)

В моём случае, последние два варианта получались совсем неудобными - кабели USB и HDMI толстые, жёсткие, да и стол с микроскопом далековато от ПК, поэтому я рассматривал только работу по WiFi. Тем более, есть возможность использовать в том числе и Android-приложение.
Софт для моей камеры, на самом деле, та ещё тема: судя по всему, есть какой-то основной вендор и он предоставляет не только услугу по вкорячиваю вашего софта в камеру, но и собирает красивый инсталлятор с названием и логотипом заказчика. Как говорится, найдите 10 отличий:


В первом случае SDK (поставлялся на диске к моей камере) называется MiiCamSdk, а во втором (скачал с официального сайта touptek) - ToupCamSdk. По начинке - один в один.
Разобравшись с тем, как подключить камеру к моей домашней сети я запустил nmap
. Не спрашивайте, зачем - просто проф-деформация. Надежда была на то, что "ну уж на камере для микроскопа-то не должно быть никакого лишнего торчащего говна порта". И не обнаружив бы лишних портов я работал бы себе спокойно и бед не знал. Но...
Как всегда
... и на что я там надеялся, наивный. Уже само наличие в списке открытых портов (на пару с какими-то сервисным барахлом) скучающего без конекта из поднебеснойtelnet
на 23/TCP
шепчет: "ты пользуйся, братишка, пользуйся, но и не забывай с вендором делиться".

И проблема ведь не только в том, что китайский брат иногда будет посматривать в твою камеру как ты паяешь. Но и запросто может быть, что пароль от телнета твоей "слишком умной" камеры уже давно во всех словарях для брута лежит. Какая-нибудь малварь залогинится к тебе и начнёт майнить бетховены. Оно тебе надо?
Вскрывать?
"Порт надо закрыть", решил я, и... ничего не сделал, побоялся. На самом деле, камера ведь не дешёвая: вскрывать, паяться к ней - запачкаю/поцарапаю матрицу, и всё - деньги на ветер. Неохота было во всё это лезть. Но и оставлять всё так как есть нельзя. Что же делать?
Приоткрою, посмотрю одним глазком
В итоге, я решился. Детали корпуса камеры держатся на винтах, которые откручиваются шестигранником, поэтому, подумал я, можно будет аккуратно вскрыть, без необратимых последствий. А там уж решу: легко ли будет подобраться к необходимым контактам и что-либо сделать.
После того, как все четыре винта были откручены, я без труда снял верхнюю крышку с надписями. А там меня ожидала следующая картина:

Видим две платы, соединённых шлейфом. На одной из плат виднеется камень от SigmaStar. Странно, но флешку с прошивкой сходу обнаружить не удалось, либо она мелкая и BGA. Что хорошо, голого сенсора тоже не видно, так что запачкать его с порога не выйдет.

Приятным удивлением для меня оказались три штырька интерфейса UART, которые располагались прямо у снятой крышки - выглядело всё так, что паять ничего не придётся (по крайней мере, если с UART можно взаимодействовать).

Работать с открытой крышкой (как у дозиметров, по-взрослому) мне не хотелось, поэтому я не придумал ничего умнее, кроме как просверлить в алюминиевой крышке отверстие и протащить через него три провода.
К сожалению, высота конектора (чёрного пластика) у обычного dupont-провода оказалась слишком большой, пришлось раскурочивать и снимать пластик, гнуть металлический коннектор и закрывать термоусадкой. Зато, как по мне, получилось отлично:



Дальше я протащил провода, собрал корпус, подключился к UART через USB-TTL и включил камеру. Сразу же после запуска, по традиции, я усиленно принялся жать Enter
в надежде попасть в меню наподобие U-Boot. Так и получилось.

Программные внутренности
Что делать дальше я плюс-минус уже представлял:
-
получить список разделов, которые доступны системе через вызов команды
mtdparts
Список разделов nand -
через команду чтения
nand read.e
прочитать в оперативную память содержимое интересующего меня разделаСогласно докам, read.e
читаетnand
с учётом кодов коррекции ошибок ECC -
через команду чтения оперативной памяти
md.b
сдампить содержимое памяти по UART в файлПервые 0x100
байтrootfs
Адрес оперативной памяти у SigmaStar SSD268G
(основной SoC), как говорит Github - 0x22000000
. Итого, у меня получился следующий скрипт для получения дампа:
Исходный код дампера
import binascii
import serial
import re
from tqdm import tqdm
MTD_PAT = re.compile(r'^(?: +)?(d+): (w+)(?: +)?(0x[0-9a-f]+)t(0x[0-9a-f]+)t0$')
HEX_PAT = re.compile(r'[0-9a-f]{8}: ((?:[0-9a-f]{2} ){16}).+')
def exec_cmd(s, cmd):
# print('executing %s' % cmd.decode())
s.write(b'%sn' % cmd)
lines = []
line = ''
while True:
b = s.read()
if b == b'n':
line = line.rstrip('rn')
lines.append(line)
line = ''
continue
elif b == b'' and line.startswith('SigmaStar # '):
break
line += b.decode()
return lines
def read_mtdparts(s):
lines = exec_cmd(s, b'mtdparts')
lines = lines[4:]
res = []
for line in lines:
m = MTD_PAT.match(line)
if m is None:
continue
idx, name, size, offset = m.groups()
size = int(size, 16)
offset = int(offset, 16)
res.append((name, offset, size))
return res
def parse_hex_dump(lines):
buf = bytearray()
for line in lines:
m = HEX_PAT.match(line)
if m is None:
continue
bb = binascii.unhexlify(m.group(1).replace(' ', ''))
buf.extend(bb)
return bytes(buf)
def read_nand_offset(w, s, offset, size, pb):
bs = min(size, 0x1000)
exec_cmd(s, b'nand read.e 0x22000000 0x%x 0x%x' % (offset, size))
off = 0
while size > 0:
size -= bs
lines = exec_cmd(s, b'md.b 0x%x 0x%x' % (0x22000000 + off, bs))
off += bs
buf = parse_hex_dump(lines)
w.write(buf)
pb.update(bs)
def read_part(s, part):
name, offset, size = part
with open('%s.bin' % name, 'wb') as w:
off = 0
with tqdm(total=size) as pb:
while size > 0:
bs = min(size, 0x100000)
size -= bs
read_nand_offset(w, s, offset+off, bs, pb)
off += bs
def main():
s = serial.Serial('COM29', 115200, timeout=0.001)
parts = read_mtdparts(s)
for part in parts:
read_part(s, part)
s.close()
if __name__ == '__main__':
main()
Наиболее интересным для старта мне показался раздел rootfs
. На нём, как я предполагал, должен храниться файл ( shadow
) прочитав который я смогу извлечь хэш и отправить его на брут в passwd
hashcat
.
Уже не помню за сколько часов, но дамп файловой системы я получил, извлёк тамошний squashfs
и отправил в брутилку лежавший там хэш:
root:$1$5I0IijLd$hE844yBxBeWIy1CvW3elD/:::::::
Пароль оказался простой: wheatfa
, и с ним я смог успешно подключиться к камере.
На удивление, очень часто встречаемый в IoT-устройствах заменитель bash
- busybox
оказался ничуть не урезанным: в нём была доступна нужная мне команда ftpd
. Через неё я в итоге и сдампил всё содержимое камеры целиком.


Конец?
По идее, на этом можно было бы и закончить этот мини-проект, но я за каким-то чёртом взялся смотреть что же там на других открытых TCP/UDP портах делается. И, опять же, лучше бы не брался - только настроение портить...
-
во-первых, у протокола передачи видео-потока RTSP, который поддерживается камерой, напрочь отсутствует аутентификация (а может и не нужна она на микроскоп-камере?)
-
во-вторых, открытые сервисные порты, у которых, по-видимому, одно предназначение - быть бэкдорными (получение изображений, запись видео), правда без возможностей удалённого исполнения команд

Более детально я не копался, решив ограничиться выключением заменой пароля на telnet
. Но, к сожалению, просто так заменить пароль не удалось: squashfs
- файловая система, которая монтируется только на чтение. И единственный способ в данном случае - переписать rootfs
. План действий приблизительно такой:
-
Сделать дамп раздела
rootfs
с помощью команды:nanddump -o -b -f /tmp/mtd8.bin /dev/mtd8
-
Скопировать файл
на ПК с линуксом (WSL пойдёт) и распаковать его командой: mtd8.bin
unsquashfs ./mtd8.bin
-
Сгенерировать новый безопасный пароль (не используйте хэш из примера) и заменить старый хэш на новый:
openssl passwd -1 -salt 12345678 12345678 $1$12345678$f8QoJuo0DpBRfQSD0vglc1 vi ./squashfs-root/etc/shadow
-
Посмотреть информацию о дампе командой
file mtd8.bin
, записатьblocksize
и тип сжатия (compressed
)file mtd8.bin mtd8.bin: Squashfs filesystem, little endian, version 4.0, xz compressed, 1944296 bytes, 420 inodes, blocksize: 131072 bytes, created: Sat Dec 2 06:33:06 2023
-
Упаковать файловую систему в новый файл:
mksquashfs squashfs-root/ rootfs_new.bin -noappend -always-use-fragments -comp xz -b 131072
-
Выровнять
rootfs_new.bin
до 8 байт FF-байтами -
Зайти в U-Boot и стереть
rootfs
командойnand erase.part rootfs
-
Записать (удобнее всего скриптом) в оперативную память содержимое файла
rootfs_new.bin
с помощью командmw.q 0x22000000 aabbccddaabbccdd
.
У меня данный процесс занял где-то час - писать по 8 байт дело небыстрое. -
Записать содержимое оперативной памяти в nand командой
nand write.e 0x22000000 rootfs
Код обратного дампера
import binascii
import struct
import time
import serial
import re
from tqdm import tqdm
MTD_PAT = re.compile(r'^(?: +)?(d+): (w+)(?: +)?(0x[0-9a-f]+)t(0x[0-9a-f]+)t0$')
HEX_PAT = re.compile(r'[0-9a-f]{8}: ((?:[0-9a-f]{2} ){16}).+')
def exec_cmd(s, cmd):
print('executing %s' % cmd.decode())
s.write(b'%sn' % cmd)
lines = []
line = ''
while True:
b = s.read()
if b == b'n':
line = line.rstrip('rn')
lines.append(line)
line = ''
continue
elif b == b'' and line.startswith('SigmaStar # '):
break
try:
line += b.decode()
except:
pass
return lines
def write_part(s, name, buf):
exec_cmd(s, b'nand erase.part %s' % name.encode())
lb = len(buf)
with tqdm(total=lb) as pb:
for i in range(lb // 8):
val = struct.unpack_from('<Q', buf, i * 8)[0]
xx = b'mw.q 0x%x %016x' % (0x22000000 + i * 8, val)
exec_cmd(s, xx)
pb.update(8)
exec_cmd(s, b'nand write.e 0x22000000 %s 0x%x' % (name.encode(), lb))
def main():
s = serial.Serial('COM29', 115200, timeout=0.001)
with open('mtd8_new.bin', 'rb') as f:
buf = f.read()
write_part(s, 'rootfs', buf)
s.close()
if __name__ == '__main__':
main()
Всё вышеописанное можно было бы сделать быстрее, будь на устройстве утилита стирания флеша. Простой записью в файла с FF-ками я также просто ушатал раздел. mtd8
Заключение
В общем, как-то так. Разгорячившись, я расковырял ещё одну свою камеру (видеонаблюдения) от TP-Link, и всё, что в итоге сделал - выбросил её в мусорку. Ситуация там примерно та же. Но об этом уже как-нибудь в другой раз.
Автор: DrMefistO