Esplorare il sistema operativo di Reolink RLC 810A

Nell’ultimo articolo ci siamo concentrati sul file system e, prima di concludere, siamo riusciti ad estrapolare i file da due immagini UBIFS contenute all’interno del firmware. Nella quinta parte cercheremo di capire meglio la struttura del file system di root, introducendo alcuni concetti fondamentali del sistema operativo utilizzato da Reolink RLC 810A, ovvero Linux.

Perché Linux?

Linux Kernel è uno dei sistemi operativi maggiormente utilizzati al mondo, se non il primo per diffusione. Potreste pensare che sistemi operativi commerciali come Windows o MacOS siano i più utilizzati, ma d’altra parte un intero esercito di dispositivi embedded (come la telecamera di questa serie) ha basato i suoi firmware su Linux.

Creato all’inizio degli anni 90 da Linus Torvalds, il kernel Linux è un kernel monolitico, modulare e multitasking principalmente sviluppato in risposta alla commercializzazione del sistema operativo UNIX, mantenuto da AT&T. Il kernel Linux e tutto l’ecosistema che sta sopra di esso è ampiamente open-source: ogni persona del pianeta è libero di poter consultare i sorgenti e modificarli.

La scelta di Linux come base per i firmware dei dispositivi embedded deriva da una serie caratteristiche che sono essenziali per lo sviluppo. Il primo tra tutti è la flessibilità di Linux: essendo modulare, è possibile caricare a tempo d’esecuzione componenti aggiuntivi che arricchiscono Linux di nuove funzionalità. Il secondo è dato dalla stabilità del Kernel, grazie a molteplici sviluppatori che dedicano notte e giorno e la grande diffusione del kernel che ha permesso di avere più occhi sullo sviluppo del codice. Il terzo motivo è forse uno dei più importanti: è gratis. Quando si commercializza un dispositivo, ci sono una serie di licenze che l’azienda deve pagare (hardware, dispositivi vari). Per le aziende che utilizzano Linux nei loro prodotti, non c’è alcun costo.

Prima di cominciare ad analizzare le varie cartelle è bene dire che la quasi totalità dei sistemi operativi adoperati per dispositivi embedded è una distribuzione basata sul Linux kernel. La caratteristica di essere open source, di avere un ampio supporto online e di essere un kernel abbastanza stabile hanno reso l’intera famiglia di sistemi operativi basati su Linux/Unix il sistema operativo ideale per questo tipo di dispositivi.

Le Cartelle del File System

Proseguiamo la nostra analisi, esplorando in dettaglio la struttura del sistema operativo. Per prima cosa, cambiamo la directory su rootfs. Eseguiamo ls per avere una panoramica più completa delle cartelle.

bin  dev  etc  init  lib  linuxrc  mnt  proc  root  sbin  sys  tmp  usr  var

Come potevamo aspettarci, si tratta della caratteristica FileSystem Hierarchy Standard utilizzata per i file system dei sistemi operativi Unix-like. Lo standard infatti definisce quali cartelle e file system virtuali devono essere montati all’avvio dal sistema operativo e che cosa ogni cartella contiene. Questo standard viene condiviso tra tutti i sistemi operativi unix-like per garantire la compatibilità tra programmi su piattaforme diverse.

Esploriamo più in dettaglio ogni cartella e file:

  • bin: contiene file binari condivise da tutti gli utenti. In questa cartella troviamo ad esempio: la shell, cat, ls, cp e molti altri.
  • dev: contiene file per le periferiche (dispositivi). La maggior parte dei sistemi operativi Unix-like adotta l’approccio di “Everything as a file”: ogni periferica viene descritta tramite alcuni file speciali che permettono di manipolare lo stato di un dispositivo. Contiene anche altri dispositivi pseudo-virtuali come /dev/null che non produce output oppure /dev/random che genera numeri pseudocasuali.
  • etc: contiene file di configurazione del sistema. Cartella molto interessante per capire come il produttore abbia configurato la camera.
  • init: l’eseguibile che Uboot carica ed esegue all’inizio del booting;
  • lib: contiene librerie essenziali per i binari in /bin/ e /sbin/;
  • linuxrc: file che viene eseguito dopo init per iniziare a popolare e montare alcuni file system virtuali (come dev, proc);
  • mnt: destinazione utilizzata per montare file system aggiuntivi (temporaneamente);
  • proc: file system virtuale che contiene informazioni sui processi attivi (principalmente leggibili come file di testo);
  • root: la cartella principale per l’utente “root” o “supervisor”;
  • sbin: cartella simile a sbin, si differenzia solo per i binari contenuti: in questo caso sono binari che devono essere eseguiti da root per l’amministrazione del sistema;
  • sys: file system che consente di interfacciarsi direttamente con i dispositivi hardware;
  • tmp: cartella temporanea. I file all’interno sono generalmente eliminati quando si riavvia il sistema.
  • usr: contiene le applicazioni e i file utilizzati dagli utenti. Applicazioni quali browser, messaggistica istantanea e tutto ciò che non è utilizzato dal sistema viene messo in questa cartella.
  • var: file con dati variabili. In particolare abbiamo /var/log che contiene i log prodotti dai vari componenti del sistema e applicativi;

