Christians Tagebuch: blackbox

The latest posts in full-text for feed readers.


Tolino 16.0 password crack attempts

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:

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:

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.

The password

Update 2023-11-15: Someone sent me the link to a gist that contains the Tolino 16 firmware debug password: 112358132fb. Source is this forum post from 2023-08-09 by AaronDewes.

Published on 2022-11-21 in ,


Unlocking Final Fantasy III on OUYA

One of the few OUYA games that my ouya-plain-purchases Xposed module did not unlock was Final Fantasy III: Its developers wrote code that makes the game crash when a running Xposed module is found.

Now nearly two years after my investigation, OUYA Saviors member ZacharyFoxx found a solution: Convert the Xposed module from a normal user-installed app into a system app.

The only difference between them is that user applications are installed in /data/app/, while system apps are located in /system/app/. The /system/ partition is mounted read-only on the OUYA, so it takes some more steps to move it.

HowTo

If plain purchases is not already installed, use the OUYA interface and go to Make > Tutorials and install it.

If you already enabled the plain purchases module, you have to disable it at first in the Xposed settings.

Now connect your OUYA via USB to your computer and use adb to execute the following steps:

$ adb shell
$ su
$ mount -o rw,remount -t ext4 /dev/block/platform/sdhci-tegra.3/by-name/APP
$ cp /data/app/de.cweiske.ouya.plainpurchases-1.apk /system/app/
$ rm /data/app/de.cweiske.ouya.plainpurchases-1.apk
$ mount -o ro,remount -t ext4 /dev/block/platform/sdhci-tegra.3/by-name/APP
$ exit
$ exit
$ adb reboot

The issue can finally be closed.

Update 2023-11: One user mentioned that he had to disable the OUYA Xposed "Mod Collection" to get it working (after rebooting).

Published on 2023-01-10 in , ,


Square Enix' red herring

To get games to unlock on the defunct OUYA gaming console, I wrote an Xposed module that disables encryption for purchase receipt requests and responses that the games send and receive.

While it worked for many games, users reported that Final Fantasy III crashes when my plain-purchases module is enabled. adb logcat shows the following stacktrace:

E/AndroidRuntime: *** FATAL EXCEPTION IN SYSTEM PROCESS: main
   java.lang.SecurityException: 1466 does not have permission:android.permission.CLEAR_APP_USER_DATA to clear datafor process:de.cweiske.ouya.plainpurchases
       at android.os.Parcel.readException(Parcel.java:1425)
       at android.os.Parcel.readException(Parcel.java:1379)
       at android.app.ActivityManagerProxy.clearApplicationUserData(ActivityManagerNative.java:2889)
       at com.android.commands.pm.Pm.runClear(Pm.java:1126)
       at com.android.commands.pm.Pm.run(Pm.java:116)
       at com.android.commands.pm.Pm.main(Pm.java:75)
       at com.android.internal.os.RuntimeInit.nativeFinishInit(Native Method)
       at com.android.internal.os.RuntimeInit.main(RuntimeInit.java:235)
       at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:135)
       at dalvik.system.NativeStart.main(Native Method)

Final Fantasy 3 is not the only game that shows this behavior; many Square Enix games exhibit this behavior.

Investigation

FF3 for Android/OUYA contains a native library lib__57d5__.so which seems to be the source of the problem. Fortunately for me, the Xposed author had a look at that library before:

Ok, whatever Final Fantasy does there: It looks like some very dirty coding.

  • com/square_enix/android_googleplay/FFIV_GP/MainActivitya loads lib__57d5__.so and then calls native function MainActivityb.a(I)I with some random (?) integer constants
  • Actually already the library loading fails because somewhere during the initialization, it calls sh -c pm clear <package> for the Xposed modules, which fails with a permission error and crashes the process
  • [...]

