Welcome to Lab Notes an experiment in publishing unpolished, annotated research notes. I often find that I have multiple projects which take a long time to see the light of day, even when people would get some use out of them in even a partial sate. To that end I am releasing the first set of notes in my sex toy security project.

Target: We-Vibe Nova

The Nova is a dual-motor vibrator with combined "G-spot stimulation with powerful clitoral vibration."

Setting Up

The Nova communicates with the We-Connect app over Bluetooth LE. Our first goal was to identify the MAC address of the device.

    sarah@lab ~> sudo hcitool lescan
    [sudo] password for sarah: 
    LE Scan ...
    40:16:3B:2A:00:8A (unknown)
    79:2F:04:B7:B5:31 (unknown)
    7F:10:B2:10:46:26 (unknown)
    20:91:48:1E:16:BD  (Nova)

Now we just need to connect to the device.

    gatttool -b 20:91:48:1E:16:BD --interactive
    [20:91:48:1E:16:BD][LE]> connect
    Attempting to connect to 20:91:48:1E:16:BD
    Connection successful
    [20:91:48:1E:16:BD][LE]>

At this point we can do some simple reconnaissance on the device. The first thing we would like to do is get a list of all the characteristics:

    [20:91:48:1E:16:BD][LE]> char-desc
    handle: 0x0001, uuid: 00002800-0000-1000-8000-00805f9b34fb
    handle: 0x0002, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0003, uuid: 00002a00-0000-1000-8000-00805f9b34fb
    handle: 0x0004, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0005, uuid: 00002a01-0000-1000-8000-00805f9b34fb
    handle: 0x0006, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0007, uuid: 00002a02-0000-1000-8000-00805f9b34fb
    handle: 0x0008, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0009, uuid: 00002a03-0000-1000-8000-00805f9b34fb
    handle: 0x000a, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x000b, uuid: 00002a04-0000-1000-8000-00805f9b34fb
    handle: 0x000c, uuid: 00002800-0000-1000-8000-00805f9b34fb
    handle: 0x000d, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x000e, uuid: 00002a05-0000-1000-8000-00805f9b34fb
    handle: 0x000f, uuid: 00002902-0000-1000-8000-00805f9b34fb
    handle: 0x0010, uuid: 00002800-0000-1000-8000-00805f9b34fb
    handle: 0x0011, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0012, uuid: 00002a23-0000-1000-8000-00805f9b34fb
    handle: 0x0013, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0014, uuid: 00002a24-0000-1000-8000-00805f9b34fb
    handle: 0x0015, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0016, uuid: 00002a25-0000-1000-8000-00805f9b34fb
    handle: 0x0017, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0018, uuid: 00002a26-0000-1000-8000-00805f9b34fb
    handle: 0x0019, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x001a, uuid: 00002a27-0000-1000-8000-00805f9b34fb
    handle: 0x001b, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x001c, uuid: 00002a28-0000-1000-8000-00805f9b34fb
    handle: 0x001d, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x001e, uuid: 00002a29-0000-1000-8000-00805f9b34fb
    handle: 0x001f, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0020, uuid: 00002a2a-0000-1000-8000-00805f9b34fb
    handle: 0x0021, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0022, uuid: 00002a50-0000-1000-8000-00805f9b34fb
    handle: 0x0023, uuid: 00002800-0000-1000-8000-00805f9b34fb
    handle: 0x0024, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0025, uuid: f000c000-0451-4000-b000-000000000000
    handle: 0x0026, uuid: 00002901-0000-1000-8000-00805f9b34fb
    handle: 0x0027, uuid: 00002803-0000-1000-8000-00805f9b34fb
    handle: 0x0028, uuid: f000b000-0451-4000-b000-000000000000
    handle: 0x0029, uuid: 00002902-0000-1000-8000-00805f9b34fb

Two characteristics stand out from the crowd 0x25 and 0x28 - we will come back to them later.

For now though let's see if there is any low hanging fruit

    ...snip...
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x3
    Characteristic value/descriptor: 4e 6f 76 61

4e 6f 76 61 correspond to ascii "Nova". Looks like we are in the right place.

The rest of the characteristics are pretty bland e.g. serial numbers etc. so let's revisit those 2 special handles.

    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x25
    Characteristic value/descriptor: 00 00 00 00 00 00 00 00
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 04 00 85 f2 49 35 00 70

0x28 looks like it contains some device information, at this point the device was in rest mode - so I decided to switch the modes and recheck each time:

    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 04 00 85 f2 49 35 00 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 04 85 f2 49 33 09 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 85 f2 49 33 ff 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 0e 37 f1 49 2c 05 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 0f 37 f1 49 2c 05 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 08 37 f1 49 2a 98 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 06 37 f1 49 29 50 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 07 37 f1 49 2c cc 70 

It looks like the second byte in 0x28 corresponds to the mode. I flicked the modes back and forth to confirm this.

