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.
Anker Roav A1 Dashcam RoavA1SWV1.9
10.0 - CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H
CWE-120: Buffer Copy without Checking Size of Input ('Classic Buffer Overflow')
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:
803CBBB0 recv_block: # CODE XREF: parse_http_0:recv_loop↓j 803CBBB0 addiu $s2, $s4, 0x6268 // 803CBBB4 addu $s2, $s0, $s2 // 803CBBB8 move $a0, $s0 // 803CBBBC move $a1, $s2 803CBBC0 li $a2, 0x800 // 803CBBC4 jal recv_or_select ;(http_sess,dst,len,0x1,???,???,???) 803CBBC8 li $a3, 1 803CBBCC beqzl $v0, path_to_ret 803CBBD0 lui $s1, 1 803CBBD4 beq $v0, $s6, error__ 803CBBD8 addiu $s2, 1 803CBBDC j find_0x0a0d0a0d // 803CBBE0 move $v1, $v0
At , 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 ), and adding 0x6268, presumably to point to an empty buffer. At , 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 , 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  to the
find_0x0a0d0a0d portion of the code:
803CBBE4 inc_search_ptr: 803CBBE4 # parse_http_0+148↓j ... 803CBBE4 beqz $v1, recv_loop # (diff => ~0x6a31) 803CBBE8 addiu $s2, 1 # $s2 => 0x80d43829 803CBBEC find_0x0a0d0a0d: 803CBBEC lbu $a0, -1($s2) # find \x0d 803CBBF0 bne $a0, $s1, inc_seach_ptr 803CBBF4 addiu $v1, -1 803CBBF8 lbu $a0, 0($s2) # find \x0a 803CBBFC bne $a0, $s3, inc_seach_ptr 803CBC00 nop 803CBC04 lbu $a0, 1($s2) # find \x0d 803CBC08 bne $a0, $s1, inc_seach_ptr 803CBC0C nop 803CBC10 lbu $a0, 2($s2) # find \x0a 803CBC14 bne $a0, $s3, inc_seach_ptr 803CBC18 move $a1, $zero
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:
803CBD7C recv_loop: # CODE XREF: parse_http_0:loc_803CBBE4↑j 803CBD7C j recv_block 803CBD80 addu $s4, $v0 # $v0 => 0x800 //
$v0 still contains the return value from the
$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:
803CBBB0 recv_block: # CODE XREF: parse_http_0:recv_loop↓j 803CBBB0 addiu $s2, $s4, 0x6268 803CBBB4 addu $s2, $s0, $s2 803CBBB8 move $a0, $s0 803CBBBC move $a1, $s2 803CBBC0 li $a2, 0x800 803CBBC4 jal recv_or_select ;(http_sess,dst,len,0x1,???,???,???) 803CBBC8 li $a3, 1
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
$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
803CAB0C loc_803CAB0C: # CODE XREF: recv_or_select+A8↑j 803CAB0C lw $a1, 0x6F60($v1) // 803CAB10 lw $v1, 0x88+offset($sp) // 803CAB14 lw $a2, 0x88+memcpy_len($sp) // 803CAB18 jal memcpy # (dst,src,len) 803CAB1C addu $a0, $v0, $v1 # //
At , the user-controlled source address is loaded into
$a1 from an offset into the
http_struct that was passed into the function as
$v1 is set to 0x0, and really isn't important, and at , 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 , 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.
*** CPU Exception!!! cause 0x02: TLB exception (load or instruction fetch) epc - 0x800b5e10 $ra - 0x803cab20 $sp - 0x80d424d0 $fp - 0x80d424ec general registers: $zero : 0x00000000 $at : 0x00000001 $v0 : 0x80d5406c $v1 : 0x00000000 $a0 : 0x80d5406c $a1 : 0x61616161 $a2 : 0x00000000 $a3 : 0x61616961 $t0 : 0x00000008 $t1 : 0x01010101 $t2 : 0x00000000 $t3 : 0x61616765 $t4 : 0x80aed924 $t5 : 0x800a0000 $t6 : 0x00000000 $t7 : 0x807d0000 $s0 : 0x80d424f4 $s1 : 0x61616161 $s2 : 0x80d424e8 $s3 : 0x80d42514 $s4 : 0x80afe8b8 $s5 : 0x80d3cd60 $s6 : 0x00000001 $s7 : 0x0007a120 $t8 : 0x8089ad00 $t9 : 0x8060f540 null : 0x00000800 null : 0x80d5406c gp : 0x8060f540 sp : 0x80d424d0 fp : 0x80d424ec ra : 0x803cab20 co-processor registers: entrylo : 0x00000001 status : 0x00000008 vector : 0x0100c403 epc : 0x800b5e10 cause : 0x00000000 badvaddr : 0x00800008 hwrena : 0x00000400 prid : 0x00019655 entrylo : 0x01605792 Thread(id) : Hfs Session(260) stack : range(0x80d3ce94 - 0x80d42e94) call stack : 0 frame(0x80d424d0 - 0x80d424e8) ............................ $pc : 0x800b5e10 + 0x80d424d0 : 0x80d53e6a 0x0000000a 0x000108a4 0x00000008 + 0x80d424e0 : 0xffffffff 0x05040017 : abort ($pc 0504000f is invalid address!) *** CPU Exception in Task! cause=0x00000002, addr=0x800b5e10
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.