Talos Vulnerability Report

TALOS-2018-0566

Yi Technology Home Camera 27US Firmware Downgrade Vulnerability

October 31, 2018
CVE Number

CVE-2018-3891

Summary

An exploitable firmware downgrade vulnerability exists in the firmware update functionality of Yi Home Camera 27US 1.8.7.0D. A specially crafted file can cause a logic flaw, resulting in a firmware downgrade. An attacker can insert an SD card to trigger this vulnerability.

Tested Versions

Yi Technology Home Camera 27US 1.8.7.0D

Product URLs

https://www.yitechnology.com

CVSSv3 Score

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

CWE

CWE-20: Improper Input Validation

Details

Yi Home Camera is an IoT home camera sold globally. The 27US version is one of the newer models sold in the U.S., and is the most basic model out of the Yi Technology camera lineup. It still, however, includes all the functionality that one would expect from an IOT device: viewing from anywhere, offline and subscription based cloud storage, and ease of setup.

When updating the firmware of the Yi Home Camera, there are two options: OTA or via a microSD card inserted physically into the device. Directions for this process can be found at http://www.yitechnology.com/Public/index/file/FirmwareUpdate.pdf, and the firmware files themselves are available at https://www.yitechnology.com/yi-720p-home-firmware. For the 27US version, the tar archive contains two files, home and home_y18m, but it only actually updates with the home_y18m, which will be the main subject of this writeup. Please note, this issue is referring to a local update only.

While the home file is a simple Uboot image and subsequent JFFS2 filesystem, the home_y18m firmware file is a lot more opaque:

 Len         |  Type |  Label    |  Example (if applicable)
(bytes)     |           |               |
 ___________________________________________
| 22 bytes | String | Version |  1.8.7.0D_201708091510 |
------------------------------------------------------------------
| 1344       | Bytes | enc_key|  N/A		                 |
-------------------------------------------------------------------
| Leftover | Bytes | 7z Part  |  N/A 		                 |
 	-------------------------------------------------------------------

During the power-on stage, the following code in /home/base/init.sh runs:

if [ -f /tmp/sd/home_y18m ]; then   //[1] 
	  dd if=/tmp/sd/home_y18m of=/tmp/newver bs=22 count=1      
	  newver=$(cat /tmp/newver)
	  curver=$(cat /home/homever)
	  if [ $newver != $curver ]; then    //[2] 
    	     insmod /home/base/mmz.ko mmz=anonymous,0,0x81600000,10M anony=1 || report_error 	//[3]
    	     insmod /home/base/hi_cipher.ko  //[4]
    	     mkdir /tmp/update
    	     cp -rf /home/base/tools/extpkg.sh /tmp/update/extpkg.sh
    	     /tmp/update/extpkg.sh /tmp/sd/home_y18m    //[5]

Which at [1] checks to see if the /tmp/sd/home_y18m file exits on the SD card root directory. If it exists, then it looks at the Version string inside the firmware image, and compares it to the current firmware version at [2]. The kernel modules loaded at [3] and [4] will become relevant soon, but to continue, the actual firmware unpacking occurs in [5], when the /tmp/update/extpkg.sh script is run, which will be broken down now:

[...]
dd if=home_y18m of=ver bs=22 count=1
dd if=home_y18m of=home1 bs=22 skip=1 count=999999999999999
rm home_y18m
dd if=home1 of=enc_key bs=1344 count=1
dd if=home1 of=home2 bs=1344 skip=1 count=99999999999999
rm home1

At first, the script will extract the portions of the firmware as listed above, ver,enc_key, and the rest as home2. The interesting things are what happen next though:

/tmp/update/rsa_pub_dec enc_key    		// [1] 
cat dec_enc_key home2 > home3		// [2] 
 	rm home2
dd if=home3 of=home4 bs=66 skip=1 count=99999999 
dd if=home3 of=md5 bs=33 count=1                // [3]
dd if=home3 of=key bs=33 skip=1 count=1    // [4] 
dd if=home4 of=verkey bs=22 count=1          // [5]
rm home3
dd if=home4 of=home_v200.7z bs=22 skip=1 count=9999999999999   //[6] 

At [1], the enc_key is decrypted by a binary, presumably doing RSA decryption with a pub_key file that was copied into the directory earlier. The result of this decryption is placed in dec_enc_key, which gets prepended to home2 and thrown into home3. The first 33 bytes [3] of home3 are the md5sum of the firmware, and then the next 33 bytes [4] are the password used to decrypt a .7z archive, which comes from home4. Finally, the first 22 bytes [5] of home4 contain a version string used for version comparison.

The assorted files are then read into variables and checked as listed below:

key=$(cat key)
nokeyver=$(cat ver)
keyver=$(cat verkey)
oldver=$(cat /home/homever)

if [ $keyver -ne $nokeyver ];then //[1]
    rm $1
 	    sync
    exit
else
    echo same ver
fi

if [ $keyver -ge $oldver ];then     //[2]
    rm /home/home_y18m
    sync
    exit
else
    echo new ver update
fi

Curiously, at [1] and [2], the “-ne” and “-ge” operators are used, which are designated for integer comparisons, not for string comparisons, and as such, since $nokeyver, $keyver, and $oldver will contain something similar to “1.8.7.0D_201708091510”, these comparisons will all error out, and the version checks will never occur, causing the firmware upgrade to proceed with a older version of the firmware.

It should be reiterated that the parent script /home/base/init.sh does contain a version check is correctly formed:

	  dd if=/tmp/sd/home_y18m of=/tmp/newver bs=22 count=1      
	  newver=$(cat /tmp/newver)
	  curver=$(cat /home/homever)
	  if [ $newver != $curver ]; then    //[2] 

However, it should also be noted that there is only a check for !=, and not anything that could prevent a downgrade.

2018-05-01 - Vendor Disclosure
YYYY-MM-DD - Public Release

Credit

Discovered by Lilith (>_>) of Cisco Talos.