Tolino e-book readers have a debug mode that can be activated by entering a secret password into the search field. Every new firmware version changes this password.
Firmware 14.2.0
Download EPubProd.apk from the Tolino with adb pull and open it in jadx-gui. Now open de.telekom.epub.debug.DebugOptionsActivity and you will find the following code:
public static final String SHOW_DEBUG_OPTIONS_PASS_CODE_FULL = "124816"; public static final String SHOW_DEBUG_OPTIONS_PASS_CODE_MINIMAL = "180914";
This was easy.
The "full" passcode are the first 5 numbers of the x² sequence.
Firmware 15.2.0
After downloading the epub reading application EPubProd.apk and opening it in jadx-gui, we find new code in de.telekom.epub.debug.DebugOptionsActivity:
private static final String HASH_SALT = "schokopups512"; public static final String PASS_CODE_FULL = "c5af5651b57b1db0fe9f373e95cd4042997ccfb7bd2c5fa0b0b45ebc7717d83a9b71fb695e22478c1d4e9dd3d85330e3d385c5c49a892972484e45d5533532c6"; public static final String PASS_CODE_MINIMAL = "f11e6ad70202da7eb56f2b932926ef53c4a4b9742bc28e00c3cb7b13d3e39ac795f774417c32c639a4873a044d7bd07dc369b9633a875f61097aa0b9b01e8464"; public static int checkOpenDebugCode(String str) { try { String bigInteger = new BigInteger(1, MessageDigest.getInstance("SHA-512").digest(str.concat(this.HASH_SALT).getBytes())).toString(16); while (bigInteger.length() < 32) { bigInteger = "0" + bigInteger; } if (bigInteger.equals(this.PASS_CODE_FULL)) { ApplicationPreferencesManager.getInstance().setDebugSettingsLastOpenCode(this.PASS_CODE_FULL); return 1; } else if (!bigInteger.equals(this.PASS_CODE_MINIMAL)) { return -1; } else { ApplicationPreferencesManager.getInstance().setDebugSettingsLastOpenCode(this.PASS_CODE_MINIMAL); return 0; } } catch (Exception e) { e.printStackTrace(); return -1; } }
The Tolino developers decided to make it more complicated to find the password and found a nice method: They calculate a hash from the combination of the entered password and "schokopups512" and compare the result with the pre-computed hashes of the original passwords.
That way the passwords are not in plain text in the code anymore, and the salt ("schokopups512") prevents people from looking up the hash in a database of pre-computed SHA-512 hashes.
John to the rescue
Luckily there is a tool for cracking passwords: John the Ripper. It takes a file with hashed passwords and tries all combinations of letters, numbers and special characters until it finds the password. Word list files are also supported; passwords contained in them will be found much faster.
At first we need to tell john the password format. The Tolino debug passcode is sha512($password + $hash). john in Debian 11's repository does not support that, but the jumbo version does! I found .deb package files for jumbo-john at http://http.kali.org/pool/main/j/john/. Verification:
$ john --help John the Ripper 1.9.0-jumbo-1+bleeding-aec1328d6c 2021-11-02 10:45:52 +0100 OMP [linux-gnu 64-bit x86_64 AVX2 AC]
Now let's find out if it supports SHA512 with salt:
$ john --list=subformats|grep sha512 Format = dynamic_80 type = dynamic_80: sha512($p) Format = dynamic_81 type = dynamic_81: sha512($s.$p) Format = dynamic_82 type = dynamic_82: sha512($p.$s) Format = dynamic_83 type = dynamic_83: sha512(sha512($p)) Format = dynamic_84 type = dynamic_84: sha512(sha512_raw($p)) Format = dynamic_85 type = dynamic_85: sha512(sha512($p).$s) Format = dynamic_86 type = dynamic_86: sha512($s.sha512($p))
dynamic_82 is the format we want.
Now we have to find out how the password needs to be stored. After some trial and error I had the following line format for password files:
name:hash$salt
.. which gives us this password file for the Tolino 15.2.0 main passcode:
tolino152-1:c5af5651b57b1db0fe9f373e95cd4042997ccfb7bd2c5fa0b0b45ebc7717d83a9b71fb695e22478c1d4e9dd3d85330e3d385c5c49a892972484e45d5533532c6$schokopups512
Now we let john do its work:
$ john --incremental=digits --fork=4 --format=dynamic_82 passwords Using default input encoding: UTF-8 Loaded 1 password hash (dynamic_82 [sha512($p.$s) 256/256 AVX2 4x]) Press 'q' or Ctrl-C to abort, almost any other key for status 1123581321 (tolino152-1) 1g 0:00:00:01 DONE (2022-11-10 22:48) 0.7692g/s 4758Kp/s 4758Kc/s 4758KC/s 1234561990..095664525 Use the "--show --format=dynamic_82" options to display all of the cracked passwords reliably Session completed.
The first password 1123581321 was found within a second when telling john only to try numbers and no letters. It consists of numbers 2-9 of the Fibonacci numbers.
Firmware 16.0
The password check code is the same as in firmware 15.2:
private static final String HASH_SALT = "CbqSbfnk9YC%"; public static final String PASS_CODE_FULL = "d820afedd912b83340429595ad855b893815bb85661b2b50e0892f7b3d720e6c8ebad365623f8896ba946957c1ff70a3fcf51a2127041804fb17c46c671c37c0"; public static final String PASS_CODE_MINIMAL = "d93b0fb26c52927f543103ab6cf2270947c55c6ed00995018eab313f57393c7c0a1dc4cc4bcb3c114ed0b0c469b1973ddebc4c55002eaf10145ddb626e2595a"; public static int checkOpenDebugCode(String str) { try { String bigInteger = new BigInteger(1, MessageDigest.getInstance("SHA-512").digest(str.concat(this.HASH_SALT).getBytes())).toString(16); while (bigInteger.length() < 32) { bigInteger = "0" + bigInteger; } if (bigInteger.equals(this.PASS_CODE_FULL)) { ApplicationPreferencesManager.getInstance().setDebugSettingsLastOpenCode(this.PASS_CODE_FULL); return 1; } else if (!bigInteger.equals(this.PASS_CODE_MINIMAL)) { return -1; } else { ApplicationPreferencesManager.getInstance().setDebugSettingsLastOpenCode(this.PASS_CODE_MINIMAL); return 0; } } catch (Exception e) { e.printStackTrace(); return -1; } }
Let's put all the known hashes in a single file:
tolino152-1:c5af5651b57b1db0fe9f373e95cd4042997ccfb7bd2c5fa0b0b45ebc7717d83a9b71fb695e22478c1d4e9dd3d85330e3d385c5c49a892972484e45d5533532c6$schokopups512 tolino152-2:f11e6ad70202da7eb56f2b932926ef53c4a4b9742bc28e00c3cb7b13d3e39ac795f774417c32c639a4873a044d7bd07dc369b9633a875f61097aa0b9b01e8464$schokopups512 tolino160-1:d820afedd912b83340429595ad855b893815bb85661b2b50e0892f7b3d720e6c8ebad365623f8896ba946957c1ff70a3fcf51a2127041804fb17c46c671c37c0$CbqSbfnk9YC% tolino160-2:0d93b0fb26c52927f543103ab6cf2270947c55c6ed00995018eab313f57393c7c0a1dc4cc4bcb3c114ed0b0c469b1973ddebc4c55002eaf10145ddb626e2595a$CbqSbfnk9YC%
I let john work on the file with different options but failed in the end. The passwords are not:
- a number with 1-8 digits (0m 21s, 4 CPUs)
- a number with 9-10 digits (21m 37s, 10 CPUs)
- a number consisting of the first 2..11, 3..12, 4..13, 5..14 numbers of all the known integer sequences (3s, 4 CPUs)
Integer sequences
I thought that the second passcode would also be a known number sequence, and so I wanted to get a list of all known sequences. The project On-Line Encyclopedia of Integer Sequences (OEIS) collected them, and they can be downloaded from their wiki.
After extracting the 69 MiB stripped file I mangled it a bit to extract the first 2 numbers from every sequence and stored it in a file:
grep -v '^#' stripped |cut -d',' -f2-3 | sed 's/,//g' | sort | uniq > sort-2-3
This was extended up to fields 2..12, 3..13, 4..14 and 5..15. All of them together were given john as password file, but 3 seconds later I found that the work bore no fruit.
Fin
I tried finding passwords consisting of ASCII characters, but stopped john after it ran for nearly two days on 4 CPU cores:
$ john --fork=4 --format=dynamic_82 --min-length=6 passwords ... 3 0g 1:19:53:51 3/3 0g/s 1242Kp/s 2484Kc/s 3726KC/s bmpa5s9r..bmpa5d1@ 4 0g 1:19:53:51 3/3 0g/s 1245Kp/s 2491Kc/s 3736KC/s 2lscie1...2lscidr4 1 0g 1:19:53:51 3/3 0g/s 1246Kp/s 2493Kc/s 3739KC/s rbadwhrhb..rbadwxyza Waiting for 3 children to terminate 2 0g 1:19:53:51 3/3 0g/s 1243Kp/s 2486Kc/s 3729KC/s 77f1ngy..77f1cs7 Session aborted
A fail for now, but maybe this helps the next tinkerer to go further.