TP-Link Tapo P100

Hey,
Not much news this evening but after quite a bit of digging and trying many many combinations of commands from the HS110 API I think I can conclude that the {'error_code': -1} is caused by invalid syntax in the request field of the api call

Hope this helped (Sorry if you’d already worked this out)

I was not able to figure out how this working, i don’t have to much knowledge about how to find information about API but with will be very cool if someone with better knowledge that me to use tools like apktool and ClassyShark.

I opened one of these last night.
Its a snap fit, no glue or ultrasonic weld.
The device is using a Realtek Ameba Z2 RTL8720CF
Single PCB with components both sides. To remove the PCB, the two large solder contacts need to be removed - beware, the PCB has tracks both sides here.
There’s no additional flash/rom on the PCB.
Very simple circuit, very little to see.
The antenna is on the main PCB and towards the bottom of the case.
The relay is an Omron brand.
I’ve attached a photo.

!

4 Likes

Hi all,

Also tried to go through this method describe here unfortunately no luck still…

1 Like

Hey,
Have you tried unpacking the android app? I have no idea how easy/hard it is but it may help in decoding the communication protocol.

1 Like

So I’ve unpacked the APK, and it doesn’t tell me much… Yet.

The app did download a firmware update, so I’m hoping that the binary is cached on my phone somewhere (haven’t found it yet) and I can look through that.

1 Like

X2 did the same thing, the only thing I found interesting in the app contents was the certificates used for communication. Other than that my coding skills are quite limited to find anything interesting.
Thanks all for the contributions and suggestions!

1 Like

Hi guys! Any update on how to add these plugs? I’m looking everywhere for a solution but nothing yet…

1 Like

Hi all,

So looks like somebody already broke down the entire communication path for the TP Link Kasa App.


I’m going to give a read on this article and understand how to possibly create an integratious for our outlets.
4 Likes

great find - way beyond my capabilities but this would seem to open up a whole load of options for tplink/tapo stuff - full local control of the cameras for example - motion detection/remote movement/ like in the app rather than just the crippled rtsp support tplink currently offer. :slight_smile:

Urm, Kasa != Tapo.

“urm” tapo is in fact a tplink brand. " TP-Link manufactures smart home devices under their Kasa Smart and Tapo product lines." See: https://en.wikipedia.org/wiki/TP-Link

Whilst they may use different branding they are highly likely to use the same or at least very similar code base.

It’s actually pretty different.
From Wireshark, I’ve seen that they post a Public Key to the broadcast address of the network where the App sits.
Thanks to Dibr I’ve found out that they have to http endpoints exposed:

When trying to access both it returns a Json Object with error code:

 {"error_code":-1009}

I don’t think that the way it communicates has anything to do with the Kasa App, when I’m starting to exhaust all of my research resources :stuck_out_tongue:

Interesting, where are you running DIBR, on your router?
Wireshark live or pcap playback?

Stuart

Actually was running DIBR from a Kali VM.
Wireshark findings were live.
Any more suggestions on new approaches?

Hi guys,

Also just bought one of these because it was cheap and smaller than some of the alternatives. It’s on a fairly non-critical item of my home (old TV in the basement). My temporarily (possibly permanent) hack is to use hook the thing up to Google Assistant, then use Assistant Relay to send a a message like “Turn on basement TV Plug” and “Turn off basement TV Plug”. This seems to work most o the time, provided you don’t try and toggle things too quickly. The issue of course is that I can’t see current state. :frowning: Ah well, it sort of works. Thought you guys might be interested for a temporary hack.

Wireshark can interpret Bluetooth captures, so my next avenue is to pair a new device, logging the Bluetooth packets for analysis using wireshark.

Stuart

@hsiboy Let us now how it went then :slight_smile:

I have posted some information about accessing the Tapo C200 camera on the local network, in another topic. You might want to have a look and try it with the Tapo P100.

@hsiboy, @pnmoliveira, @tachikoma1373, @tnmendes, @fishbigger, @lorenzor95, @iain

2 Likes

Ok, I’ve got a P100 plug as well and I can see, that the plug uses a completely different method for the communication. It uses RSA key pairs and AES encryption. @tnmendes’ posts above are really useful.

@hsiboy, I see that it does the initial pairing by Bluetooth. The cameras have an AP where the app joins, and passes the Wifi network’s name and password to the camera. Probably exactly the same happens when the app connects by Bluetooth to the plug as well.

Indeed the communication is different after, the plug uses pure HTTP, but encrypts all the messages.

As I can see, there are a few things what the app does. It does a UDP broadcast with an RSA public key and waits for replies from the devices on the network. I guess this is just a discovery function.

Then when the app knows the IP address of the device on the network, then it does a handshake.

Like this:

POST /app HTTP/1.1
Host: 192.168.X.XXX
Referer: http://192.168.X.XXX:80
Accept: application/json
Accept-Encoding: gzip
User-Agent: okhttp/3.12.2
Connection: Keep-Alive
requestByApp: true
Content-Type: application/json; charset=UTF-8
Host: 192.168.8.XXX
Cookie: TP_SESSIONID=4C22EA2D8FDDD4DA6EAAC3A916994898