To summarize: A strange native library in FF seems to get all /data/app/*-?.apk entries from the memory mapping file and tries to clear the data for them.

So the lib tries to clear the application user data for all apks that somehow linger in the memory of the FF3 game. It does not do this directly, but by opening a shell and using the android package manager command line tool pm:

$ sh -c pm clear packagename

After reading the sources of pm I came to the following call stack:

final fantasy 3
+ lib__57d5__.so
  + sh -c pm clear $package
    + android.app.ActivityManagerProxy::clearApplicationUserData
      + ?? IPackageDataObserver - transact(CLEAR_APP_DATA_TRANSACTION)
           something with binder
        + ??? PackageManager::clearApplicationUserData()
          + com.android.server.pm.PackageManagerService::clearApplicationUserData
            (final String packageName, final IPackageDataObserver observer, final int userId)

The pm cli tool does not directly call the package manager, but - to my limited understanding - uses some Android IPC mechanism to call the one authorative instance of the actual package manager.

Attempt #1: Hack package manager service

At first I wrote an xposed module that hooked into PackageManagerService::clearApplicationUserData and simply said "success" when the data of my own module were to be cleaned:

 iPackageDataObserverClass = Class.forName("android.content.pm.IPackageDataObserver");
                            Class[] paramTypes = {String.class, boolean.class};
                            Method onRemoveCompletedMethod = iPackageDataObserverClass.getMethod("onRemoveCompleted", paramTypes);
                            Object[] params = {packageName, true};
                            try {
                                onRemoveCompletedMethod.invoke(observer, params);
                                //observer.onRemoveCompleted(packageName, true);
                            } catch (Exception e) {
                                XposedBridge.log("Observer no longer exists.");
                            }
                        }
                        //end dance
                    }
                }
            }
        );
    }
}]]>

The unaccessible IPackageDataObserver made it a bit hard to call the right methods, but it was still relatively easy. Unfortunately, it did not work at all because it was too late: ActivityManagerProxy.clearApplicationUserData() already does the permission checks!

Attempt #2: Hack activity manager proxy

My goal was to hook into ActivityManagerProxy that does the IPC call to the package manager.

This proved to be extremely hard for me because hooking into command line tools with IXposedHookCmdInit is not supported anymore by Xposed: See XDA: Hook cmds (PM) and the XPosed API changelog for 2.6:

IXposedHookCmdInit is deprecated now, initCmdApps() won't be called anymore unless an undocumented file has been created. Only two modules used it, both got rid of it without any loss of functionality.

Rolling back my OUYA's XPosed from version 2.6.1 (API v31) to 2.5.1 (API v50) worked, but then I faced the most difficult problem: I could not compile the code, because the compileOnly de.robv.android.xposed:api dependency is only available for API version 53 and higher - and 52 was the last one with support for command line tool hooks :/

It took quite some time to find an older XposedBridgeApi jar file, but eventually one turned up: XposedBridgeApi-52.jar. Moving that to the module's lib/ directory let me compile the module with the cli hook! Now I faced a very strange error:

I/Xposed: Running ROM 'JZO54L-OUYA' with fingerprint 'OUYA/ouya_1_1/ouya_1_1:4.1.2/JZO54L-OUYA/1427:user/test-keys'
I/Xposed: Loading modules from /data/app/de.cweiske.ouya.plainpurchases-2.apk
I/Xposed:   Loading class de.cweiske.ouya.plainpurchases.PlainPurchases
I/Xposed: java.lang.IllegalAccessError: Class ref in pre-verified class resolved to unexpected implementation
        at dalvik.system.DexFile.defineClass(Native Method)
        at dalvik.system.DexFile.loadClassBinaryName(DexFile.java:211)
        at dalvik.system.DexPathList.findClass(DexPathList.java:315)
        at dalvik.system.BaseDexClassLoader.findClass(BaseDexClassLoader.java:58)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:501)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:461)
        at de.robv.android.xposed.XposedBridge.loadModule(XposedBridge.java:441)
        at de.robv.android.xposed.XposedBridge.loadModules(XposedBridge.java:407)
        at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:121)
        at dalvik.system.NativeStart.main(Native Method)

The source of the problem was that I moved XposedBridgeApi-52.jar into the lib/ folder, which meant it got compiled into the xposed module jar file. Now on the OUYA, there were suddenly two implementations of the bridge API, and the JVM could not decide which was the right one.

I moved it into a different directory, told gradle/Android Studio to use it for compilation only and could load the mod without problems!

 iPackageDataObserverClass = Class.forName("android.content.pm.IPackageDataObserver");
                        Class[] paramTypes = {String.class, boolean.class};
                        Method onRemoveCompletedMethod = iPackageDataObserverClass.getMethod("onRemoveCompleted", paramTypes);
                        Object[] params = {packageName, true};
                        try {
                            onRemoveCompletedMethod.invoke(observer, params);
                            //observer.onRemoveCompleted(packageName, true);
                        } catch (Exception e) {
                            XposedBridge.log("Observer no longer exists.");
                        }
                    }
                    //end dance
                }
            }
        );
    }
}]]>

Success?

Running pm clear ... from shell as limited user worked flawlessly and gave exit code 0 and no exceptions! Now I had beaten Square Enix! I started Final Fantasy 3 via shell:

$ adb shell am start -n com.square_enix.android_OUYA.FFIII/com.square_enix.FFIII_J.MainActivity

... and FF3 stopped as it had before:

I/Xposed: clearApplicationUserData: de.cweiske.ouya.plainpurchases
D/AndroidRuntime: Shutting down VM
I/AndroidRuntime: NOTE: attach of thread 'Binder_3' failed
D/dalvikvm: JIT code cache reset in 0 ms (86452 bytes 1/0)
D/dalvikvm: Debugger has detached; object registry had 1 entries
D/Zygote: Process 1572 exited cleanly (1)
I/ActivityManager: Process com.square_enix.android_OUYA.FFIII (pid 1572) has died.
W/ActivityManager: Force removing ActivityRecord{420f33f0 com.square_enix.android_OUYA.FFIII/com.square_enix.FFIII_J.MainActivity}: app died, no saved state

lib__57d5__.so did not care about pm's exit code, it always stops when it detects that XPosed is running.

I'm very sure that the SecurityException was just a red herring to make people spend time with a pretty difficult problem that has nothing to do with the actual problem.

/me tips his hat to the lib__57d5__.so developers.

Update 2023-01-10: We have a solution.

Published on 2020-02-26 in , , ,


Bluetooth GAIA protocol

While investigating the Bluetooth functionality of my Libratone Zipp wireless speaker I stumbled across a non-standard profile with UUID 00001107-d102-11e1-9b23-00025b00a5a5. It turned out to be a proprietary extension to Bluetooth called "GAIA protocol" and was made by Cambridge Silicon Radio CSR, which was bought by Qualcomm in 2015.

Profiles

Bluetooth is a communication standard that is split into a core specification that defines the basics, and "profiles" that specifiy the things that are wanted by humans: Accessing the phonebook for synchronization ("Phonebook Access Server"), transferring music from phone to bluetooth speaker ("Audio Sink"), controlling the volume of the speaker ("A/V Remote Control Target").

Each bluetooth device can provide several of those profiles. My Libratone Zipp speaker has the following:

$ bluetoothctl
Agent registered

[bluetooth]# scan le
Discovery started
[CHG] Controller 18:CF:5E:D7:23:42 Discovering: yes
[CHG] Device C4:67:B5:3F:23:42 RSSI: -50
[CHG] Device C4:67:B5:3F:23:42 TxPower: 0

[bluetooth]# info C4:67:B5:3F:23:42
Device C4:67:B5:3F:23:42 (public)
	Name: Libratone Zipp
	Alias: Libratone Zipp
	Class: 0x00240418
	Icon: audio-headphones
	Paired: yes
	Bonded: yes
	Trusted: no
	Blocked: no
	Connected: no
	LegacyPairing: no
	UUID: Vendor specific           (00001107-d102-11e1-9b23-00025b00a5a5)
	UUID: Headset                   (00001108-0000-1000-8000-00805f9b34fb)
	UUID: Audio Sink                (0000110b-0000-1000-8000-00805f9b34fb)
	UUID: A/V Remote Control Target (0000110c-0000-1000-8000-00805f9b34fb)
	UUID: A/V Remote Control        (0000110e-0000-1000-8000-00805f9b34fb)
	UUID: Handsfree                 (0000111e-0000-1000-8000-00805f9b34fb)
	UUID: PnP Information           (00001200-0000-1000-8000-00805f9b34fb)
	Modalias: bluetooth:v000Ap0001d0102
	RSSI: -50
	TxPower: 0

Each profile as a globally unique ID, the UUID. Profiles standardized by the Bluetooth Special Interests Group (SIG) have a 128bit-UUID in the form of 0000XXXX-0000-1000-8000-00805F9B34FB.

GAIA protocol

I wondered what the one vendor-specific profile was:

UUID: Vendor specific (00001107-d102-11e1-9b23-00025b00a5a5)

Since UUIDs are globally unique, searching for it yields very targeted results.

GAIA is the Generic Application Interface Architecture, which was originally thought up by Cambridge Silicon Radio and is now owned by Qualcomm.

There is no public specification available; you apparently have to sign a NDA to get it.

It seems to be a generic protocol to implement custom bluetooth commands. You press your data into a pre-defined structure, and the GAIA library takes care of the actual transmission, checksumming and packet combination.

It can be used to transfer firmware updates via bluetooth from PC/smartphone to your headphone or speaker (OTA-update), but also any other commands that the device chooses to implement:

Gaia has customized related commands, which can realize specific functions, such as querying battery power, version number, etc.; at the same time, it is also used for data transmission in OTA (but does not parse data)

YingbinLi 蓝牙: BC5-MM, CSR867x的DFU更新流程之八:Gaia的理解与使用

and

CSR GAIA (Generic Application Interface Architecture) implements an end-to-end, host-agnostic ecosystem supporting host application access to device functionality.

Guanghua Cheng: Bluetooth connection & GAIA protocol

So instead of properly defining and specifying an official profile, all Qualcomm licensees cook up their own commands. (But maybe the firmware update command is stadardized - I do not know)

Despite the NDA, the source code for some Android apps implementing GAIA is available:

Devices using GAIA

Note that this list is totally incomplete, because the profile UUIDs are only ever noted in bug reports by Linux users that know how to use the command line for getting information.

I suspect that there are hundreds if not thousands products implementing that protocol.

  • KEF MUO (bluetooth speaker) (source)
  • Libratone Zipp (bluetooth speaker)
  • OnePlus Buds (source)
  • OnePlus Buds Z2 (source)
  • Surface Earbuds (source)

Published on 2022-10-26 in ,


Tolino: Debug Mode, Hotel Mode, no Ads

With some time at my hands and a rooted adb on my Tolino Vision 4 HD (firmware 13.2.1), I had a look at the de.telekom.epub.apk ebook reading app source code.

Since I bought the reader I wanted to get rid of the unsolicited advertisements on the home screen and the "you finished reading the book" screens that the support told me I could not disable.

Removing advertisements

The recommendations appear once the Tolino has been given WiFi access, e.g. for a firmware update or to sync the time via NTP.

They are stored with other data in the SQLite database file at /data/data/de.telekom.epub/databases/pageplace.db. With the rooted adb is possible to download it to the PC, remove the recommendations and upload it back to the e-reader:

$ adb pull /data/data/de.telekom.epub/databases/pageplace.db
$ sqlite3 pageplace.db "DELETE FROM recommendation"
$ adb push pageplace.db /data/data/de.telekom.epub/databases/pageplace.db
$ adb shell chown system:system /data/data/de.telekom.epub/databases/pageplace.db
$ adb shell am force-stop de.telekom.epub

Table recommendations are the advertisements, and table comments are your own marked text areas.

The last command kills the ebook app, which is started automatically again. It is faster than rebooting the whole device.

See Own ads on a Tolino e-reader for more work on this area.

Debug mode

Firmware 13.2.1

If the right switch is set, the main Tolino ebook reading app de.telekom.epub shows additional information in the logs available at adb logcat.

The DEBUG switch cannot be activated directly. It is defined in the environment configuration, which in turn are stored as resource files inside the .apk at assets/environments/. The default environment is 0 and it loads application.properties.prod. There is another environment 2 called application.properties.prod.debug, which is a clone of the first environment, just with the debug flag enabled.

The environment can be selected by setting de.telekom.epub.PREFS_APP_PROPERTES (no, that is not a typo on my side) inside the configuration file /data/data/de.telekom.epub/shared_prefs/ePub.xml. Since there is no command line editor available on the Tolino, we again have to download the file to the PC, modify it and upload it back to the reader:

$ adb pull /data/data/de.telekom.epub/shared_prefs/ePub.xml
# now add this line to ePub.xml:
  <int name="de.telekom.epub.PREFS_APP_PROPERTES" value="2" />
$ adb push ePub.xml /data/data/de.telekom.epub/shared_prefs/ePub.xml
$ adb shell chown system:system /data/data/de.telekom.epub/shared_prefs/ePub.xml
$ adb shell am force-stop de.telekom.epub

When debugging is enabled, adb logcat will show that line:

D/ApplicationEnviromentProperties( 2852): isATolino=true

Also, in -> Settings -> More Settings (German: Erweiterte Einstellungen) a new button appears: Open God-Settings. It simply shows the Android 4.4 standard settings.

"Open god-settings" button on firmware v13

Firmware 14.2

The old God mode button is gone now, you have to click the "search" button on the main screen, enter the number 124816 and press search. A dedicated debug settings screen will open.

The numbers are the first 5 "2^x" numbers.

Source: Eigene Apps installieren (Firmware 14.2.0) by AaronDewes.

Firmware 15.2

The manufacturer changed the debug code for the search screen to 1123581321, which are the first 8 numbers of the Fibonacci sequence.

When searching for that number, the following debug screen opens:

Debug settings on fimware 15: Page 1 Debug settings on fimware 15: Page 2 Debug settings on fimware 15: Page 3 Debug settings on fimware 15: Page 4 Quick access button on the search screen

Source: Tolino Update 15.2.0 post by Ohmugin

More information about finding the passwords: Tolino 16.0 password crack attempts.

Hotel Mode

The Tolino contains a special Hotel mode that shows your own logo on the start screen instead of the book shop ads and disables USB access.

It needs two prerequisites:

  • No book shop configuration
  • A directory hotelMode on the USB flash drive

You can store your own logo file as hotelMode/hotelMode.png, but that is optional.

The book shop configuration is also stored in /data/data/de.telekom.epub/shared_prefs/ePub.xml as de.telekom.epub.PREFS_APPLICATION_PROPERTIES_JSON. Modify the file as described above. I simply renamed the name attribute by prefixing it with old- so that I can restore it later.

Then open the usb-connected Tolino drive and create the hotelMode directory, optionally putting a hotelMode.png file into it. You can do that via adb shell; the directory then would be /sdcard/hotelMode. Now reboot or restart the epub application, and hotel mode is active.

When hotel mode is active, the log shows the following line:

D/Globals ( 3017): Filesystem is locked!

That "lock" only influences the ability to access the Tolino files via USB. It still properly stores bookmarks and reading progress.

Hotel Mode showing my own logo on my Tolino

Reseller selection

If you want to switch the book shop (away from Thalia to buecher.de or so), modify ePub.xml the following way:

  • Remove or rename de.telekom.epub.PREFS_APPLICATION_PROPERTIES_JSON
  • Set de.telekom.epub.PREFS_IS_REBRANDABLE_DEVICE to true:
<boolean name="de.telekom.epub.PREFS_IS_REBRANDABLE_DEVICE" value="true" />

Now reboot/restart the reader app and the lower main screen will allow you to select the book shop.

Shop selection on the Tolino main screen

Published on 2020-09-27 in , , ,


Getting ADB root access on a Tolino

On my quest to get rid of advertisements on my Tolino e-book reader I already made my own boot image to enable adbd, the Android Debug Bridge Daemon on the device.

After I opened a adb shell I saw that it had the "shell" user and group, and that I could not access /data/ and /system/ directories.

Time to get root:

$ adb root
$ adb shell
shell@ntx_6sl:/ $ id
uid=2000(shell) gid=2000(shell) groups=1003(graphics),1004(input),1007(log),1009(mount),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats)

Although "adb root" did not give any error, adbd on the reader was still running as limited user.

su?

The alternative to "adb root" was using a su binary that one can run in the shell to gain root access.

My biggest problem was how to get a sane non-infected su binary.

The 1.9.0 root patch contains one, but it is 380 kiB large (adbd itself is only 160 kiB, and the su on my Debian laptop is 63 kiB), and it requires an accompanying Superuser.apk. That su would speak with the .apk which would show a confirmation dialog on the reader screen that one has to confirm.

I decided against that because I do not know where the su is from and what kind of malicious code is inside that 380 kiB blob.

Always having to type "su" in the shell would also mean that I could not simply run adb copy and adb push on /data/ and /system/, which would slow me down when tinkering with the system.

adb root

There are two settings that determine if adb root works, and they are documented in android-core/adb/daemon/main.cpp:

ro.debuggable
Allowed to become root, but not necessarily the default. Set to 1 on eng and userdebug builds.
ro.secure
Drop privileges by default. Set to 1 on userdebug and user builds.

So setting ro.debuggable in default.prop is the absolute minimum to get root access via adb.

Setting ro.secure to 0 only means that adbd will always run as root, making it superfluous to always type adb root at the beginning of a debugging session.

I added ro.debuggable in my boot image, flashed it and .. still was not root.

I switched my android core checkout to branch android-4.4.2_r2, looked into adb.c and found the compile time flag ALLOW_ADBD_ROOT. If that was not set, all the reading of "ro.debuggable" and "ro.secure" would not even be compiled into the adbd binary.

So the Tolino firmware makers had not enabled root access.

Insecure adb

I did not want to compile adbd for the Tolino myself, because that would require installing the Android SDK, the Android NDK, and finding out what the original compilation options were.

The XDA developers forum has a thread [2014.11.10][ROOT] adbd Insecure v2.00 that lets us download an .apk with adbd compilations for different Android SDK versions.

I tried all of them, but only got an offline status when I listed the adb devices:

$ adb devices
List of devices attached
86346346,2f12e2342c894d9e8afa3cf5564223ac	offline

I think this means that adbd on the Tolino crashed and was thus unreachable.

Chainfire's rootadb

There is an Android app that does live patching on an adbd binary on the device itself: rootadb.

It works by opening the adbd file, searching for system calls to capset(), setuid() and setgid() and replacing them with 0xe3a00000.

Unfortunately there was no pre-built binary on Google Play nor in the Github releases section. Compiling it myself would mean the same steps as outlined above for recompiling adbd, so I ditched that idea.

Binary patching

Rootadb's idea was still valid: Patch out the system calls that drop privileges through changing user and group IDs and removing capabilities.

I installed Ghidra (open source IDA Pro alternative) for the first time and loaded my Tolino's adbd binary into it. It shows the assembler code in a nicer way than hex editors, and I could see the mov, cmp and bne statements with their parameters.

The big question was how to find the place inside the 160 kiB that called the system methods. In parallel I looked at the adb.c source code:

/* then switch user and group to "shell" */
if (setgid(AID_SHELL) != 0) {
    exit(1);
}
if (setuid(AID_SHELL) != 0) {
    exit(1);
}

