Reverse Engineering the FFS Flash File System Format


As part of de-bricking a Talkswitch TS-450i IP Phone I needed to see and extract the files within the flash image so that I could replace corrupt ones with known good versions. That required reverse engineering the in-flash format of the file system as there was no way to get files off the device, only onto it.

The ls command provided a list of files in the flash file systems and all their attributes, as shown below. The length of the flash sections was known from the output of the fa command.

(psbl) ls

FlashDiskDump for /
     drwxrwxrwx 0:0 a:  0 i:0 Jul 27, 1999  2:17:40am       2 etc
     drwxrwxrwx 0:0 a:  0 i:0 Jul 27, 1999  2:17:40am       2 bin
     drwxrwxrwx 0:0 a:  0 i:0 Jul 27, 1999  2:17:40am       2 ttyS
      rwxrwxrwx 0:0 a: -1 i:2 Jan  1, 1970 12:00:00am 2673184 lip8800
      rwxrwxrwx 0:0 a: -1 i:1 Jan  1, 1970 12:00:00am  527977 pt1.ld
      rwxrwxrwx 0:0 a: -1 i:1 Jan  1, 1970 12:00:00am   13528 titan_brf6350_init.bts
      rwxrwxrwx 0:0 a: -1 i:2 Jan  1, 1970 12:00:00am    9635 defrag
      rwxrwxrwx 0:0 a: -1 i:2 Jan  1, 1970 12:00:00am    3256 update
(psbl)

The top of the FFS section image contained the following data:

0000000: 4646 5300 0000 4600 0000 ff83 da3a 3412 FFS...F......:4.
0000010: 0000 0302 0000 0000 0000 0000 0000 0065 ...............e
0000020: 7463 0100 0000 ff83 da3a 3412 0000 0302 tc.......:4.....
0000030: 0000 0000 0000 0000 0000 0062 696e 0200 ...........bin..
0000040: 0000 ff83 da3a 3412 0000 0402 0000 0000 .....:4.........
0000050: 0000 0000 0000 0074 7479 5303 0002 00ff .......ttyS.....
0000060: 8100 0000 0000 0007 20ca 28ff cb4d 5114 ........ .(..MQ.
0000070: 1712 0720 6c69 7038 3830 3054 695a 7060 ... lip8800TiZp`
0000080: efd9 0014 c928 0000 0100 0004 4000 0096 .....(......@...
0000090: 3751 14b2 e830 3700 0000 0000 0000 0000 7Q...07.........
00000a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000f0: 0000 0000 0000 0000 0000 0000 0000 0000 ................

Initially the top three directory names are visible, as well as the first file (lip8800), after which there appears to be some garbage.

As we’re dealing with a hex dump, lets convert the output of the ls command into hex values and see if we can line anything up.

Jan  1, 1970 12:00:00am is the unix time epoch and is 0x00000000. The directories will have some other value in this place, so it shouldn’t be too hard to line it up without having to convert the other date.

The file sizes are as follows:
0x0028CA20 lip8800
0x00080E69 pt1.ld
0x000034D8 titan_brf6350_init.bts
0x000025A3 defrag
0x00000CB8 update

The file permissions look like standard unix permissions, for which rwxrwxrwx is 777 octal or 0x1FF.

FFS Header Decoding

The first four bytes of the filesystem image seem to be a signature. The next several bytes could be interperted as a 16 bit value followed by a 32 bit value or vice versa. The flash image size is 4587520 bytes or 0x00460000. Decoding the 4 bytes after the signature in a little-endian format gives 0x00460000, so it’s the file system maximum size and integers are encoded in little endian. Ignoring the two zero bytes after the filesystem size, we see a 16 bit value of 0x83FF which looks like the permission value with a few extra bits set, which could indicate that it’s a directory. Lets assume that this value is the first part of the file entry.

The header for the FFS flash file system looks like:


struct FFSHeader
{
uint32_t Signature; //'FFS\0'
uint32_t Length; //Size of the filesystem in bytes, including this header.
uint16_t Unknown1; //Set to 0
};

FFS File Entry Decoding

From the hexdump above, it looks like there are 4 files present (etc, bin,ttyS, lip8800) and that the 0x83FF value repeats several times. Assuming that value indicates the start of a file entry, let’s re-organise the data to help with decoding.

ff83 da3a 3412 0000 0302 0000 0000 0000  ...:4...........
0000 0000 0065 7463 0100 0000            .....etc....
ff83 da3a 3412 0000 0302 0000 0000 0000  ...:4...........
0000 0000 0062 696e 0200 0000            .....bin....
ff83 da3a 3412 0000 0402 0000 0000 0000  ...:4...........
0000 0000 0074 7479 5303 0002 00         .....ttyS....
ff81 0000 0000 0000 0720 ca28 ffcb 4d51  ......... .(..MQ
1417 1207 206c 6970 3838 3030 5469 5a70  .... lip8800TiZp

It appears that the first 16 bits does indicate the mode, with 0x200 indicating a directory instead of a file. Next up we have what looks like a 32 bit value which is set for the directories, but all zeros for the file. This is probably the date value. The next 16 bits is all zero for every entry that we can see, which could be the user:group values presented by the ls output. The next 16 bits are 0x0203, 0x0203, 0x0204 and 0x2007 for etc, bin, ttyS and lip8800 respectively. It looks like the low byte contains the length of the file name.

So far for the file entry structure we have:


struct FFSFileEntry
{
uint16_t    Mode;        //0x200 = Directory. High bit always set
uint32_t    Timestamp;
uint8_t     User;
uint8_t     Group;
uint8_t     NameLength;  //Size of the file name in bytes
};

Looking at the next several bytes on the lip8800 entry we can see that it appears to be the file size, in little endian. However the top 8 bits are all set, whereas the entries for the directories have the high bits clear and what appears to be a size of 2. Correlating with the output of the ls command, it seems that the top 8 bits are the ‘a’ field and the directories do have a size of 2.

The next 8 bytes are all 0 for the directories and appears to be random data for the file. This may be additional dates or checksums. It’s not offsets to data as both of them are larger than the flash section when looking at them as 32 bit values.

Finally we have the filename, followed by what is probably the file data. There does appear to be an extra 2 bytes after the file data that is unaccounted for. It’s zero for the first two directory entries, and 2 for ttyS. The other field that hasn’t been accounted for yet is the ‘i’ field, which should be 2 for lip8800 and 0 for the entries. It looks like Unknown_1 in the FFSHeader actually belongs in the FFSFileEntry structure. Given that the directory entries contain a 16 bit value, it looks like the ‘i’ field is actually the index of the directory in which the file lives, where 0 is the root directory.

Putting It All Together

Putting together all the information that was discovered, the FFS file system seems to be structured as follows:


//All integers are encoded little-endian
struct FFSHeader
{
uint32_t Signature;     //'FFS\0'
uint32_t Length;        //Size of the filesystem in bytes, including this header.
};

struct FFSFileEntry
{
uint16_t    Parent;       //Field 'i' in ls output
uint16_t    Mode;         //0x200 = Directory. High bit always set
uint32_t    Timestamp;    //unix epoch
uint8_t     User;
uint8_t     Group;
uint8_t     NameLength;   //Size of the file name in bytes
uint32_t    FileLengthA;  //Size of the file, high byte is field 'a'
uint32_t    Unknown_1;
uint32_t    Unknown_2;
};

Testing

After bashing up a quick python script, the following output was obtained:

nada@unit-01:~/Documents/Projects/talkswitch$ python talkswitch-tools/ffs-dump.py broken/FFS.img
Mode U:G A   P   Date     Unk1     Unk2     Size     Filename
83FF 0:0   0   0 12343ADA 00000000 00000000        2 bin
81FF 0:0 255   2 00000000 14514DCB 20071217  2673184 lip8800
81FF 0:0 255   1 00000000 000B9E5B 20071217    13528 titan_brf6350_init.bts
81FF 0:0 255   2 00000000 00133A7A 20071217     9639 defrag
81FF 0:0 255   2 00000000 00065BE5 20071217     3252 update
83FF 0:0   0   0 12343ADA 00000000 00000000        2 etc
83FF 0:0   0   0 12343ADA 00000000 00000000        2 ttyS
81FF 0:0 255   1 00000000 03418B09 20071217   527977 pt1.ld
nada@unit-01:~/Documents/Projects/talkswitch$

The script is available at http://github.com/nada-labs/talkswitch-tools.git for anyone who wants to get files from a FFS filesystem image.

, , , , , , , ,

  1. #1 by Hong-Minh Pho on May 11, 2016 - 11:33 am

    I saw you had a python script to dump the flash from the TS-450i in the tools directory. How did you get console access on the device to download the flash. Is there serial pinouts on the board. Is there jtag? Any assistance would be appreciated. Thanks

  2. #2 by nada on May 14, 2016 - 7:45 am

    Yes, there is a serial console on the board. It’s an empty 4-pin header labeled ‘SDIO’ and it’s located near the LCD connector.

    The pinout, from closest to the LCD connector is:
    TX
    RX

    GND

    But it’s been a while so I could have TX & RX swapped. The console runs at 115200,8,N,1 with 3.3V signalling.

    There is also a empty 14-pin connector labelled ‘JTAG’ near the phone sockets, if you want more control. I never did discover its pinout but it’s probably a standard TI pinout.

    Good luck with whatever you’re doing to the phone.

    -nada

(will not be published)