Sunday 1 March 2015

Sunluxy DVR mkII - quick firmware mod investigation

The last post in this series saw the firmware being dumped from the device. This post looks at the format of the data and some annoyances that were encountered while trying to write a modified image back to the device.

One of the first thing I tend to do whenever I'm investigating a file is to generate an entropy plot. This habit developed from years of reverse engineering malware samples where a simple entropy plot would give you a lot of information about the next steps you'd probably be taking. For instance, packed executables would look significantly different to non-packed samples and files with appended data (think self-extracting archives or tools such as AutoIT) would have the interesting functionality contained in appended data (data that resides outside of the section tables). These are just two basic examples, but I can't stress how useful these graphs can be.

Back to the task at hand, the dumped firmware. The entropy plot looks like this:


The high entropy indicates compressed data and the gaps indicate, well, gaps. Immediately these gaps indicate some sort of distinct sections, which is nice because it opens up the possibility of doing changes in isolations. But back to that later.

A hex dump of the header shows us this:

00000000  47 4d 38 32 38 37 00 00  00 00 01 00 00 00 05 00  |GM8287..........|
00000010  00 00 00 00 00 00 01 00  00 00 05 00 55 42 4f 4f  |............UBOO|
00000020  54 00 00 00 00 00 00 00  00 00 06 00 00 00 3a 00  |T.............:.|
00000030  4c 49 4e 55 58 00 00 00  00 00 00 00 00 00 40 00  |LINUX.........@.|
00000040  00 00 30 00 46 53 00 00  00 00 00 00 00 00 00 00  |..0.FS..........|
00000050  00 00 70 00 00 00 10 00  4d 54 44 00 00 00 00 00  |..p.....MTD.....|
00000060  00 00 00 00 00 00 80 00  00 00 4e 00 55 53 45 52  |..........N.USER|
00000070  00 00 00 00 00 00 00 00  00 00 ce 00 00 00 18 00  |................|
00000080  57 45 42 00 00 00 00 00  00 00 00 00 00 00 e6 00  |WEB.............|
00000090  00 00 12 00 4c 4f 47 4f  00 00 00 00 00 00 00 00  |....LOGO........|
000000a0  00 00 f8 00 00 00 06 00  43 55 53 54 4f 4d 00 00  |........CUSTOM..|
000000b0  00 00 00 00 00 00 fe 00  00 00 02 00 41 4b 50 32  |............AKP2|
000000c0  50 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |P...............|
000000d0  00 00 00 00 00 00 00 00  00 00 00 00 08 00 00 00  |................|
000000e0  0c 00 00 00 17 00 00 00  04 00 00 00 00 00 00 00  |................|
000000f0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 55 aa  |..............U.|
00000100  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|

The GM8287 signature is the model of the DVR, so (unsurprisingly) a custom  header. The other readable text is what immediately jumps out and surprise surprise, there are nine of them, which just happens to correlate with the nine high entropy blocks in the graph. The format of the structure is pretty simple. The header is basically 0x14 bytes long and is mostly irrelevant to what I'm looking at but it is immediately followed by 9 section headers. These take the form:

{
  uint32_t offset;
  uint32_t size;
  char     Name[0xc];
}

So the data for each section can be pulled out using whatever method you fancy. Just go to the offset in the file and copy the correct number of bytes. I ended up writing a C program to dump out the file and patch stuff back in automatically, but it also dumps out section information:

Index Name Offset Size                 Start of data

0 UBOOT  0x00010000 327680 (0x00050000) 130000ea14f09fe514f0

1 LINUX 0x00060000 3801088 (0x003a0000) 2705195613a0f7f953a1
2 FS 0x00400000 3145728 (0x00300000) 68737173970000004c1c
3 MTD 0x00700000 1048576 (0x00100000) 851903000c000000b1b0
4 USER 0x00800000 5111808 (0x004e0000) 68737173ff010000551c
5 WEB 0x00ce0000 1572864 (0x00180000) 6873717305000000561c
6 LOGO 0x00e60000 1179648 (0x00120000) 6873717302000000561c
7 CUSTOM 0x00f80000 393216 (0x00060000) 6873717316000000561c
8 AKP2P 0x00fe0000 131072 (0x00020000) ffffffffffffffffffff

The sections that start with 68737173 ('hsqs') are Squash file systems. A quick sanity check with unsquashfs showed that there wasn't anything funky about them and they could be unpacked easily. The goal now was to check that replacing them didn't break the device.

The easiest section to experiment with was the LOGO section as it only contained one file, bmp_logo.bmp. It normally looks like this:


I modified the logo in GIMP, created a new squash file system using mksquashfs and inserted the new file into the firmware image at the correct location. Using the TL866 again, I uploaded the firmware and rebooted the device.

The device booted, but the modified splash screen didn't show. So at least I knew that there wasn't any form of signature verification but it wasn't clear why the logo didn't show.

A quick check with binwalk showed that the original LOGO squashfs section was compressed using lzma:

binwalk LOGO.dmp 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Squashfs filesystem, little endian, version 4.0, compression:lzma (non-standard type definition), size: 19732 bytes,  2 inodes, blocksize: 262144 bytes, created: Thu Nov  6 06:59:34 2014

My version of mksquashfs from the ubuntu repo didn't support lzma, so a quick build from source was necessary.

Repeating the firmware updating process with the new lzma compressed LOGO section gave the same result, no splash screen! At this point I plugged into the serial console to see if there were any useful error messages being spat out. Luckily, there was:

Setting hostname ...
Bringing up interfaces ...
/bin/sh: run-parts: not found
Mounting user's MTD partion
SQUASHFS error: Filesystem uses "lzma" compression. This is not supported
mount: mounting /dev/mtdblock6 on /opt/logo failed: Invalid argument
Frammap: DDR0: memory base=0x3800000, memory size=0xbe00000, align_size = 4K.
Frammap: version 1.1.2, and the system has 1 DDR.

This was confusing. The original LOGO section was compressed using lzma, yet my newly compressed lzma version was causing an issue. Time to check with the gzip version again:

Setting hostname ...
Bringing up interfaces ...
/bin/sh: run-parts: not found
Mounting user's MTD partion
Frammap: DDR0: memory base=0x3800000, memory size=0xbe00000, align_size = 4K.
Frammap: version 1.1.2, and the system has 1 DDR.

Hmm so no error yet no splash screen either. The gzip version was apparently being loaded without issue. That only left my modification to the image being the problem and that is what it turned out to be. The original BMP was a 16bit image but when I saved my new version in GIMP it defaulted to 24bit. Sigh. At least it didn't turn out to be some weird hacked version of lzma, that would have been much more of a pain to deal with.

So another firmware update, another reboot and....



Success.

In the grand scheme of things, being able to modify the firmware of a device where you have physical access to SPI flash and the images aren't signed isn't really that big a deal, but it really makes this DVR a far more hackable and therefore fun device than the JuanDVR version was.  

2 comments:

  1. Nice work on this! I'm guessing you're going to be pulling apart the app at some point? There seemed to be a fair bit of logic supporting it in terms of lua code to make things happen.

    ReplyDelete
    Replies
    1. Yeah, one of the first goals is to try and get streaming to the browser without the ocx they expect you to install. Luckily, they also have an android app ;-)

      Delete