D("Local port disabled\n");

Now I knew where to look: Search for the "Local port disabled" string and find the place where it is used.

After skipping backwards about all the code generated for the D() function I found the setuid() call at offset 0x1288 (ghidra: 0x9288).

Looking for 0x7d0 ("2000" in hex) was even easier because that's the user id for the "shell" user.

00009288 4f f4 fa 60     mov.w      r0,#0x7d0
0000928c 0e f0 86 ed     blx        FUN_00017d9c
00009290 00 28           cmp        r0,#0x0
00009292 df d1           bne        LAB_00009254

What happens here:

  • mov.w pushes 2000 onto register 0,
  • blx then executes a function (setuid).
  • cmp compares the return value in register 0 with zero,
  • and bne jumps to the exit function if the comparison failed.

The fastest solution was to patch out all the commands by replacing them with NOP (no operation) commands, which is 00 BF in the little endian ARM code.

Ghidra told me that it does not support 32 bit ARMv7 binaries, and that saving them could break them.

So I opened my hex editor bless, subtracted 0x8000 from the offsets that Ghidra showed me and manually NOPed all the commands.

I did that for the setuid() call only at first, built and flashed a new boot image with the modified adbd, ran it and opened a shell on the Tolino:

$ adb shell
root@ntx_6sl:/ # id
uid=0(root) gid=2000(shell) groups=1003(graphics),1004(input),1007(log),1009(mount),1011(adb),1015(sdcard_rw),1028(sdcard_r),3001(net_bt_admin),3002(net_bt),3003(inet),3006(net_bw_stats)

