Reverse-engineering TP-Link KC100

 This week I got my hands on two pieces of the TP-Link KC100 Spot camera. It is a very nice product from TP-Link with some very cool features:

  • 130° wide-view angle;
  • Night-vision up to 6 meters;
  • Very easy setup through the Kasa Smart App (available for iOS and for Android);
  • Easy wall mount;
  • etc.

But it has a very big drawback — as all other IP cameras — it phones home. Normally I trust TP-Link and its products and I cannot blame them for spying on me. But what I am always scared of is some malicious person breaking into their systems and stealing / eavesdropping on my data.

Therefore I decided to block access to the Internet for both MAC addresses inside my router. The problem this comes with, is when outside of the home network, the cameras are shown as offline in the Kasa Smart App, even when I am logged to my network via VPN.

 
 

The way how the Kasa Smart App worksis, that upon launch it sends a UDP broadcast 

packet
 on port 9999 with an encrypted content.

Press enter or click to view image in full size
UDP broadcast packet on port 9999

Every TP-Link device on your network, which receives this packet answers to your smartphone with information about itself. That’s how the app knows which devices are online.

Now, when you’re outside your network and you’re connected over VPN over Layer 3 (e.g. tun, not tap) then the broadcast is not forwarded. So no devices answer back to you, thus the app doesn’t know which devices you have.

 
 

What’s inside the encrypted UDP packets?Following the information in this very well written article, I was able to decrypt the data in the UDP packets. Here is what the Kasa Smart App sends:

{"system":{"get_sysinfo":{}}}

And here is what one of the cameras answers:

{"system":{"get_sysinfo":{"err_code":0,"system":{"sw_ver":"2.3.0 Build 20200415 rel.61333","hw_ver":"2.0","model":"KC100(UN)","hwId":"DEADBEAFDEADBEAFDEADBEAFDEADBEAF","oemId":"CAFECAFECAFECAFECAFECAFECAFECAFE","deviceId":"CAFEDEADBEAFCAFEDEADBEAFCAFEDEADBEAFCAFE","dev_name":"Kasa Spot","c_opt":[0,1],"type":"IOT.IPCAMERA","alias":"My Kasa Spot Name","mic_mac":"DEADBEAFCAFE","mac":"00:11:22:33:44:55","longitude":01.234567,"latitude":09.876543,"rssi":-52,"system_time":1596600000,"led_status":"on","updating":false,"status":"configured","resolution":"1080P","camera_switch":"on","bind_status":true,"last_activity_timestamp":1596611111}}}}

Basically speaking the 9999 port interface is a configuration interface, where you can send JSON-structured commands and receive JSON-structured responses. Review the article linked above for more information and commands how this works.

 
 

The I fired up my Kali Linux and performed an nmap on the device:

Press enter or click to view image in full size
nmap results

The ports 10443, 18443 and 19443 all seem like HTTPS (usually 443) ports. That’s why I fired the browser and opened the first one:

Press enter or click to view image in full size
Trying port 10443 in the browser

So they really speak the HTTP protocol, but there is no “default” page. What was more important to notice, was the yellow exclamation mark in the address bar. This meant, the certificate was not CA signed one, but a self-signed. This meant the application did not search for a specific certificate.

Then I decided to try with the infamous dirb command:

# dirb https://192.168.10.40:10443/ /usr/share/wordlists/dirb/big.txt 

-----------------
DIRB v2.22 
By The Dark Raver
-----------------

START_TIME: Thu Aug 6 13:49:43 2020
URL_BASE: https://192.168.10.40:10443/
WORDLIST_FILES: /usr/share/wordlists/dirb/big.txt

-----------------

GENERATED WORDS: 20458 

---- Scanning URL: https://192.168.10.40:10443/ ----

-----------------
END_TIME: Thu Aug 6 14:51:03 2020
DOWNLOADED: 20458 - FOUND: 0

Well, not good.

 
 

Get Geistless’s stories in your inbox

