I recently bought a Kobo Aura H2O, and while it’s really good to read, I’m a little bit disappointed that I need an account to use the reader. Multiple blog posts like a3nm’s “fnacbook kobo hacking” or mobileread forum threads explain how to avoid registration and tracking, but as they often concern specific hardware, software versions, and data is scattered around many different threads, I think this kind of “review” article may be useful.
Connecting the device to WiFi¶
After language selection, the home page of the Kobo is asking us if we want to configure the device using WiFi or without WiFi.
I chose the first option; the reasoning behind this step is to allow the Kobo to update itself to the last firmware verssion.
After this step, the Kobo will ask for credentials or prompt an account creation, which is what we want to avoid. To do this, we go back to the home screen and select the second option, to configure the Kobo without WiFi.
Mounting the device¶
Using the second option enables the device USB port, and allows us to mount one of the device partition:
usb 1-2: new high-speed USB device number 24 using xhci_hcd
usb 1-2: New USB device found, idVendor=2237, idProduct=4227, bcdDevice= 4.01
usb 1-2: New USB device strings: Mfr=3, Product=4, SerialNumber=5
usb 1-2: Product: eReader-4.9.11311
usb 1-2: Manufacturer: Kobo
usb 1-2: SerialNumber: [REDACTED]
usb-storage 1-2:1.0: USB Mass Storage device detected
scsi host3: usb-storage 1-2:1.0
scsi 3:0:0:0: Direct-Access Linux File-Stor Gadget 0401 PQ: 0 ANSI: 2
sd 3:0:0:0: Power-on or device reset occurred
sd 3:0:0:0: [sdb] 14139389 512-byte logical blocks: (7.24 GB/6.74 GiB)
sd 3:0:0:0: [sdb] Write Protect is off
sd 3:0:0:0: [sdb] Mode Sense: 0f 00 00 00
sd 3:0:0:0: [sdb] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
We can mount the device directly (/dev/sdb
in my case), as there are no partitions on the device.
Directory Structure¶
We can look at the directory structure of the device:
./
├── .adobe-digital-editions/
│ └── device.xml
├── Digital Editions/
│ ├── Annotations
│ └── Manifest
├── .kobo/
│ ├── affiliate.conf
│ ├── BookReader.sqlite
│ ├── certificates/
│ ├── device.salt.conf
│ ├── dict/
│ ├── kepub/
│ ├── Kobo/
│ │ ├── Analytics.conf
│ │ └── Kobo eReader.conf
│ ├── KoboReader.sqlite
│ └── version
└── .kobo-images/
We find well-known files, such as the KoboReader.sqlite
database, but also dubious ones: the affiliate.conf
, Analytics.conf
, and the device.xml
file that has something to do with Adobe Digital Editions, which is a DRM system I obviouslyy won’t be using. The BookReader.sqlite
file is also dubious: it’s an encrypted database.
Affiliate.conf [Clean]¶
The affiliate.conf
only contain the two following lines.
[General]
affiliate=Kobo
As no unique identifier is present, i’ll let it be.
Analytics.conf [Detrimental]¶
The Analytics.conf
contains an unique ID (redacted) and many lines related to google analytics using this ID:
[General]
ClientID=REDACTED-REDA-CTED-REDA-CTEDREDACTED
GAQueue="@Variant(\0\0\0\x11\0\0\0\xf7https://ssl.google-analytics.com/collect?v=1&tid=UA-6177406-38&cid=redacted-reda-cted-reda-ctedredacted&uid=redacted-reda-cted-reda-ctedredacted&av=4.9.11311&an=nickel&sr=1080x1440&ul=fr-fr&t=event&ec=Light&ea=BrightnessAdjusted&el=MenuTapped&ev=0)", @ByteArray(), "@Variant(\0\0\0\x11\0\0\0\xf4https://ssl.google-analytics.com/collect?v=1&tid=UA-6177406-38&cid=redacted-reda-cted-reda-ctedredacted&uid=redacted-reda-cted-reda-ctedredacted&av=4.9.11311&an=nickel&sr=1080x1440&ul=fr-fr&t=event&ec=ReadingExperience&ea=DictionaryLookup&el=fr)", […]
Many events seems tracked through the get parameters of the URLs:
AdobeErrorEncountered
AutoColorToggled
BrightnessAdjusted
CreateBookmark
DictionaryLookup
FontSettings
HomeWidgetClicked
LeaveContent
MainNavOption
NaturalLightAdjusted
NewWifiNetwork
OpenContent
OpenReadingSettingsMenu
PluggedIn
ReadingSettings
SearchExecuted
StatusBarOption
TabSelected
TimeToUpdate
WifiToggle
The GAQueue stores Google-Analytics callbacks until the next time I connect the Kobo to the Internet. When empty, the file looks like this:
[General]
ClientID=REDACTED-REDA-CTED-REDA-CTEDREDACTED
GAQueue=@Invalid()
Version [Detrimental]¶
The version
file is a csv (comma separated values) file containing the device serial number, as well as multiple version numbers:
SERIAL_NUMBER, 4.1.15, 4.9.11311, 4.1.15, 4.1.15, 00000000-0000-0000-0000-000000000378
Device.salt.conf [Detrimental]¶
The device.salt.conf
file contains a salt, but this salt seems unique enough to be a tracking id:
[General]
salt=@ByteArray(\xYY\xYY\bL\xYY\xYY\xYY\xYY\xYY\xYY\xYY\xYY\xYY\xYYZ\xYY)
KoboReader.sqlite [Detrimental]¶
The KoboReader.sqlite
file is a sqlite3 database. It has the following tables:
AbTest OverDriveCards WordList
Achievement OverDriveCheckoutBook content
Activity OverDriveLibrary content_keys
AnalyticsEvents Reviews content_settings
Authors Rules ratings
BookAuthors Shelf shortcover_page
Bookmark ShelfContent user
DbVersion SyncQueue volume_shortcovers
Dictionary Tab volume_tabs
Event Wishlist
Each of the table has many columns. For example, the user table has the following columns:
0|UserID|TEXT|1||1
1|UserKey|TEXT|1||0
2|UserDisplayName|TEXT|0||0
3|UserEmail|TEXT|0||0
4|___DeviceID|TEXT|0||0
5|FacebookAuthToken|TEXT|0||0
6|HasMadePurchase|BIT|0|FALSE|0
7|IsOneStoreAccount|BIT|0|FALSE|0
8|IsChildAccount|BIT|0|FALSE|0
9|RefreshToken|TEXT|0||0
10|AuthToken|TEXT|0||0
11|AuthType|TEXT|0||0
12|Loyalty|BLOB|0||0
13|IsLibraryMigrated|BIT|1|true|0
14|SyncContinuationToken|TEXT|0||0
15|Subscription|INT|1|0|0
16|LibrarySyncType|TEXT|0||0
17|LibrarySyncTime|TEXT|0||0
18|SyncTokenAppVersion|TEXT|0||0
The full schema of the database is available there.
Looking at differences between an activated and not-yet activated device, we can discover the following:
- The events sent to google analytics are also stored in the AnalyticsEvent table;
- The Achievement table is full of shitty achievements, related to the “Reading Life” gamification thing;
- The AbTest table contains unique IDs and non-evocating names like EPDHome2018, EPDStorefront2018 or KoboPlusDiscoveryEPD. Maybe Kobo is doing A/B testing on the users?
- The User table contains the following default data:
2a362501-e2cf-41fd-88b1-47ade6d09da4|17314f8f-9d48-4ec2-8cbf-eda1e70b1127|demofinal@magtest.kobo.com|demofinal@magtest.kobo.com|a65c3a5eaebcf65fb184764bb99d2270cb71d8c8cd8bece3a7dcb271204f645a||false|false|false|||||false||0|||
; - The User table is used to detect if the Kobo has been connected // activated online.
Bypassing registration¶
To bypass registration with an account, we can put fake data in the KoboReader.sqlite
database. Yet filling everything mindlessly will not do; it will allow to bypass the registration, but you won’t be able to find your books after. As of today, the following works:
# echo "INSERT INTO user VALUES('ID','Masterkey','Rémy','nomail','',NULL,'false','false','false','RefreshToken','AuthToken','Bearer','','false','SyncContinuationToken',1,NULL,NULL,'4.9.11311');" | sqlite3 /mnt/.kobo/KoboReader.sqlite
A real SyncContinuationToken field is composed of base64 encoded, dot separated and encapsulated Json data:
# Content of a SyncContinuationToken field in a real user
eyJ0eXAiOjEsInZlciI6bnVsbCwicHR5cCI6IlN5bmNUb2tlbiJ9.eyJJbnRlcm5hbFN5bmNUb2tlbiI6ImV5SjBlWEFpT2pFc0luWmxjaUk2Ym5Wc2JDd2ljSFI1Y0NJNklrNWxlSFJUZVc1alZHOXJaVzRpZlEuZXlJa2RIbHdaU0k2SWs1bGVIUlRlVzVqVkc5clpXNGlMQ0pUZFdKelkzSnBjSFJwYjI1RmJuUnBkR3hsYldWdWRITWlPbTUxYkd3c0lrVnVkR2wwYkdWdFpXNTBjeUk2ZXlKVWFXMWxjM1JoYlhBaU9pSXlNREU0TFRBM0xUSTRWREUxT2pFd09qQXpXaUlzSWt4aGMzUlNaWFIxY201bFpFbGtJanB1ZFd4c0xDSk1ZWE4wVW1WMGRYSnVaV1JKWkhOSVlYTm9Jam90TmpVNU16QTJNVGM0ZlN3aVJHVnNaWFJsWkVWdWRHbDBiR1Z0Wlc1MGN5STZiblZzYkN3aVVtVmhaR2x1WjFOMFlYUmxjeUk2ZXlKVWFXMWxjM1JoYlhBaU9pSXlNREU0TFRBM0xUSTRWREUxT2pFd09qQXlXaUlzSWt4aGMzUlNaWFIxY201bFpFbGtJanB1ZFd4c0xDSk1ZWE4wVW1WMGRYSnVaV1JKWkhOSVlYTm9Jam93ZlN3aVZHRm5jeUk2ZXlKVWFXMWxjM1JoYlhBaU9pSXlNREU0TFRBM0xUSTRWREUxT2pFd09qQXpXaUlzSWt4aGMzUlNaWFIxY201bFpFbGtJanB1ZFd4c0xDSk1ZWE4wVW1WMGRYSnVaV1JKWkhOSVlYTm9Jam93ZlN3aVJHVnNaWFJsWkZSaFozTWlPbTUxYkd3c0lsQnliMlIxWTNSTlpYUmhaR0YwWVNJNmV5SlVhVzFsYzNSaGJYQWlPaUl5TURFNExUQTNMVEk0VkRBeU9qQXhPakkxTGpnMU15SXNJa3hoYzNSU1pYUjFjbTVsWkVsa0lqcHVkV3hzTENKTVlYTjBVbVYwZFhKdVpXUkpaSE5JWVhOb0lqb3dmU3dpU1hOR2RXeHNVM2x1WXlJNlptRnNjMlVzSWxCeVpYWnBiM1Z6VTNsdVkxUnBiV1VpT2lJeU1ERTRMVEE0TFRBeVZEQTRPalE0T2pJMUxqRXhPVGs0T1RaYUluMCIsIklzQ29udGludWF0aW9uVG9rZW4iOmZhbHNlfQ
# base64 decode
{"typ":1,"ver":null,"ptyp":"SyncToken"}{"InternalSyncToken":"eyJ0eXAiOjEsInZlciI6bnVsbCwicHR5cCI6Ik5leHRTeW5jVG9rZW4ifQ.eyIkdHlwZSI6Ik5leHRTeW5jVG9rZW4iLCJTdWJzY3JpcHRpb25FbnRpdGxlbWVudHMiOm51bGwsIkVudGl0bGVtZW50cyI6eyJUaW1lc3RhbXAiOiIyMDE4LTA3LTI4VDE1OjEwOjAzWiIsIkxhc3RSZXR1cm5lZElkIjpudWxsLCJMYXN0UmV0dXJuZWRJZHNIYXNoIjotNjU5MzA2MTc4fSwiRGVsZXRlZEVudGl0bGVtZW50cyI6bnVsbCwiUmVhZGluZ1N0YXRlcyI6eyJUaW1lc3RhbXAiOiIyMDE4LTA3LTI4VDE1OjEwOjAyWiIsIkxhc3RSZXR1cm5lZElkIjpudWxsLCJMYXN0UmV0dXJuZWRJZHNIYXNoIjowfSwiVGFncyI6eyJUaW1lc3RhbXAiOiIyMDE4LTA3LTI4VDE1OjEwOjAzWiIsIkxhc3RSZXR1cm5lZElkIjpudWxsLCJMYXN0UmV0dXJuZWRJZHNIYXNoIjowfSwiRGVsZXRlZFRhZ3MiOm51bGwsIlByb2R1Y3RNZXRhZGF0YSI6eyJUaW1lc3RhbXAiOiIyMDE4LTA3LTI4VDAyOjAxOjI1Ljg1MyIsIkxhc3RSZXR1cm5lZElkIjpudWxsLCJMYXN0UmV0dXJuZWRJZHNIYXNoIjowfSwiSXNGdWxsU3luYyI6ZmFsc2UsIlByZXZpb3VzU3luY1RpbWUiOiIyMDE4LTA4LTAyVDA4OjQ4OjI1LjExOTk4OTZaIn0","IsContinuationToken":false}
# base64 decode of the InternalSyncToken
{"typ":1,"ver":null,"ptyp":"NextSyncToken"}.{"$type":"NextSyncToken","SubscriptionEntitlements":null,"Entitlements":{"Timestamp":"2018-07-28T15:10:03Z","LastReturnedId":null,"LastReturnedIdsHash":-659306178},"DeletedEntitlements":null,"ReadingStates":{"Timestamp":"2018-07-28T15:10:02Z","LastReturnedId":null,"LastReturnedIdsHash":0},"Tags":{"Timestamp":"2018-07-28T15:10:03Z","LastReturnedId":null,"LastReturnedIdsHash":0},"DeletedTags":null,"ProductMetadata":{"Timestamp":"2018-07-28T02:01:25.853","LastReturnedId":null,"LastReturnedIdsHash":0},"IsFullSync":false,"PreviousSyncTime":"2018-08-02T08:48:25.1199896Z"}
Preventing communication with Google, etc.¶
Updates for the Kobo are not signed, or even encrypted. By putting an archive named KoboRoot.tgz
in the .kobo
directory, on the partition of the Kobo (which is mounted internally as /mnt/onboard
), we can update the Kobo, and for example modify the /etc/hosts
file:
mkdir ./etc
echo "0.0.0.0 baddomain.com" >> ./etc/hosts
tar czf KoboRoot.tgz ./etc/hosts
cp KoboRoot.tgz /mnt/.kobo/
Indeed, the content of the archive is extracted into the root of the Kobo system. When unplugged, the Kobo will go through an update cycle, and the KoboRoot.tgz will be deleted.
To check with which domains the Kobo communicate, we can use tcpdump, wireshark or mitmproxy (the latter being more useful to access to actual data sent to http or https endpoints). Looking for dns queries when the Kobo is used in a classical way (i.e. no web bworsing), then we get (192.168.12.214
being the ip address of the Kobo):
% tshark -r dump.pcapng -T fields -e ip.src -e dns.qry.name -2 -R "dns.flags.response eq 0" | sort | uniq
192.168.12.214 api.ipinfodb.com
192.168.12.214 api.kobobooks.com
192.168.12.214 auth.kobobooks.com
192.168.12.214 authorize.kobo.com
192.168.12.214 kbdownload1-a.akamaihd.net
192.168.12.214 kbimages1-a.akamaihd.net
192.168.12.214 mobile.kobobooks.com
192.168.12.214 pool.ntp.org
192.168.12.214 script.hotjar.com
192.168.12.214 social.kobobooks.com
192.168.12.214 ssl.google-analytics.com
192.168.12.214 static.hotjar.com
192.168.12.214 stats.g.doubleclick.net
192.168.12.214 storeapi.kobo.com
192.168.12.214 vars.hotjar.com
192.168.12.214 www.google-analytics.com
192.168.12.214 www.google.com
192.168.12.214 www.google.fr
192.168.12.214 www.googletagmanager.com
192.168.12.214 www.msftncsi.com
Using mitmproxy, we can check whether our assumptions about the tracked events were good. And they are: if you toggle your Wifi, google knows it. If you plug your reader, google knows it. If you do a dictionary lookup, if you adjust the brightness, if you tap the menu, google analytics knows it.
You sideload a book on your Kobo? Google knows it, and knows which book it is, because the ISBN-13 is sent:
You can disable those reports to google inside the “confidentiality” section, in the settings of the Kobo; this “feature” is at the end of the pages (it pretends it only share “features” you use on the device, but as seen on the previous screenshot, it also share what you read).
Resources¶
- https://a3nm.net/blog/fnacbook_kobo_hacking.html
- https://www.mobileread.com/forums/showthread.php?t=162713
- http://shallowsky.com/blog/tech/kobo-hacking.html
- https://www.graa.nl/articles/1760-H.html
More ?¶
You have information about the Kobo Aura H2O you want to share? Email me at rémy@grünblatt.org (replace the accentuated characters with ther non-accentuated counterpart)