root@ntx_6sl:/ $ ls /data/
opendir failed, Permission denied

So adbd was running as root user now, but did not yet have the root group, and could still not access /data because it had dropped capabilities.

Now I patched out the calls to setgid(), setgroups() and prctl() and could finally declare success:

$ adb shell
root@ntx_6sl:/ # id
uid=0(root) gid=0(root)

root@ntx_6sl:/ # ls /data
app
app-asec
app-lib
app-private
backup
bugreports
dalvik-cache
data
dontpanic
drm
g-ESD.log
local
lost+found
media
mediadrm
misc
property
resource-cache
security
share
ssh
system
tombstones
user

The Tolino user interface data are in /data/data/de.telekom.epub/. Inspecting them is the next task. See Tolino: Debug Mode, Hotel Mode, no Ads.

Analyzing the ADBD C source code Ghidra showing the setuid() call in assembler Trying to find the assembler instructions in my hex editor

Download

You can find my Tolino 12.2.0 boot image with the patched adbd in my binary file archive.

Update 2020-09: Firmware 13.2.1

I made the rooted boot image for firmware version 13.2.1 which was easy, because 13.2.1 still uses Android 4.4.2 and I could just copy over the previously patched adb. Download it from my binary file archive.

Update 2021-01: Firmware 14.1.0

Rooted boot image for firmware 14.1.0 is available. As before I could use the same previously rooted adb. This also works on the Tolino Vision 3 HD and Tolino Shine 2 HD. It should work on the Tolino Vision 2 and Tolino page because they all have the same update file.

Update 2022-03: Firmware 15.2.0

Rooted boot image for Tolino 4 HD with firmware 15.2.0 is available. As before I could use the same previously rooted adb. This also works on the Tolino Vision 3 HD and Tolino Shine 2 HD. It should work on the Tolino Vision 2 and Tolino page because they all have the same update file.

Published on 2019-10-02 in , , ,


Modifying an Android boot image

After my old nook reader died, I bought a Tolino Vision 4 HD ebook reader. It is usable without registration - as opposed to Kobo readers which you have to register. It is water proof enough to drop it into the bath tub, and it has thesauri and translation dictionaries. For 140€ I bought it at buecher.de.

