Format du firmware

De neufbox 4

Le firmware principale est composé, dans l'ordre, d'une entête, puis du rootfs, et enfin du kernel.

Format de l'entête

Début du firmware

Regardons à quoi ressemble le début de l'image grâce à l'utilitaire hd sous Linux :

$ hd NB4-R1.2.10-MAIN | head -n 20
00000000  36 00 00 00 42 72 6f 61  64 63 6f 6d 20 43 6f 72  |6...Broadcom Cor|
00000010  70 6f 72 61 74 69 6f 00  76 65 72 2e 20 32 2e 30  |poratio.ver. 2.0|
00000020  00 00 00 00 00 00 36 33  35 38 00 00 39 36 33 35  |......6358..9635|
00000030  38 56 57 00 00 00 00 00  00 00 00 00 31 00 33 39  |8VW.........1.39|
00000040  39 31 34 35 33 00 00 00  00 00 00 00 00 00 00 00  |91453...........|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 33 32  |..............32|
00000060  31 37 30 39 36 39 36 30  00 00 33 30 31 34 36 35  |17096960..301465|
00000070  36 00 00 00 33 32 32 30  31 31 31 36 31 36 00 00  |6...3220111616..|
00000080  39 37 36 37 39 37 00 00  00 00 00 00 00 00 4e 42  |976797........NB|
00000090  34 2d 52 31 2e 32 2e 31  30 2d 4d 41 49 4e 00 00  |4-R1.2.10-MAIN..|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000000d0  00 00 00 00 00 00 00 00  89 7f 71 0c 5c d0 ca ac  |..........q.\...|
000000e0  b1 8d 81 9b 00 00 00 00  00 00 00 00 6d 86 09 12  |............m...|
000000f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000100  73 71 73 68 00 00 02 c9  00 2d f1 0a 00 2d f0 fa  |sqsh.....-...-..|
00000110  00 2d f1 06 00 2d 92 ee  00 2d d0 7e 00 02 00 00  |.-...-...-.~....|
00000120  00 00 00 10 51 03 01 46  07 ee 05 00 00 00 00 20  |....Q..F....... |
00000130  02 1d 7d 00 01 00 00 00  00 00 00 00 2d f0 fa 5d  |..}.........-..]|
00000140  00 00 08 00 00 3f 91 45  84 68 34 8a 09 0a 40 62  |.....?.E.h4...@b|

On remarque 2 choses:

  • une sorte d'entête tout au début qui semble contenir plusieurs informations, dont le nom Broadcom, ainsi que le nom de l'image
  • la signature (sqsh) du système de fichier SquashFS, à l'adresse 0x100

Entête de firmware

On peut voir uniquement les 256 premiers octets de l'image (0x100 en hexadécimal), qui correspondent à l'entête.

$ hd -n 256 NB4-R1.2.10-MAIN
00000000  36 00 00 00 42 72 6f 61  64 63 6f 6d 20 43 6f 72  |6...Broadcom Cor|
00000010  70 6f 72 61 74 69 6f 00  76 65 72 2e 20 32 2e 30  |poratio.ver. 2.0|
00000020  00 00 00 00 00 00 36 33  35 38 00 00 39 36 33 35  |......6358..9635|
00000030  38 56 57 00 00 00 00 00  00 00 00 00 31 00 33 39  |8VW.........1.39|
00000040  39 31 34 35 33 00 00 00  00 00 00 00 00 00 00 00  |91453...........|
00000050  00 00 00 00 00 00 00 00  00 00 00 00 00 00 33 32  |..............32|
00000060  31 37 30 39 36 39 36 30  00 00 33 30 31 34 36 35  |17096960..301465|
00000070  36 00 00 00 33 32 32 30  31 31 31 36 31 36 00 00  |6...3220111616..|
00000080  39 37 36 37 39 37 00 00  00 00 00 00 00 00 4e 42  |976797........NB|
00000090  34 2d 52 31 2e 32 2e 31  30 2d 4d 41 49 4e 00 00  |4-R1.2.10-MAIN..|
000000a0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
000000d0  00 00 00 00 00 00 00 00  89 7f 71 0c 5c d0 ca ac  |..........q.\...|
000000e0  b1 8d 81 9b 00 00 00 00  00 00 00 00 6d 86 09 12  |............m...|
000000f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000100

Format de l'entête

On peut en apprendre un peu plus sur le format de cette entête dans un message de sgda sur le forum de C-Alice, ainsi que sur le site de Skaya.

Mais le plus intéressant se trouve dans le fichier bcmTag.h que l'on peut trouver sur le site d'OpenWrt, et qui contient une structure décrivant le format de l'entête :