Ora che abbiamo introdotto le diverse cartelle possiamo incominciare ad esplorarne il contenuto una per una. Per i prossimi articoli ci occuperemo di andare a fondo di alcune cartelle per capirne meglio il loro significato.

Cartella Bin

La cartella bin contiene i file binari essenziali per il sistema operativo. All’interno troviamo eseguibili come ls (per ottenere una lista dei file), cp (per copiare un file), echo (per stampare una stringa a video) e molti altri ancora.

Nel nostro caso, possiamo procedere a vedere più in dettaglio i vari binari utilizzando ls.

ash      chmod     date           echo     fgrep   hostname  linux64  mkdir   netstat       ping           rc_profile    setarch    su        umount      zcat
busybox  chown     dd             ed       fsync   hush      ln       mknod   nice          ping6          rev           setserial  sync      uname
cat      conspy    df             egrep    getopt  iostat    login    more    nvtrtspd      pq_video_rtsp  rm            sh         tar       uncompress
catv     cp        dmesg          false    grep    ipcalc    ls       mount   nvtrtspd_2ch  printenv       rmdir         sleep      test_vos  usleep
chattr   cpio      dnsdomainname  fatattr  gunzip  kill      lsattr   mpstat  nvtrtspd_ipc  ps             scriptreplay  stat       touch     vi
chgrp    cttyhack  dumpkmap       fdflush  gzip    linux32   lzop     mv      pidof         pwd            sed           stty       true      watch

Molti di questi sono comandi che troviamo abitualmente in qualsiasi sistema operativo. mkdir serve per creare cartelle, date per stampare la data attuale, more per leggere un file. Destano però subito all’occhio alcuni file binari particolari che non sono abitualmente contenuti in distribuzioni unix-like, come ad esempio nvtrtspd oppure pq_video_rtsp. Ci ritorneremo sopra perché sembrano essere binari interessanti.

Procediamo quindi ad identificare la shell. La shell è un’interfaccia a riga di comando utilizzata per interfacciarsi con il sistema operativo: è possibile eseguire comandi, richiedere l’avvio di programmi, modificare impostazioni. Insieme al kernel, è un componente fondamentale per un sistema operativo.

Sono diverse le shell testuali più diffuse: la più famosa, Bash, utilizzata per sistemi GNU/Linux; ma anche cmd.exe per Windows, Z Shell per MacOS. Per Reolink RLC 810A, la shell è contenuta all’interno di un file binario chiamato busybox. A seconda di come è stato configurato l’applicativo, può esserci sia la bash sia la zshell.

BusyBox

BusyBox è uno strumento che permette di incapsulare molte applicazioni Unix standard in un unico piccolo eseguibile da poter rilasciare all’interno del firmware senza troppe complicazioni. Il principale vantaggio è una ridotta dimensione del sistema operativo dal momento che è necessario solo un binario per poter eseguire molteplici comandi. La chiamata ai diversi comandi con un singolo eseguibile funziona grazie a collegamenti simbolici che richiamano il singolo binario.

Considerato il coltellino svizzero del Linux Embedded, Busybox è principalmente scritto in linguaggio C e attualmente viene utilizzato da un enorme varietà di distribuzione per dispositivi embedded, incluso BuildRoot e OpenWRT.

Ogni comando del sistema è collegato simbolicamente al binario di busybox. Quando un qualsiasi comando viene eseguito, busybot legge da argv[0] il nome del comando da eseguire e in argv[i] tutti i parametri di quel comando. Il collegamento tra nome del comando e binario su busybox viene confermato da un semplice file ls nella cartella /bin.

ls: symbolic link to busybox

Per esclusione, troviamo gli unici file che sono binari custom, non propri di BusyBox: nvtrtspd, nvtrtspd_2ch, nvtrtspd_ipc, pq_video_rtsp, rc_profile, test_vos. Possiamo spostare alcuni binari in una cartella ad-hoc per il futuro, momento in cui approfondiremo tramite reverse engineering le funzionalità e le caratteristiche di ogni programma.

