Talos Vulnerability Report


Novatek NT9665X XML_GetThumbNail denial-of-service vulnerability

May 13, 2019
CVE Number



An exploitable denial-of-service vulnerability exists in the thumbnail display functionality of the NT9665X Chipset firmware, running on the Anker Roav A1 Dashcam, version "RoavA1SWV1.9". A specially crafted packet can cause a null pointer dereference, resulting in a device reboot.

Tested Versions

Anker Roav A1 Dashcam RoavA1SWV1.9

Product URLs


CVSSv3 Score

7.5 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H


CWE-476: NULL Pointer Dereference


The Novatek NT9665X SOC is a chipset used in an large number of consumer camera devices, particularly in dashboard cameras. The chip provides default firmware that is a fork of the Embedded Configurable Operating System (eCOS) project, which is found within the Roav A1 Dashcam,the product we are focusing on in this advisory.

The Roav A1 Dashcam by Anker is a dashboard camera that allows users to connect using the Roav app for Android and iOS so they can control the camera remotely. In order to do this, users must first enable the “Wi-Fi AP” setting manually on the dashcam, and then connect to the “RoavA1” SSID, with the default password “goroavcam”.

From here, the app interacts with the dashcam mainly via an eCOS web server running on port 80 that requires no authentication. The standard HTTP POST, GET and DELETE requests can be used to upload, download, or delete videos and pictures from the dashcam, but there’s also a separate interface used for configuration. When requesting any URL, a set of commands is accessed by providing the following HTTP query string: ?custom=1&cmd=<0000-9999>. It should be noted that only a firmware-specific subset of commands are implemented on any given device, the list of which can be found here:

This specific vulnerability covers the XML_GetThumbNail command. This particular command is not just found in the Roav line of dashcams, but seems to be a default command in the Novatek NT966XX firmware. Note, however, that while this command shows up in all the NT966XX devices, the device in question might not be vulnerable, as certain devices (e.g. The SJ6 Camera w/ NT96660 chipset) could not be tested.

When the Roav A1 asks for a given file’s thumbnail, it parses the HTTP paths for the following file extensions first: “.MOV”,”.mov”,”.MP4”,”.mp4.” In the case that the file requested ends with one of these strings, the code path handles the situation correctly, and the given crash does not occur. Also, if the HTTP path does not contain a ‘.’ character, the code will return a default response, avoiding the vulnerability.

For all other files of various extensions, if the file exists, it reaches the following code block:

ROM:80224BD4    lui     $s6, 1
ROM:80224BD8    lui     $a0, 1
ROM:80224BDC    sw      $zero, 0x1C0+toc_8($fp)
ROM:80224BE0    sw      $zero, 0x1C0+var_1A0($fp)
ROM:80224BE4    jal     get_movie_temp_buff //[0]
                    // => Movie_GetTempBuffer
ROM:80224BE8    sw      $s6, 0x1C0+toc_4($fp) 
ROM:80224BEC    move    $a1, $v0    
ROM:80224BF0    addiu   $a2, $fp, 0x1C0+toc_4
ROM:80224BF4    move    $a3, $zero
ROM:80224BF8    move    $a0, $s0        
ROM:80224BFC    sw      $zero, 0x1C0+toc($sp)
ROM:80224C00    jal     FileSys_ReadFile  //[1]
# (fd,dst,size,NULL,NULL) 
ROM:80224C04    move    $s7, $v0
ROM:80224C08    jal     FileSys_CloseFile //[2]
ROM:80224C0C    move    $a0, $s0
ROM:80224C10    move    $a0, $zero
ROM:80224C14    addiu   $a1, $fp, 0x1C0+var_198
ROM:80224C18    sw      $s6, 0x1C0+var_194($fp)
ROM:80224C1C    jal     Read_EXIF         //[3]
ROM:80224C20    sw      $s7, 0x1C0+var_198($fp)
ROM:80224C24    bnez    $v0, loc_80225004

In summary, we grab a temporary buffer using a function pointer inside of [0] that calls the Movie_GetTempBuffer function, and then at [1] we read the open file descriptor (of the file found in the HTTP request) and put the bytes into our Movie_TempBuffer. The open file is then closed at [2] and parsed for EXIF data at [3], which tries to look for a thumbnail.

In our case, the call to Read_EXIF will fail if there’s no EXIF data, causing a branch to a new case, where it repeats the above disassembly (Movie_GetTempBuffer => FileSys_OpenFile => FileSys_ReadFile => FileSys_CloseFile), except instead of looking for EXIF data, the server will try to parse out thumbnail information using the GxImgFile_ParseHeader function instead.

GxImgFile_ParseHeader takes two parameters, the first of which is a pointer to a struct on the stack of the parent function, defined as:

struct {
    0x0: char *file_buff;
    0x4: unsigned int file_size;

Assuming that the address of the file_buff plus the file_size does not cause an overflow/wrap-around (which would cause an error case), the following function will be called with our file_buff as the only parameter:

    ROM:80085508 var_4  = -4
    ROM:80085508  addiu   $sp, -8
    ROM:8008550C  lw      $v0, 0($a0)      //[4]
    ROM:80085510  sw      $fp, 8+var_4($sp)
    ROM:80085514  move    $fp, $sp
    ROM:80085518  move    $sp, $fp
    ROM:8008551C  addiu   $v1, $v0, 1      
    ROM:80085520  lw      $fp, 8+var_4($sp)  
    ROM:80085524  lbu     $v0, 0($v0)      //[5]
    ROM:80085528  sw      $v1, 0($a0)
    ROM:8008552C  jr      $ra
    ROM:80085530  addiu   $sp, 8

At [4], the first dereference is performed on the stack address pointing to the GXImgFile structure, and then at [5], a second dereference is performed, which will provide the buffer from Movie_GetTempBuffer.

As we can see, there’s never any error checking on this particular code flow, and if the device is not in “Movie mode” (which can be toggled by sending wificmd 3001), Movie_GetTempBuffer will actually return a null buffer, causing the instruction at [5] to dereference a null pointer, crashing the process and device.

It should be noted that while the null buffer is actually checked for on multiple occasions (as noted by the "Crash Output" log messages), none of the checks stop or redirect the code flow.

Crash Output

WifiCmd_GetData(): Data2 path = asdf.JPG, argument -> custom=1&cmd=4001, mimeType= text/xml, bufsize= 66560, segmentCount= 0
WifiCmd_Lock(): Lock
WifiCmd_DispatchCmd(): cmd:4001 evt:0 par:0 CB:802249fc wait:0
WifiCmd_DispatchCmd(): ret 0
ERR:Movie_GetTempBuffer() get buffer fail
ERR:FileSys_ReadFile() ReadFile to NULL buffer
ERR:graph_flushAopCache() Invalid image A addr: 0x0
ERR:graph_flushAopCache() Invalid image A addr: 0xf000
WRN:ExifR_ReadExif() Appx marker not found! No APP0 or APP1
ERR:Movie_GetTempBuffer() get buffer fail
ERR:FileSys_ReadFile() ReadFile to NULL buffer
*** CPU Exception!!! cause 0x02: TLB exception (load or instruction fetch)
epc  - 0x80085524
$ra  - 0x80327b7c
$sp  - 0x80d42358
$fp  - 0x80d42360
general registers:
     $zero : 0x807d0000       $at : 0xb0290000       $v0 : 0x00000000       $v1 : 0x00000001
       $a0 : 0x80d42370       $a1 : 0x80d42458       $a2 : 0x80d42458       $a3 : 0x00000008
       $t0 : 0x00000008       $t1 : 0x01010101       $t2 : 0x80d3cf74       $t3 : 0x8060f5b0
       $t4 : 0x80d422c8       $t5 : 0x80d422c8       $t6 : 0x8001652c       $t7 : 0x0000cccc
       $s0 : 0x80d42428       $s1 : 0x000000ff       $s2 : 0x80d42370       $s3 : 0x00000000
       $s4 : 0x80d42458       $s5 : 0x00000000       $s6 : 0x00000000       $s7 : 0x00000000
       $t8 : 0x0100c403       $t9 : 0xdeadbeef      null : 0xdeadbeef      null : 0xdad94150
        gp : 0x8060f5b0        sp : 0x80d42358        fp : 0x80d42360        ra : 0x80327b7c
co-processor registers:
   entrylo : 0xcccd0000    status : 0x00000008    vector : 0x0100c403       epc : 0x80085524
     cause : 0x00000000  badvaddr : 0x00800008    hwrena : 0x00000400      prid : 0x00019655
   entrylo : 0x001de850
Thread(id) :

  Hfs Session(458)
stack      :
    range(0x80d3cf54 - 0x80d42f54)
call stack :
  0 frame(0x80d42358 - 0x80d42360) ............................ $pc : 0x80085524
     + 0x80d42350 :                       0x00000039 0x80d42360
  1 frame(0x80d42368 - 0x80d423e0) ............................ $pc : 0x80327b74
     + 0x80d42360 :                       0x0000005c 0x800f3840
     + 0x80d42370 : 0x00000000 0x80d43088 0x80d42684 0x80d42480
     + 0x80d42380 : 0x00000000 0x80000000 0x806b58e0 0x802ffc1c
     + 0x80d42390 : 0x00000000 0x00000000 0x00000000 0x80d423b8
     + 0x80d423a0 : 0x80d423a8 0x8030057c 0x80d423b0 0x801e538c
     + 0x80d423b0 : 0x80d42428 0x80d42650 0x80d424e4 0x00010200
     + 0x80d423c0 : 0x80d43088 0x80d42684 0x80d42480 0x00000000
     + 0x80d423d0 : 0x80d423d8 0x80225094 0xb0290000 0x80677548
^[[31m    :
  abort ($pc 00007520 is invalid address!)


2018-10-29 - Talos contacts vendor
2018-11-02 - Report disclosed to vendor
2018-12-04 - 30 day follow up
2019-01-18 - 60 day follow up - Talos reaches out to TWNCERT for assistance reaching vendor (Novatek)>br> 2019-01-22 - TWNCERT contacted Novatek and advised Novatek will check emails for reports
2019-03-06 - 90+ day follow up - Talos asks TWNCERT for direct point of contact for Novatek
2019-03-27 - Talos sends follow up to TWNCERT
2019-04-02 - Talos sends copies of email correspondence and reports to TWNCERT
2019-04-18 - Suggested pubic disclosure date of 2019-05-13 (171 days after initial disclosure)
2019-04-19 - Vendor fixed issue and provided patch to their IDH
2019-05-13 - Public disclosure


Discovered by Lilith [<_<] of Cisco Talos.