Talos Vulnerability Report

TALOS-2018-0701

Novatek NT9665X HFS Recv buffer overflow code execution vulnerability

May 13, 2019

CVE Number

CVE-2018-4029

Summary

An exploitable code execution vulnerability exists in the HTTP request-parsing function of the NT9665X Chipset firmware running on the Anker Roav A1 Dashcam, version "RoavA1SWV1.9.” A specially crafted packet can cause an unlimited and arbitrary write to memory, resulting in code execution.

Tested Versions

Anker Roav A1 Dashcam RoavA1SWV1.9

Product URLs

CVSSv3 Score

CWE

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 “RoavA1” 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. Regardless of the request, when reading any HTTP request, a function dubbed parse_http is used to start parsing out the individual pieces of the request.

Before the parsing even occurs though, the function will set some socket options on the client socket, turning it into a non-blocking socket. After this, it will start receiving the HTTP request in 0x800 bytes chunks. The code that handles this is as such:

At [0], the destination write address of the recv_or_select call is calculated from the $a0 HTTP session struct passed into parse_http function, which is now in $s0 (at [1]), and adding 0x6268, presumably to point to an empty buffer. At [2], the source of the recv_or_select call is calculated by just passing in the HTTP session struct itself, which gets further parsed inside. Finally at [3], we can see the length of the read is always 0x800 bytes. The function will return the amount of bytes read, which in our case will always be 0x800, and since it is not returning less than 0x0, we end up taking the branch at [4] to the find_0x0a0d0a0d portion of the code:

In short, the above code will search only for the bytes "\x0a\x0d\x0a\x0d" or more familiarly, "\r\n\r\n", denoting the end of the HTTP request (assuming there’s no POST data). Upon finding this delimiter, the program will start to parse the request for the different HTTP verbs. However, if there isn't a "\r\n\r\n" within the length 0x800 buffer that we read, we go to the recv_loop basic block and the following occurs instead:

As $v0 still contains the return value from the recv_or_select function, $s4 gets incremented by the number of bytes read, in this case, 0x800. This causes the destination of the write to be increased by 0x800, and the process repeats again:

Note that there is no exit condition for this recv_loop, unless 0x0 bytes are read or a "\r\n\r\n" string is found, which leads to a traditional buffer overflow. In this situation, there’s an unusual circumstance, as for the first iteration of this loop, the parameters to recv_or_select are:

$a0 : 0x80d3cdc0
$a1 : 0x80d43028
$a2 : 0x00000800
$a3 : 0x00000001

But, if the HTTP buffer is greater than ~0x11044 bytes, a crash will occur inside of the recv_or_select function, as the source address of a memcpy call inside of the recv_or_select function is a user-controlled value, and the destination buffer is 0x11044 bytes further along than the first iteration, sitting at 0x80d5406c. To understand why the source address gets overwritten, it is necessary to delve into the crashing memcpy inside of the recv_or_select function:

At [0], the user-controlled source address is loaded into $a1 from an offset into the http_struct that was passed into the function as $a0.

At [1], $v1 is set to 0x0, and really isn't important, and at [2], the length of the memcpy is the value that was passed in as $a2 to this function (in our case, this is once again 0x800). At [3], we add the offset (0x0) to the destination of the write, which was the $a1 parameter passed into this function.

With all this in mind, the current running theory is that the recv_loop will eventually overwrite the pointer to the input buffer of the http_session, causing the subsequent memcpy to read from a user-controlled address. It should be noted that this vulnerability was researched without a debugger due to time constraints, so this might not be completely accurate. Regardless of the accuracy of the root cause, the end result is still an unlimited and arbitrary write to memory.