Frontier Silicon firmware: FSH1 file system

After downloading the firmware file for my TechniSat DigitRadio 580, I could start analyzing it.

I used binwalk and bless, along with the usual set of unix command line tools like hexdump, dd and so. Using bless as GUI hex editor instead of plain hexdump proved very helpful because it has features like automatic binary-to-number conversion, highlight-other-places-that-have-the-selected-bytes and nice search and offset jumping.

One of the things I found was a kind of embedded file system that contains the files that are available via the HTTP port.

File system format

All offsets here are for firmware file ir-mmi-FS2026-0500-0286.2.11.12.EX65933-1A22.isu.bin, which is the current version for the DigitRadio 580. MD5 sum of the file: 92be288749f7ca73feba0126a09973cb. Mirrored at github.com/cweiske/frontier-silicon-firmwares .

To find it inside the firmware file, search for FSH1 and look at the second match, which is at 0x1f81dc:

001f81d0  00 00 00 00 00 00 00 00  00 00 00 00 46 53 48 31  |............FSH1|
001f81e0  9a 88 04 00 44 8c 0a 03  00 00 01 00 03 01 03 77  |....D..........w|
001f81f0  65 62 09 01 03 63 73 73  01 01 04 62 61 73 65 03  |eb...css...base.|
001f8200  01 06 69 6d 61 67 65 73  01 00 0d 68 65 61 64 65  |..images...heade|
001f8210  72 5f 62 67 2e 70 6e 67  23 03 00 00 0e 03 00 00  |r_bg.png#.......|
001f8220  23 03 00 00 00 12 73 74  79 6c 65 2d 68 61 6e 64  |#.....style-hand|
001f8230  68 65 6c 64 2e 63 73 73  5c 0a 00 00 31 06 00 00  |held.css\...1...|

All numbers are little endian!

46 53 48 31
"FSH1" marker that seems to mark the beginning of this file system. Note that the structure described here does not match on the first FSH1 found in the firmware file, so maybe it does not have to do anything with it.
9a 88 04 00

Size of the following file system, in this case 297114 bytes. This can be used to copy the file system's data with dd into a separate file:

$ dd if=ir-mm....bin of=fsh1-2 skip=$((0x1f81e4)) count=297114
44 8c
Unknown.
0a 03 00 00 01 00 03 01 03 77
The file system's data.

Index

The file system itself is split into an index and a data section. The index is built as follows:

001f81d0  00 00 00 00 00 00 00 00  00 00 00 00 46 53 48 31  |............FSH1|
001f81e0  9a 88 04 00 44 8c 0a 03  00 00 01 00 03 01 03 77  |....D..........w|
001f81f0  65 62 09 01 03 63 73 73  01 01 04 62 61 73 65 03  |eb...css...base.|
001f8200  01 06 69 6d 61 67 65 73  01 00 0d 68 65 61 64 65  |..images...heade|
001f8210  72 5f 62 67 2e 70 6e 67  23 03 00 00 0e 03 00 00  |r_bg.png#.......|
001f8220  23 03 00 00 00 12 73 74  79 6c 65 2d 68 61 6e 64  |#.....style-hand|
001f8230  68 65 6c 64 2e 63 73 73  5c 0a 00 00 31 06 00 00  |held.css\...1...|
0a 03 00 00
Size of the index itself. Afterwards, the data starts.
01
Entry type: 00 for file, 01 for folder
00
03
Length of name. The root folder has no name, thus the length is 0.
77 65 62
Name (only ASCII characters seen yet): "web"
Directory-specific
03
Number of entries (files+folders) in the directory.
File-specific
23 03 00 00
File size, here 803 bytes
0e 03 00 00
File offset (first index size byte is position 0)
23 03 00 00
Compression indicator. If the number is the same as the file size, then the file is not compressed.

Immediately after the end of an entry, the next entry follows.

FSH1 parser

After I had decoded the meaning of the single bytes, I wrote a small parser that takes the dd'ed fsh1-2 file and prints the tree: fsh1-parser.php.

It will print the following for the DigitRadio 580 firmware:

$ ./fsh1-parser.php fsh1-2
Index length: 778
/   (3 files)
  web/   (9 files)
    css/   (1 files)
      base/   (3 files)
        images/   (1 files)
          header_bg.png              803 bytes, offset 782       compressed: no
        style-handheld.css          2652 bytes, offset 1585      compressed: yes
        style-screen.css            3184 bytes, offset 2471      compressed: yes
    images/   (2 files)
      company_logo.png              4471 bytes, offset 3509      compressed: no
      favicon.ico                   1150 bytes, offset 7980      compressed: yes
    iperf/   (3 files)
      control.html                  4804 bytes, offset 8357      compressed: yes
      index.html                     132 bytes, offset 9813      compressed: yes
      info.html                       38 bytes, offset 9907      compressed: no
    js/   (6 files)
      LICENSE                       1544 bytes, offset 9945      compressed: yes
      jquery-1.6.2.min.js          91556 bytes, offset 10790     compressed: yes
      jsbn.js                      15161 bytes, offset 42838     compressed: yes
      prng4.js                      1009 bytes, offset 47946     compressed: yes
      rng.js                        1883 bytes, offset 48412     compressed: yes
      rsa.js                        2644 bytes, offset 49211     compressed: yes
    languages/   (2 files)
      en.json                       5528 bytes, offset 50287     compressed: yes
      test.json                     5555 bytes, offset 51959     compressed: yes
    Network.html                    9076 bytes, offset 53822     compressed: yes
    Status.html                     3855 bytes, offset 55281     compressed: yes
    common-1.0.3.js                74443 bytes, offset 56124     compressed: yes
    index.html                      1878 bytes, offset 70670     compressed: yes
  icons/   (5 files)
    dlna_icon_large.jpg             2630 bytes, offset 71431     compressed: yes
    dlna_icon_large.png             8935 bytes, offset 73893     compressed: no
    dlna_icon_small.jpg              970 bytes, offset 82828     compressed: yes
    dlna_icon_small.png             2889 bytes, offset 83661     compressed: no
    icons_NE8251.rar               30051 bytes, offset 86550     compressed: yes
  FwImage/   (1 files)
    sd8782_uapsta.bin             250064 bytes, offset 116564    compressed: yes

Interesting files

Just looking through the file tree gives us some yet unknown information:

/web/iperf/index.html

The radio has an "iperf" web interface that has a "iperf Command" text field and the following buttons:

  • Add new task
  • Get tasks
  • Execute
  • Stop Execution
  • Delete tasks

The buttons use the netremote.test.iperf API.

I don't yet know which commands can be use here.

FwImage/sd8782_uapsta.bin
This is a firmware blob for a Marvell WiFi chip. See JianRuiqian/marvellwifi.
js/rsa.js
I'm really interested how RSA signatures/encryption is used on the radio. See below.

File extraction

Now that I had the offsets, I could extract the single files from the file system with dd:

$ dd if=fsh1-2 of=company_logo.png bs=1 skip=3509 count=4471
$ file company_logo.png
company_logo.png: PNG image data, 140 x 36, 8-bit/color RGBA, non-interlaced

Most files are compressed:

$ dd if=fsh1-2 of=dlna_icon_large.jpg.zz bs=1 skip=71431 count=2630
$ file dlna_icon_large.jpg.zz 
dlna_icon_large.jpg.zz: zlib compressed data

They can be decompressed with pigz:

$ pigz --keep --zlib --decompress dlna_icon_large.jpg.zz
$ file dlna_icon_large.jpg
dlna_icon_large.jpg: JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, Exif Standard: [TIFF image data, little-endian, direntries=5, orientation=upper-left, software=ACD Systems Digital Imaging, datetime=2015:08:18 15:32:25], baseline, precision 8, 120x120, components 3

Great success!

RSA key usage

The netRemote API has two methods for RSA keys: netRemote.sys.rsa.status and netRemote.sys.rsa.publicKey. The first can be used to check if the radio has already generated a RSA key, the second to fetch the public key.

This public key is used by the web interface to encrypt the Wifi password before sending it to the radio. Doing so prevents eavesdroppers to sniff the passphrase over the unencrypted HTTP connection.

Written by Christian Weiske.

Comments? Please send an e-mail.