Reverse engineering the TP-Link Smart Plug API with Charles

I went to the LA Ruby User Group a while ago and watched a hilarious and pointless talk about automating Pokemon Go to beat all your friends or something.

Except like is so often awesome and amazing about the way the Ruby community encourages learning, there was a nugget in there and that was the amazing app Charles that the presenter used to figure out what requests Pokemon Go was making.

From the site:

Charles is an HTTP proxy / HTTP monitor / Reverse Proxy that enables a developer to view all of the HTTP and SSL / HTTPS traffic between their machine and the Internet.

So essentially, use Charles to spy on shit.

When you launch Charles it will start capturing all the traffic originating from your computer. Initially, if it’s SSL it is just flagged as encrypted. If it’s SSL with SNI then you’ll be able to see the destination hostname.

To solve the SSL “problem”, Charles provides a root cert that you can download and trust which will allow Charles to decrypt those messages as well.

Which is great. Fantastic. And a little terrifying. Now anything on my computer that is connecting to any* website I get to see what is being said.

Which doesn’t really help me since I have an app on my phone I need to spy on.

Happily, Charles has this solved. When you’re connected to Wifi, iOS has a built in screen in settings where you can input information for a proxy. Easy. To get the SSL decoding, Charles also provides a root cert for iOS to install.

Quick note if you’re following along at home: uninstall these certs when you’re done. Reinstall them later if you need them again. I mean, it’s a small attack vector but sheesh why leave it open. It’s devastating if exploited.

So, all that configured and connected, I pop open the app and am extremely disappointed when literally nothing shows up when I toggle the smart plug from my phone.

Zilch. Zero.

As best I could tell at the time (and have since confirmed), if the app and the plug are on the same Wifi then the app talks directly to the plug, and Charles wasn’t spying on local network traffic from the phone.

Which, again, probably a good thing for the world, but nicht zehr gut for my project.

Not one to leave well enough alone, I kept digging.

Since this is getting almost ridiculous as the project itself, I’ll summarize.

I tethered my laptop to my phone, and then set my phone to use Charles, running on my laptop, as a proxy. Which is a giant mess that I can’t even believe worked but it did.

Having done all that, I could now see the traffic running between the control app and the TP-Link servers to toggle the switch. While I was at it I was also able to spy the login sequence so that in the case that the authentication token expired I would be able to duplicate the login flow in my code and grab a new token, which did in fact end up necessary.

In the end, the actual HTTP flow was incredibly simple. All of the toggle requests ended up having a small & concise structure, as did the login flow. A small JSON payload with an “action” key, usually all sent to one endpoint.

The code is sweaty and gross and available here if you want to take a look at it. I make no apologies for how gross it is. It is cooling my face right now as I type. Fight me.

Anyway, it was a fun project. This was the first time I have reverse-engineered (if you can call it that) something that was past “pop open the dev tools and copy as curl”. It is always fun to de-mistify something like this. I feel like I’ve hopped a tiny mental barrier now that I can see requests from apps on my phone and opened the door to a realm of possibilities previously unexplored, by me at least.


678 Words