#define TAG_LEN         256
#define TAG_VER_LEN     4
#define SIG_LEN         20
#define SIG_LEN_2       14   // Original second SIG = 20 is now devided into 14 for SIG_LEN_2 and 6 for CHIP_ID
#define CHIP_ID_LEN		6	
#define IMAGE_LEN       10
#define ADDRESS_LEN     12
#define FLAG_LEN        2
#define TOKEN_LEN       20
#define BOARD_ID_LEN    16
#define RESERVED_LEN    (TAG_LEN - TAG_VER_LEN - SIG_LEN - SIG_LEN_2 - CHIP_ID_LEN - BOARD_ID_LEN - \
                        (4*IMAGE_LEN) - (3*ADDRESS_LEN) - (3*FLAG_LEN) - (2*TOKEN_LEN))

typedef struct _FILE_TAG
{
    unsigned char tagVersion[TAG_VER_LEN];       // tag version.  Will be 2 here.
    unsigned char signiture_1[SIG_LEN];          // text line for company info
    unsigned char signiture_2[SIG_LEN_2];        // additional info (can be version number)
    unsigned char chipId[CHIP_ID_LEN];           // chip id 
    unsigned char boardId[BOARD_ID_LEN];         // board id
    unsigned char bigEndian[FLAG_LEN];           // if = 1 - big, = 0 - little endia of the host
    unsigned char totalImageLen[IMAGE_LEN];      // the sum of all the following length
    unsigned char cfeAddress[ADDRESS_LEN];       // if non zero, cfe starting address
    unsigned char cfeLen[IMAGE_LEN];             // if non zero, cfe size in clear ASCII text.
    unsigned char rootfsAddress[ADDRESS_LEN];    // if non zero, filesystem starting address
    unsigned char rootfsLen[IMAGE_LEN];          // if non zero, filesystem size in clear ASCII text.
    unsigned char kernelAddress[ADDRESS_LEN];    // if non zero, kernel starting address
    unsigned char kernelLen[IMAGE_LEN];          // if non zero, kernel size in clear ASCII text.
    unsigned char imageSequence[FLAG_LEN * 2];   // increments everytime an image is flashed
    unsigned char reserved[RESERVED_LEN];        // reserved for later use
    unsigned char imageValidationToken[TOKEN_LEN];// image validation token - can be crc, md5, sha;  for
                                                 // now will be 4 unsigned char crc
    unsigned char tagValidationToken[TOKEN_LEN]; // validation token for tag(from signiture_1 to end of // mageValidationToken)
} FILE_TAG, *PFILE_TAG;

Cette ligne nous confirme bien que la taille de l'entête est de 256 octets :

#define TAG_LEN         256

Les sommes de contrôle

Pour comprendre ce que sont imageValidationToken et tagValidationToken, un autre fichier est particulièrement intéressant : 001-brcm_boards.patch.

Voici les parties intéressantes :

/***************************************************************************
// Function Name: getCrc32
// Description  : caculate the CRC 32 of the given data.
// Parameters   : pdata - array of data.
//                size - number of input data bytes.
//                crc - either CRC32_INIT_VALUE or previous return value.
// Returns      : crc.
****************************************************************************/
UINT32 getCrc32(byte *pdata, UINT32 size, UINT32 crc)
{
    while (size-- > 0)
        crc = (crc >> 8) ^ Crc32_table[(crc ^ *pdata++) & 0xff];

    return crc;
}

et :

        crc = CRC32_INIT_VALUE;
        crc = getCrc32(sectAddr, (UINT32)TAG_LEN-TOKEN_LEN, crc);

Après plusieurs essais plus ou moins fructueux, les mystères sont résolus...

Le champ imageValidationToken est constitué de 3 CRC32 (4 octets chacun) qui se suivent :

  • le CRC32 de l'image complète
crc = CRC32_INIT_VALUE;
crc = getCrc32((uint8_t *) (start + TAG_LEN / sizeof(uint32_t)), (uint32_t) (rootfs_size + kernel_size), crc);
  • le CRC32 du rootfs
crc = CRC32_INIT_VALUE;
crc = getCrc32((uint8_t *) (start + TAG_LEN / sizeof(uint32_t)), (uint32_t) rootfs_size, crc);
  • le CRC32 du kernel
crc = CRC32_INIT_VALUE;
crc = getCrc32((uint8_t *) (start + (TAG_LEN + rootfs_size) / sizeof(uint32_t)), (uint32_t) kernel_size, crc);

Le champ tagValidationToken contient le CRC32 de l'entête, sans le champ tagValidationToken, et calculé après le remplissage du champ imageValidationToken, donc de l'offset 0 à l'offset 235 compris (TAG_LEN - TOKEN_LEN = 236) :

