When I had to write (in a hurry) my comments on Abbott’s DMCA notice to GitHub, I note that I have not had a chance to get my hands onto the Libre 2 system yet, because it’s not sold in the UK. After that, Benjamin from MillionFriends reached out to me to hear if I would be interested in giving a try to figure out how the reader itself speaks with the software. I gladly obliged, and spent most of the time I had during a sick week to get an idea of where we are with this.
While I would lie if I said that we’re now much closer to be able to download data from a Libre 2 reader, I can at least say that we have some ideas of what’s going on right now. So let me try to introduce the problem a second, because there’s a lot of information out there, and while there’s some of it that is quite authoritative (particularly as a few people have been reverse engineering the actual binaries), a lot of it is guesswork and supposition.
The Libre and Libre 2 systems are very similar — they both have sensors, and they both have readers. Technically, you could say that the mobile app (for Android or iOS) also makes up part of the system. The DMCA notice from Abbott that stirred so much trouble above was related to modifications to the mobile application — but luckily for me, my projects and my interests lay quite far away from that. The sensors can be “read” by their respective reader devices, or with a phone with the correct app on it. When reading the sensors with the reader, the reader itself stores the historical data and a bunch more information. You can download that data from the reader onto a computer with Windows or macOS with the official software from Abbott (assuming you can download a version of it that works for you, I couldn’t find a good download page for this on the UK website, but I was provided an EU version of the software as well.)
For the Libre system, I have attempted reversing the protocol, but ultimately it was someone else contributing the details of an usable protocol that I implemented in glucometerutils. For the Libre 2, the discussion already suggested it wouldn’t be the same protocol, and that encryption was used by the Libre 2 reader. As it turns out, Abbott really does not appear to appreciate customers having easy access to their data, or third parties building tools around their devices, and they started encrypting the communication between the sensors and the reader or app in the new system.
So what’s the state with the Libre 2? Well, one of the good news is that the software that I was given works with both the Libre and Libre 2 systems, so I could compare the USB captures on both systems, and that will (as I’ll show in a moment) help significantly. It also showed that the basics of the Abbott HID protocol were maintained: most of the handshake, the message types and the maximum message size. Unfortunately it was clear right away that indeed most of the messages exchanged were encrypted, because the message length made no sense (anything over 62 as length was clearly wrong).
Now, the good news is that I have built up enough interfaces in usbmon-tools that I could use to build a much more reusable extractor for the FreeStyle protocol, and introduce more of the knowledge of the encryption to it, so that it can be used for others. Being able to release these extraction tools I write, instead of just using them myself and letting them rot, was my primary motivation behind building usbmon-tools, so you could say that that’s an achieved target.
So earlier I said that I was lucky the software works with both the Libre and Libre 2 readers. The reason why that was luck, it’s because it shows that the sequence of operations between the two is nearly the same, and that some of the messages are not actually encrypted on the Libre 2 either (namely, the keepalive messages). Here’s the handshake from my Libre 1 reader:
[ 04] H>>D 00000000:
[ 34] H<<D 00000000: 16 .
[ 0d] H>>D 00000000: 00 00 00 02 ....
[ 05] H>>D 00000000:
[ 15] H>>D 00000000:
[ 06] H<<D 00000000: 4A 43 4D 56 30 32 31 2D 54 30 38 35 35 00 JCMV021-T0855.
[ 35] H<<D 00000000: 32 2E 31 2E 32 00 2.1.2.
[ 01] H>>D 00000000:
[ 71] H<<D 00000000: 01 .
[ 21] H>>D 00000000: 24 64 62 72 6E 75 6D 3F $dbrnum?
[ 60] H<<D 00000000: 44 42 20 52 65 63 6F 72 64 20 4E 75 6D 62 65 72 DB Record Number
[ 60] H<<D 00000010: 20 3D 20 33 37 32 39 38 32 0D 0A 43 4B 53 4D 3A = 372982..CKSM:
[ 60] H<<D 00000020: 30 30 30 30 30 37 36 31 0D 0A 43 4D 44 20 4F 4B 00000761..CMD OK
[ 60] H<<D 00000030: 0D 0A ..
[ 0a] H>>D 00000000: 00 00 37 C6 32 00 34 ..7.2.4
[ 0c] H<<D 00000000: 01 00 18 00 ....
Code language: JavaScript (javascript)
Funnily enough, while this matches the sequence that Xavier described for the Insulinx, and that I always reused for the other devices too, I found that most of this exchange is for the original software to figure out which device you connected. And since my tools require you to know which device you’re using, I actually cleaned up the FreeStyle support code in glucometerutils to shorten the initialization sequence.
To describe the sequence in prose, the software is requesting the serial number and software version of the reader (commands 0x05 and 0x15), then initializing (0x01) and immediately using the text command $dbrnum?
to know how much data is stored on the device. Then it starts using the “binary mode” protocol that I started on years ago, but never understood.
For both the systems, I captured the connection establishment, from when the device is connected to the Windows virtual machine to the moment when you can choose what to do. The software is only requesting a minimal amount of data, but it’s still quite useful for comparison. Indeed, you can see that after using the binary protocol to fetch… something, the software sends a few more text commands to confirm the details of the device:
[ 0b] H<<D 00000000: 12 3C AD 93 0A 18 00 00 00 00 00 9C 2D EA 00 00 .<..........-...
[ 0b] H<<D 00000010: 00 00 00 0E 00 00 00 00 00 00 00 7C 2A EA 00 00 ...........|*...
[ 0b] H<<D 00000020: 00 00 00 16 00 00 00 00 00 00 00 84 2D EA 00 21 ............-..!
[ 0b] H<<D 00000030: C2 25 43 .%C
[ 21] H>>D 00000000: 24 70 61 74 63 68 3F $patch?
[ 0d] H>>D 00000000: 3D 12 00 00 =...
[ 60] H<<D 00000000: 4C 6F 67 20 45 6D 70 74 79 0D 0A 43 4B 53 4D 3A Log Empty..CKSM:
[ 60] H<<D 00000010: 30 30 30 30 30 33 36 38 0D 0A 43 4D 44 20 4F 4B 00000368..CMD OK
[ 60] H<<D 00000020: 0D 0A ..
[ 21] H>>D 00000000: 24 73 6E 3F $sn?
[ 60] H<<D 00000000: 4A 43 4D 56 30 32 31 2D 54 30 38 35 35 0D 0A 43 JCMV021-T0855..C
[ 60] H<<D 00000010: 4B 53 4D 3A 30 30 30 30 30 33 32 44 0D 0A 43 4D KSM:0000032D..CM
[ 60] H<<D 00000020: 44 20 4F 4B 0D 0A D OK..
[ 21] H>>D 00000000: 24 73 77 76 65 72 3F $swver?
[ 60] H<<D 00000000: 32 2E 31 2E 32 0D 0A 43 4B 53 4D 3A 30 30 30 30 2.1.2..CKSM:0000
[ 60] H<<D 00000010: 30 31 30 38 0D 0A 43 4D 44 20 4F 4B 0D 0A 0108..CMD OK..
Code language: PHP (php)
My best guess on why it’s asking again for serial number and software version, is that the data returned during the handshake is only used to select which “driver” implementation to use, while this is used to actually fill in the descriptor to show to the user.
If I look at the capture of the same actions with a Libre 2 system, the initialization is not quite the same:
[ 05] H>>D 00000000:
[ 06] H<<D 00000000: 4D 41 47 5A 31 39 32 2D 4A 34 35 35 38 00 MAGZ192-J4558.
[ 14] H>>D 00000000: 11 .
[ 33] H<<D 00000000: 16 B1 79 F0 A1 D8 9C 6D 69 71 D9 1A C0 1A BC 7E ..y....miq.....~
[ 14] H>>D 00000000: 17 6C C8 40 58 5B 3E 08 A5 40 7A C0 FE 35 91 66 .l.@X[>..@z..5.f
[ 14] H>>D 00000010: 2E 01 37 88 37 F5 94 71 79 BB ..7.7..qy.
[ 33] H<<D 00000000: 18 C5 F6 DF 51 18 AB 93 9C 39 89 AC 01 DF 32 F0 ....Q....9....2.
[ 33] H<<D 00000010: 63 A8 80 99 54 4A 52 E8 96 3B 1B 44 E4 2A 6C 61 c...TJR..;.D.*la
[ 33] H<<D 00000020: 00 20 .
[ 04] H>>D 00000000:
[ 0d] H>>D 00000000: 00 00 00 02 ....
[ 34] H<<D 00000000: 16 .
[ 05] H>>D 00000000:
[ 15] H>>D 00000000:
[ 06] H<<D 00000000: 4D 41 47 5A 31 39 32 2D 4A 34 35 35 38 00 MAGZ192-J4558.
[ 35] H<<D 00000000: 31 2E 30 2E 31 32 00 1.0.12.
[ 01] H>>D 00000000:
[ 71] H<<D 00000000: 01 .
[x21] H>>D 00000000: 66 C2 59 40 42 A5 09 07 28 45 34 F2 FB 2E EC B2 f.Y@B...(E4.....
[x21] H>>D 00000010: A0 BB 61 8D E9 EE 41 3E FC 24 AD 61 FB F6 63 34 ..a...A>.$.a..c4
[x21] H>>D 00000020: 7B 7C 15 DB 93 EA 68 9F 9A A4 1E 2E 0E DE 8E A1 {|....h.........
[x21] H>>D 00000030: D6 A2 EA 53 45 2F A8 00 00 00 00 17 CF 84 64 ...SE/........d
[x60] H<<D 00000000: 7D C1 67 28 0E 31 48 08 2C 99 88 04 DD E1 75 77 }.g(.1H.,.....uw
[x60] H<<D 00000010: 34 5A 88 CA 1F 6D 98 FD 79 42 D3 F2 4A FB C4 E8 4Z...m..yB..J...
[x60] H<<D 00000020: 75 C0 92 D5 92 CF BF 1D F1 25 6A 78 7A F7 CE 70 u........%jxz..p
[x60] H<<D 00000030: C2 0F B9 A2 86 68 AA 00 00 00 00 F9 DE 0A AA .....h.........
[x0a] H>>D 00000000: 9B CA 7A AF 42 22 C6 F2 8F CA 0E 58 3F 43 9C AB ..z.B".....X?C..
[x0a] H>>D 00000010: C7 4D 86 DF ED 07 ED F4 0B 99 D8 87 18 B5 8F 76 .M.............v
[x0a] H>>D 00000020: 69 50 4F 6C CE 86 CF E1 6D 9C A1 55 78 E0 AF DE iPOl....m..Ux...
[x0a] H>>D 00000030: 80 C6 A0 51 38 32 8D 01 00 00 00 62 F3 67 2E ...Q82.....b.g.
[ 0c] H<<D 00000000: 01 00 18 00 ....
[x0b] H<<D 00000000: 80 37 B7 71 7F 38 55 56 93 AC 89 65 11 F6 7F E6 .7.q.8UV...e....
[x0b] H<<D 00000010: 31 03 3E 15 48 7A 31 CC 24 AD 02 7A 09 62 FF 9C 1.>.Hz1.$..z.b..
[x0b] H<<D 00000020: D4 94 02 C9 5F FF F2 7B 3B AC F0 F7 99 1A 31 5A ...._..{;.....1Z
[x0b] H<<D 00000030: 00 B8 7B B7 CD 4D D4 01 00 00 00 E2 D4 F1 13 ..{..M.........
The 0x14/0x33 command/response are new — and they clearly are used to set up the encryption. Indeed, trying to send out a text command without having issued these commands has the reader respond with a 0x33 reply that I interpret as a “missing encryption” error.
But you can also see that there’s a very similar structure to the commands: after the initialization, there’s an (encrypted) text command (0x21) and response (0x60), then there’s an encrypted binary command, and more encrypted binary responses. Funnily enough, that 0x0c response is not encrypted, and the sequence of responses of the same type is very similar between the Libre 1 and Libre 2 captures as well.
The similarities don’t stop here. Let’s look at the end of the capture:
[x0b] H<<D 00000000: A3 F6 2E 9D 4E 13 68 EB 7E 37 72 97 6C F9 7B D6 ....N.h.~7r.l.{.
[x0b] H<<D 00000010: 1F 7B FB 6A 15 A8 F9 5F BD EC 87 BC CF 5E 16 96 .{.j..._.....^..
[x0b] H<<D 00000020: EB E7 D8 EC EF B5 00 D0 18 69 D5 48 B1 D0 06 A6 .........i.H....
[x0b] H<<D 00000030: 30 1E BB 9B 04 AC 93 DE 00 00 00 B6 A2 4D 23 0............M#
[x21] H>>D 00000000: CB A5 D7 4A 6C 3A 44 AC D7 14 47 16 15 40 15 12 ...Jl:D...G..@..
[x21] H>>D 00000010: 8B 7C AF 15 F1 28 D1 BE 5F 38 5A 4E ED 86 7D 20 .|...(.._8ZN..}
[x21] H>>D 00000020: 1C BA 14 6F C9 05 BD 56 63 FB 3B 2C EC 9E 3B 03 ...o...Vc.;,..;.
[x21] H>>D 00000030: 50 B1 B4 D0 F6 02 92 14 00 00 00 CF FA C2 74 P.............t
[ 0d] H>>D 00000000: DE 13 00 00 ....
[x60] H<<D 00000000: CE 96 6D CD 86 27 B4 AC D9 46 88 90 C0 E7 DB 4A ..m..'...F.....J
[x60] H<<D 00000010: 8D CC 8E AA 5F 1B B6 11 4E A0 2B 08 C0 01 D5 D3 ...._...N.+.....
[x60] H<<D 00000020: 7A E9 8B C2 46 4C 42 B8 0C D7 52 FA E0 8F 58 32 z...FLB...R...X2
[x60] H<<D 00000030: DE 6C 71 3F BE 4E 9A DF 00 00 00 7E 38 C6 DB .lq?.N.....~8..
[x60] H<<D 00000000: 11 06 1C D2 5A AC 1D 7E E3 4C 68 B2 83 73 DF 47 ....Z..~.Lh..s.G
[x60] H<<D 00000010: 86 05 4E 81 99 EC 29 EA D8 79 BA 26 1B 13 98 D8 ..N...)..y.&....
[x60] H<<D 00000020: 2D FA 49 4A DF DD F9 5E 2D 47 29 AB AE 0D 52 77 -.IJ...^-G)...Rw
[x60] H<<D 00000030: 2E EB 42 EC 7E CF BB E0 00 00 00 FE D4 DC 7E ..B.~.........~
… Yeah many more encrypted messages …
[x60] H<<D 00000000: 53 FE E5 56 01 BB C2 A7 67 3E A6 AB DB 8E B7 13 S..V....g>......
[x60] H<<D 00000010: 6D F7 80 5C 06 23 09 3E 49 B4 A7 8B D3 61 92 C9 m..\.#.>I....a..
[x60] H<<D 00000020: 72 1D 5A 04 AE E3 3E 05 2E 1B C7 7C 42 2D F8 42 r.Z...>....|B-.B
[x60] H<<D 00000030: 37 88 7E 16 D9 34 8B E9 00 00 00 11 EE 42 05 7.~..4.......B.
[x21] H>>D 00000000: 01 84 3F 02 36 1E A6 82 E2 C5 BF C2 40 78 B9 CD ..?.6.......@x..
[x21] H>>D 00000010: E9 55 17 BE E9 16 8A 52 D2 D9 85 69 E4 D5 96 7A .U.....R...i...z
[x21] H>>D 00000020: 55 6D DF 2E AF 96 36 53 64 C5 C7 D1 B6 6F 1A 1A Um....6Sd....o..
[x21] H>>D 00000030: 4F 2F 25 FF 58 F4 EE 15 00 00 00 F6 9A 52 64 O/%.X........Rd
[x60] H<<D 00000000: 19 F4 D4 F0 66 11 E3 CE 47 DE 82 87 22 48 3C 8D ....f...G..."H<.
[x60] H<<D 00000010: BA 2D C0 37 12 25 CD AB 3A 58 C2 C4 01 88 60 21 .-.7.%..:X....`!
[x60] H<<D 00000020: 15 1E D1 EE F2 90 36 CA B0 93 92 34 60 F5 89 E0 ......6....4`...
[x60] H<<D 00000030: 64 3C 20 39 BF 4C 98 EA 00 00 00 A1 CE C5 61 d< 9.L........a
[x21] H>>D 00000000: D5 89 18 22 97 34 CB 6E 76 C5 5A 23 48 F4 5E C6 ...".4.nv.Z#H.^.
[x21] H>>D 00000010: 0E 11 0E C9 51 BD 40 D7 81 4A DF 8A 0B EF 28 82 ....Q.@..J....(.
[x21] H>>D 00000020: 1F 14 47 BC B8 B8 FA 44 59 7A 86 14 14 4B D7 0F ..G....DYz...K..
[x21] H>>D 00000030: 37 48 CC 1F C5 A2 9E 16 00 00 00 00 A3 EE 69 7H............i
[x60] H<<D 00000000: 62 33 4B 90 3B 68 3A D1 01 B1 15 4C 48 A1 6E 20 b3K.;h:....LH.n
[x60] H<<D 00000010: 12 6F BC D5 50 33 9E C3 CC 35 4E C8 46 81 3E 6B .o..P3...5N.F.>k
[x60] H<<D 00000020: 96 17 DF D5 8C 22 5C 3A B7 52 C2 D9 37 71 B7 E2 ....."\:.R..7q..
[x60] H<<D 00000030: 5F C4 88 81 2A 91 65 EB 00 00 00 69 E2 A8 DE _...*.e....i...
Code language: PHP (php)
These are once again text commands. In particular one of them gets a response that is long enough to span multiple encrypted text responses (0x60). Given that the $patch?
command on the Libre 1 suggested it’s a multirecord command, it might be that the Libre 2 actually has a long list of patches.
So my best guess of this is that, aside for the encryption, the Libre 2 and Libre 1 systems are actually pretty much the same. I’m expecting that the only thing between us and being able to download the data out of a Libre 2 system is to figure out the encryption scheme and whether we need to extract keys to be able to do so. In the latter case that is something we should proceed carefully with, because it’s probably going to be the way Abbott is going to enforce their DMCA requests.
What do we know about the encryption setup, then? Well, I had a theory, but then it got completely trashed. I still got some guesses that for now appear solid.
First of all, the new 0x14/0x33 command/reply: this is called multiple time by the software, and the reader uses the 0x33 response to tell you either the encryption is missing or wrong, so I’m assuming these commands are encryption related. But since there’s more than one meaning for these commands, it looks like the first byte for each of these selects a “sub-command”.
The 0x14,0x11 command appears to be the starting point for the encryption; the device responds with what appears to be 15 random bytes. Not 16! The first byte again appears to be a “typing” specification and is always 0x16. You could say that the 0x14,0x11 command gets a 0x33,0x16 response. In the first three captures I got from the software, the device actually sent exactly the same bytes. Then it started giving a different response for each time I called it. So I guess it might be some type of random value, that needs some entropy to be re-generated. Maybe it’s a nonce for the encryption?
The software then sends a 0x14,0x17 command, which at first seemed to have a number of constant bytes in positions, but now I’m not so sure. I guess I need to compare more captures for that to be the case. But because of the length, there’s at most 25 bytes that are sent to the device.
The 0x33,0x18 response comes back, and it includes 31 bytes, but the last two appear to be constant.
Also if I compare the three captures, two that received the same 0x33,0x16 response, and one that didn’t, there are many identical bytes between the two with the same response (but not all of them!), and very few with the third one. So it sounds like either this is a challenge-response that uses the provided nonces, or it actually uses that value to do the key derivation.
If you’re interested in trying to figure out the possible encryption behind this, the three captures are available on GitHub. And if you find anything else that you want to share with the rest of the people looking at this, please let us know.