Modification du firmware Fon pour une Fonera+

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+. 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 ‘xdexadxc0xde’ >> $(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 ‘xdexadxc0xde’ >> 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 ‘xdexadxc0xde’ >> 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
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 !