crc = CRC32_INIT_VALUE;
crc = getCrc32(header, (uint32_t) TAG_LEN - TOKEN_LEN, crc);

Extraction avec dd

L'utilitaire dd sous Linux nous permet de découper l'entête et de l'enregistrer dans un fichier :

$ dd if=NB4-R1.2.10-MAIN of=NB4-R1.2.10-HEADER bs=1 count=256
256+0 records in
256+0 records out
256 bytes (256 B) copied, 0.0010944 seconds, 234 kB/s

Comparaison de l'entête des firmwares 1.2.10, 1.3.8 et 1.3.11

$ ./nb4-extract NB4-R1.2.10-MAIN

Signature de l'entête trouvée
----------------------------------------------------------------
[0x00] Signature entête         : 6
[0x04] Infos société            : Broadcom Corporatio
[0x18] Infos supplémentaires    : ver. 2.0
[0x26] ID de la puce            : 6358
[0x2C] ID de la carte           : 96358VW
[0x3C] Ordre des bits           : big endian
[0x3E] Taille de l'image        : 3991453 octets
[0x5E] Adresse du rootfs        : 0xBFC10100
[0x6A] Taille du rootfs         : 3014656 octets
[0x74] Adresse du kernel        : 0xBFEF0100
[0x80] Taille du kernel         : 976797 octets
[0x8E] Espace réservé           : NB4-R1.2.10-MAIN
----------------------------------------------------------------

$ ./nb4-extract NB4-R1.3.8-MAIN

Signature de l'entête trouvée
----------------------------------------------------------------
[0x00] Signature entête         : 6
[0x01] Infos société            : Broadcom Corporatio
[0x06] Infos supplémentaires    : ver. 2.0
[0x09] ID de la puce            : 6358
[0x0B] ID de la carte           : 96358VW
[0x0F] Ordre des bits           : big endian
[0x0F] Taille de l'image        : 3949968 octets
[0x17] Adresse du rootfs        : 0xBFC10100
[0x1A] Taille du rootfs         : 3006464 octets
[0x1D] Adresse du kernel        : 0xBFEEE100
[0x20] Taille du kernel         : 943504 octets
[0x23] Espace réservé           : NB4-R1.3.8-MAIN
----------------------------------------------------------------

$ ./nb4-extract NB4-R1.3.11-MAIN

Signature de l'entête trouvée
----------------------------------------------------------------
[0x00] Signature entête         : 6
[0x04] Infos société            : Broadcom Corporatio
[0x18] Infos supplémentaires    : ver. 2.0
[0x26] ID de la puce            : 6358
[0x2C] ID de la carte           : 96358VW
[0x3C] Ordre des bits           : big endian
[0x3E] Taille de l'image        : 3950490 octets
[0x5E] Adresse du rootfs        : 0xBFC10100
[0x6A] Taille du rootfs         : 3006464 octets
[0x74] Adresse du kernel        : 0xBFEEE100
[0x80] Taille du kernel         : 944026 octets
[0x8E] Espace réservé           : NB4-R1.3.11-MAIN
----------------------------------------------------------------

$ hd NB4-R1.2.10-HEADER > NB4-R1.2.10-HEADER.txt
$ hd NB4-R1.3.8-HEADER  > NB4-R1.3.8-HEADER.txt
$ hd NB4-R1.3.11-HEADER > NB4-R1.3.11-HEADER.txt

Entête firmware 2.1.x

Dans les firmware 2.1.x, le rootfs et le kernel ont été inversés (ou plutôt le kernel est à la place du rootfs et ce dernier le suis). D'ailleurs l'outil nb4-extract à été mis à jour (cf dépo gna rev 174) pout tenir compte de cette inversion et extraire correctement les différentes partie du firmware.

$/.nb4-extract ../NB4-MAIN-R2.1.x
Taille du firmware '../NB4-MAIN-R2.1.x' : 5150649 octets
L'ordre des bits n'est pas le même que sur la Neuf Box 4

Signature de l'entête trouvée
----------------------------------------------------------------
[0x00] Signature entête		: 6
[0x04] Infos société		: Broadcom Corporatio
[0x18] Infos supplémentaires	: ver. 2.0
[0x26] ID de la puce		: 6358
[0x2C] ID de la carte		: 96358VW
[0x3C] Ordre des bits		: big endian
[0x3E] Taille de l'image	: 5150393 octets
[0x5E] Adresse du rootfs	: 0xBFD147B9
[0x6A] Taille du rootfs		: 4083712 octets
[0x74] Adresse du kernel	: 0xBFC10100
[0x80] Taille du kernel		: 1066681 octets
[0x8E] Espace réservé		: NB4-MAIN-R2.1.x
[0xD8] Checksum de l'image	: 0x2151BF7F
[0xDC] Checksum du rootfs	: 0x00000000
[0xE0] Checksum du kernel	: 0x00000000
[0xEC] Checksum de l'entête	: 0x1C681DE8
----------------------------------------------------------------