The reader is based on Android 4.4.2, and it is rootable. Unfortunately the root patch is for firmware version 1.9.0, while I updated already to 12.2.0.

For the firmware update, I let it once into my wifi. During that time it also downloaded the latest recommendations from the buecher.de store, which it now shows on the home screen - taking the lower half of it - and after I finish reading a book:

Tolino shows ads on the homescreen Tolino shows ads after a book has been read

I wanted to get rid of this unsolicited advertisements, and asked support how to do so. They replied with "cannot be disabled" :/

I thought I could simply clear that cached recommendation data on my own but found that I could not simply plug the micro USB cable and run adb shell - the Tolino does not have adbd running :/

Activating adb without developer settings GUI

In a standard Android user interface I would open settings, tap seven times on the build number, and then activate ADB debugging in the now visible developer options.

The Tolino has its own GUI, and their settings UI has no ADB checkbox.

There is an alternative: Setting some properties in the boot image's default.prop file, which is what the ADB checkbox in the normal developer options does.

persist.service.adb.enable=1
persist.sys.usb.config=mass_storage,adb

The standard USB config for the Tolino was "mass_storage", so I only added ",adb" to it.

Now I needed to modify my Tolino's default.prop file.

Building my own boot image

I did not create a boot image from scratch, I only modified an existing one.

The Tolino Vision 4 update website had a .zip file for updating from 11.2.0 to 12.2.0, so I fetched and extracted that. Among others, it contained a 4.5 MiB boot.img file.

Unpacking boot.img

At first I tried sophiehuiberts/Bootimg-scripts but found out that the extracted kernel file does not have the correct size.

The Android core git repository up to commit a6abd821d contained two files in the mkbootimg directory: unpack_bootimg.py and mkbootimg.py, which I used in the end.

When extracting the boot image, the unpack script shows important meta data:

$ ./unpack_bootimg.py --boot_img boot-12.2.0.img --out ex-boot-12.2.0
boot_magic: ANDROID!
kernel_size: 3936664
kernel load address: 0x80808000
ramdisk size: 508752
ramdisk load address: 0x81800000
second bootloader size: 0
second bootloader load address: 0x81700000
kernel tags load address: 0x80800100
page size: 2048
boot image header version: 0
os version and patch level: 0
product name: ntx_6sl
command line args: console=ttymxc0,115200 init=/init androidboot.console=ttymxc0 max17135:pass=2, fbmem=6M video=mxcepdcfb:E060SCM,bpp=16 no_console_suspend
additional command line args:

Before actually modifying something, I rebuilt it until my repacked image was bit for bit equal to the original one.

With the help of the meta data above, I ended up with the following parameters for repacking:

$ mkbootimg.py --kernel ex-boot-12.2.0/kernel --ramdisk ex-boot-12.2.0/ramdisk\
  --cmdline "console=ttymxc0,115200 init=/init androidboot.console=ttymxc0 max17135:pass=2, fbmem=6M video=mxcepdcfb:E060SCM,bpp=16 no_console_suspend"\
  --board ntx_6sl\
  --kernel_offset 0x70808000 --ramdisk_offset 0x71800000\
  --second_offset 0x71700000 --tags_offset 0x70800100\
  -o rebuilt-boot-12.2.0.img

Flashing the boot image

I flashed my rebuilt boot image onto the Tolino to see if it works at all.

The Tolino must be in fastboot mode for flashing, and I got to there that way:

  1. Connect it via USB to your computer.
  2. Power off by holding the power button until the shutdown options appear and shut it down.
  3. The power button has its own white LED. Wait until it is off (~5 seconds after the screen is off), because only then it is really powered off.
  4. Hold the light button and then also press the power button. Wait ~3-4 seconds until the LEDs on the power button and the home button are on.
  5. Release the buttons.

The reader is in fastboot mode now and shows an empty screen only. The mode can be verified by listing the fastboot devices:

$ fastboot devices
86346346,2f12e2342c894d9e8afa3cf5564223ac	fastboot

Flashing is easy now:

$ fastboot flash boot rebuilt-boot-12.2.0.img
$ fastboot reboot

If the Tolino starts up normally, then the rebuilt boot image works.

The Tolino Shine 2 HD needs the command fastboot flash:raw boot ...

Unpacking ramdisk

The rebuilt boot image uses the original kernel and boot image files. default.prop is inside the ramdisk, so I had to extract it:

$ cd ex-boot-12.2.0/
$ mkdir ex-ramdisk
$ cd ex-ramdisk
$ gunzip -c ../ramdisk | cpio -i

This gave me a directory full of files:

$ ls
charger             init.E60Q50.rc         property_contexts
data                init.E60Q50.usb.rc     res
default.prop        init.E60Q60.rc         rle
dev                 init.E60Q60.usb.rc     RTL8189_init.rc
file_contexts       init.E60QF0.rc         sbin
fstab.E60K00        init.E60QF0.usb.rc     seapp_contexts
fstab.E60Q30        init.E60QH0.rc         sepolicy
fstab.E60Q50        init.E60QH0.usb.rc     sys
fstab.E60Q60        init.E60QJ0.rc         system
fstab.E60QF0        init.E60QJ0.usb.rc     ueventd.E60K00.rc
fstab.E60QH0        init.E70Q20.rc         ueventd.E60Q30.rc
fstab.E60QJ0        init.E70Q20.usb.rc     ueventd.E60Q50.rc
fstab.E70Q20        init.ED0Q00.rc         ueventd.E60Q60.rc
fstab.ED0Q00        init.ED0Q00.usb.rc     ueventd.E60QF0.rc
fstab.freescale     init.environ.rc        ueventd.E60QH0.rc
init                init.freescale.rc      ueventd.E60QJ0.rc
init.common.usb.rc  init.freescale.usb.rc  ueventd.E70Q20.rc
init.E60K00.rc      init.rc                ueventd.ED0Q00.rc
init.E60K00.usb.rc  init.trace.rc          ueventd.freescale.rc
init.E60Q30.rc      init.usb.rc            ueventd.rc
init.E60Q30.usb.rc  proc                   WC121A2_init.rc

I went the safe route and did not modify anything at this stage. I only re-packed the directory back to a ramdisk file:

$ find . | cpio -o -H newc | gzip > ../repack-ramdisk

Then I re-built the boot.img as shown above with the new ramdisk file, flashed it onto the reader and booted it up. All fine.

Enabling adb

Now that I knew that the whole boot image building process worked, I actually modified default.prop in the ramdisk folder and added the two lines:

persist.service.adb.enable=1
persist.sys.usb.config=mass_storage,adb

After building, flashing and booting my adb-enabled boot image I was finally able to use adb:

