Hacking TechLife Pro bulbs

Actually there is no such a script you could run on a computer to override DNS unless the DNS service is provided by an other device than your WiFi router on your local network.
So to give a straight answer is difficult, since some routers has their own built-in DNS server, some does not. You can check the router’s admin interface to see if there is a local DNS server support and it’s enabled to your LAN. If it’s available, you can override any hostname to have the desired IP for that.

For example on DD-WRT routers, there is a DNS server provided by DNSMasq application. Therefore under the Service->Service menu you just have to enter this to the “Additional DNSMasq Options” section:

address=/cloud.qh-tek.com/192.168.1.xx ← your MQTT server’s address

Like this:
image

Ok, I want script which may replace record (string) <cloud.qk-tek.com> in the bulb :slight_smile:.
When connected with network, bulb start search for domain cloud.qk-tek.com using DNS. When replace that cloud .qk-tek .com with my local IP I will not necessary forwarding DNS to my local DNS server in router :slight_smile:

I found this image showing how you can connect the module to a PC (using usb to ttl adapter). Remember its 3.3v only! Baudrate is 921600. Send enter right after boot to enter bootloader.


The bootloader seems unlocked, digging around a bit I found this string:
http://icloud.qh-tek.com:8080/DN/packed03.bin
Which is probably the OTA firmware image. It contains cloud.qh-tek.com as a plaintext string, so we might be able to change lt :grinning: Still need to find a way to flash the modified image, it probably has a checksum that needs to be modified

1 Like

That was cool.

According to https://github.com/alibaba/AliOS-Things/blob/c9074e99fbb7ba5c5ecde2ae38ae30b27a913afe/platform/mcu/rda5981x/tools/ota_pack_image_lzma.py#L7-L21 the header has no CRC (it’s zero). But that’s just an assumption.

And here is some fun:

To instruct the device to download the firmware, send this command over MQTT:

echo -en "\xa9\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xaa" | mosquitto_pub ....

The command is “0xA9 0xF0”

In order to set an IP for an MQTT broker (instead of using cloud .qh-tek.com broker), here is a small python script which generates the necessary sequence for you. I tried to restart the bulb and did not see DNS requests to their cloud anymore. However I can’t confirm the new IP address setting will persist for several reboots (just haven’t had time to play with it).
You can run this at eg. https://pyfiddle.io/ and put the result into your echo command. Please note, the bulb must be connected to an MQTT server (most likely to their cloud) :slight_smile:

def calcChecksum(stream):
    checksum = 0
    for i in range(1, 14):
        checksum = checksum ^ stream[i]
    stream[14] = checksum & 255

    return bytearray(stream)


def changeIP (ipAddr, port):
    Command = bytearray.fromhex("AF 00 00 00 00 00 00 f0 00 00 00 00 00 00 00 b0")
    l = list(Command)
    idx = 1
    for ip in map(int,ipAddr.split('.')):
        l[idx] = ip
        idx = idx + 1
    l[5] = port & 0xff
    l[6] = port >> 8
    return calcChecksum(l)

print 'Change IP payload: \\x' + '\\x'.join(format(x, '02x') for x in changeIP('192.168.1.100',1883))

Awesome! How did you find that command to change the IP and download the firmware? Did you look at the bin or the provided Android app?

The OTA image does have a checksum, in this case it is “E1 B9 04 CB” (0xCB04…) . VERSION_SZ seems to be 24. It should be trivial to modify the image and recalculate the checksum, I might try to do it during next weekend.

I will try to add support for those MQTT commands to my fork of gBridge, so that this type of lamps can still be used with Google Home:

Yes, the bin file helped to find some interesting commands. I updated my gist (link above) with a new script which has several device queries over MQTT.

Did you have time to rebuild the image?

Hi guys,

A newcomer to these bulbs here…

I tried your script, but it fails with this message:

ConnectionRefusedError: [WinError 10061] No connection could be made because the target machine actively refused it

My bulb is also a Techlife Pro bulb, however, I noticed a difference to yours (maybe irrelevant): When I create an AP with the bulb, this is the name of it:

BroadlinkProv-8bb250

Hello Jakab :wink:

based on the SSID you provided, I believe that bulb uses Broadlink protocol which is completely different from the Techlife bulbs, so the scripts won’t work for that particular device.
Broadlink uses broadcasts/direct packets to port 80 while Techlife is based on MQTT (and can be configured via port 8000)

Szia!

Hmmm, interesting… The box the bulb came in says Techlife Pro. That’s how I found this forum.

Also, I checked out this link (earlier in this thread), and after opening the bulb I can see that is looks very similar, if not identical.

So for me it is not a huge problem, I can (and next week I will) replace the wifi module in it for a 8266 based one.

Thank you for your input, and if I can be of any help exploring these bad boys any further, let me know.

Indeed, it’s interesting. If it turns out that’s a broadlink device and need some advice, send me a PM (without highjacking this thread)

I’m afraid, I can’t send private messages on this forum yet…

Now I opened up the bulb and took a couple of pics.

I can now see that this is a different wifi module than the one you guys were talking about.

Sorry about this.

