cubing.js icon indicating copy to clipboard operation
cubing.js copied to clipboard

support for i carry

Open psifertex opened this issue 3 years ago • 21 comments

Here's an option source implementation for the BT in the new GAN I Carry if ya'll wanted to port it to JS:

https://github.com/D0ntPanic/tpscube/blob/rust_rewrite/lib/src/bluetooth/gan.rs

psifertex avatar Jun 24 '21 17:06 psifertex

Thanks!

Do you know a short summary of what has changed? Mainly the obfuscation, or other parts of the protocol? (I'll read through it in detail when I get a chance to unbox my Gan 356 i Carry.)

lgarron avatar Jun 24 '21 19:06 lgarron

From talking to the developer, it's a new protocol that's push based instead of polling based. Much better designed which is nice, but not just a trivial change to the existing code either. The benefit of that repo besides the example code is that the encryption mechanism is already solved.

psifertex avatar Jun 24 '21 20:06 psifertex

The primary difficulty for Web Bluetooth is the cube protocol's reliance on device's MAC which is not accessible via Web Bluetooth. I solve this in Cubeast by asking the user to perform 5 turns and then bruteforcing the MAC address by assuming the first 4 bytes are always the same.

tomekpiotrowski avatar Jun 25 '21 15:06 tomekpiotrowski

Yeah, I had noticed that (though I've noticed I have to re-enter it more than I'd expect -- is it only saved in localstorage as opposed to the account config?)

I'm surprised you assume four fixed bytes though. I would think that three bytes is still brute-able but I guess it's much quicker that way. Do you have debugging on cubeast to report if the search fails to find a valid mac/key so you'd know whether they start using macs outside that 65k range? Also, I'm curious why five turns are required? It seems like if you're using the mac brute force in isolation a single packet should suffice. There's another potential attack possible as they're using AES CBC but re-initializing each time which turns it into a simple repeated XOR which I wondered if you were using with the repeat of five.

Do you have any more insight into the code they're using as it definitely looks like they lifted some OEM standard BTLE stack code (given the re-used GUIDs) and know the ranges are likely to be restricted to a smaller space of the "local" MAC chunks?

psifertex avatar Jun 25 '21 17:06 psifertex

Yeah, I had noticed that (though I've noticed I have to re-enter it more than I'd expect -- is it only saved in localstorage as opposed to the account config?)

The MAC is bound to a given device (stored server-side), so you should have to only do it once. Did you remove the device in your settings?

I'm surprised you assume four fixed bytes though. I would think that three bytes is still brute-able but I guess it's much quicker that way.

I can brute force 2 bytes in under a second. 3 bytes could be few minutes. I'm sure this could be optimized. I'm doing that search server-side where I use Ruby... but once I realized the first 4 bytes are fixed I didn't really care.

Do you have debugging on cubeast to report if the search fails to find a valid mac/key so you'd know whether they start using macs outside that 65k range?

As an extra precaution I also search values 1-16 on the first byte if nothing is found for 0, however I currently have 242 macs from my users in my db and not a single one is non-zero on the 4th byte. No users have reported any issues.

Also, I'm curious why five turns are required? It seems like if you're using the mac brute force in isolation a single packet should suffice.

Actually there's a limited number of checks you can do on a single packet. During my testing I even got false positives on 2 packet checks. I think 3 packets would suffice, but I just wanted to be extra cautious.

This is the Ruby method I use to check if a packet 'looks ok':

  def is_valid?(packet)
    message_type = read_bit_value(0, 0, 4, 1, packet)[0]
    return false if message_type != 2

    turns = read_bit_value(1, 4, 5, 7, packet)
    turns.all? { |turn| turn < 17 }
  end

Probability of this returning true on a random packet is 2/16 (message_type) * (17/32)^7 (turns) ~ 0.0015. There's also the sequence number in there which I'm not using, but probably could. Intervals seem to be unusable here as their values are not restricted by anything. So basically with a 1 packet check like mine you'd get multiple false positives in every single full search. 2 packets seem to me like an absolute minimum and 3 to feel comfortable. I decided to go with 5 to be sure.

There's another potential attack possible as they're using AES CBC but re-initializing each time which turns it into a simple repeated XOR which I wondered if you were using with the repeat of five.

I was not using that. That's a good point, thanks!

Do you have any more insight into the code they're using as it definitely looks like they lifted some OEM standard BTLE stack code (given the re-used GUIDs) and know the ranges are likely to be restricted to a smaller space of the "local" MAC chunks?

Nope.

tomekpiotrowski avatar Jun 25 '21 17:06 tomekpiotrowski

Thanks, that was very helpful! Also, I realized after dumping some of the raw data that I was conflating MAC with the device key field which isn't the same thing.

Another possibility is that the unique parts of the key are actually mapped into the device's unique name. Wouldn't be surprised if it's just a bitpacked version of those two bytes and there's something like 6bits per character encoded in the name.

psifertex avatar Jun 25 '21 19:06 psifertex

Lol, yup apparently Rusty (@d0ntpanic from the original repo) just figured it out: The three unique characters in the device name map to the key: char1 * 62^2 + char2 * 62 + char3

All you need is the device name. Though we've noticed that there's apparently some race condition where sometimes the name itself is masked with XXX values. But once you get that you don't need to brute force anything.

psifertex avatar Jun 25 '21 19:06 psifertex

Here's a quick python snippet to demo it working:

import string
mycube = 'GANic97n'
chars = [x for x in string.digits + string.ascii_uppercase + string.ascii_lowercase]
key = 0
for counter, char in enumerate(mycube[-3:]):
    key += chars.index(char) * 62**(2-counter)
print(hex(key))

Also, for what it's worth, this does align with your observation that only two bytes are actually used -- though I suppose they could just lengthen the string if they really wanted to if they end up selling more than 65k cubes which is likely why that third byte is simply zero now.

psifertex avatar Jun 25 '21 21:06 psifertex

As for the name thing, mine is ALWAYS listed as: GANicXXX - I can't remember seeing anything else. Seems like this is the case for some, not sure if this is only version 3.10 of the firmware (ziicube first batch). Cubeast currently has a majority of 3.8 (The Cubicle / CSC batch), followed by 3.10 (and 3.12). Pretty annoying that we can't fix cubes with software updates. Supposedly the losing connection cubes (which was guessed to be battery / silicone in battery compartment) are all the version 3.8, where the firmware crashes. That is why removing/reseating battery works - causes hard reboot. They need a hardware reboot button as well, and a pin to press it. There are users who got their 3.8 replaced with 3.8 and did not lube 2nd and still same issues so likely not hardware, but software issue.

povlhp avatar Jun 30 '21 06:06 povlhp

The name is apparently listed in a separate field as well so even when the cube itself shows up with XXX in the connection list I've noticed that https://github.com/D0ntPanic/tpscube/blob/rust_rewrite is still able to connect to it without any brute forcing.

If you want to test if that implementation will work for your 3.8, I've added some basic developer docs (note that you don't even have to do most of that if you're not building for web). Alternatively I can throw a native build somewhere online if you let me know your platform.

psifertex avatar Jun 30 '21 14:06 psifertex

A non-web-bluetooth implementation will not need any bruteforcing probably because it'll either have access to the Bluetooth MAC through the Bluetooth API it's using or by accessing the device's BLE advertising packet where one can also find the MAC.

tomekpiotrowski avatar Jun 30 '21 14:06 tomekpiotrowski

Ahh that's right, forgot about that bit. Rusty mentioned that too, I just wasn't thinking. Thanks for the reminder.

psifertex avatar Jun 30 '21 15:06 psifertex

We should set up some kind of a 'Bluetooth cube hackers' group to coordinate our efforts in the future :)

