venerdì 13 marzo 2015

Avtech DVR custom firmware - fixing MJPEG streaming

Author: BigNerd95 (Lorenzo Santina)

Prologue

In 2011 I bought a DVR for my home video surveillance, an Avtech AVD 744B.
The feature i preferred was the MJPEG streaming, so I made a webapp to view my cameras with any smartphone or pc without installing any kind of software, but only using a browser.
In 2012 Apple updated Safari including some new rules on the management of the received stream by multipart urls, so i couldn't see the stream on my iPhone any more.
I was very annoyed by this!
This is the why I started analyzing the firmware of my dvr.

Traffic Analysis

The first thing I did was dumping the traffic with netcat:
Request:

$ nc 192.168.2.10 554
GET /cgi-bin/guest/Video.cgi?media=MJPEG&resolution=CIF&channel=1 HTTP/1.1
[...other session headers copied from browser request...]

Response:

HTTP/1.1 200 OK
Expires: 0
Pragma: no-cache
Cache-Control: no-cache
Content-Type: multipart/x-mixed-replace;boundary="boundary"


--boundary
Content-Type: image/jpeg
Content-Length: 13117

JFIFC


[...frame...]--boundary
Content-Type: image/jpeg
Content-Length: 13155

JFIFC


[...frame...]

Multipart standard want that any frame is recognized by the starting string --[boundary header variable] and a new line at the end.
How you can see the parameter boundary, is with double quotes in the header, while in the payload is without double quotes, in addition after any frames the \r\n is missing.
This mistakes make the streaming corrupted for updated browsers, freezing the video after the first frame, because they can't recognize the next frames.

Firmware Analysis

I started unpacking the firmware update to find where were this bugs:

  1. Download
    I downloaded the last firmware update.
    The firmware of my dvr is 744B_AVD.

  2. Extract
    Extracting the archive, I found 4 files:
    • AppImg_4.bin (Applications archive)
    • kernel_4.bin (Linux kernel)
    • fboot_4.bin (Bootloader)
    • xml_4.bin (Default configs)

  3. Simple analysis
    .bin extension is not enough to know what kind of file it is, in fact this means that it is a generic binary file, so I tried to see what the command file could find:
    
    $ file AppImg_4.bin
    AppImg_4.bin: data
    

