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 .
Header
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.