tomekpiotrowski avatar Jun 30 '21 15:06 tomekpiotrowski

Reading here and following links I can see the arguments against MAC But maybe some 3rd party browser will expose it.

https://stackoverflow.com/questions/45742331/how-can-i-get-the-id-or-mac-address-of-my-android-bluetooth-device

povlhp avatar Jun 30 '21 19:06 povlhp

Just in case some of you guys are working on an implementation of this. It seems that Gan have broadened the range of the MAC addresses they use and I now have to search through more values of the 1st byte of the non-manufacturer part of the MAC address (I'm currently searching through ranges 0-3, and 90-93). Luckily it still doesn't impact user experience much.

To be honest I don't understand why they even use the MAC address as the key. They could use an arbitrary 6 byte number and that would make brute forcing pretty much impossible and would make it impossible for tools using Web Bluetooth (like Cubeast) to connect to those Gan cubes. Maybe we have some friends at Gan after all?

tomekpiotrowski avatar Feb 20 '22 22:02 tomekpiotrowski

Then we'd just need to pull the implementation out of the app. Not like they can completely stop it when they give you both sides of the connection... They can only make it more or less difficult.

psifertex avatar Feb 21 '22 16:02 psifertex

@psifertex No. In this case, because Web Bluetooth can't access BLE Manufacturer Data yet, if they hide the encryption key in there, then there's nothing you can do from Web Bluetooth.

tomekpiotrowski avatar Feb 21 '22 17:02 tomekpiotrowski

Ahh, right, purely from a web stack that's true. That said, it would still be possible to query it from another device with native access and then use the extracted key in a session. Would be a pain but a one-time setup step per-cube.

psifertex avatar Feb 21 '22 17:02 psifertex

Yes. That'd be the only option.

tomekpiotrowski avatar Feb 21 '22 17:02 tomekpiotrowski

Has support been added in the last couple of years that this ticket has been open?

CSTimer now supports the i carry. It asks for the MAC address when connected, when is easily found manually (Chrome, for example will show the MAC address).

newzealandpaul avatar Jun 30 '23 09:06 newzealandpaul

Hi All, I need help as I'm struggling a lot for long time to decrypt the movement data of my GAN i Carry. I'm not sure which one is device key and how to derive the key & IV. And how to decrypt it. I tried AES CBC some online decryptor and in Python but both are giving different output using same input/key/IV. PFB my cube details. Your help is highly appreciated. Thank you in advance. MAC AB:12:34:5F:BC:86 device name GANicKCU Found ab:12:34:5f:bc:86 'GANicKCU' Connecting ... Connected Discovering attributes ... Attributes discovered

Device name: GANicXXX Appearance: 0x0

Service 1800 Characteristic 2a00, properties 0xA, value 0x47414E6963585858 Descriptor 2803, value 0x0A0500012A Descriptor 2a01, value 0x0000 Characteristic 2a01, properties 0xA, value 0x0000 Descriptor 2803, value 0x020700042A Descriptor 2a04, value 0x500050000000FA00 Characteristic 2a04, properties 0x2, value 0x500050000000FA00 Descriptor 2803, value 0x020900C92A Descriptor 2ac9, value 0x Characteristic 2ac9, properties 0x2 Service 1801 Characteristic 2a05, properties 0x22, value 0x0100FFFF Descriptor 2902, value 0x0000 Service f95a48e6-a721-11e9-a2a3-022ae2dbcce4 Characteristic f95a4b66-a721-11e9-a2a3-022ae2dbcce4, properties 0x2, value 0x312E312E324E31000000000000000000 Descriptor 2803, value 0x081200E4CCDBE22A02A3A2E91121A734505AF9 Descriptor cce4, value 0x Descriptor 11e9, value 0x Descriptor 180a, value 0x Characteristic f95a5034-a721-11e9-a2a3-022ae2dbcce4, properties 0x8 Descriptor 2803, value 0x121400E39A5C880381E0915B4EFC816DFF4CEC Descriptor 9ae3, value 0x Descriptor 4e5b, value 0x Descriptor 180a, value 0x Characteristic ec4cff6d-81fc-4e5b-91e0-8103885c9ae3, properties 0x12 Descriptor 2902, value 0x Service 6e400001-b5a3-f393-e0a9-e50e24dc4179 Characteristic 28be4a4a-cd67-11e9-a32f-2a2ae2dbcce4, properties 0xC Descriptor 2803, value 0x101A00E4CCDBE22A2A2FA3E91167CDB64CBE28 Descriptor cce4, value 0x Descriptor 11e9, value 0x Descriptor 180a, value 0x Characteristic 28be4cb6-cd67-11e9-a32f-2a2ae2dbcce4, properties 0x10 Descriptor 2902, value 0x0100

probudhabishayee avatar Oct 31 '23 12:10 probudhabishayee