Custom IP address for local MQTT broker in the bulb persists between pulling the plug cord and removing the bulb from its socket.

Thanks

is there a repository somewhere for all the codes? I have a fcmila bulb and above codes work fine. However, I need to have color codes as well, I was trying to insert the color hex code, but somehow it is not working, maybe some command bit is needed. Any idea how to pass the color codes?

@cvirag Can you please help on the color codes commands for the devices. I can control the ON & OFF and the brightness.

Sorry, missed your message earlier.

To be honest, I only have black&white bulb, so never had a chance to play with colors. But, I added another script to the gist library for sniffing the communication between the client application and the cloud and it dumps whatever your official Android app would have send to the bulb.

  1. launch the python script. Hopefully you will have the same cloud server, so you don’t need to change any parameter
  2. Launch the Android app and chose the bulb. This means, you must have the bulb configured in the TechLife Pro app, but you can have the bulb reconfigured to your local mqtt server. That means, without the python script, the app can’t recognize the bulb, but the script “emulates” the necessary response to it :slight_smile:
  3. Adjust your bulb (turn on/off, brightness, color) and it should be dumped by the script.

Thanks @cvirag . will try that out. However, I got the source code for the same. here is the 2 java methods that trigger the RGB functionality. Please check if you are able to figure out the MQTT message structure

    public void sendRGB(String str, int i, boolean z) {
        int i2;
        int i3;
        int i4 = 10000;
        if (this.colors.containsKey(str)) {
            MyColor myColor = (MyColor) this.colors.get(str);
            i4 = myColor.f616r;
            i2 = myColor.f615g;
            i3 = myColor.f613b;
        } else {
            i3 = 10000;
            i2 = 10000;
        }
        if (i < 1) {
            i = 1;
        }
        final int i5 = i > 100 ? 100 : i;
        final int i6 = (i4 * i5) / 100;
        final int i7 = (i2 * i5) / 100;
        final int i8 = (i3 * i5) / 100;
        if (!this.colors.containsKey(str)) {
            MyColor myColor2 = new MyColor();
            myColor2.f614cw = 0;
            myColor2.f617ww = 0;
            myColor2.f616r = i4;
            myColor2.f615g = i2;
            myColor2.f613b = i3;
            this.colors.put(str, myColor2);
        }
        final String str2 = str;
        final boolean z2 = z;
        C116212 r4 = new Runnable() {
            public void run() {
                byte[] bArr = {40, (byte) (i6 & 255), (byte) ((i6 >> 8) & 255), (byte) (i7 & 255), (byte) ((i7 >> 8) & 255), (byte) (i8 & 255), (byte) ((i8 >> 8) & 255), 0, 0, 0, 0, (byte) i5, 0, 15, 0, 41};
                char c = 0;
                for (int i = 1; i < 14; i++) {
                    c = (char) (c ^ bArr[i]);
                }
                bArr[14] = (byte) (c & 255);
                if (!MultipleMQTT.this.isRead(str2)) {
                    return;
                }
                if (z2) {
                    MultipleMQTT.this.sendData(str2, bArr, 0);
                } else {
                    MultipleMQTT.this.sendData(str2, bArr, 0);
                }
            }
        };
        new Thread(r4).start();
    }

and also



    public void sendRGB(String str, int i, int i2, int i3, int i4, boolean z) {
        int i5 = 1;
        int i6 = i4;
        if (i6 >= 1) {
            i5 = i6;
        }
        final int i7 = i5 > 100 ? 100 : i5;
        final int i8 = (i * i7) / 100;
        final int i9 = (i2 * i7) / 100;
        final int i10 = (i3 * i7) / 100;
        final String str2 = str;
        final boolean z2 = z;
        final int i11 = i;
        final int i12 = i2;
        final int i13 = i3;
        C116313 r2 = new Runnable() {
            public void run() {
                byte[] bArr = {40, (byte) (i8 & 255), (byte) ((i8 >> 8) & 255), (byte) (i9 & 255), (byte) ((i9 >> 8) & 255), (byte) (i10 & 255), (byte) ((i10 >> 8) & 255), 0, 0, 0, 0, (byte) i7, 0, 15, 0, 41};
                char c = 0;
                for (int i = 1; i < 14; i++) {
                    c = (char) (c ^ bArr[i]);
                }
                bArr[14] = (byte) (c & 255);
                if (MultipleMQTT.this.isRead(str2)) {
                    if (MultipleMQTT.this.wifiColors.containsKey(str2)) {
                        WifiColor wifiColor = (WifiColor) MultipleMQTT.this.wifiColors.get(str2);
                        if (wifiColor != null) {
                            if (wifiColor.firmwareytpe != 243 && wifiColor.firmwareytpe != 244 && wifiColor.firmwareytpe != 245) {
                                return;
                            }
                            if (z2) {
                                MultipleMQTT.this.sendData(str2, bArr, 0);
                            } else {
                                MultipleMQTT.this.sendData(str2, bArr, 0);
                            }
                        }
                    }
                    if (MultipleMQTT.this.colors.containsKey(str2)) {
                        MyColor myColor = (MyColor) MultipleMQTT.this.colors.get(str2);
                        myColor.f614cw = 0;
                        myColor.f617ww = 0;
                        myColor.f616r = i11;
                        myColor.f615g = i12;
                        myColor.f613b = i13;
                        MultipleMQTT.this.colors.put(str2, myColor);
                    } else {
                        MyColor myColor2 = new MyColor();
                        myColor2.f614cw = 0;
                        myColor2.f617ww = 0;
                        myColor2.f616r = i11;
                        myColor2.f615g = i12;
                        myColor2.f613b = i13;
                        MultipleMQTT.this.colors.put(str2, myColor2);
                    }
                }
            }
        };
        new Thread(r2).start();
    }

