Zmotywowany zabawą wideodomofonem, postanowiłem kupić chińską IP kamerkę opartą na tym samym układzie co domofon. Niestety, w opisie aukcji jest błąd i tak naprawdę kamerka przyszła na innym SoC, a zakupu dokonałem tylko w celu podmiany Firmware. Lepiej bawić się na produkcie w cenie za 49,50 zł, niż ponad 2000zł.
Tak naprawdę produkt jest oparty na SoC IPC 510a (ARMv7 Processor rev 1 (v7l)) posiadający 32MB RAM do dyspozycji. Całe Firmware zaszyte jest w kości W25Q64CV czyli 64MiB = 8MB. Domyślnym adresem IP jest 192.168.1.10, więc jako że mój wideodomofon ma taki adres IP na interfejsie sieciowym eth0 (nie ma możliwości zamiany), to w pierwszej kolejności musiałem zmienić ten adres. I tu spotkała mnie mała niespodzianka, web interfejs działa tylko z IE. W dodatku wymaga kontrolki AcitvX (naah).
Po udanej zamianie adresu IP przystąpiłem do dalszej zabawy. W pierwszej kolejności poleciał nmap:
dawid@arch:~/$ sudo nmap -Pn -sV -p- 192.168.1.201
Starting Nmap 7.70 ( https://nmap.org ) at 2019-06-20 12:12 CEST
Nmap scan report for 192.168.1.201
Host is up (0.0021s latency).
Not shown: 65528 closed ports
PORT STATE SERVICE VERSION
80/tcp open http uc-httpd 1.0.0
554/tcp open rtsp H264DVR rtspd 1.0
8899/tcp open soap gSOAP 2.7
9530/tcp open unknown
34567/tcp open dhanalakshmi?
O proszę, brak telnet i tylko "standardowe porty" pootwierane.
Próba wstrzyknięcia komend poprzez Onvif czy http również zakończyła się porażką.
No nic, port szeregowy musi ruszyć do akcji. Po kilku minutach szukania w Google znalazłem opis dostępnych pinów. Pierwsze przechwycenie boot-owania i kolejne zdziwienie. Po załadowaniu Kernela - nic, brak jakichkolwiek informacji. Kurcze.. naprawdę się postarali.
U-Boot 2014.04 (Dec 16 2016 - 14:37:27)
CPU: XM510
DRAM: 32 MiB
In: serial
Out: serial
Err: serial
Net: dwmac.20040000
Press Ctrl+C to stop autoboot
### CRAMFS load complete: 1640256 bytes loaded to 0x81000000
## Booting kernel from Legacy Image at 81000000 ...
Image Name: Linux-3.0.101
Image Type: ARM Linux Kernel Image (uncompressed)
Data Size: 1640192 Bytes = 1.6 MiB
Load Address: 80008000
Entry Point: 80008000
Loading Kernel Image ... OK
Starting kernel ...
Uncompressing Linux... done, booting the kernel.
Przerwijmy boot systemu i zobaczmy co jest w U-BOOT, a dokładniej mówiąc jakie mamy "zmienne środowiskowe".
U-Boot> printenv
baudrate=115200
bootargs=mem=18M console=ttyAMA0,115200 root=/dev/mtdblock1 rootfstype=cramfs mtdparts=xm_sfc:256K(boot),3520K(romfs),2560K(user),1280K(web),256K(custom),320K(mtd)
bootcmd=cramfsload;bootm 0x81000000
bootdelay=1
cramfsaddr=0x40000
da=mw.b 0x81000000 ff 800000;tftp 0x81000000 u-boot.bin.img;sf probe 0;flwrite
dc=mw.b 0x81000000 ff 800000;tftp 0x81000000 custom-x.cramfs.img;sf probe 0;flwrite
dd=mw.b 0x81000000 ff 800000;tftp 0x81000000 mtd-x.jffs2.img;sf probe 0;flwrite
dr=mw.b 0x81000000 ff 800000;tftp 0x81000000 romfs-x.cramfs.img;sf probe 0;flwrite
du=mw.b 0x81000000 ff 800000;tftp 0x81000000 user-x.cramfs.img;sf probe 0;flwrite
dw=mw.b 0x81000000 ff 800000;tftp 0x81000000 web-x.cramfs.img;sf probe 0;flwrite
ethact=dwmac.20040000
ethaddr=00:12:16:cd:5f:9d
ipaddr=192.168.1.10
netmask=255.255.255.0
serverip=192.168.1.107
stderr=serial
stdin=serial
stdout=serial
telnetctrl=0
tk=mw.b 0x81000000 ff 800000;tftp 0x81000000 uImage; bootm 0x81000000
ua=mw.b 0x81000000 ff 800000;tftp 0x81000000 upall_verify.img;sf probe 0;flwrite
up=mw.b 0x81000000 ff 800000;tftp 0x81000000 update.img;sf probe 0;flwrite
verify=n
Pierwsza z ciekawszych która rzuciła mi się w oczy to telnetctrl=0
. Prawdopodobnie odpowiada za start telnet-a, zmieńmy ją i zobaczmy co się stanie:
U-Boot> setenv telnetctrl 1
U-Boot> saveenv
U-Boot> reset
Bingo, po chwili byłem w stanie się połączyć z użyciem telnet. Teraz wystarczyło odgadnąć hasło. Doświadczony z zabawy z wideodomofonem, spróbowałem użyć haseł z:
https://gist.github.com/gabonator/74cdd6ab4f733ff047356198c781f27d
Pierwszy strzał "xmhdipc" i bingo jesteśmy w środku.
dawid@arch:~/$ telnet 192.168.1.201 23
Trying 192.168.1.201...
Connected to 192.168.1.201.
Escape character is '^]'.
(none) login: root
Password:
~ #
Inne sposoby odczytywania haseł, których nauczyłem się w trakcie projektu:
U-Boot> cramfsload /etc/passwd
### CRAMFS load complete: 59 bytes loaded to 0x81000000
U-Boot> md 0x81000000
81000000: 746f6f72 2431243a 77495952 41526945 root:$1$RYIwEiRA
81000010: 367a7824 58787257 46535a52 2e41362f $xz6WrxXRZSF/6A.
/home # ls -la /
total 13
drwxr-x--- 1 1000 232 1012 Jan 1 1970 bin
drwxr-x--- 1 1000 232 20 Jan 1 1970 boot
drwxrwxrwt 3 root root 1320 Jan 1 1970 dev
drwxr-x--- 1 1000 232 184 Jan 1 1970 etc
drwxr-x--- 4 1000 1000 4096 Jun 20 2019 home
drwxr-x--- 1 1000 232 920 Jan 1 1970 lib
lrwxrwxrwx 1 1000 232 11 Jan 1 1970 linuxrc -> bin/busybox
drwxr-x--- 1 1000 232 68 Jan 1 1970 mnt
dr-xr-xr-x 43 root root 0 Jan 1 1970 proc
drwxr-x--- 1 1000 232 0 Jan 1 1970 root
drwxr-x--- 1 1000 232 488 Jan 1 1970 sbin
drwxr-xr-x 11 root root 0 Jan 1 1970 sys
drwxr-xr-x 2 root root 0 Jan 1 1970 tmp
drwxrwxr-x 1 527 15 84 Jan 1 1970 usr
drwxr-xr-x 3 root root 0 Jan 1 1970 var
W systemach typu "embedded" cały system uproszczony jest do maksimum. Tak naprawdę wszystkie narzędzia wchodzące w skład GNU Coreutils są trzymane w jednym pliku wykonywalnym busybox. Ten znajdujący się kamerce jest obcięty do niezbędnego minimum, binarka zajmuje zaledwie 358k:
/bin # ls -la busybox
-rwxr-x--- 1 1000 1000 358948 Jan 1 1970 busybox
/bin # ./busybox
BusyBox v1.22.1 (2016-03-10 17:02:14 CST) multi-call binary.
BusyBox is copyrighted by many authors between 1998-2012.
Licensed under GPLv2. See source distribution for detailed
copyright notices.
Usage: busybox [function [arguments]...]
or: busybox --list[-full]
or: busybox --install [-s] [DIR]
or: function [arguments]...
BusyBox is a multi-call binary that combines many common Unix
utilities into a single executable. Most people will create a
link to busybox for each function they wish to use and BusyBox
will act like whatever it was invoked as.
Currently defined functions:
[, [[, arp, arping, ash, awk, basename, cat, chmod, clear, cp, cttyhack, cut, date, devmem,
dhcprelay, dumpleases, echo, egrep, env, false, fgrep, free, getty, grep, halt, head, hush,
hwclock, ifconfig, init, insmod, iostat, kill, killall, klogd, linuxrc, ln, logger, login,
logread, ls, lsmod, lsof, mdev, mkdir, mkfifo, mknod, modinfo, mount, mpstat, mv, netstat,
ping, ping6, pmap, poweroff, ps, pwd, reboot, rm, rmdir, rmmod, route, sed, setfont, sh,
sleep, sync, syslogd, telnetd, test, time, top, touch, true, tty, udhcpc, udhcpd, umount
To wyjaśnia dlaczego po wpisaniu komendy dmesg wyskoczył mi komunikat dmesg: applet not found
. Ilość dostępnych komend jest zatrważająco mała.
Głównym programem obsługującym sensor kamery jest Sofia (własnościowy program o zamkniętym kodzie źródłowym). On tak naprawdę obsługuje wszystkie dostępne funkcje kamery tj: Web Server, RTSP Server, Onvif Server etc. Generalnie ta sama zasada co z busybox-em - wszystko w jednej binarce.
Kolejną ciekawą sprawą jest sposób przechowywani systemu. Tak naprawdę cały mieści się w kości flash (w tym wypadku SoC łączy się z kością używając interfejsu SPI) i jest odczytywany przez jądro systemu za pomocą urządzeń blokowych mtd. Można rzec, że to jest nasz "firmware". Większość urządzeń typu embeded wykorzystuję tą metodę przechowywania oprogramowania, tj: routery, switch-e, drukarki, kamerki etc. Kompilując sprawę jeszcze bardziej flash podzielony jest na obszary, które mogą być odczytywane przez jadro systemu jako różne systemy plików, przykładowo:
W ramach zabawy spróbujmy odczytać główny FS z flash-a używając "mtd". Dlaczego akurat główny FS, ano bo tam jest mój ukochany /etc/passwd. W przypadku tej kamerki mamy:
~ # ls -la /dev/mtdblock*
brw-rw---- 1 root root 31, 0 Jan 1 1970 /dev/mtdblock0
brw-rw---- 1 root root 31, 1 Jan 1 1970 /dev/mtdblock1
brw-rw---- 1 root root 31, 2 Jan 1 1970 /dev/mtdblock2
brw-rw---- 1 root root 31, 3 Jan 1 1970 /dev/mtdblock3
brw-rw---- 1 root root 31, 4 Jan 1 1970 /dev/mtdblock4
brw-rw---- 1 root root 31, 5 Jan 1 1970 /dev/mtdblock5
I teraz rodzi się pytanie, który blok za co odpowiada?. Można do tego dojść na kilka sposobów:
bootargs=mem=18M console=ttyAMA0,115200 root=/dev/mtdblock1 rootfstype=cramfs mtdparts=xm_sfc:256K(boot),3520K(romfs),2560K(user),1280K(web),256K(custom),320K(mtd)
Jak sama nazwa wskazuje, podane jak natacy (pogrubione). Dodatkowo kursywą zaznaczyłęm typ FS-a. Również według kolejności argumentów możemy przyjąć
mtdblock0 = 256K(boot)
mtdblock1 = 3520K(romfs)
mtdblock2 = 2560K(user)
mtdblock3 = 1280K(web)
mtdblock4 = 256K(custom)
mtdblock5 = 320K(mtd)
Sumując daje nam 8192K = 8MB (nawiasem mówiąc mały ten ROM)
~ # cat /proc/mtd
dev: size erasesize name
mtd0: 00040000 00010000 "boot"
mtd1: 00370000 00010000 "romfs" <--- To nas będzie iteresować
mtd2: 00280000 00010000 "user"
mtd3: 00140000 00010000 "web"
mtd4: 00040000 00010000 "custom"
mtd5: 00050000 00010000 "mtd"
...
Creating 6 MTD partitions on "xm_sfc":
0x000000000000-0x000000040000 : "boot"
0x000000040000-0x0000003b0000 : "romfs"
0x0000003b0000-0x000000630000 : "user"
0x000000630000-0x000000770000 : "web"
0x000000770000-0x0000007b0000 : "custom"
0x0000007b0000-0x000000800000 : "mtd"
...
Na nasze szczęście "devy" zostawili komendę mount. Zacznijmy od pod montowania wyeksportowanego FS-a mojego NAS-a pod kamerkę oraz stworzenia katalogu:
~ # mount -t nfs -o rw,nolock 192.168.1.5:/homes/dawid/xm510 /home
~ # cd home/
/home # mkdir flash
~ #
I teraz znowu mamy 2 sposoby "zrzucenia" cat badź dd. Niestety dd jest nie dostępne aczkolwiek podam komende (bieda busybox, patrz wyżej)
~ # cat /dev/mtdblock1 > /home/flash/rootfs.img
~ # dd if=/dev/mtdblock1 of=/home/flash/rootfs_dd.img bs=1M
Tak utworzony plik możemy sprawdzić komendą file:
dawid@arch:~/nas/xm510/flash$ file rootfs.img
rootfs: Linux Compressed ROM File System data, little endian size 2912256 version #2 sorted_dirs CRC 0xafe8201e, edition 0, 1130 blocks, 153 files
Czyli by się zgadzało z tym co jest podane w bootargs U-boot. Plik zawiera w sobie/bądź jest cramfs. Dodatkowym narzędziem którym warto mieć w trakcie zabawy z FW, a nie wspominałem o nim jeszcze, jest binwalk. Przetestujmy:
dawid@arch:~/nas/xm510/flash$ binwalk rootfs.img
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
0 0x0 CramFS filesystem, little endian, size: 2912256 version 2 sorted_dirs CRC 0xAFE8201E, edition 0, 1130 blocks, 153 files
Do tego celu potrzebujemy dodatkowych narzędzi. Najprostszym sposobem w moim przypadku jest pobranie gotowego repo firmware-mod-kit z GitHub-a. Zestaw tych narzędzi wymaga kompilacji jako że są w postaci kodu źródłowego. Może się okazać, że prostszym sposobem dla Ciebie drogi czytelniku, będzie pobranie dystrybucji Kali Linux. Wszystkie narzędzia wymienione w tym artykule są "wbudowane" w tą dystrybucję.
Przechodząc do sedna. Do rozpakowania cfamfs-a mamy narzędzie uncramfs. Podstawowa składnia jest banalna: uncramfs <katalog> <plik_z_obrazem>
. Stworzy nam to całą strukture katalogów i plików, tak samo jak na urządzeniu.
dawid@arch:~/nas/xm510/flash$ uncrmfs rootfs rootfs.img
[Volume size: 0x370000]
[Volume serial: 1e20e8af000000006a04000099000000]
[Volume name: Compressed]
drwxrwx--- 1000/232 244(244) /
/:
drwxr-x--- 1000/232 1012(1012) bin
drwxr-x--- 1000/232 20(20) boot
drwxr-x--- 1000/232 0(0) dev
drwxr-x--- 1000/232 184(184) etc
drwxr-x--- 1000/232 0(0) home
drwxr-x--- 1000/232 920(920) lib
lrwxrwxrwx 1000/232 11(23) linuxrc -> bin/busybox
drwxr-x--- 1000/232 68(68) mnt
drwxr-x--- 1000/232 0(0) proc
drwxr-x--- 1000/232 0(0) root
drwxr-x--- 1000/232 488(488) sbin
drwxr-x--- 1000/232 0(0) sys
drwxr-x--- 1000/232 0(0) tmp
drwxr-x--- 1000/232 48(48) usr
drwxr-x--- 1000/232 0(0) var
/bin:
lrwxrwxrwx 1000/232 7(19) [ -> busybox
lrwxrwxrwx 1000/232 7(19) [[ -> busybox
lrwxrwxrwx 1000/232 7(19) ash -> busybox
...
...
...
/usr/sbin:
/var:
[Summary:]
[Total uncompressed size: 4468058]
[Total compressed size: 2912892]
[Number of entries: 153]
[Number of files compressed: 50]
[Number of files expanded: 103]
Mając taki główny system plików moglibyśmy podmienić hasło root-a na własne, bardziej bezpieczne.
Nasz plik /etc/passwd wygląd tak:
root:$1$RYIwEiRA$d5iRRVQ5ZeRTrJwGjRy.B0:0:0:root:/:/bin/sh
Każde pole oddzielone jest dwukropkiem ":", więc rozkładając na czynniki pierwsze ten zapis będzie to wyglądać następująco:
użytkownik:hash hasła:uid:guid:dodadkow infromacje:katalog domowy:powłoka systemowa
Hash hasła składa się również z pól, aczkolwiek tym razem oddzielonych znakiem dolara "$":
$2 =Blowfish Algorithm is in use.
$2a=eksblowfish Algorithm
$5 =SHA-256 Algorithm
$6 =SHA-512 Algorithm
RYIwEiRA
d5iRRVQ5ZeRTrJwGjRy.B0
Spróbujmy je odtworzyć. Do tego celu użyjemy komendy w naszym systemie:
dawid@arch:~$ openssl passwd -1 -salt RYIwEiRA
Password:
$1$RYIwEiRA$d5iRRVQ5ZeRTrJwGjRy.B0
Jak widzimy wszystko się zgadza. Ja wygenerowałem sobie znacznie dłuższe i bardziej skomplikowane hasło (oczywiście nie słownikowe). Następnie je podmieniłem. Teraz przyszła pora na "wrzucenie" tego z powrotem do kamerki.
Mała dygresja. Krótkie hasła bardzo szybko się łamie nawet metodą brutalforce. Sześcio znakowe hasło mojego wideodomofonu "padło" w ciągu niespełna 25 minut.
Proces ten może się wydawać bardzo skomplikowany, jednakże postaram się go wytłumaczyć najlepiej jak umiem.
W moim przypadku cała kośc ma pojemność 8MB i jest podzielon na bloki (przez producenta). Jak już wyżej wspomniałem w artykule, nas będzie interesować blok - mtdblock1 = 3520K(romfs). Musimy znać jego adres początkowy oraz końcowy do poprawnego "sflashowania kamerki". Aby obliczyć te adresy, musimy wziąść pod uwagę długość pierwszego bloku mtd0 - jest to 256K (mtdblock0 = 256K(boot)).
256 KiloBytes = 262144 Bytes ---> szesnastkowo 0x40000
Jest to adres końcowy bloku mtd0, a zarazem początkowy mtd1. W drugiej kolejności policzymy długość bloku mtd1.
3520 KiloBytes = 3604480 Bytes ---> szesnastkowo 0x370000
Na samym końcu musimy je razem dodać, aby otrzymać końcowy adres bloku mtd1
262144+3604480=3866624 ---> szesnastkowo 0x3B0000
Mając te adresy jesteśmy gotowi do zbudowania obrazu. Zwróćcie uwagę, że moje wyliczenia dokładnie pokrywają się z tym co podaje nam system. Mam na myśli dział "Odczyt zawartości flash"
Aby zbudować obraz cramfs, będziemy potrzebowali dwóch narzędzi:
mkcramfs - dostępne w paczce fimware-mod-kit
mkimage - dostępne w pacze ArchLinux pod nazwą uboot-tools (pacman -S uboot-tools)
Dodatkowo musimy znać dokładny adress w pamięci flash gdzie znajduje się nasz rootfs. Obliczyliśmy to w poprzednim rozdziale.
Najpierw tworzymy plik z zawartością całego głownego systemu plików. Inaczej mówiąc, przywracamy do stanu dump-a. Przypominam, że hash hasła powinien być już podmieniony.
Składnia polecenia jest banalna: mkcramfs <katalog> <nazwa_noego_pliku>
dawid@arch:~/nas/xm510/flash$ mkcramfs rootfs romfs-x.cramfs
Directory data: 3220 bytes
Everything: 2844 kilobytes
Super block: 76 bytes
CRC: 1973df0f
warning: gids truncated to 8 bits (this may be a security concern)
Następnie tworzymy właściwy obraz pliku poleceniem mkimage. Właśnie w ten sposób producenci urządzeń dostarczają nam swoje aktualizacje. Nie będę się rozpisywał na temat wszystkich przełączników polecenia bo jest ich sporo (mkimage -h : wyjasni wszystko). Chciałbym natomiast zwrócić uwagę na 2 dosyć istotne, a mianowicie:
-a 0x00040000 Load Adress
-e 0x003B0000 Entry Point
To są te adresy które wyliczaliśmy wcześniej. Dzięki nim U-Boot wie który obszar pamięci kości flash ma nadpisać. W trakcie tworzenia obrazu są one dołączane jako nagłówek o długości 64 bajtów.
dawid@arch:~/nas/xm510/flash$ mkimage -A arm -O linux -T ramdisk -n "linux" -a 0x00040000 -e 0x003B0000 -d romfs-x.cramfs romfs-x.cramfs.img
Image Name: linux
Created: Fri Jun 21 12:19:25 2019
Image Type: ARM Linux RAMDisk Image (gzip compressed)
Data Size: 2912256 Bytes = 2844.00 KiB = 2.78 MiB
Load Address: 00040000
Entry Point: 003b0000
Tak przygotowany pliczek będziemy musieli przesłać do kamery. Do tego celu użyjemy protokołu TFTP. Ze względu na jego "prostotę", U-boot używa go jako podstawowego narzędzia do przesyłania/odbierania plików.
Ja do tego celu użyłem paczki tftp-hpa (pacman -S tftp-hpa). Sama konfiguracja jest dosyć prosta. W moim przypadku była to edycja pliku i wystartowaniu daemona.
dawid@arch:~/nas/xm510/flash$ sudo cat /etc/conf.d/tftpd
TFTPD_ARGS="--secure /home/dawid/nas/xm510/flash"
dawid@arch:~/nas/xm510/flash$ sudo systemctl start tftpd.service
Jak można zauważyć w trakcie ładowania systemu poprzez U-boot wykorzystywana jest pamięć RAM od adresu 0x81000000 (ładowanie jądra systemu). My również wykorzystamy ten adres do załaowania obrazu. Następnie z tego adresu nastąpi właściwy zapis do kości flash. Ten proces jest podzielony na kilka etapów:
mw.b 0x81000000 ff 800000
- wyczyszenie pamięci RAM. Wpisanie "FF" razy 0x800000. Przeliczając daje nam to 8.388.608 Bytes = 8,192 Kilobytes
tftp 0x81000000 romfs-x.cramfs.img
- załadowanie obrazu z servera TFTP
sf probe 0
- inicjacja kosci flash
flwrite
- właściwe flash-owanie. Komenda ta odczytuje Load Adress oraz Entry Point z nagłówka obrazu. Dzieki temu wie, który dokładnie obszar kości ma nadpisać.
Tuż przed uruchomieniem tego procesu należy ustawić jeszcze adres IP kamery, maskę sieci oraz adres IP servera TFTP.
#------------ Ustawienia adresów IP --------
U-Boot> setenv ipaddr 192.168.1.201
U-Boot> setenv netmask 255.255.255.0
U-Boot> setenv serverip 192.168.1.89
#------------ Flashowanie ------------------
U-Boot> mw.b 0x81000000 ff 800000
U-Boot> tftp 0x81000000 romfs-x.cramfs.img
Speed: 100, full duplex
Using dwmac.20040000 device
TFTP from server 192.168.1.89; our IP address is 192.168.1.201
Filename 'romfs-x.cramfs.img'.
Load address: 0x81000000
Loading: ##################################################
2.3 MiB/s
done
Bytes transferred = 2912320 (2c7040 hex)
U-Boot> sf probe 0
U-Boot> flwrite
## Checking Image at 0x81000000 ...
hdr->ih_magic=0x56190527
Header CRC Checking ... OK
Image Name: linux
Image Type: ARM Linux RAMDisk Image (gzip compressed)
Data Size: 2912256 Bytes = 2.8 MiB
Load Address: 00040000
Entry Point: 003b0000
Data CRC Checking ... OK
Programing start at: 0x00040000
Programing end at: 0x003b0000
FLASH_ERASE-------[100%]
done.
Erased sectors.
Saving Image to Flash ...
FLASH_WRITE-------[100%]
done.
U-Boot> reset
resetting ...
Tym sposobem dobrnęliśmy do końca. Udało nam się poprawnie podmienić hasło root-a ze standardowego "xmhdipc" na własne. W następnym artykule postaram się wyjaśnić jak można "dorzucić" swoje oprogramowanie, bądź zmodyfikować istniejące. Mam tu na myśli busybox, zwiększenie ilości komend. Dodatkowo istnieje możliwość zarządzania kamerą bez używania web interfejsu ze śmieszną kontrolką ActiveX.