I File di Configurazione (etc)

Una volta che abbiamo concluso la parte dei binari, un po’ noiosa, possiamo addentrarci in un’altra cartella che risulterà molto interessante: etc. La cartella etc contiene tutti i file di configurazione per gli applicativi di sistema; tali file di configurazioni sono condivise tra tutti gli utenti che accedono al sistema.

In origine la cartella etc doveva contenere tutti i file che non appartenessero ad alcuna categoria particolare (da qui l’utilizzo dell’“et cetera” dal Latino), ma diventò subito la cartella della configurazione dei file de facto. Alcune moderne interpretazioni rimandano “etc” a “Editable Text Configuration” oppure “Extended Tool Chest.”

Riprendendo con la nostra analisi, proseguiamo, focalizzandoci su ogni file trovato. Per ragioni di semplicità e di vastità, ho preferito includere tutti i file rilevanti all’interno della cartella /etc/ in ordine alfabetico.

Device Tree dei Sensori (application.dtb)

Il primo file che analizziamo all’interno della cartella /etc/ si chiama application.dtb ed è un device tree, un file che permette di descrivere una particolare configurazione hardware. Eseguiamo il comando utilizzato durante la terza parte per visualizzarne il contenuto:

dtc -I dtb -O dts -o - application.dtb

ed ecco il risultato:

/dts-v1/;

/ {

        sensor@1 {
                dev_name = "nvt_sen_imx291";
                cfg = "sen_imx291_cfg1\0sen_imx291_cfg2";
                sie = <0x00 0x01>;
        };

        sensor@2 {
                dev_name = "nvt_sen_imx323";
                cfg = "sen_imx323_cfg1";
                sie = <0x03>;
        };
};

Il device tree serve per descrivere due sensori ottici, apparentemente non utilizzati in questa videocamera. Viene utilizzato da un programma di testing incluso nel firmware, ma quasi mai utilizzato dall’utente finale.

bootchartd.conf

Il file bootchard.conf permette di impartire alcune impostazioni al famoso strumento bootchart che serve per misurare le performance dell’avvio di Linux. In contesti normali, che sia un computer o un dispositivo cellulare non abbiamo molto interesse a spendere qualche secondo in più durante l’avvio.

Quando però si tratta di dispositivi embedded, ogni secondo è cruciale perché non esiste per l’utente finale il concetto di “aspettare”. Ad esempio per iniziare a registrare un qualsiasi video, ci basta accendere una videocamera e premere un pulsante. L’utente non sa che in quel frangente di tempo una serie di operazioni viene eseguita e pertanto si aspetta di poter utilizzare lo strumento subito!

Lo strumento è utile anche in contesti embedded per cercare di individuare pericolosi colli di bottiglia. Nel caso di Reolink sono due le impostazioni date al kernel: sample_period (ovvero l’intervallo di tempo per misurare le performance) e process_accounting che permette di tracciare tutti i processi eseguiti all’avvio.

firmware.info

Il file firmware.info contiene qualche informazione utile riguardo il firmware e il Software Development Kit utilizzato per generare la distribuzione embedded. Non è granché utile ai fini del sistema operativo, ma consente di estrapolare un paio di informazioni in più.

SDK_VER="NVT_NT96660_Linux_V0.4.8"
BUILDDATE="Tue Mar 1 18:25:28 CST 2016"

fstab, group e hostname

Il file fstab indica quali file system virtuali montare, in che posizione ed eventualmente altri parametri. Il file fstab viene letto dal comando mount che avviene automaticamente all’avvio per determinare la struttura complessiva del file system.

proc            /proc                   proc    defaults        0       0
sysfs           /sys                    sysfs   defaults        0       0
tmpfs           /dev                    tmpfs   defaults        0       0
tmpfs           /tmp                    tmpfs   defaults,noatime,nosuid,nodev,exec,mode=1777,size=5M 0 0
tmpfs           /var/run                tmpfs   defaults,rw,nosuid,mode=0755    0       0
tmpfs       	/mnt/tmp                tmpfs   defaults,noatime,nosuid,nodev,exec,mode=1777,size=20M 0 0 
debugfs         /sys/kernel/debug       debugfs defaults        0       0

Il file group invece riporta la suddivisione degli utenti in differenti gruppi. Di per sé non è molto interessante per la nostra indagine. root:x:0:

Il file hostname riporta il nome della macchina: BAICHUAN, un chiaro riferimento all’azienda dietro Reolink.

Inetd.conf

