Modifier le firmware par le OpenWrt
1 Introduction
Comme vous le savez, il y a quelque temps, j’ai tenté d’installer OpenWrt sur une Fonera+ (la grosse avec deux connecteur Ethernet). A force d’insister, j’ai fini par obtenir quelque chose de partiellement fonctionnel : recompilation du firmware Fon et mise en marche des deux ports Ethernet. Cependant, le Wifi reste toujours un problème.
Après une periode de travail sur mes Fonera Frankensteinisés, j’ai donc décidé de m’y remettre et de tenter une autre approche. Utiliser le firmware binaire Fon et activer une console série.Première étape, récupérer une mise à jour binaire pour la Fonera+ sous la forme du fichier foneraplus_1.1.1.1.fon.
Il s’agit d’une archive tar compressée gzip à laquelle Fon à ajouté une entête spécifique comme l’explique Stefan Tomanek sur son site. Ses explications pour le firmware de la Fonera classique semble parfaitement valables pour la Fonera+.
2 Modification du firmware
On joue donc de cat et de tail pour supprimer l’entête et obtenir une véritable archive :
cat foneraplus_1.1.1.1.fon | tail -c +520 - > arch.tar.gz
Le fichier obtenu contient trois fichiers qui sont utilisés par le routeur pour se mettre lui-même à jour :
hotfix image_full upgrade
Le fichier image_full est construit avec l’image du kernel, le système de fichier racine et un cheksum pour le loader stage2 lancé par RedBoot. Un coup d’oeil au contenu du Makefile du BuildRoot Fon (les sources du firmware) et on comprend la procédure de construction :
- le l’image du kernel compressé lzma est construite classiquement
- le script Perl fonimage.pl ajoute une entête de 12 octets contenant un CRC via le module Digest::CRC
- dd est utilisé pour obtenir un fichier multiple de 64Ko avec padding de zéro si nécessaire (bs=64k conv=sync)
- le fichier image du kernel ne semble pas utilisée
- fonimage.pl est également utilisé pour regouper le kernel et le système de fichiers racine en squashfs. Le script Perl et lancé pour construire l’image du système complet avec le CRC.
On en conclu donc que le fichier image_full est un entête/CRC de 12 octets suivi du kernel lzma puis de l’image squashfs du système de fichiers racine. Un coup d’oeil au script Perl nous apprend également la composition de l’entête de 12 octets :
- 4 octets pour la taille
- 4 octets pour le CRC
- 4 octets pour l’emplacement du système de fichier racine
Vérifier est un jeu d’enfant, il suffit d’afficher les valeurs en utilisant fonimage.pl à la main :
./fonimage.pl daimage vmlinux.lzma root.squashfs size : 2295397 offset : 715783
ls -l vmlinux.lzma daimage 2295409 2007-11-18 12:20 daimage 715783 2007-09-29 20:31 vmlinux.lzma
L’offset correspond à la taille du kernel et la taille (size) correspond à celle de l’image finale moins celle de l’entête. Les valeurs numériques sont enregistrées dans l’entête dans un format spécifique appelé unsigned long in “network” (big-endian) order. Effectivement dans l’entête du fichier daimage on retrouve :
hexdump daimage | head -n 1 0000000 2300 6506 3616 ac9f 0a00 07ec 006d 8000
23006506 c’est en réalité 00230665, soit 2295397, la taille de daimage moins 12. Le fichier image_full a comme entête :
hexdump image_full | head -n 1 0000000 2100 debf 14a2 9bd3 0a00 3450 006d 8000
2100debf c’est 0021bfde, soit, 2211806. 2211806+12 égale 2211818. Mais la taille du fichier est 2293764. Ça ne correspond pas ! Retour au Makefile.
Après avoir utilisé le script fonimage.pl, le Makefile appel prepare_generic_squashfs déclaré dans image.mk, un fichier d’OpenWrt. La fonction utilise dd :
dd if=$(1) of=$(KDIR)/tmpfile.1 bs=64k conv=sync $(call add_jffs2_mark,$(KDIR)/tmpfile.1) dd of=$(1) if=$(KDIR)/tmpfile.1 bs=64k conv=sync $(call add_jffs2_mark,$(1))
On retrouve le padding en blocs de 64k et un appel à add_jffs2_mark qui fait :
echo -ne '\xde\xad\xc0\xde' >> $(1)
Bref :
- on resize l’image en blocs de 64k
- on ajout 0xdeadc0de (Dead Code ???)
- on resize encore
- on ajoute encore 0xdeadc0de
Vérifions avec un BuildRoot de chez Fon compilé par nos soins :
cd bin find .. -name *.lzma ../build_mips/linux-2.6-fonera/vmlinux.lzma find .. -name root.squashfs ../build_mips/linux-2.6-fonera/root.squashfs
../target/linux/fonera-2.6/image/fonimage.pl \ daimage \ ../build_mips/linux-2.6-fonera/vmlinux.lzma \ ../build_mips/linux-2.6-fonera/root.squashfs
dd if=daimage of=temp1 bs=64k conv=sync 35+1 enregistrements lus 36+0 enregistrements écrits 2359296 octets (2.4 MB) copiés, 0.00566277 seconde, 417 MB/s
echo -ne '\xde\xad\xc0\xde' >> temp1
dd if=temp1 of=daimage1 bs=64k conv=sync 36+1 enregistrements lus 37+0 enregistrements écrits 2424832 octets (2.4 MB) copiés, 0.00597735 seconde, 406 MB/s
echo -ne '\xde\xad\xc0\xde' >> daimage1
ls -l daimage1 openwrt-fonera-2.6.image 2424836 2007-11-18 13:37 daimage1 2424836 2007-09-29 20:31 openwrt-fonera-2.6.image
md5sum daimage1 openwrt-fonera-2.6.image b57f8b2279bdb9a6483e094c58fc3381 daimage1 b57f8b2279bdb9a6483e094c58fc3381 openwrt-fonera-2.6.image
Bingo ! Nous sommes en mesure de recréer manuellement une image. Nous avons donc parfaitement analysé le processus de construction. Reste à inverser ce processus pour obtenir un fichier du noyau et une image du rootfs que nous pourrons modifier.
Le plus simple pour être sûr est de démonter notre propre essai. On commence donc par regarder l’entête de daimage1 :
hexdump daimage1 | head -n 1 0000000 2300 6506 3616 ac9f 0a00 07ec 006d 8000
Les valeurs qui nous intéressent ici sont :
- la taille de l’image : 23006506 soit 00230665 soit 2295397 octets
- la taille du kernel (l’offset pour trouver le rootfs) : 0a0007ec soit 000aec07 soit 715783
On retire l’entête :
cat daimage1 | tail -c +13 > nohead
Puis le padding en utilisant la taille de l’image spécifiée dans l’entête :
cat nohead | head -c +2295397 > nopad
Notre fichier est maintenant la concaténation du kernel et du rootfs. On utilise l’offset précisé dans l’entête pour récupérer le rootfs. taille image - offset = taille rootfs (2295397-715783=1579614):
cat nopad | tail -c 1579614 > squash
Tant qu’à faire on extrait également le kernel pour avoir tous les éléments :
cat nopad | head -c +715783 > dakern
Vérifions :
md5sum dakern ../build_mips/linux-2.6-fonera/vmlinux.lzma 0c50b77f12c3e18a91db1d027fe0ecc6 dakern 0c50b77f12c3e18a91db1d027fe0ecc6 ../build_mips/linux-2.6-fonera/vmlinux.lzma
md5sum squash ../build_mips/linux-2.6-fonera/root.squashfs 19576d7b96f07ba7694d615e2afe78d1 squash 19576d7b96f07ba7694d615e2afe78d1 ../build_mips/linux-2.6-fonera/root.squashfs
Ca marche, nous sommes en mesure de démonter une mise à jour officielle Fon. Appliquons cela à image_full :
hexdump image_full | head -n 1 0000000 2100 debf 14a2 9bd3 0a00 3450 006d 8000
Un peu de calcul :
- taille de l’image : 2100debf soit 0021bfde soit 2211806
- taille du kernel : 0a003450 soit 000a5034 soit 675892
- taille du rootfs : 2211806-675892=1535914
Démontage :
cat image_full | tail -c +13 > nohead cat nohead | head -c +2211806 > nopad cat nopad | tail -c 1535914 > squash cat nopad | head -c +675892 > dakern
Ridicule vérification :
hexdump dakern | head -n 1 0000000 006d 8000 ff00 ffff ffff ffff 00ff 0204
C’est bien une entête de kernel lzma.
Il nous faut maintenant démonter le squashfs. Nous nous heurtons à plusieurs problèmes dont la compression lzma et l’endian. L’image est construite pour un système big endian (MIPS) mais un PC x86 est little endian. Oubliez simplement l’idée de monter le système de fichiers en loopback. Il faut regarder du côté de l’outil unsquashfs mais là encore, beaucoup de problèmes. Après une grosse période de recherche, je suis finalement tombé sur un kit de modification de firmware : firmware_mod_tools_prebuilt.tar.gz.
Cette archive contient un unsquashfs-lzma qui fonctionne parfaitement avec les images pour la Fonera (la nouvelle comme l’ancienne) :
/tmp/unsquashfs-lzma squash Reading a different endian SQUASHFS filesystem on squash created 407 files created 67 directories created 165 symlinks created 0 devices created 0 fifos
Et nous retrouvons un répertoire squashfs-root contenant l’arborescence utile. Un petit coup d’oeil dans /etc/inittab et effectivement, là, il manque quelque chose que j’aurai aimé :
ttyS0::askfirst:/bin/ash --login
On l’ajoute et on reconstruit un squashfs tout neuf en utilisant les outils du BuildRoot :
/chemin/vers/openwrt/staging_dir_mips/bin/mksquashfs-lzma \ squashfs-root root.squashfs -nopad -noappend -root-owned -be Creating big endian 3.0 filesystem on root.squashfs, block size 65536. Big endian filesystem, data block size 65536, compressed data, compressed metadata, compressed fragments Filesystem size 1499.77 Kbytes (1.46 Mbytes) 31.47% of uncompressed filesystem size (4766.19 Kbytes) Inode table size 4801 bytes (4.69 Kbytes) 23.44% of uncompressed inode table size (20479 bytes) Directory table size 5572 bytes (5.44 Kbytes) 57.00% of uncompressed directory table size (9776 bytes) Number of duplicate files found 4 Number of inodes 639 Number of files 407 Number of fragments 28 Number of symbolic links 165 Number of device nodes 0 Number of fifo nodes 0 Number of socket nodes 0 Number of directories 67 Number of uids 1 root (0) Number of gids 0
3 Upload du Firmware
Nous avons un kernel et un nouveau rootfs. Nous réutilisons les commandes permettant de créer une image : fonimage.pl, dd, echo, dd, echo. Nous obtenons une nouvelle image qu’il ne reste plus qu’à flasher sur la Fonera+ via RedBoot :
fis delete image load -r -b 0x80041000 monimage fis create -b 0x80041000 -f 0xA8040000 -l 0x00230004 -e 0x80040400 image
Après cette opération la Fonera+ est redémarrée par la commande reset. Aucun message du noyau n’apparaît, c’est normal, mais après la phase de démarrage complète on a bien un shell utilisable. Les messages du DHCP ne cessent de pourrir la console mais ça marche !
4 Ressources
http://www.lefinnois.net/wp/index.php/2007/11/18/modification-du-firmware-fon-pour-une-fonera/
http://www.dd-wrt.com/wiki/index.php/LaFonera_Software_Flashing
http://www.dd-wrt.com/wiki/index.php/LaFonera_(fr)
http://www.cure.nom.fr/blog/archives/141-Fonera-et-le-firmware-alternatif-DD-WRT.html
http://www.dd-wrt.com/wiki/index.php/Wireless_Access_Point