Comme autre différence, on notera que les checksum pour le rootfs et kernel dans l'entête sont nul (il ne sont donc plus testable).

Rootfs

Connaissant la taille du rootfs (3014656 pour la version 1.2.10), on peut également extraire directement le rootfs :

$ dd if=NB4-R1.2.10-MAIN of=NB4-R1.2.10-ROOTFS bs=1 skip=256 count=3014656
3014656+0 records in
3014656+0 records out
3014656 bytes (3.0 MB) copied, 13.048 seconds, 231 kB/s

Kernel.lz

Connaissant la taille du kernel compressé (976797 pour la version 1.2.10), et avec un peu d'arithmétique : offset = 256+ taille du rootfs = 3014912 (pour la version 1.2.10) on peut également extraire directement le kernel compressé :

$ dd if=NB4-R1.2.10-MAIN of=NB4-R1.2.10-kernel bs=1 skip=3014912
976797+0 records in
976797+0 records out
976797 bytes (1.0 MB) copied, 4.228 seconds, 231 kB/s

Les fainéants patients peuvent se dispenser d'arithmétique avec :

$ dd if=NB4-R1.2.10-MAIN bs=1 skip=256 | dd of=NB4-R1.2.10-kernel bs=1 skip=3014656
976797+0 records in
976797+0 records out
976797 bytes (1.0 MB) copied, 4.228 seconds, 231 kB/s
3014656+0 records in
3014656+0 records out
3014656 bytes (3.0 MB) copied, 13.048 seconds, 231 kB/s

Le kernel compressé (kernel.lz) est généré à partir du vmlinux après compilation avec de la suite de commandes suivante :

/opt/toolchains/uclibc-crosstools/bin/mips-linux-uclibc-strip --remove-section=.note --remove-section=.comment vmlinux
/opt/toolchains/uclibc-crosstools/bin/mips-linux-uclibc-objcopy -O binary vmlinux .vmlinux.bin
/opt/V44-08/hostTools/cmplzma -k -2 vmlinux vmlinux.bin kernel.lz

Le fichier kernel.lz se compose d'une entête de 12 octets et du kernel compressé au format lzma mais avec le paramètre dictionary égal à 22 et non la valeur par défaut (23)*

#hexdump -C -n 256 kernel.lz
00000000  80 01 00 00 80 2c 10 18  00 0d 35 09 5d 00 00 40  |.....,....5.]...|
00000010  00 00 00 6f fd ff ff a3  b7 7f 63 c5 55 81 b7 e0  |...o......c.U...|
00000020  8b fd 94 23 dc 39 8e dd  43 62 87 09 90 f1 e2 c6  |...#.9..Cb......|
00000030  77 49 88 94 82 83 be 0a  91 f4 74 22 cb e2 5a 48  |wI........t"..ZH|
00000040  5b f2 fe 58 ac 36 48 ca  9c 06 f1 9e 99 ac 78 56  |[..X.6H.......xV|

L'entête comporte 3 champs

4 octets : Code Address:  0x80010000  (où le bootloader doit charger le kernel décompressé en RAM)
4 octets : Entry Address: 0x802c1018  (où le bootloader doit sauter pour exécuter le kernel décompressé)
4 octets : Longueur du kernel compressé (ou longueur du fichier vmlinux.lz -12)

Ceci explique pourquoi la commande cmplzma utilise les fichiers vmlinux et vmlinux.bin en entrée. Les deux premiers champs sont lus dans le fichier vmlinux tandis que la compression est faite avec le fichier vmlinux.bin qui est un dump mémoire à partir de l'adresse de chargement

objcopy is used to generate a raw binary file by using an output target of `binary' (e.g., use `-O binary'). When objcopy generates a raw binary file, it will essentially produce a memory dump of the contents of the input object file. All symbols and relocation information will be discarded. The memory dump will start at the load address of the lowest section copied into the output file.

Driver ADSl (adsl_phy.bin)

L'image du driver se compose d'une entête de 32 octets + le fichier adsl_phy.bin.

L'entête comprend trois champs

4  octets : longueur du driver
4  octets : somme de contrôle de l'image (hors entête) donc du driver (complément à 1 du CRC32 du driver)
24 octets : version du driver (ex : NB4-A2pB022b)

Le driver sera recopié dans la flash à partir de l'adresse 0x00780000 (0x0000000 = début de la flash) qui correpond à la partition /dev/mdtblock4. Cette partition sera ensuite montée sur le fichier /etc/adsl/adsl_phy.bin