HS-110 Smart Plug Account Takeover / Insecure Design

2016.11.30
Credit: Curesec
Risk: Medium
Local: No
Remote: Yes
CVE: N/A
CWE: N/A

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


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2019, cxsecurity.com

 

Back to Top