Livebox Sagem Firmwares

Let's take a 260 series Livebox Sagem firmware, found for instance on the CD-ROM distributed by Orange in France, and look into it.

Let's extract the firmware 26014A for instance:

It's made of three parts

  • the first part is a header : 140 bytes of binary data.

    dd if=firmware of=header.raw bs=1 skip=0 count=140
    0x00-0x03: 01 00 00 01 magic bytes
    0x04-0x07: 00 00 0a 74 size of compressed firmware loader 0xa74=2676
    0x08-0x0b: 80 80 00 00 ?
    0x0c-0x0f: 80 ef 00 00 ?
    0x10-0x13: 00 01 00 00 ?
    0x14-0x17: 00 00 17 98 size of uncompressed firmware loader 0x1798=6040
    0x18-0x1b: 80 c0 00 00 ?
    0x1c-0x1f: 80 c0 00 00 ?
    0x20-0x23: 80 01 40 00 ?
    0x24-0x27: 80 01 40 00 ?
    0x28-0x7b: name of firmware "FAST3202 \tFAST3202_26014A (OpenRG 4.0.21.3.3.1.31)\0"
    0x7c-0X7f: 43 45 2c 50 checksum?
    0x80-0x83: 81 4a 03 ca checksum?
    0x84-0x87: ff e8 3b 5c checksum?
    0x88-0x8b: 00 00 00 01 magic bytes

    The checksums are probably some sort of CRC32, I don't have time to investigate more at the moment.

  • then comes a zlib compressed that looks like some sort of flash loader. The compressed and uncompressed sizes of this part are found in the header.
    dd if=firmware of=loader.raw bs=1 skip=140 count=2676
    zpipe -d loader.inflated.

    The zpipe program can be found here. The inflated file (6040 bytes long) is some binary executable.
  • The remaining part is the actual payload.

    dd if=firmware of=payload.raw bs=1 skip=2816
    (2816 is 2676+140). The paload itself seams to be made of several parts:

    • A header: 0x500=1280 bytes, all zeros except the last four bytes: 26 10 70 96. Extract it with dd if=payload.raw of=payload_header.raw bs=1 skip=0 count=1280
    • A bootloader, 9504 bytes long. Extract it with dd if=payload.raw of=payload_code.raw bs=1 skip=1280 count=9504. The main role of this executable code seems to be uncompressing and booting the kernel.
    • The payload data (see below). dd if=pawload.raw od=payload_data.raw bs=1 skip=10784
    • This payload data is compressed using LZMA, bur with a slight variation: the length is stored using 4 bytes instead of 8. So let's insert four zero bytes to fix it:
      dd if=payload_data.raw bs=1 count=8 of=/tmp/file1
      dd if=/dev/zero bs=1 count=4 of=/tmp/file2
      dd if=payload_data.raw bs=1 skip=8 of=/tmp/file3
      cat /tmp/file1 /tmp/file2 /tmp/file3 >payload_data.fixed
      lzmadec < payload_data.fixed >payload_data.uncompressed

      The payload_data.uncompressed contains a linux kernel with an initrd, and a rootfs filesystem:

      • The linux kernel code can be extracted with dd if=2277376 bs=1 count=2277376 of=kernel.raw
      • The initrd contents can be extracted with dd if=payload_data.uncompressed of=initrd.raw bs=1 skip=2277376 count=5745
      • The kernel+Initrd part is terminated by 2448 zero bytes
      • The rootfs comes at the end: dd if=payload_data.uncompressed of=rootfs.raw skip=2285568 bs=1
      • Extracting the initrd.
        It is a gzip-compressed cpio archive:
        gzcat initrd.raw | cpio -i --no-absolute-filenames
        Nothing fancy here, only symbolic links to the actual file in /mnt/cramfs

        Extracting the rootfs
        It is some sort of cramfs. I did not find a way to mount it using standard linux x86 utilities, so I had to write a simple program to extract the contents. The cramfs format has some documentation in order to do so.

Note: The files can be found here