Il file inetd.conf è il file di configurazione per il daemon inetd ed ogni riga rappresenta un servizio che viene gestito da questo demone. Il demone inetd che permette di controllare i servizi Internet che vengono resi disponibili dalla macchina. Il demone avvia i servizi in base al file di configurazione. In particolare troviamo: ftpd che viene avviato sulla porta 21 da root e rende disponibile upload/downloading di file da sd, e tftpd sulla porta 69.

[..redatto..]
21 stream tcp nowait root ftpd ftpd -w /mnt/sd
69 dgram udp nowait root tftpd tftpd -l -c 

Inittab

Il file inittab specifica la configurazione iniziale che il processo init deve eseguire per busybox. È composto da entry che seguono il formato:

Identificatore:Livello:Azione:Comando

dove:

  • identificatore: una stringa che univocamente identifica un entry;
  • livello: livello associato all’entry, rappresentato da un numero (da 0 a 9). Ogni entry viene eseguita nel momento in cui il sistema è nel livello uguale all’entry;
  • azione: specifica al processo init come trattare il processo;
  • comando: il comando da eseguire;
::sysinit:sh /etc/init.d/rcS

# Stuff to do when restarting the init process
::restart:/sbin/init

# Stuff to do before rebooting
::ctrlaltdel:/sbin/reboot
::shutdown:/etc/init.d/rcK
::shutdown:/bin/umount -a -r
::shutdown:/sbin/swapoff -a

Le seguenti azioni sono specificate dall’inittab:

  • all’avvio del sistema, avvia gli script iniziali (/etc/init.d/rcS);
  • se il sistema viene riavviato, allora riavvia anche il processo init;
  • quando viene registrata una qualsiasi azione tra riavvio o spegnimento, allora esegue lo script /etc/init.d/rcK e smonta il filesystem.

mdev.conf

Mdev è lo strumento sviluppato ad-hoc da Busybox per sostituire udev. Udev è il gestore dei dispositivi per i sistemi operativi Linux-like e viene eseguito come daemon. A differenza di sistemi operativi basati su Unix che si basano solamente sulla gestione dei dispositivi tramite di file di testo statici su /dev, udev permette di gestire in modo dinamico la creazione o rimozione di questi file speciali.

La sintassi che segue mdev.conf è abbastanza arzigolata:

[-]devicename_regex user:group mode [=path]|[>path]|[!] [@|$|*cmd args...]

Nel nostro caso mmcblk[0-2] root:root 660 */etc/mdev-script/autosd.sh, si traduce in “se trovi dispositivi che sono mmcblk allora esegui lo script autosd.sh”. Mmcblk è il nome del sottosistema che i dispositivi SD/MMC utilizzano quando sono collegati alla board. Quando viene rilevata una nuova micro SD, lo script autosd.sh verifica una serie di parametri a livello hardware (nome della microSD, tipo) e se corrisponde, allora provede a creare un nuovo hard link al contenuto della microSD. Dal momento che viene fatto un controllo molto accurato sul tipo di scheda microSD, si spiega perché alcune microSD potrebbero non funzionare.

passwd

Il file passwd serve per configurare quali utenti hanno accesso al sistema di login e con quale combinazione di username e password possono entrare. La sintassi per comprendere un file passwd è la seguente:

username:password:userid:groupid:useridinfo:homedirectory:shell

dove:

  • username: il nome utente che viene utilizzato durante l’accesso;
  • password: se c’è un solo carattere x, allora la password è crittografata e il suo contenuto è disponibile nel file /etc/shadow
  • userid: è l’ID dell’utente
  • groupid: è il gruppo di cui l’utente fa parte
  • useridinfo: campo aggiuntivo che permette di memorizzare informazioni aggiuntive riguardo l’utente
  • homedirectory: percorso assoluto in cui si troverà l’utente una volta effettuato l’accesso
  • shell: percorso assoluto della shell da avviare

Per Reolink RLC 810A, in particolare troviamo questo file:

root:XF4sg5T82tV4k:0:0:root:/root:/bin/sh

Significa che l’utente di default per la webcam è il seguente username: root, password: XF4sg5T82tV4k. Questa password non consente di entrare in interfacce esposte sulla Rete come quella tramite browser. Piuttosto, se attaccassimo la board attraverso UART ad un terminale, potremo entrare all’interno del sistema utilizzando quella determinata combinazione di username/password.

Includere un file passwd e lasciare la password visibile all’interno di un firmware non pone alcun rischio aggiuntivo, se il login richiede l’accesso fisico e locale alla board. Non posso però pronunciarmi rispetto alla sicurezza di questo login dato che non ho trovato alcuna informazione riguardo questa password. Se qualcuno scopre qualcosa di più, mi può scrivere una e-mail.