Join Medium for free to get updates from this writer.

Enter: disassembly of the Kasa Smart App for Android. I downloaded the app from the Google Play Store and used jadx to disassemble it. After spending another one hour analyzing the code I found the following part:

Press enter or click to view image in full size
Part of the Kasa Smart App disassembled code

In the code, the path “/https/stream/mixed” was stated for one of our ports: 19443. So I tried this one, and:

Press enter or click to view image in full size
Trying another URL

Voila! Now we only needed to find the username and password.

 
 

Since the App was not checking the certificate, we could’ve either started mitmproxy, or (the solution I decided) fire a simple HTTPS server and forward all the requests with it.

Since exactly at this time I started learning rust and my learning app was a simple HTTPS server, I decided to test it live. The handler function for each request will print us all the request’s data, so we can then see what kind of authorization they’re using.

But before getting the request, we first have to trick the Kasa Smart App to send a request to it. For this I used arpspoof. So here are some commands I executed on my Kali machine:

Press enter or click to view image in full size
Preparing the HTTPS server

And then started the spoofing process:

Press enter or click to view image in full size
Activating arpspoof

What this basically does is to tell my smarphone on IP address 192.168.10.37, that the IP address 192.168.10.40, where the camera should be, is located at the MAC address of my Kali machine. Thus my smartphone will try to search for the camera on the Kali machine.

Not long after starting this, my Kasa Smart App lost connection to the camera stream and I got output from my HTTPS server:

Press enter or click to view image in full size
Request from Kasa Smart App

Now we can see that it is using basic authorization and the string is base64 encoded. After encoding it, I found out how it’s built. Here is a simple pseudo-code for the algorithm:

Press enter or click to view image in full size
Pseudo-code algorithm for basic authorization

The two first lines have to be replaced with the account data, that you’ve used to login into the Kasa Smart App / setup the camera.

 
 

Now it was time to try it out!

Press enter or click to view image in full size
First curl try

OK, this is a good result — authentication works, but there is no “Content-length” header, according to curl. Why is this?

If we check the response headers, we see the one saying:

Content-Type: multipart/x-mixed-replace;boundary=data-boundary--

This shows, that the content length is actually infinite and data will be constantly sent to us, delimiting the different parts of the video and audio with the delimiter:

--data-boundary--

Now let’s modify this a bit: just add the option “--ignore-content-length” to curl. Then let it run for a couple of seconds and stop it via Ctrl+C. Open the file called fl.test and look its contents:

Structure of the downloaded data

Great, we have an MJPEG stream of audio and video, which we can easily convert to mp4 with ffmpeg:

# curl -vv -k -u admin:YWRtaW4= --ignore-content-length "https://192.168.10.40:19443/https/stream/mixed?video=h264&audio=g711&resolution=hd&deviceId= CAFEDEADBEAFCAFEDEADBEAFCAFEDEADBEAFCAFE" --output - | ffmpeg -y -i - test.mp4

The newly created file called “test.mp4” can be now played with your favorite multimedia player.

From this point on, you can start an RTSP server, or do h264 m3u streaming, etc.

All the paths are open.

29
Geistless

Written by Geistless

Developing software since I was 7 years old. Privacy and security advocate, in my spare time I reverse-engineer software and hardware, to learn how they work.

Responses (11)

To respond to this story,
get the free Medium app.

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
fiveangle
Great work ! I'll poke around to see about figuring out the protocol it uses for controlling the camera movements on the Kasa EC70 tracking cameras that I have. go2rtc just released support for restreaming the video from these Kasa network cameras…
taras
Brilliant, thank you. Works perfectly with my ancient Kasa KC115.
To save anyone else time figuring it out - you can get a single frame by changing the ffmpeg portion of the command to:
ffmpeg -y -i - -update 1 -frames:v 1 -q:v 2 frame.jpg
Philip
Brilliant, just what i was looking for. Any experience with the /vod/data/mixstream?