I found out some Color MQTT Codes. So the variable seems to be 1 to 6 bytes. But does not seem like these are color hex codes - Any ideas?

The 14th byte (5c, 6b, b7) is the checksum and can be generated as listed above in this thread.
Constants bytes are 0 (28), 11 (64) & 15 (29)

RED---------------:b’281027000000000000000064000f5c29’
GREEN----------: b’280000102700000000000064000f5c29’
BLUE-------------: b’280000000010270000000064000f5c29’
TURQUOISE—: b’280000102710270000000064000f6b29’
PINK--------------: b’281027000010270000000064000f6b29’
BLUEDARK-----: b’280000e90210270000000064000fb729’
LIME YELLOW-: b’28d018102700000000000064000f9429’

Thos are HEX codes, within the range of 0-10000.
As in your example: RED light was 1027 0000 0000 (in sequence of R G B codes). 1027 is 0x2710 (due to the little endian byte order), which is 10000 in decimal. You can see in the Java code, the value of the RGB codes is calculated as final int i6 = (i4 * i5) / 100 where i6 is the final value of the Red payload and i4 (which is the Red color’s code) multiplied by the brightness (??) of i5.

According to this calculation, eg. Lime yellow looks like:
R => 0x18d0 (Dec: 6352) , G=> 0x2710 (Dec:10000), B=> 0x0000. Since the brightens (??) is always 100 (0x64 -> stored at the 11th position) in your case, these are the original RGB codes for this (Lime yellow) color code. Of course if you adjust the brightness (??), these values will lower.

Therefore the structure looks like this:

byte value example for Lime yellow
0 Command 0x28
1 Second byte of RED value (little endian) 0xd0
2 First byte of RED value 0x18
3 Second byte of GREEN value 0x10
4 First byte of GREEN value 0x27
5 Second byte of BLUE value 0x00
6 First byte of BLUE value 0x00
7 0x00 0x00
8 0x00 0x00
9 0x00 0x00
10 0x00 0x00
11 brightness (??) in range of 0x00 - 0x64 0x64
12 0x00 0x00
13 0x0f 0x0f
14 Checksum 0x94
15 Closing tag for Command 0x28 is 0x29 0x29

Perhaps you can get the predefined HEX values for colors from the source where you got the Java examples.

Thanks so much. Finally figured it out. Let me know if any error in my understanding

  1. Gather RGB values in Decimals (for example a shade of blue would be 0,0,153)
  2. Multiply each color channel value by 10000 and divide by 255. The blue channel above would be (153 *10000)/255 = 6000
  3. Find out the hex value of the decimal value. 6000 Dec = 17 70 hex
  4. Use the little endian for the hex value = 70 17
  5. The overall hex array will be 28 00 00 00 00 70 17 00 00 00 00 64 00 0f 00 29
  6. By running the progam to compute the checksum and generate the format required by MQTT will be \x28\x00\x00\x00\x00\x70\x17\x00\x00\x00\x00\x64\x00\x0f\x0c\x29

I passed this value and it works great!

Next task - Write a generic python program and capture value from RGB color wheel to dynamically generate MQTT messages.

Special thanks to @cvirag What started as a simple weekend project to get $5 bulb locally controlled resulted in lot of learning.

I know this is something big, but I just don’t know exactly how to do it…
I can’t redirect through the router (since it doesn’t have that option) so if i can change the firmware, it coul be great.

Ok I copied the lines and change the IP to 192.168.1.132 (my localhost, where I want to redirect instead of cloud .qh-tek) and i used https://pyfiddle.io/ , so I’ve got:

\xaf\xc0\xa8\x01\x84\x5b\x07\xf0\x00\x00\x00\x00\x00\x00\x41\xb0

I have tried
echo -en “\xaf\xc0\xa8\x01\x84\x5b\x07\xf0\x00\x00\x00\x00\x00\x00\x41\xb0” | mosquitto_pub -t ‘dev_sub_86:18:d3:2b:52:xx’ -s

and

echo -en “\xa9\xf0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\xaa” | mosquitto_pub -t ‘dev_sub_86:18:d3:2b:52:xx’ -s -u testuser -P testpassword

where 86:18:d3:2b:52:xx is the bulb’s mac

But nothing happens, I know I’m doing something really wrong (I’m a newbie in all this), but i gueess I’m not that far.

Any ideas i how to change the firmware so I can control the bulb locally without dnsmask-like solutions?