Talos Vulnerability Report

TALOS-2018-0699

Novatek NT9665X XML_UploadFile WifiCmd denial-of-service vulnerability

May 13, 2019
CVE Number

CVE-2018-4027

Summary

An exploitable denial-of-service vulnerability exists in the XML_UploadFile Wi-Fi command of the NT9665X Chipset firmware, running on the Anker Roav A1 Dashcam, version “RoavA1_SW_V1.9.” A specially crafted packet can cause a semaphore deadlock, which prevents the device from receiving any physical or network inputs. An attacker can send a specially crafted packet to trigger this vulnerability.

Tested Versions

Anker Roav A1 Dashcam RoavA1_SW_V1.9

Product URLs

https://goroav.com/products/roav-dash-cam-a1

CVSSv3 Score

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

CWE

CWE-833: Deadlock

Details

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 that 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 “Roav_A1_” SSID, with the default password of “goroavcam”.

From here, the app interacts mainly with the dashboard camera 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 by accessing http://192.168.1.254/?custom=1&cmd=3012.

For the following vulnerability, the XML_UploadFile command (5001) will be discussed. Like all the other Wi-Fi commands, the prototype for this function is:

XML_UploadFile(char *URLPath,
char *QueryStr,
char *POSTData,
size_t PostDataLen,
u_int32 SegmentCount,
u_int32 putStatus);

Stepping back before the call to XML_UploadFile, there’s two functions that handle the setup and calling of a given Wi-Fi opcode: WifiCmd_PutData and WifiCmd_DispatchCmd, the former of which calls the latter, the latter of which sometimes waits for the WifiCmd to finish before returning.

This particular vulnerability focuses on the WifiCmd_PutData in combination with XML_UploadFile, as this particular command seems to be the only WifiCmd that causes a specific code path inside of WifiCmd_PutData to occur.

The following function is called inside of WifiCmd_PutData before any processing is done on the HTTP request:

ROM:80356E10 loc_80356E10:   # CODE XREF: WifiCmd_PutData+AC↑j
ROM:80356E10                 jal     WifiCmd_Lock
ROM:80356E14                 nop
ROM:80356E18                 j       loc_80356C94
ROM:80356E1C                 nop

The WifiCmd_Lock, aptly named, locks the global WIFICMD_SEM_ID semaphore, such that only one thread can access the WiFi commands at any given time. As such, when calling the XML_UploadFile function via a POST command to http://192.168.1.254/?custom=1&cmd=5001, the following log messages are displayed over UART:

WifiCmd_PutData(): path =/sdcard, argument = custom=1&cmd=5001, bufAddr = 0x80d430cc, bufSize =0x1 , segmentCount  =0 , putStatus = 0
WifiCmd_Lock(): Lock   //[0]
WifiCmd_DispatchCmd(): cmd:5001 evt:0 par:0 CB:80221be8 wait:0
WifiCmd_DispatchCmd(): ret 0  // [1]
USB 1.5A
CONNECT to CHARGER
uiUsage=1
CHK: 2306, XML_UploadFile
CHK: 2314, XML_UploadFile	 
CHK: 2330, XML_UploadFile  //[2]

At [0] we can see that the WifiCmd_Lock is correctly locked, and at [1] that the program did in fact spawn a new thread for the XML_UploadFile function. At [2], we hit a log message that indicates a file was opened for writing:

lw      $a0, 0x80658D28
move    $a1, $s3
addiu   $a2, $fp, 0xE8+tos_4
move    $a3, $zero
jal     FileSys_WriteFile  
sw      $zero, 0xE8+tos($sp)
lui     $a0, 0x8046
lui     $a2, 0x8047
la      $a0, a37mchkDS0m  # "\x1B[37mCHK: %d, %s\x1B[0m\r\n"
la      $a2, aXmlUploadfile  # "XML_UploadFile"
jal     Do_Logging
li      $a1, 2330        //[3]
j       loc_80221CE4     
li      $v0, 1

At [3], we see the 2330 for the CHK value printed above, assuring that this is indeed the right code path. From here, if the provided request does not provide a “par=%d” HTTP query parameter, then the function returns 0x0 back to WifiCmd_PutData() at the following disassembly:

ROM:80356D34   sw      $s6, 0x68+tos($sp)
ROM:80356D38   sw      $s7, 0x68+tos_4($sp)
ROM:80356D3C   move    $a0, $s3
ROM:80356D40   move    $a2, $s4
ROM:80356D44   jalr    $v0         //[4] #WifiCmd_Funcptr
ROM:80356D48   move    $a3, $s5
ROM:80356D4C   li      $v1, 1
ROM:80356D50   beq     $s7, $v1, loc_80356E20  //[5]
ROM:80356D54   nop                      

The call at [4] is the WifiCmd that was just returned from, and the $s7 value that the code flow depends on at [5] is equal to 0x0, which causes the comparison to fail, and we reach the last basic block of the function which returns. To finally get around to where the vulnerability lies, let us examine the branch we did not take:

ROM:80356E20 loc_80356E20:  # CODE XREF: WifiCmd_PutData+170↑j
ROM:80356E20    jal     WifiCmd_Unlock   //[6]

The path we take does not reach [6], and as such the WIFICMD_SEM_ID is never unlocked, thus all other Wi-Fi commands are disabled at this point until the device reboots. Note, however, that the Wi-Fi can still be reset via the buttons on the device, allowing normal operations to resume.

More severely, if five more Wi-Fi requests of any type are sent to the device, the following can be seen via the ker dump command over UART:

Semaphore[125,       WIFICMD_SEM_ID] -> * (Max: 01, Cur: 00)
Waiting Task Queue:
  eCos Thread[261, Hfs Session]
  eCos Thread[262, Hfs Session]
  eCos Thread[264, Hfs Session]
  eCos Thread[266, Hfs Session]
  eCos Thread[268, Hfs Session]
  eCos Thread[270, Hfs Session]


eCos Thread[261,       Hfs Session] ->  Priority: 20
State:      SLEEP, Stack Base: 0x80D3CE94 (Stack Size:  24576, Stack Used:   3200)

eCos Thread[260,   hfs data thread] ->  Priority: 20
State:      SLEEP, Stack Base: 0x80D56D20 (Stack Size:   8192, Stack Used:    380)

[…] 

The device seems to lock up immediately as six new threads are contending for the WIFICMD_SEM_ID, which will never unlock. At this point, any subsequent HTTP communication results in the error message ERR: Exceed max clients 6, moreover, any and all button presses are ignored, even the power button.

Additionally, while the device still does record valid video to the SD card, it is possible to completely disable the device until the battery runs out.

Log Output

WifiCmd_PutData(): path =/sdcard, argument = custom=1&cmd=5001, bufAddr = 0x80d430cc, bufSize =0x1 , segmentCount  =0 , putStatus = 0
WifiCmd_Lock(): Lock
WifiCmd_DispatchCmd(): cmd:5001 evt:0 par:0 CB:80221be8 wait:0
WifiCmd_DispatchCmd(): ret 0
USB 1.5A
CONNECT to CHARGER
uiUsage=1
CHK: 2306, XML_UploadFile
CHK: 2314, XML_UploadFile
CHK: 2330, XML_UploadFile
UINet_DhcpCb(): REQUEST_IP 128487 ms
WifiCmd_PutData(): path =/sdcard, argument = custom=1&cmd=5001, bufAddr = 0x80d4306c, bufSize =0x1 , segmentCount  =0 , putStatus = 0
WifiCmd_Lock(): Lock
WifiCmd_PutData(): path =/sdcard, argument = custom=1&cmd=5001, bufAddr = 0x80d5f0cc, bufSize =0x1 , segmentCount  =0 , putStatus = 0
WifiCmd_Lock(): Lock
WifiCmd_PutData(): path =/sdcard, argument = custom=1&cmd=5001, bufAddr = 0x80d7b0cc, bufSize =0x1 , segmentCount  =0 , putStatus = 0
WifiCmd_Lock(): Lock
UINet_DhcpCb(): REQUEST_IP 143546 ms
WifiCmd_PutData(): path =/sdcard, argument = custom=1&cmd=5001, bufAddr = 0x80d970cc, bufSize =0x1 , segmentCount  =0 , putStatus = 0
WifiCmd_Lock(): Lock
WifiCmd_PutData(): path =/sdcard, argument = custom=1&cmd=5001, bufAddr = 0x80db30cc, bufSize =0x1 , segmentCount  =0 , putStatus = 0
WifiCmd_Lock(): Lock
WifiCmd_PutData(): path =/sdcard, argument = custom=1&cmd=5001, bufAddr = 0x80dcf0cc, bufSize =0x1 , segmentCount  =0 , putStatus = 0
WifiCmd_Lock(): Lock
ERR: Exceed max clients 6

Timeline

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

Credit

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