profile e profile_prjcfg

Il file profile serve per inizializzare la shell di default a livello di sistema. Quando la shell infatti è avviata come una shell di tipo interattiva, legge il file /etc/profile, esegue i comandi al suo interno e legge un altro file ~/.bash_profile che è l’equivalente del file profile però specifico di un utente.

# /etc/profile: system-wide .profile file for the Bourne shell (sh(1))
# and Bourne compatible shells (bash(1), ksh(1), ash(1), ...).

# source profile_prjcfg on /etc/init.d/rcS (init script cycle) and /etc/profile (after startup cycle)
source /etc/profile_prjcfg
export PATH="/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:/bin:/sbin"
export LD_LIBRARY_PATH="/lib:/usr/local/lib:/usr/lib:/mnt/app"
export TERMINFO=/usr/share/terminfo
#export LD_PRELOAD="libnvtlibc.so"

if [ -f /etc/hostname ]; then
        /bin/hostname -F /etc/hostname
fi

# coredump setting
echo 1 > /proc/sys/kernel/core_uses_pid
ulimit -c unlimited
echo "/var/log/core-%e-%p-%t" > /proc/sys/kernel/core_pattern

export HOSTNAME=`/bin/hostname`
export PS1='[\u@\h:\w]\$ '

echo "$HOSTNAME Linux shell..."

Alcuni comandi interessanti di questo file sono le seguenti:

  • source /etc/profile_prjcfg: esegue il file profile_prjcfg, impostando alcune variabili di ambiente;

  • echo 1 > /proc/sys/kernel/core_uses_pid e echo "/var/log/core-%e-%p-%t" > /proc/sys/kernel/core_pattern: queste due opzioni sono utilizzate per configurare lo strumento core che permette di generare core dump nel momento in cui un’applicazione fallisce.

  • ulimit -c unlimited: non c’è alcun limite di memoria per la shell; il comando ulimit permette di impostare un limite di memoria che la shell o uno dei programmi avviati da shell deve rispettare, pena la “decapitazione” (kill) da parte del sistema operativo. L’utilizzo di questo comando pone seri problemi dato che un processo è libero di poter allocare tanta memoria quanto ne richiede. Talvolta si rende necessario aumentare la memoria disponibile per un processo (e quindi bypassare il limite) quando un programma ha qualche strano memory leak. Il workaround però dovrebbe essere temporaneo, non permanente in produzione! Questo suggerisce una pratica di sviluppo di bassa qualità.

Per quanto riguarda il file profile_prjcfg, si tratta principalmente di una serie di variabili di ambiente che vengono impostate. Per ora il loro significato è sconosciuto dato che la maggior parte dei nomi delle variabili di ambiente è basato sul NovaTek SDK. Cercheremo di trovare qualche indizio che possa aiutarci a chiarire le idee.

export MODEL=/home/zfj/520_project/sdk/v2.02/NT9852x_linux_sdk_v2.02.000/na51055_linux_sdk_bc/configs/Linux/cfg_IPCAM1_523_EVB/ModelConfig.mk
export BOARD_DRAM_ADDR="0x00000000"
export BOARD_DRAM_SIZE="0x08000000"
export BOARD_FDT_ADDR="0x00100000"
export BOARD_FDT_SIZE="0x00100000"
export BOARD_SHMEM_ADDR="0x00200000"
export BOARD_SHMEM_SIZE="0x00100000"
[.... continua ....]

Anche in questo caso possiamo vedere come la fase di building di un firmware lasci alcuni metadata. Lo sviluppatore che ha costruito la distribuzione embedded ha incluso all’interno del file in produzione alcuni riferimenti, ovviamente inesistenti. Non è la prima volta che possiamo notare questo e durante il corso della serie ne troveremo ben altre.

Altri file di configurazione

All’interno della cartella /etc ci sono altri file di configurazione come services, udhcpdw.conf, wifiap_wpa2.conf e wpa_supplicant.conf. Molto interessante notare come alcuni servizi (ad esempio i file di configurazione per il Wi-fi) che non sono minimamente utilizzati da parte di questa videocamera sono comunque presenti all’interno del firmware. La videocamera RLC-810A infatti ha un unico modo per poter essere connessa ad Internet: Ethernet che utilizza anche come risorsa elettrica grazie a PoE (Power Over Ethernet).

Qui si conclude la nostra analisi dei file di configurazione e dei file binari. È stato interessante capire come ogni file di configurazione potesse darci alcuni indizi sui servizi abilitati della macchina e su come funzionasse. Quello che ci concentreremo sul prossimo articolo riguarderà la cartella /lib e le successive cartelle da analizzare.