It returns data, so the file might contains an header to obfuscate the firmware.


  • Advanced analysis
    Analyzing it with Binwalk returned useful information:
    
    $ binwalk AppImg_4.bin 
    
    DECIMAL       HEXADECIMAL     DESCRIPTION
    --------------------------------------------------------------------------------
    200           0xC8            JFFS2 filesystem, little endian
    10948228      0xA70E84        Zlib compressed data, compressed
    
  • After 200 bytes there is a jffs2 file system. So now I know that there is an header of 200 bytes.


  • JFFS2 image
    Removing the header I got the clean jffs2 image:
    
    $ dd if=AppImg_4.bin of=AppImg_4.jffs2 bs=1 skip=200
    


  • Mount JFFS2
    To mount a JFFS2 file system I needed to load some kernel module and create a volume in ram (I chose 40 megabytes but for other firmware maybe required a larger size).
    I needed to be root from now on.
    
    # modprobe mtdblock
    # modprobe jffs2
    # modprobe mtdram total_size=40000 erase_size=256
    # dd if=AppImg_4.jffs2 of=/dev/mtdblock0
    # mkdir AppImgMnt
    # mount -t jffs2 /dev/mtdblock0 AppImgMnt
    


  • Make a copy
    To don't have to mount this file system each time I want to modify the firmware, I copied all files in a different directory.
    
    # mkdir AppImgCnt
    # cp -a AppImgMnt/* AppImgCnt/
    
  • From now on I worked only inside AppImgCnt (not Mnt) because I then created a new jffs2 file system and I didn't modify the existing one.

  • Look for "boundary"
    Moving inside AppImgCnt I could find a lot of interesting file, so I started looking for the "boundary" string to find the executable containing it:
     
    # fgrep -r "boundary" .
    
  • But I got none results.


  • Squashfs
    I noticed that there were three squash file system:
    • arch.lzma.sqfs (binary, libraries and html for web interface)
    • osg.lzma.sqfs (theme for gui)
    • osg-STYLE1.lzma.sqfs (other theme for gui)


  • Extract squashfs
    Before extracting any squash i copied them outside AppImgCnt to don't dirty the firmware image.
    I tried to extract these squash fs but they require modified tools to unsquash them because they are compressed with lzma, so I used slax tools.
    After downloaded the unsquashfs tool I extracted arch.lzma.sqfs:
    
    # wget http://ftp.slax.org/useful-binaries/linux/unsquashfs.gz
    # gunzip unsquashfs.gz
    # chmod 755 unsquashfs
    # ./unsquashfs arch.lzma.sqfs 
    Parallel unsquashfs: Using 1 processor
    625 inodes (625 blocks) to write
    
    [======================================================================-] 625/625 100%
    created 537 files
    created 45 directories
    created 88 symlinks
    created 0 devices
    created 0 fifos
    
  • A new "squashfs-root" folder is created. I moved inside it and i found these folders:
    • bin/
    • HTML/
    • lib/


  • Look for "boundary" again
    Trying to search again for "boundary" in the unsquashed archive (inside squashfs-root):
    
    # fgrep -r "boundary" .
    Binary file ./bin/streamd matches
    Binary file ./bin/SmtpClient matches
    Binary file ./lib/libcurl.so.4 matches
    Binary file ./lib/libcgi.so matches
    
  • I found some files containing this string!


  • Binaries analysis
    To reduce the number of files to fix, I analyzed where this four binaries contained "boundary" string:
    
    # strings bin/streamd | grep "boundary"
    --boundary
    --boundary
    --boundary
    --boundary
    multipart/x-mixed-replace;boundary="boundary"
    
    
    # strings bin/SmtpClient | grep "boundary"
    Content-Type: %s; boundary="%s"
    "; boundary="----%s"%s%s
    boundary
    
    
    # strings lib/libcurl.so.4 | grep "boundary"
    %s; boundary=%s
    Content-Type: multipart/mixed, boundary=%s
    
    
    # strings lib/libcgi.so | grep "boundary"
    find_next_boundary
    boundary
    multipart/x-mixed-replace;boundary="boundary"
    --boundary
    
  • In the dumped traffic the affected line is:
    
    multipart/x-mixed-replace;boundary="boundary"
    
    So the only binaries to fix are:
    • bin/streamd
    • lib/libcgi.so

    Patching Firmware


    1. Fixing Binaries
      To fix this binary I need to remove double quotes before and after boundary string, and insert two chars \r\n before each new frame header.
      • Streamd
        Before modifying anything I controlled the file size:
        
        # ls -l streamd
        -rwxr-xr-x 1 root root 434668 Mar  5  2012 streamd
        

    Is very important keep the same file size, or the binary will result in a corrupted executable.
    I opened streamd with an hex editor and I searched the string boundary:
    The piece of string selected is a part of frame header found in traffic dump:
    
    --boundary
    Content-Type: image/jpeg
    Content-Length:
    
    I need to insert hex byte 0D (\r) and 0A (\n) at offset 0x5CE2C and 0x5CE2D. Then I removed two null bytes (offset 0x5CE6A and 0x 5CE6B) at the end of the string to compensate the new length. This still works because there is at least one null byte (string terminator).
    I did the same thing for other three headers.
    Searching again boundary string I found "boundary" section to fix:
    This is the Content type header, (reference from traffic dump):
    
    Pragma: no-cache
    Cache-Control: no-cache
    Content-Type: multipart/x-mixed-replace;boundary="boundary"
    
    I removed two double quotes (at addresses 0x5D18B and 0x5D194) and I added two null byte at the end (at addresses 0x5D193 and 0x5D194) always to compensate the new length.
    I controlled the binary size again:
    
    # ls -l streamd
    -rwxr-xr-x 1 root root 434668 Feb  7 12:45 streamd
    
    The file size is the same, so if I modified all correctly I should still have a valid executable.


  • libcgi.so
    Control file size:
    
    # ls -l libcgi.so 
    -rwxr-xr-x 1 root root 43280 Mar  5  2012 libcgi.so
    

  • Open with hex editor and search boundary:
    I removed double quotes (at offset 0x9AF3 and 0x9AFC) and added two null bytes (at offset 0x9AFB and 0x9AFC) at the end of the boundary header, then before --boundary i added a new line (bytes 0D and 0A at offset 0x9B00 and 0x9B01) and removed two null bytes at the end of the string (at offset 0x9B36 and 0x9B37).
    Control the file size again:
    
    # ls -l streamd
    -rwxr-xr-x 1 root root 43280 Feb  7 12:50 libcgi.so
    
    Same size, perfect.
    Both binary are now fixed.


  • Make squashfs
    Considering I modified these two binary in their location (squashfs-root/bin/ and squashfs-root/lib/), I directly made the squash fs.
    I need to know the Block size of original squashfs:
    
    # ./unsquashfs -s arch.lzma.sqfs 
    Found a valid little endian SQUASHFS 3:1 superblock on arch.lzma.sqfs.
    Creation or last append time Mon Mar  5 03:10:49 2012
    Filesystem is exportable via NFS
    Inodes are compressed
    Data is compressed
    Fragments are compressed
    Check data is not present in the filesystem
    Fragments are present in the filesystem
    Always_use_fragments option is not specified
    Duplicates are removed
    Filesystem size 3532.44 Kbytes (3.45 Mbytes)
    Block size 1048576
    Number of fragments 13
    Number of inodes 670
    Number of uids 6
    Number of gids 0
    

  • Block size is 1048576. This is also a Little endian squashfs.
    So I made the new squash fs.
    
    # wget http://ftp.slax.org/useful-binaries/linux/mksquashfs.gz
    # gunzip mksquashfs.gz
    # chmod 755 mksquashfs
    # cd squashfs-root
    # ../mksquashfs bin/ HTML/ lib/ arch.lzma.sqfs -b 1048576 -le
    Parallel mksquashfs: Using 1 processor
    Creating little endian 3.1 filesystem on arch.lzma.sqfs, block size 1048576.
    lzmadic 1048576
    [===========================================================================|] 537/537 100%
    Exportable Little endian filesystem, data block size 1048576, compressed data, compressed metadata, compressed fragments, duplicates are removed
    lzmadic 1048576
    Filesystem size 3532.46 Kbytes (3.45 Mbytes)
     27.85% of uncompressed filesystem size (12683.66 Kbytes)
    Inode table size 4657 bytes (4.55 Kbytes)
     21.53% of uncompressed inode table size (21630 bytes)
    Directory table size 6132 bytes (5.99 Kbytes)
     53.95% of uncompressed directory table size (11367 bytes)
    Number of duplicate files found 12
    Number of inodes 670
    Number of files 537
    Number of fragments 13
    Number of symbolic links  88
    Number of device nodes 0
    Number of fifo nodes 0
    Number of socket nodes 0
    Number of directories 45
    Number of uids 6
     root (0)
     unknown (503)
     unknown (1009)
     unknown (501)
     unknown (500)
     unknown (502)
    Number of gids 0
    
    Inside squashfs-root I have the new squashfs archive.

  • Replace squashfs
    Now I have to replace the original squashfs archive with the fixed one. (I'm inside squashfs-root folder)
    
    # cp arch.lzma.sqfs ../AppImgCnt/
    
  • New JFFS2
    Inside AppImgCnt there is the firmware completely patched, but I need to create a new jffs image to flash it on the dvr rom.
    This rom has a erase block size of 128k (--eraseblock=0x20000) and is a nand (--no-cleanmarkers) so doesn't need cleanmakers.
    (To make the new jffs2 I installed mtd-utils).
    
    # apt-get install mtd-utils
    # cd AppImgCnt/
    # mkfs.jffs2 -r . -o ../AppImg_fixed.jffs2 --eraseblock=0x20000 --little-endian --no-cleanmarkers
    
  • Outside the folder AppImgCnt there is the patched jffs2 image. I controlled file size to be sure the image can be flashed on the rom.
    
    # ls -l AppImg_4.jffs2 
    -rw-r--r-- 1 root root 10948772 Feb  7 12:10 AppImg_4.jffs2
    # ls -l AppImg_fixed.jffs2 
    -rw-r--r-- 1 root root 10947556 Feb  7 13:15 AppImg_fixed.jffs2
    
    The file size is almost the same, so I shouldn't have any problem flashing it.

    Flashing Firmware

    Now I have a valid firmware, but without a signed header (200 byte I removed from original firmware), so I can't flash it from web interface or usb, but I can try using serial console.
    (This does NOT avoid warranty)
    1. Opening dvr
      To open the dvr I removed six screws, four on the back and the others two on both sides.
      Raised upwards the case and I could see the mother board.

    2. Connecting to UART
      UART scheme:
      To connect to UART port I used my Arduino board to convert it from serial to usb.

      I opened GTKterm, in Configuration > Port I set usbTTY0 as port and 34800 as baud rate.

    3. Boot message
      Booting the dvr, on serial console appears this:
      
      012346
      
      U-Boot 2008.10 (Jul  1 2011 - 16:49:39)
      
      DRAM:  128 MB
      Flash: 16 MB
      Using default environment
      
      flash no default environment
      In:    serial
      Out:   serial
      Err:   serial
      Net:   FTGMAC#0
      flash size=0x01000000
      checking keyboard is exist.. iic type KEYBOARD_ID=00000098
      PLAT_ID=0x81812000(T)
      PLAT_ID_ALL=0x81812141(T)
      boardid=0x27
      version=0x02
      force_output=0x30303030
      Starting probe decoder...
      found NVP1114A
      decoder is nvp1114A
      Set 108Mz Setting..
      check video std: no signal
      
      check video std: no signal
      
      check video std: no signal
      
      check video std: no signal
      
      Set 108Mz Setting..
      Composite Out
      @LCD2 set scaler
      OEMName=OEM:STYLE1_AVD744B
      using IVS bitmap
      Not BMP Type !!
      Not support bmp 65535 Bits Per Pixel !!
      Error bitmap format using default
      bmp using RLE 8-bit/pixel
      rle total length=76802
      clock info:
      PLL1: 810 MHz       PLL2: 750 MHz       PLL3: 648 MHz       DDR: 810 MHz
      CPU : 540 MHz       HCLK: 270 MHz       PCLK: 135 MHz
      UART:  25 MHz       IDE :  81 MHz       PCI :  32 MHz
      MPEG: 162 MHz       H.264 enc: 270 MHz  H.264 dec: 270 MHz
      Scaler: 129 MHz     SSP:  81 MHz    
      
      Press SPACE to abort autoboot in 1 seconds
      
    So if I press space bar a menu is shown containing some options:
    
    Partition plan:
    Name:BootLoader         offset:0x10000000 size:0x00040000  upgrade file:fboot_4.bin
    Name:Initial bitmap     offset:0x10040000 size:0x00020000  upgrade file:custom_logo.bmp
    Name:Factory default    offset:0x100A0000 size:0x00060000  upgrade file:
    Name:Linux System       offset:0x10100000 size:0x00500000  upgrade file:kernel_4.bin
    Name:Application        offset:0x10600000 size:0x00900000  upgrade file:AppImg_4.bin
    Name:XML                offset:0x10F00000 size:0x00100000  upgrade file:xml_4.bin
    Name:FULL               offset:0x10000000 size:0x01000000  upgrade file:full_4.bin
    
    **********************************************
    * AVC793 Please select option...
    *   1 : Kermit Update BootLoader Firmware
    *   2 : Kermit Update Kernel Firmware
    *   3 : Kermit Update Application (AppImg)
    *   4 : Kermit Update XML 
    *   5 : TFTP Configuration
    *   6 : TFTP Update BootLoader Firmware
    *   7 : TFTP Update Kernel Firmware
    *   8 : TFTP Update Application (AppImg)
    *   9 : TFTP Update XML 
    *   0 : TFTP Update Initialize Bitmap
    *   a : TFTP Update FullInOne Image
    *   b : Dump to MEM & Execute Use TFTP
    *   c : Reboot
    *   d : Start Linux
    *   r : Reset Default
    *   u : USB Upgrade
    *   v : Show fw version info
    **********************************************
    avtech>
    
    The number 8 allow tftp flash of AppImg


  • Setup TFTP server
    I installed and started a tftp server on my pc
    
    apt-get install tftp
    tftp start

  • I moved the new jffs2 image (not signed) in the shared tftp folder.


  • TFTP flash
    I connect the LAN to the dvr and pressed 8.
    It asked me three things:
    • Host ip (dvr)
    • Server ip (pc running tftp)
    • File name (fixed jffs2 image)
    The image will be downloaded from pc to the dvr, then is checked the integrity of the file and is asked if you want to flash, answer yes.
    
    Tftp upgrade Application
    
    HSOT IP 192.168.5.153: 192.168.2.10
    SERVER IP 192.168.5.152: 192.168.2.15
    File name AppImg_4.bin: AppImg_fixed.jffs2
    Using eth0 device
    TFTP from server 192.168.2.15; our IP address is 192.168.2.10
    Filename 'AppImg_fixed.jffs2'.
    Load address: 0x2000000
    Loading: t RD_REQ, file: AppImg_fixed.jffs2
    #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #################################################################
             #####################
    done
    Bytes transferred = 10947556 (a70be4 hex)
    upgrade(y/n):y
    
    upgrade fw ...
    upgrade Application
    erase 0x10600000 +0x00900000
    
    ........................................................................ done
    Erased 72 sectors
    cp.b  0x02000000 0x10600000 $(filesize)
    Copy to Flash... done
    filesize=10947556
    

  • DO NOT POWER OFF the dvr while it is writing blocks in memory or it will result in a bad flash.
    (Anyway is possible to flash again with tftp, the only dangerous thing to flash is u-boot, if the power is removed while it's flashing the bootloader, is very easy to break the dvr!)
    I pressed "d" to boot the dvr after flash.

    Testing New Firmware

    Now if I try to open the MJPEG streaming on my iPhone I can see again my cameras!



    Adding features and easy flash

    In this new article I show how to add features to the custom firmware and flash it form usb as an official update.


    14 commenti:

    1. Great post! I'd like to try to modify some of the firmware on my DVR, but I don't have much experience connecting to a board using JTAG. Can you explain exactly how you did that? Thanks a lot.

      RispondiElimina
      Risposte
      1. Thank you!
        I added some picture, tell me if you have any problem.

        Elimina
    2. I have used your post to guide me in my project to enable ssh of avtech kdp677, Do you have any advice?

      Thank you

      RispondiElimina
      Risposte
      1. Yes, root password is avtech97 ;) enjoy
        Shortly i'll publish two new articles about creating custom firmware with additional features and exploiting the webserver

        Elimina
    3. Thank you for the info, I wish to inform you with my progress and notes , I have bought the UART and connected successfully to the DVR, I couldn't connect from VMWARE ubuntu , So i was forced to use windows 10, I have used putty to get into the Uboot menu, My only note is that I have used 115200, 38400 gave me garbage output, I have made simple analysis to the bin file using Binwalk , I hope i could make the necessary modifications to enable telnet and be able to upload it via FTP. My linux info isn't that great but i will catch up.

      RispondiElimina
      Risposte
      1. All you want to do is possible and i have already a custom firmware with all this things
        If you have patience to wait about a week or two I'll publish an article about creating and signig firmware, so you can install custom firmware without using uart any more, but simply upgrading from usb ;)

        Elimina
    4. I will wait for your tutorial , mean while I have one question , do i have an option to backup all partitions before uploading files, I am afraid my lack of experience could corrupt U-Boot partition and end up with a bricked DVR.

      RispondiElimina
      Risposte
      1. Flashing only the AppImg you can always flash again from uboot

        Elimina
    5. I have been analyzing the binary files but i think my root password isn't like yours, I have initiated john the ripper against it and gave john my guess words from avtech1 to avtech99999 and it didn't find the right hash, I am trying to run john again and hopefully it will crack it in reasonable time. Or if i reached a point that i could change the hash and repack the binary file i could reset it , right?

      RispondiElimina
      Risposte
      1. You can log in as root with an high privileged account too
        For example, reset the dvr and log in with user: admin and password: admin

        Elimina
    6. plz tell me how flash this using windows? i have av tech 16ch dvr but no video vga and BNC i want to flash it. help me

      RispondiElimina
      Risposte
      1. You can prepare the usb drive like in my second article (http://orangesec.blogspot.it/2016/02/avtech-dvr-custom-firmware-adding.html) and then flash during hard reset (http://soporte.tvc.mx/Ingenieria/BaseDeConocimiento/Hardware%20Reset/KPD677l_Hardware_Reset.pdf) (insert the usb in the lower usb port)

        Elimina
    7. Hi,
      May i know the linux distro type of this dvr

      RispondiElimina
      Risposte
      1. It is embedded, so it is an "ad hoc" distro
        Anyway I hope this can help you:
        # uname -a
        Linux avtech 2.6.14 #457 PREEMPT Thu Nov 18 14:07:16 CST 2010 armv5tel GNU/Linux
        # cat /proc/version
        Linux version 2.6.14 (root@debianaa) (gcc version 3.4.4) #457 PREEMPT Thu Nov 18 14:07:16 CST 2010

        Elimina