$ adb devices
List of devices attached
86346346,2f12e2342c894d9e8afa3cf5564223ac	no permissions (user in plugdev group; are your udev rules wrong?); see [http://developer.android.com/tools/device.html]

.. or not. I first had to add a new udev rule:

$ emacs /etc/udev/rules.d/99-tolino.rules
SUBSYSTEM=="usb", ATTR{idVendor}=="1f85", ATTR{idProduct}=="6052", MODE="0660", GROUP="plugdev", TAG+="uaccess"

$ udevadm control --reload

Then I unplugged and replugged the USB cable, and it finally worked:

$ adb devices
List of devices attached
86346346,2f12e2342c894d9e8afa3cf5564223ac	device

$ adb shell
shell@ntx_6sl:/ $ getprop | grep product
[ro.build.product]: [ntx_6sl]
[ro.product.board]: [EVK]
[ro.product.brand]: [RakutenKobo]
[ro.product.cpu.abi2]: [armeabi]
[ro.product.cpu.abi]: [armeabi-v7a]
[ro.product.device]: [ntx_6sl]
[ro.product.display]: [eink]
[ro.product.hardwareType]: [E60Q50]
[ro.product.locale.language]: [en]
[ro.product.locale.region]: [US]
[ro.product.manufacturer]: [Rakuten Kobo Inc.]
[ro.product.model]: [tolino]
[ro.product.name]: [ntx_6sl]

How I got root access is a different story.

Published on 2019-09-30 in , , ,


OUYA server replacement is online

I wrote a replacement API for the OUYA gaming console because the original one had been shut down, making useless bricks out of the OUYAs in your living rooms.

If you just want to get the discover store running on your OUYA, have a look at the instructions.

OUYA?

At the beginning of 2014 I bought the android-based OUYA gaming console to have a device in the living room that I could use to play old Super Nintendo games on. The console did not use Google Play to install games with, but had its own "Discover" store that was hosted by OUYA itself.

In 2015, Razer bought OUYA to fill its own Cortex store with the 1200+ games that were available for the OUYA already. At that time they said:

If you already own the hardware, we're going to be keeping the lights on for at least a year.

In may 2019, Razer sent an e-mail to all registered users and developers:

From: Razer <no.reply@razer.com>
Subject: OUYA & Razer Forge TV Services Shutdown
Date: Wed, 22 May 2019 04:07:17 +0000
Reply-To: no.reply@razer.com

Dear Developer,

We would like to inform you that the Forge TV Games store and OUYA service will cease operations on 6-25-2019

This email serves as notice of termination of the Marketplace Agreement, in accordance with section 9 of the agreement, with effect on 5-25-2019.

Thank you for the support which you have extended us these past years. It has been a privilege to have worked with you and to have had you as part of our community.

- Razer

FAQ: https://support.razer.com/console/razer-forge-tv/

Server shutdown

With the OUYA server not available anymore, the OUYA cannot be used anymore:

  • If you buy a used OUYA, or did a factory reset, you're stuck at the startup screen at which you have to login or register with the server.
  • You cannot install any of the games, because the "Discover" store is gone.
  • Games you have already bought are back into demo mode because the server does not confirm that you really bought them.

A group of people formed that were determined to preserve what OUYA had, and they coordinated in the OUYA Saviors Discord chat server. I decided to let them do their work and went on with my life.

In november I went back to see what they managed to do and found... not much. No working replacement server software, only the "work in progress" BrewyaOnOuya store that let your OUYA login.

With a heavy heart I stepped in and did it myself.

Games!

To build a game store, I needed a list of games and their meta data like title, description, categorization/genres, images and download links. There was no such game data repository :/

Game data schema

My plan was to have a git repsitory with one file for each game that contains all data about it, including an .apk download link. This game data repository could then be used to fill the store.

At first I analyzed the API: The list of games ("discover") contained title, genres and an image, but not the description. The detail page API had the description and .apk file information, but the website. The app data API had the website, the "like count" and the video URL that were missing in the other API responses.

In the end of the first day I had a big HTML table that listed every data point a game could have.

On day two I build the schema for the data files, and manually added the first game - Bloo Kid 2.

On day 3 I found that "apps" and "details" were not the same, and that my API backup did not have "apps" data. I got a "apps" data backup from Devin Rich (@szeraax) and integrated their fields into my schema.

The next two days were filled with doing refinements to the schema, and at the end of day 5 my store API generation script was good enough so that I could use the OUYA to register, browse the store, look at game details and download and install a game!

First store api demo: Downloading Bloo Kid 2

All the games

Now I had the proof that the game data schema was usable for an actual store, and I began to build a script that created the game data files from the API backups I had, and from the games that Jason Scott put into the Internet Archive.

During that work I found that the app uuid was not an UUID for the application itself but for the release. Also, details responses allowed videos and images in custom order, which I had not seen yet. That and some other observations required schema adjustments.

Another big issue were the images and videos. The API backup files contained image URLs to cloudfront.net, filepicker.io and amazonaws.com. The amazon bucket was still available, but the other two were down already. Some games in the Internet Archive had copies of those images; they had the same file name as they had on cloudfront. I had backed up some others, and got another large backup from @blackcharcz in chat.

In the end, only 419 of the 20159 images could not be recovered. The images are currently hosted on ouya.cweiske.de/game-images/, and I asked Jason Scott to import them into the Internet Archive. The whole import process took a bit under two weeks of work.

You can find the OUYA game meta data in a git repository: ouya-saviors/ouya-game-data.

Server

I laid the basis for the server already in 2013 when I built an image store that converted the discover section to an image browser, and in 2015 when I documented the OUYA server API.

A normal API server would need to manage users and allow registration, support uploads of new games and updates to existing games. It would need to track user downloads, purchases and maybe even the Push to OUYA feature.

To have all of that, a programming language and a database would be required, which means constant maintenance and adjustments when the Python or PHP version gets updated. Another big issue is security - if someone finds a bug in the code, the libraries, the framework or the interpreter they could be able to break in and do bad things with the server.

I did not want to have any of those issues and thus had decided very early that I would build a script that creates a couple of static files, which would be served by a web server without any dynamics in it.

Limitations of static

My server's user registration API responds with "OK", but does not even look at the data the user sends to it. The "login" API route returns static data: A hard-coded session ID, and a static name "stouyapi". Every user is the same.

The "agreements" API does not track which marketplace terms you have already read and confirmed, it only says that you already did :) If the OUYA sends a crash report, the server says "ok" but does not look at the data.

Game rating submissions are ignored as well; the number of votes and average rating is taken from the static game data files and will never change. The server also does not track which user messages you have read or not.

PUT

It turned out that it is impossible to let Apache 2.4 return static responses to PUT requests - it needs a scripting language for that.

Apache's own content handler returns "405 Method not allowed" if it gets a HTTP PUT request, even if there is a rewrite rule that says "return 200 OK". So I had to resort to providing an empty-json.php PHP script that has to be registered as PUT handler in the Apache configuration:

Script PUT /empty-json.php

Without it, the OUYA will forever try to submit the user's public key and the marketplace agreement status.

Categorization

The "hard" part was creating the "Discover" menu because I could not remember how the original thing looked like. In the meantime @szeraax had implemented game data import and a first discover section in his BrewyaOnOuya store, and I took some ideas from there:

  • A row of "last updated" games
  • A row of "best rated" games
  • My own favorites, "cweiske's picks"
  • Categories with games for "2 players", "3 players" and "4 players"
  • Games grouped by content rating: Everyone, 9+, 12+ and 17+
  • Categories for all the original genres
  • A category for each letter

Each category begins with a "last updated" and a "best rated" row, and then lists 4 games per row. That way you scroll vertically instead of the horizontal scrolling in the original OUYA store, but it gives you the chance to really see all of the games.

Using the server

When the OUYA saviors chat server was launched, a former OUYA employee joined and gave us some information that helped us very much.

One of those important bits of information was that there was a ini-style config file that the OUYA developers used when they programmed: The configuration file ouya_config.properties. Just connect to the OUYA via USB, and create a plain text file with that name in the auto-mounted root folder.

Setting the options OUYA_SERVER_URL and OUYA_STATUS_SERVER_URL will immediately change the server that the OUYA uses, and lets us point our OUYAs to the new server in the easiest way imaginable.

Success

My stouyapi store went live on november 22, 3 weeks after I began to work on the project. The first gamers are using it already.

stouyapi's source code is on my git server (github mirror). I use the ouya-game-data repository to build it.

The next big task is getting all the now unpurchasable games into a fully playable state...

Screenshots

OUYA's main menu DISCOVER store main page cweiske's picks Alphabetical game categories Game details 3 players category Scrolling in a category

Also on: Twitter, ouya.world.

Published on 2019-11-25 in , ,


OUYA easter egg menu

On 2013-09-19 OUYA shipped a system update and wrote in its blog post:

4) We’ve even had our intern put a few fun easter eggs on OUYA =)

...

Our intern has hidden an “Easter Eggs” menu somewhere on the OUYA… can you find it?

imnotanerd pointed out a day later that you have to go to "console info" and press Y to get into the easter egg menu.

6 years later, this does not work anymore :/

OUYA development kit

ODK 1.0.12 was the last that included a ouya-launcher.apk, which is the OUYA user interface. It is easily decompilable with jadx.

The code tells us that there is a tv.ouya.console.launcher.settings.EasterEggs activity class, and its getEasterEggList method returns toggle_ou_ya_bumpers and crazy_button_legend.

All the easter eggs are only available when OuyaActivity.EASTER_EGGS_ACTIVATED.booleanValue() is true, which it is in ODK 1.0.12.

The code also suggests what had been on reddit: In the "console info" screen, simply press "Y" to show the easter eggs menu. But no avail today :/

Current firmware

The current firmware version is 1.2.1427_r1, and that obviously differs from the launcher in 1.0.12.

The only way to obtain the OUYA launcher code is to get OUYALauncher.apk and OUYALauncher.odex from the OUYA's /system/app/ folder. The apk file does not contain any code; that one is in the ODEX file whose format changes with every Android version and is much harder to decompile than plain .apk files. Luckily GDorn had already used Universal Deodexer V5 to get a half-way readable representation of the compiled code.

Today the OuyaActivity uses

android.content.Context.getSharedPreferences("EasterEggPreferences", 0);

to load easter egg settings and then looks for a boolean R.string.easter_eggs_enabled to see if easter eggs may be used at all. There is nothing that sets this configuration value, it is only read.

It turns out that those shared preferences are stored in an application's shared_prefs/ data directory; in our case the file path is /data/user/0/tv.ouya.console/shared_prefs/EasterEggPreferences.xml.

R.string.easter_eggs_enabled is a translated string that has a different value in every language :/

So when we put a boolean with that name into the preferences file and set it to true, the easter eggs menu will be enabled.

How to activate easter eggs

You need to have adb installed and running, and the OUYA connected via USB (or networked adb):

$ adb shell
$ su
$ vi /data/user/0/tv.ouya.console/shared_prefs/EasterEggPreferences.xml
type "i"
copy + paste the following:
---







]]>
---
press escape
type :wq
$ chown u0_a16:u0_a16 /data/user/0/tv.ouya.console/shared_prefs/EasterEggPreferences.xml
$ reboot

Now go to Manage -> System -> Console Info and press Y. The Easter Eggs menu will appear.

OUYA easter eggs activation menu

Easter eggs

OU-YA

There is only a single easter egg to activate in that menu: OU-YA Bumper Noise Maker. When it is enabled, pressing the left and right top shoulder buttons (KEYCODE_BUTTON_L1 and KEYCODE_BUTTON_R1) will play a sound "OU" and "YA". This works in any menu.

Key combo

The EasterEggs screen code also registers a KeyComboListener that wants the following key codes:

19, 19, 20, 20, 21, 22

It turns out that 19 is DPAD_UP, 20 is down, 21 left and 22 right.

Pressing up up down down left right on the left thumb stick in the easter eggs menu will play the "boot-up finished" video (R.raw.boot_video_long).

Published on 2019-12-10 in ,


Frontier Silicon firmware: FSH1 file system

After downloading the firmware file for my TechniSat DigitRadio 580, I could start analyzing it.

I used binwalk and bless, along with the usual set of unix command line tools like hexdump, dd and so. Using bless as GUI hex editor instead of plain hexdump proved very helpful because it has features like automatic binary-to-number conversion, highlight-other-places-that-have-the-selected-bytes and nice search and offset jumping.

One of the things I found was a kind of embedded file system that contains the files that are available via the HTTP port.

File system format

All offsets here are for firmware file ir-mmi-FS2026-0500-0286.2.11.12.EX65933-1A22.isu.bin, which is the current version for the DigitRadio 580. MD5 sum of the file: 92be288749f7ca73feba0126a09973cb. Mirrored at github.com/cweiske/frontier-silicon-firmwares .

To find it inside the firmware file, search for FSH1 and look at the second match, which is at 0x1f81dc:

001f81d0  00 00 00 00 00 00 00 00  00 00 00 00 46 53 48 31  |............FSH1|
001f81e0  9a 88 04 00 44 8c 0a 03  00 00 01 00 03 01 03 77  |....D..........w|
001f81f0  65 62 09 01 03 63 73 73  01 01 04 62 61 73 65 03  |eb...css...base.|
001f8200  01 06 69 6d 61 67 65 73  01 00 0d 68 65 61 64 65  |..images...heade|
001f8210  72 5f 62 67 2e 70 6e 67  23 03 00 00 0e 03 00 00  |r_bg.png#.......|
001f8220  23 03 00 00 00 12 73 74  79 6c 65 2d 68 61 6e 64  |#.....style-hand|
001f8230  68 65 6c 64 2e 63 73 73  5c 0a 00 00 31 06 00 00  |held.css\...1...|

