HS-110 Smart Plug Account Takeover / Insecure Design

2016-11-25T00:00:00
ID PACKETSTORM:139902
Type packetstorm
Reporter curesec.com
Modified 2016-11-25T00:00:00

Description

                                        
                                            `Content Table  
  
1. Introduction  
2. The Firmware  
3. The Android Application  
4. The Problems  
5. Conclusion  
6. Appendix  
6.1. Excursion Dalvik  
6.2 Control script  
  
1. Introduction  
  
The HS-110 is a Smart Plug meaning it is capable of being controlled with  
commands via a network. TP-Link released a mobile application called "Kasa for  
Mobile" for Android and iOS devices to control the Smart Plug. The  
possibilities range from simple tasks like turning the Plug on and off to  
advanced options like planing schedules and timers. The HS-110 additionally has  
the possibility to measure and store data regarding power consumption. These  
are screenshots of the app home screen, the main control and the settings for a  
plug:  
  
app control screen plug control screen plug settings  
  
The device itself is pretty straightforward with only two buttons. The one at  
the top is the reset button and the other one in the front is the power button  
and status led:  
  
plug from the front plug from the top plug from the back  
  
To open it we remove the hidden screw under the information sheet and then  
break it open using a little bit of force:  
  
[open1] [open2]  
  
Now we remove the top part of the board and the two screws on the second part  
to get rid of the plastic hull:  
  
[open3] [open4] [open5]  
  
We can now see the Atheros AR9331 (Hornet) on the right board in the middle  
picture above. It is a System-on-a-Chip (SOC) which has a MIPS 24K processor  
and is a full featured IEEE 802.11n 1x1 AP/Router. It also has a 32 MiB RAM  
(Zentel A3S56D40GTP-50l) on the opposite side of the same board. The other  
board hosts the electronics for the actual plug. But the interesting question  
is: What this SOC is actually running so let's move on to the next section.  
  
2. The Firmware  
  
The Smart Plug runs on a 64-bit Linux (2.6.31). The Firmware is available at  
the Website of TP-Link. Our version is 1.0.7. There is also an unofficial  
unstable API on GitHub.  
  
For a first analysis of the Firmware we used binwalk . It is important to also  
install sasquatch for this since unsquashfs appears to have issues with TP-Link  
firmware. You can just install the necessary tools for the installation of  
sasquatch via apt  
  
sudo apt-get install build-essential liblzma-dev liblzo2-dev zlib1g-dev  
  
or the corresponding packages if you don't use apt. After that just clone the  
sasquatch git repository and run the build script. At the end we have to  
install binwalk by cloning it's git repository and running the setup.py script  
via  
  
sudo python setup.py install  
  
or  
  
sudo python3 setup.py install  
  
if you are using python3.x. For the dependencies we can run deps.sh, at least  
when we are using apt. Otherwise you have to install them by yourself. A list  
is available at github .  
  
Now we are ready to run binwalk at the firmware with following command:  
  
root@kali:~/Desktop/test# binwalk hs110v1_us_1.0.7_Build_151016_Rel.24186.bin  
DECIMAL HEXADECIMAL DESCRIPTION  
--------------------------------------------------------------------------------  
15904 0x3E20 U-Boot version string, "U-Boot 1.1.4 (Oct 16 2015 - 11:22:22)"  
15952 0x3E50 CRC32 polynomial table, big endian 17244 0x435C uImage header,  
header size: 64 bytes, header CRC: 0xA2B5F4E6, created: 2015-10-16 03:22:22,  
image size: 38777 bytes, Data Address: 0x80010000, Entry Point: 0x80010000,  
data CRC: 0xFED80D4A, OS: Linux, CPU: MIPS, image type: Firmware Image,  
compression type: lzma, image name: "u-boot image" 17308 0x439C LZMA compressed  
data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size:  
112564 bytes 66240 0x102C0 uImage header, header size: 64 bytes, header CRC:  
0x4D2B83AC, created: 2015-10-16 03:22:56, image size: 772570 bytes, Data  
Address: 0x80002000, Entry Point: 0x8019BF90, data CRC: 0xC849B1ED, OS: Linux,  
CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name:  
"Linux Kernel Image" 66304 0x10300 LZMA compressed data, properties: 0x5D,  
dictionary size: 33554432 bytes, uncompressed size: 2238780 bytes 1114816  
0x1102C0 Squashfs filesystem, little endian, version 4.0, compression:lzma,  
size: 2112689 bytes, 194 inodes, blocksize: 16384 bytes, created: 2015-10-16  
03:25:36  
  
It is the most basic command of binwalk and only tells it to analyze the  
specified file. As we can see binwalk detects quite a few things. First of all  
there is the U-Boot version string and -image header together with its lzma  
archive and the polynomial table. U-Boot is a common bootloader, as we can see  
it was created on October 16th 2015 at 11 o'clock but it is out of our scope to  
go through it. Next thing we notice is the Kernel header and archive which is a  
little bit more interesting but we are still looking for the actual system  
which is the last entry, the squashfs filesystem, compressed with lzma. Now we  
could extract the squashfs filesystem via dd but we can also modify our command  
with the argument -e to let binwalk do this. The e argument is the command to  
extract the firmware using predefined dd rules. The output should look like  
this:  
  
root@kali:~/Desktop/test# binwalk -e  
hs110v1_us_1.0.7_Build_151016_Rel.24186.bin Scan Time: 2016-11-17 13:13:18  
Target File: /root/Desktop/test/hs110v1_us_1.0.7_Build_151016_Rel.24186.bin MD5  
Checksum: 73ad741d2256755f78cfb65d73b798c6 Signatures: 344 DECIMAL HEXADECIMAL  
DESCRIPTION  
--------------------------------------------------------------------------------  
15904 0x3E20 U-Boot version string, "U-Boot 1.1.4 (Oct 16 2015 - 11:22:22)"  
15952 0x3E50 CRC32 polynomial table, big endian 17244 0x435C uImage header,  
header size: 64 bytes, header CRC: 0xA2B5F4E6, created: 2015-10-16 03:22:22,  
image size: 38777 bytes, Data Address: 0x80010000, Entry Point: 0x80010000,  
data CRC: 0xFED80D4A, OS: Linux, CPU: MIPS, image type: Firmware Image,  
compression type: lzma, image name: "u-boot image" 17308 0x439C LZMA compressed  
data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size:  
112564 bytes 66240 0x102C0 uImage header, header size: 64 bytes, header CRC:  
0x4D2B83AC, created: 2015-10-16 03:22:56, image size: 772570 bytes, Data  
Address: 0x80002000, Entry Point: 0x8019BF90, data CRC: 0xC849B1ED, OS: Linux,  
CPU: MIPS, image type: OS Kernel Image, compression type: lzma, image name:  
"Linux Kernel Image" 66304 0x10300 LZMA compressed data, properties: 0x5D,  
dictionary size: 33554432 bytes, uncompressed size: 2238780 bytes 1114816  
0x1102C0 Squashfs filesystem, little endian, version 4.0, compression:lzma,  
size: 2112689 bytes, 194 inodes, blocksize: 16384 bytes, created: 2015-10-16  
03:25:36  
  
We see that everything was extracted perfectly fine. When we now look into that  
new directory binwalk extracted the files to we see this:  
  
root@kali:~/Desktop/test# ls  
_hs110v1_us_1.0.7_Build_151016_Rel.24186.bin.extracted/ 10300 10300.7z  
1102C0.squashfs 439C 439C.7z squashfs-root  
  
There are a couple of files and directories. They are the Kernel, Firmware and  
the squashfs filesystem. We know what everything is by looking at the name of  
the specific file which is the beginning of the archives in hexadecimal. For  
example we see in our output that the u-boot lzma archive begins at 0x439C in  
hexadecimal numbers or 17308 in decimal so the lzma archive is labeled 439C  
accordingly. The interesting directory is squashfs-root, the unpacked  
firmware's root directory.  
  
root@kali:~/Desktop/test/_hs110v1_us_1.0.7_Build_151016_Rel.24186.bin.extracted  
/squashfs-root# ls -la total 48 drwxrwxr-x 12 root root 4096 Nov 17 16:00 .  
drwxr-xr-x 4 root root 4096 Nov 17 13:55 .. drwxrwxr-x 2 root root 4096 Okt 16  
2015 bin drwxr-xr-x 3 root root 4096 Okt 16 2015 dev drwxrwxr-x 5 root root  
4096 Okt 16 2015 etc drwxrwxr-x 3 root root 4096 Okt 16 2015 lib lrwxrwxrwx 1  
root root 11 Nov 17 13:55 linuxrc -> bin/busybox lrwxrwxrwx 1 root root 8 Nov  
17 13:55 mnt -> /tmp/mnt drwxrwxr-x 2 root root 4096 Okt 16 2015 proc  
drwxrwxr-x 2 root root 4096 Okt 16 2015 root drwxrwxr-x 2 root root 4096 Okt 16  
2015 sbin drwx--xr-x 2 root root 4096 Okt 16 2015 sys drwxrwxr-x 2 root root  
4096 Okt 16 2015 tmp drwxrwxr-x 4 root root 4096 Okt 16 2015 usr  
  
So now that we have our filesystem we start looking into it. Doing so we found  
a BusyBox installation with version 1.01. Having a look into etc/ we find the  
shadow file, containing a single password hashed with DES for user root:  
  
root@kali:~/Desktop/test/_hs110v1_us_1.0.7_Build_151016_Rel.24186.bin.extracted  
/squashfs-root# cat etc/shadow root:7KBNXuMnKTx6g:15502:0:99999:7:::  
  
Using john the ripper we cracked this password, it is 'media'.  
  
root@kali:~/Desktop/test/_hs110v1_us_1.0.7_Build_151016_Rel.24186.bin.extracted  
/squashfs-root# john pw Using default input encoding: UTF-8 Loaded 1 password  
hash (descrypt, traditional crypt(3) [DES 128/128 SSE2-16]) Press 'q' or Ctrl-C  
to abort, almost any other key for status media (root) 1g 0:00:00:00 DONE 2/3  
(2016-11-17 15:50) 3.571g/s 12382p/s 12382c/s 12382C/s garlic..overkill Use the  
"--show" option to display all of the cracked passwords reliably Session  
completed root@kali:~/Desktop/test/  
_hs110v1_us_1.0.7_Build_151016_Rel.24186.bin.extracted/squashfs-root# john  
--show pw root:media 1 password hash cracked, 0 left  
  
The next thing to notice is the 2048_newroot.cer which is a certificate:  
  
VeriSign Class 3 Public Primary Certification Authority - G5 Identity: VeriSign  
Class 3 Public Primary Certification Authority - G5 Verified by: VeriSign Class  
3 Public Primary Certification Authority - G5 Expires: 16.07.2036  
  
This certificate is obviously used for SSL communication and checking if the  
Smart Plug is connected to the correct remote server.  
There is also the file sw.version which includes the softwareversion:  
  
root@kali:~/Desktop/test/_hs110v1_us_1.0.7_Build_151016_Rel.24186.bin.extracted  
/squashfs-root# cat etc/sw.version 1.0.7 Build 151016 Rel 24186  
  
But the most interesting executable for us was at usr/bin/:  
  
root@kali:~/Desktop/test/_hs110v1_us_1.0.7_Build_151016_Rel.24186.bin.extracted  
/squashfs-root# ls -la usr/bin/ total 1988 drwxrwxr-x 2 root root 4096 Okt 16  
2015 . drwxrwxr-x 4 root root 4096 Okt 16 2015 .. lrwxrwxrwx 1 root root 17 Nov  
17 13:55 [ -> ../../bin/busybox lrwxrwxrwx 1 root root 17 Nov 17 13:55 arping  
-> ../../bin/busybox -rwxrwxr-x 1 root root 4804 Okt 16 2015 calDump -rwxrwxr-x  
1 root root 1976548 Okt 16 2015 shd -rwxrwxr-x 1 root root 38052 Okt 16 2015  
shdTester lrwxrwxrwx 1 root root 17 Nov 17 13:55 test -> ../../bin/busybox  
lrwxrwxrwx 1 root root 17 Nov 17 13:55 tftp -> ../../bin/busybox lrwxrwxrwx 1  
root root 17 Nov 17 13:55 tty -> ../../bin/busybox  
  
shdTester is the a client to configure the emeter and calDump appears to dump  
calibration data from /dev/caldata. The interesting executable is shd which is  
the main server application. Running strings against it we were able to get a  
list of commands to control the plug via network. An analysis with IDA Pro   
(mipsb processor type is not included in free version) is left as an excercise  
for the reader.  
  
3. The Android Application  
  
We had to reverse-engineer the Android app using dex2jar and JD-GUI because the  
communication between plug and app is encrypted. More precisely the app  
communicated via tcp with an encrypted payload containing the commands. To do  
so we used dex2jar on the apk file like this:  
  
santoku@santoku-VirtualBox:~/Desktop/test$ d2j-dex2jar  
Kasa.ver.1.2.4.583.build.583.apk dex2jar Kasa.ver.1.2.4.583.build.583.apk ->  
Kasa.ver.1.2.4.583.build.583-dex2jar.jar  
  
Dex2jar essentially only builds a jar file out of the dex files contained in  
the apk file. Using JD-GUI we can then analyze the jar file to view the Java  
source code. If you wish to learn more about dex files, apks and the Android  
Runtime Environment here is a small excursion in the Appendix.  
  
Although the app was obfuscated we found the de-/encoder which uses an  
xor-cipher with a fixed key. It appears that the key is the same for every Plug  
and app and even for completely different devices like the HS-100 Smart Plug.  
  
The Cipher xor-es the first byte with -85 and the next one with the result from  
the xor before and so on. The final payload send to the plug also has the  
static prefix '\x00\x00\x00\x23'. This is the encoder from the app, the  
byte-array is the payload/command:  
  
byte[] arrayOfByte = paramString.getBytes(); int k = -85; for (int m = 0; m <  
arrayOfByte.length; m++) { arrayOfByte[m] = ((byte)(k ^ arrayOfByte[m])); k =  
arrayOfByte[m]; }  
  
Knowing the mechanism we could reverse it to decode the payloads of the tcp  
packets captured. These were actually commands send with the json format.  
Knowing this we could then send our own commands. For this we used the python  
control script  
  
4. The Problems  
  
Using the known commands we were able to get complete control over the plug  
because there is no authentication method provided, as long as we are on a  
local network. So anyone who knows the IP of the Plug can compromise it A list  
of commands with a little control script in which the most interesting commands  
are implemented is in the Appendix.  
  
The most valuable ones were the bind/unbind command which are used to bind the  
plug to an TP-Link account if you want to be able to control it remotely. The  
process for a normal user goes like this:  
  
First you log in on your smartphone with your TP-Link account, then your mobile  
sends your username and password to the Smart Plug which then uses this info to  
tell an amazon web services (AWS) server that it is now bound to that account.  
After that the app communicates with the Server via TLS.  
  
Maybe you already saw the problem. The app sends username and password to the  
plug, encoded with the xor-cipher we found earlier which means that we get the  
login credentials if we manage to intercept the communication.  
  
The remaining question is, how do we get the user to bind his account to the  
plug again. Of course we canat force him to do so but we can trap him using the  
unbind command from the control script which unbinds the plug from the existing  
account. We donat need any specific information to do so and the only visible  
change for the owner is that the remote access in the settings is switched off  
which doesnat look too suspicious.  
  
root@kali:~/Desktop/Kasa# ./control.py -d -H 10.0.0.188 unbind DEBUG: About to  
send: { "cnCloud": { "unbind": null } } DEBUG: Got following answer: {  
"cnCloud": { "unbind": { "err_code": 0 } } }  
  
remote switch off  
  
And the moment he switches it back on we have his data.  
  
Other possible attacks include capturing the plug by binding it to our own test  
account using the bind command.  
  
root@kali:~/Desktop/Kasa# ./control.py -d -H 10.0.0.188 bind DEBUG: About to  
send: { "cnCloud": { "bind": { "username": "username@mail.com", "password":  
"password" } } } DEBUG: Got following answer: { "cnCloud": { "bind": {  
"err_code": 0 } } }  
  
As we can see in the screenshot below, the owner canat revoke our access using  
the app and has to reset the Plug manually if he isnat using a script like we  
do. This means that we can now control the device conveniently via our own app  
as long as we are logged in with a viable TP-Link Account which we send to the  
plug.  
  
remote switch disabled  
  
We can also control the state of the Smart Plug and keep it on while switching  
its LED off or vice versa. This might actually be useful if the owner doesn't  
like having a constantly running LED at night. It is also possible to get the  
system state and to reboot or reset the device.  
  
5. The Conclusion  
  
TP-Link addressed the problem with the possible stealing of account data in  
another app version (currently at 1.3.3.593, 04.11.2016; tested was 1.2.4.583)  
by changing the communication protocol between plug and app to a custom one.  
However it is still possible to control the plug with the commands provided in  
this article it is not possible anymore to see login credentials.  
  
6. Appendix  
  
6.1. Excursion Dalvik  
  
What is an apk file  
  
An apk file is basically just a zip archive containing necessary data for the  
Java Virtual Machine used by Android, called Dalvik.  
  
Why the Dalvik Virtual Machine was developed  
  
As you may know Android apps are commonly written in Java. The problem is that  
although the license for the standard Java Virtual Machine is GPL2, which means  
it is free and open source, on mobile devices the Java Micro Edition should be  
used which isn't under GPL2 license. So there was the need of an alternative VM  
for Android. This is the reason the Dalvik VM was developed by Google, claiming  
it is a "clean room" implementation of a standard Java VM. Oracle disagreed and  
tried to sue Google in August 2010 but failed in May 2012.  
  
How it works  
  
The Dalvik VM was build with the limited resources of a mobile VM in mind, so  
it was slimmed down, is able to run multiple instances and so on. Essentially  
the Java bytecode generated from the source code is translated into Dalvik  
bytecode which is stored in a dex file (Dalvik EXecutable). You can think of a  
dex file in a similar way as a jar file. This means multiple class files are  
converted into a single "classes.dex" file, while being optimized. For example  
strings and constants used throughout different class files are included only  
once in the dex file in order to save space. The Dalvik executables can also be  
modified during installation for further optimization, including the swapping  
of byte-order and linking data structure and function libraries inline, making  
it smaller and faster. Android 2.2 (Froyo) extended the Dalvik VM with  
trace-based just-in-time (JIT) compilation. This enables the device to trace  
which parts of a program are used frequently and translate them into native  
machine code dynamically, speeding up the execution by a significant amount. So  
when the application is started the first time, the Dalvik VM parses the  
classes.dex file from the apk archive and stores the processed file in its so  
called Dalvik cache from where it is executed, meaning you end up with two  
files. To avoid this and get better startup times a dex file can be  
pre-compiled. These files are called odex files (Optimized Dalvik EXecutable)  
and are stored in an apk archive, just like the dex files. But because they are  
execution ready the Dalvik VM does not need to process these files or store a  
copy in its cache, saving precious space. The drawback of this is that it is  
more difficult to hack odex files.  
  
The Successor  
  
The Dalvik VM was replaced by the Android Runtime (ART) with Android Version  
5.0 (Lollipop) after being introduced as an alternative runtime environment on  
Android 4.4 (KitKat). For backward compatibility the ART also uses dex files as  
an input but the odex files are replaced by Executable and Linkable Format  
(ELF) executables. Once an application is compiled by ART only the ELF  
executable is used. The reason for this lies in the compilation method itself.  
While Dalvik used a JIT compilation ART uses an ahead-of-time (AOT)  
compilation. The difference is that ART translates the whole application into  
native machine code upon installation, improving the execution efficiency by 2  
to 3 times and saving power while using slightly more space, a trade-off that  
is valuable considering modern hardware. ART also has more advantages,  
including improved memory allocation, better garbage collection and new and  
better debugging features.  
  
Further Information  
  
For further information on Dalvik and ART you may visit androidcentral(a little  
bit less confusing but also less detailed, only Dalvik) or the Wikipedia pages  
of Dalvik and Android Runtime respectively . For more information upon odex  
files I can recommend this post on stackoverflow, and for really detailed  
information on both, Dalvik and ART, go to the Android Developers Website.  
  
6.2. Control script  
  
We used a script originally written and published by Adrian Reber for the  
HS-100 and implemented more commands. Find the adjusted version in our github  
at https://github.com/curesec/Blog/. Interesting files for the KASA Project are  
the commands.txt and control.py.  
  
  
Blog Reference:  
https://www.curesec.com/blog/article/blog/The-HS-110-Smart-Plug-aka-Projekt-Kasa-165.html  
  
--  
blog: https://www.curesec.com/blog  
tweet: https://twitter.com/curesec  
  
Curesec GmbH  
Curesec Research Team  
Josef-Orlopp-StraAe 54  
10365 Berlin, Germany  
  
  
`