After buying a new Philips Sonicare toothbrush I was surprised to see that it reacts to the insertion of a brush head by blinking an LED. A quick online search reveals that the head communicates with the toothbrush handle to remind you when it’s time to buy a new one.
From the Philips product page: seems to be REALLY smart!
Looking at the base of the head shows that it contains an antenna and a tiny black box that is presumably an IC. The next hint can be found in the manual where it says that: “Radio Equipment in this product operates at 13.56 MHz”, which would indicate that it is an NFC tag. And indeed when holding the brush head to my phone it opens a link to a product page: https://www.usa.philips.com/c-m-pe/toothbrush-heads.
Using the NFC Tools app we can learn a lot about this tag:
- It is an NTAG213
- It uses NfcA
- It is password protected
- We can see the link to the Philips webpage
Also using NFC Tools, the memory and memory access conditions can be read:
|0x1F||00:01:07:00||DATA||Readable, write protected by PW|
|0x24||B3:02:02:00||DATA||Readable,write protected by PW|
|0x25||00:00:00:00||DATA||Readable,write protected by PW|
|0x26||00:00:00:00||DATA||Readable,write protected by PW|
|0x27||00:00:00:01||DATA||Readable,write protected by PW|
|0x28||00:03:30:BD||LOCK2 - LOCK4||Readable,write protected by PW|
I repeated this process for one black and two white W DiamondClean brush heads and learned the following:
- Address 0x00-0x02 contains a unique ID and its checksum
- Address 0x04-0x0C contains the link to the Philips store
- Address 0x22 is 31:32:31:34 for black and 31:31:31:31 for white heads respectively
- Address 0x24 contains the total brush time
- All other readable data is identical between all heads
Decoding the stored time
Let’s do an experiment to see what changes happen to the tag when using the toothbrush:
- Read the tag
- When reading a new brush head that has never been in contact with the data at addr. 0x24 is 00:00:02:00.
- Simply attaching it to the handle (without brushing) changes nothing
- Brush for some time
- In this case, I let the toothbrush run for 5s
- Read the tag again
- The data at addr. 0x24 is now 05:00:02:00
- Observe the difference
- Looks like addr. 0x24 saves the number of seconds that the brush head was in use
When the brush is used for more than 255s, this timer rolls over to the second bit (02:01:02:00 -> 258s).
Trying to overwrite the stored time is unfortunately unsuccessful, as this memory address is password protected.
Sniffing the password
Luckily it turns out that the required password is sent over plain text! So all I need to do is to sniff the communication between the toothbrush and the head. After digging out my HackRF software defined radio and some trial and error, I came up with the following workflow.
Record RF signal
When opening gqrx and tuning it to 13.736 MHz while holding the toothbrush close to the antenna, it is visible that the head gets polled multiple times a second. It is a welcome surprise that my simple monopole antenna gets a signal that is strong enough for this purpose. You can download the relevant gqrx configuration file here.
While brushing, the NFC polling takes a brief pause and the first burst of packets that follows updates the time counter. With the ability of gqrx to make I/Q recordings, we can capture the password RF signals like this:
- Turn on the toothbrush
- Start recording
- Turn off the toothbrush
- Stop the recording
The first packets in the file should now contain the password in plain text.
Before this raw I/Q file can be decoded it needs to be converted into a slightly different format to be read by the decoding program.
I created a small gnuradio companion script that applies a lowpass filter and converts the data into a wav file with two channels that contain the real and imaginary components of the complex signal.
Make sure to substitute the correct paths in the source/sink blocks and check the sampling frequency (I used 2MHz).
You can download the script here.
I found the perfect tool for this task called NFC-laboratory.
After opening the newly created WAV file, it should look something like the picture above. In this case, the recording is only good enough to see the communication that goes from host to tag (green arrow). But to sniff the password this is perfect.
When looking at the datasheet for the NTAG213, we can see what is happening:
- Line #0-#6: communication is established with the tags’ unique ID
- Line #7: The toothbrush sends the password (command 0x1B = PWD_AUTH)
- Line #9: The time counter is updated to the new value (command 0xA2 = WRITE)
- All lines below are repeated polling without password authentication or writing anything
So the password for this brush head is 67:B3:8B:98 (underlined in the picture).
Writing to the brush
With the password successfully acquired, it’s now possible to set the counter on the brush head to anything we want by sending the relevant bytes over NFC.
NFC Tools comes to the rescue again:
- Go to Other -> Advanced NFC commands
- Set the I/O Class to NfcA
- Set the data to 1B:67:B3:8B:98,A2:24:00:00:02:00
- Enjoy a factory-new brush head (at least as far as the time counter is concerned)
Here is the breakdown of the command in step 3:
|24||To address 0x24|
|00:00:02:00||Timer set to 0s|
Below you can see the memory of the brush head before and after the custom NFC commands:
With this, the toothbrush is now successfully hacked and we can play around with the timer as we wish.
Here are some interesting observations:
- Only the first two bytes at address 0x24 are used for timekeeping. Once the counter reaches FF:FF:02:00 it stops going up (18 hours of continuous brushing).
- When the stored time is greater than 0x5460 the toothbrush blinks the LED to notify you to change heads. This corresponds to 21’600s -> 180 x 2min -> 3 months of brushing twice a day, which is exactly in line with Philips recommendation to change heads every 3 months.
Password verification protection
You might have noticed the color of the brush head changing throughout of this post. This is because I had to run out and buy a new one after getting locked out of the first one.
When having a close look at the contents of address 0x2A which is 43:00:00:00 and page 18 of the datasheet, we can see that the tag is configured to permanently disable all write access after three wrong password attempts. (Which I promptly exceeded when playing around) This means that not even the toothbrush handle itself can write to this head again.
Unfortunately, the password of every brush head is unique and this process of extracting it with an SDR is quite involved and requires special hardware. At the bottom of page 30 in the datasheet, NXP recommends generating the password from the 7-byte UID. Below are all the UID - password pairs I obtained from my 3 heads:
All my tries to guess to one-way function for generating the passwords failed. Depending on the care that the Philips engineers took, guessing this function could be almost impossible. But if you manage to solve this puzzle, feel free to hit me up with an E-mail.
Update (August 16, 2023)
After publishing this article, I was pleasantly surprised to see it picked up by some big news sites such as Hacker News and Hackaday. The resulting discussions and comments proved to be both enlightening and entertaining. Thanks to everyone who dropped positive comments and messages!
A special shoutout has to go to Aaron Christophel who got inspired by this post to:
- Dump and reverse engineer the Philips Sonicare firmware to extract the password generation algorithm: Video
- Wrote a password generator: GitHub
- And just for fun, he made the toothbrush bust out a Rick Roll
Please go check his content if you are interested in the solution to the puzzle.