All numbers are little endian!

46 53 48 31
"FSH1" marker that seems to mark the beginning of this file system. Note that the structure described here does not match on the first FSH1 found in the firmware file, so maybe it does not have to do anything with it.
9a 88 04 00

Size of the following file system, in this case 297114 bytes. This can be used to copy the file system's data with dd into a separate file:

$ dd if=ir-mm....bin of=fsh1-2 skip=$((0x1f81e4)) count=297114
44 8c
Unknown.
0a 03 00 00 01 00 03 01 03 77
The file system's data.

Index

The file system itself is split into an index and a data section. The index is built as follows:

001f81d0  00 00 00 00 00 00 00 00  00 00 00 00 46 53 48 31  |............FSH1|
001f81e0  9a 88 04 00 44 8c 0a 03  00 00 01 00 03 01 03 77  |....D..........w|
001f81f0  65 62 09 01 03 63 73 73  01 01 04 62 61 73 65 03  |eb...css...base.|
001f8200  01 06 69 6d 61 67 65 73  01 00 0d 68 65 61 64 65  |..images...heade|
001f8210  72 5f 62 67 2e 70 6e 67  23 03 00 00 0e 03 00 00  |r_bg.png#.......|
001f8220  23 03 00 00 00 12 73 74  79 6c 65 2d 68 61 6e 64  |#.....style-hand|
001f8230  68 65 6c 64 2e 63 73 73  5c 0a 00 00 31 06 00 00  |held.css\...1...|
0a 03 00 00
Size of the index itself. Afterwards, the data starts.
01
Entry type: 00 for file, 01 for folder
00
03
Length of name. The root folder has no name, thus the length is 0.
77 65 62
Name (only ASCII characters seen yet): "web"
Directory-specific
03
Number of entries (files+folders) in the directory.
File-specific
23 03 00 00
File size, here 803 bytes
0e 03 00 00
File offset (first index size byte is position 0)
23 03 00 00
Compression indicator. If the number is the same as the file size, then the file is not compressed.

Immediately after the end of an entry, the next entry follows.

FSH1 parser

After I had decoded the meaning of the single bytes, I wrote a small parser that takes the dd'ed fsh1-2 file and prints the tree: fsh1-parser.php.

It will print the following for the DigitRadio 580 firmware:

$ ./fsh1-parser.php fsh1-2
Index length: 778
/   (3 files)
  web/   (9 files)
    css/   (1 files)
      base/   (3 files)
        images/   (1 files)
          header_bg.png              803 bytes, offset 782       compressed: no
        style-handheld.css          2652 bytes, offset 1585      compressed: yes
        style-screen.css            3184 bytes, offset 2471      compressed: yes
    images/   (2 files)
      company_logo.png              4471 bytes, offset 3509      compressed: no
      favicon.ico                   1150 bytes, offset 7980      compressed: yes
    iperf/   (3 files)
      control.html                  4804 bytes, offset 8357      compressed: yes
      index.html                     132 bytes, offset 9813      compressed: yes
      info.html                       38 bytes, offset 9907      compressed: no
    js/   (6 files)
      LICENSE                       1544 bytes, offset 9945      compressed: yes
      jquery-1.6.2.min.js          91556 bytes, offset 10790     compressed: yes
      jsbn.js                      15161 bytes, offset 42838     compressed: yes
      prng4.js                      1009 bytes, offset 47946     compressed: yes
      rng.js                        1883 bytes, offset 48412     compressed: yes
      rsa.js                        2644 bytes, offset 49211     compressed: yes
    languages/   (2 files)
      en.json                       5528 bytes, offset 50287     compressed: yes
      test.json                     5555 bytes, offset 51959     compressed: yes
    Network.html                    9076 bytes, offset 53822     compressed: yes
    Status.html                     3855 bytes, offset 55281     compressed: yes
    common-1.0.3.js                74443 bytes, offset 56124     compressed: yes
    index.html                      1878 bytes, offset 70670     compressed: yes
  icons/   (5 files)
    dlna_icon_large.jpg             2630 bytes, offset 71431     compressed: yes
    dlna_icon_large.png             8935 bytes, offset 73893     compressed: no
    dlna_icon_small.jpg              970 bytes, offset 82828     compressed: yes
    dlna_icon_small.png             2889 bytes, offset 83661     compressed: no
    icons_NE8251.rar               30051 bytes, offset 86550     compressed: yes
  FwImage/   (1 files)
    sd8782_uapsta.bin             250064 bytes, offset 116564    compressed: yes

Interesting files

Just looking through the file tree gives us some yet unknown information:

/web/iperf/index.html

The radio has an "iperf" web interface that has a "iperf Command" text field and the following buttons:

  • Add new task
  • Get tasks
  • Execute
  • Stop Execution
  • Delete tasks

The buttons use the netremote.test.iperf API.

I don't yet know which commands can be use here.

FwImage/sd8782_uapsta.bin
This is a firmware blob for a Marvell WiFi chip. See JianRuiqian/marvellwifi.
js/rsa.js
I'm really interested how RSA signatures/encryption is used on the radio. See below.

File extraction

Now that I had the offsets, I could extract the single files from the file system with dd:

$ dd if=fsh1-2 of=company_logo.png bs=1 skip=3509 count=4471
$ file company_logo.png
company_logo.png: PNG image data, 140 x 36, 8-bit/color RGBA, non-interlaced

Most files are compressed:

$ dd if=fsh1-2 of=dlna_icon_large.jpg.zz bs=1 skip=71431 count=2630
$ file dlna_icon_large.jpg.zz 
dlna_icon_large.jpg.zz: zlib compressed data

They can be decompressed with pigz:

$ pigz --keep --zlib --decompress dlna_icon_large.jpg.zz
$ file dlna_icon_large.jpg
dlna_icon_large.jpg: JPEG image data, JFIF standard 1.02, aspect ratio, density 1x1, segment length 16, Exif Standard: [TIFF image data, little-endian, direntries=5, orientation=upper-left, software=ACD Systems Digital Imaging, datetime=2015:08:18 15:32:25], baseline, precision 8, 120x120, components 3

Great success!

RSA key usage

The netRemote API has two methods for RSA keys: netRemote.sys.rsa.status and netRemote.sys.rsa.publicKey. The first can be used to check if the radio has already generated a RSA key, the second to fetch the public key.

This public key is used by the web interface to encrypt the Wifi password before sending it to the radio. Doing so prevents eavesdroppers to sniff the passphrase over the unencrypted HTTP connection.

Published on 2019-05-22 in ,