Also take a look at the 4th byte, it decreases by 1 during my burst of testing...could this be the battery indicator?

I left the vibe vibrating at full power for a minute then rechecked:

    .. snip ..
    Characteristic value/descriptor: 01 04 0b ef 49 31 00 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 04 0b ef 49 31 00 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 04 0b ef 49 31 00 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    .. snip .. 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 04 ac ec 49 31 00 70  

This certainly looks like a good candidate, I fired up the weconnect app to check the battery level at this point and it read 92% which just so happens to be 236 (0xEC) over 255 (0xFF).

Cool so 3 bytes down. You may have noticed at this point that the 1st, 4th and 8th byte are always 0x01 0x49 and 0x70 respectively. Because they are static we can assume they don't hold any useful information about the status of the device.

At this point, we have 3 bytes left byte 3, byte 5 and byte 6. We have already found vibration mode and battery, the only other functionality provided by the vibrator itself is vibration intensity - which we must assume correlates to 1 of the remaining bytes.

I switched the vibrator to a constant vibe mode and started moving the intensity up and down.

    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a aa 70 
    ... increase vibration..
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a 88 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a 88 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a 88 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    ... decrease vibration ....
    Characteristic value/descriptor: 01 03 a1 e5 49 2a aa 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a aa 70 
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a aa 70 

Looks like byte 7 is a pretty strong contender for vibration. However reviewing previous log you might see that the 2 nibbles aren't always the same. This is because the vibe has 2 motors, and the intensity of each one can be controlled individually - but only through the app (we will come back to this).

At this point our understanding of characteristic 25 is as folllows:

         Battery Use
             |_
              | 
    01 03  c0 ff  49 28 40 70
       |                |
       |                |
      Mode      Vibration Intensity

But we still have 2 bytes left - but we need more control over our inputs to fully understand them. It's time to start controlling the device.

Understanding the Control Protocol

When we first examined 0x25 we saw it was empty. We know that one of these characteristics must be used to send commands to the device, and that 0x28 was for reading...

After some playing around sending incremental values I discovered that would stop the device.

    [20:91:48:1E:16:BD][LE]> char-write-req 0x25 0F

And that

    [20:91:48:1E:16:BD][LE]> char-write-req 0x25 0F03

Would switch the device to mode 0x03 as reflected in 0x25. This is awesome! It means we can change the modes.

No value in byte 3 seemed to impact the device at all, however sending values to byte 4 changed the vibration speed:

    [20:91:48:1E:16:BD][LE]> char-write-req 0x25 0F0300CC
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a 11 70        
    [20:91:48:1E:16:BD][LE]> char-write-req 0x25 0F030055
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a DD 70  

Some more trial and error uncovered that using this method I could give different values to the 2 motors:

    [20:91:48:1E:16:BD][LE]> char-write-req 0x25 0F0300C5
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a 1D 70  
    [20:91:48:1E:16:BD][LE]> char-write-req 0x25 0F03005C
    [20:91:48:1E:16:BD][LE]> char-read-hnd 0x28
    Characteristic value/descriptor: 01 03 a1 e5 49 2a D1 70  

Through this process I also noticed a pattern with the 3rd byte. It also decreased over time until byte 4 decreased, at which point it would set to a higher number. I theorized that this meant that battery indicator was actually 2 bytes (b[4] << 8) + b[3]. I was able to prove this later on by decompiling the weconnect apk and finding reference to this exact calculation.

Finally, through more trial and error, I discovered that while bytes 5,7 and 8 in 0x25 had no impact on the device, sending certain values to byte 6 seemed to also impact vibration intensity.

At first I couldn't see the pattern. Setting the value to 0x03 made it intense, setting it to 0x04 made the vibrator stop, setting it to 0x05 made it vibrate again by quiet, 0x06 was quiet again and 0x7 made it intense.

It was then I realized that the vibrator was only using the last 2 bits of the 6th byte - and was using those bits to set the status of the 2 motors! This meant that I could now set the vibration intensity and whether the motor was on or off!

Finally I was able to compile a specification for the command protocol:

           Sets Internal (A) and 
         External (B) Motor Intensity   
                    |
          0F 03 00 AB 00 0Z 00 00
              |           |
          Vibration     Last 2 bits turn off/on A & B
            Mode

But what about byte 6 on 0x28?

By now you may be asking...what about 0x28 we didn't finish understanding it...and you would be right! At the time of writing the specification for 0x28 looks like this:

    Battery Use is (((b[3] << 8) + b[2]) / 65535) * 100
            _|_
           |   |
    01 03  c0 ff  49 28 40 70
       |             |_ _|
       |               |
      Mode      Vibration Intensity / Motor Status

Byte 6 is still somewhat of an enigma, it seems to be partially related to to the status of motors, but also seems to be impacted by mode. The first nibble is always a 1,2, or 3 as far as I can tell, and the second nibble seems to fluctuate based on motor status, but I have been unable to nail down the pattern.