{"method":"handshake","params":{"key":"-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCNIZEwdOB78kqRRLdh7WRnEa6ubxgYhYDXrb7vkQjGqY7DuzVVGXt05XjT8KB5SovHzXzhMbKLZK1vgNDBbAXtfxkhifYZ6sOw+1GwlZWTEOlme9bfTNyqJ9gJxBjzJ+IsLVKoNi65QM3c3Ft1yBqXULibC7GkNA7ZFa3i5TJiEwIDAQAB\n-----END PUBLIC KEY-----\n"},"requestTimeMils":0}

In return the plug replies:

{

    "error_code": 0,

    "result": {

        "key": "HpL/IhOrGg9VU7Fx2M3/3/asHSKEdeerUVWoA2nlfpbH7KnskW8j+Kwtgk6In5+b95wTYY0TFQQFX3M3dSZ8ncz+j1/IOI7EyTJ+u0k9QLo8eNcdgDDyq/cTeBuq+/tIQcrFpea1sS0I90NndkiXPhFigVGdYZQcb0GU43iz7JU="

    }

}

The TP_SESSIONID is used along the communication further on, the validity of the ID is 24 hours. In the next step the app post another messages to the plug, which I believe confirms the handshake, and the plug replies with a token (of course all encrypted). I haven’t managed to mimic those yet, even I haven’t managed to decrypt the reply from the plug yet. But here are some hints from the decompiled app, what is really happening:

    public void mo35029c() {
        KeyPairGenerator instance = KeyPairGenerator.getInstance("RSA");
        instance.initialize(1024, new SecureRandom());
        KeyPair generateKeyPair = instance.generateKeyPair();
        String str = new String(Base64.encode(((RSAPublicKey) generateKeyPair.getPublic()).getEncoded(), 0));
        String str2 = new String(Base64.encode(((RSAPrivateKey) generateKeyPair.getPrivate()).getEncoded(), 0));
        this.f20965b.put(0, str);
        this.f20965b.put(1, str2);
    }

    /* renamed from: d */
    public String mo35030d() {
        if (TextUtils.isEmpty(this.f20965b.get(0))) {
            mo35029c();
        }
        return "-----BEGIN PUBLIC KEY-----\n" + this.f20965b.get(0) + "-----END PUBLIC KEY-----\n";
    }

    /* renamed from: a */
    public void mo35024a(String str) {
        byte[] decode = Base64.decode(str.getBytes("UTF-8"), 0);
        byte[] decode2 = Base64.decode(this.f20965b.get(1), 0);
        Cipher instance = Cipher.getInstance("RSA/NONE/PKCS1Padding");
        instance.init(2, (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decode2)));
        byte[] doFinal = instance.doFinal(decode);
        byte[] bArr = new byte[16];
        byte[] bArr2 = new byte[16];
        System.arraycopy(doFinal, 0, bArr, 0, 16);
        System.arraycopy(doFinal, 16, bArr2, 0, 16);
        String encodeToString = Base64.encodeToString(bArr, 0);
        String encodeToString2 = Base64.encodeToString(bArr2, 0);
        this.f20966c = new C6586a(bArr, bArr2);
        mo35025a(encodeToString, encodeToString2);
    }

This confirms that the RSA key is an 1024bit key, and the encryption method is RSA/NONE/PKCS1Padding. But unfortunately it is not that straight forward. There is a second part of the code which handles bArr and bArr2 and it turns out that they are the AES Secret Key and IV:

public C6586a(byte[] bArr, byte[] bArr2) {
        try {
            SecretKeySpec secretKeySpec = new SecretKeySpec(bArr, "AES");
            IvParameterSpec ivParameterSpec = new IvParameterSpec(bArr2);
            this.f21776a = Cipher.getInstance("AES/CBC/PKCS7Padding");
            this.f21776a.init(1, secretKeySpec, ivParameterSpec);
            this.f21777b = Cipher.getInstance("AES/CBC/PKCS7Padding");
            this.f21777b.init(2, secretKeySpec, ivParameterSpec);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        } catch (NoSuchPaddingException e2) {
            e2.printStackTrace();
        } catch (InvalidKeyException e3) {
            e3.printStackTrace();
        } catch (InvalidAlgorithmParameterException e4) {
            e4.printStackTrace();
        }
    }

I believe further on this AES encryption is used to transmit information between the app and device, but that is a hard guess. And I have no idea is it hard coded to the device or generated every time.

Anyhow, some pieces to start reverse engineering.

@JurajNyiri, @Dpavey are you fancy to crack this nut and make the integration for the Tapo plugs as well?

PS.: I used Postman to send the handshake request, and manually generated the RSA key. Initially I used HttpCanary on Android to capture some communication between the app and the plug. (and fortunately managed to catch the handshake, without CA Pinning, might be it is due to the pure HTTP use)

4 Likes