The latest posts in full-text for feed readers.
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.
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.
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.
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.
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:
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.
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.
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 blackbox, tolino
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.
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 android, blackbox, ouya
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.
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.
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!
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
}
}
);
}
}]]>
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 android, blackbox, bigsuck, ouya
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.
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.
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)
and
CSR GAIA (Generic Application Interface Architecture) implements an end-to-end, host-agnostic ecosystem supporting host application access to device functionality.
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:
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.
Published on 2022-10-26 in blackbox, hardware
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.
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.
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.
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.
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:
Source: Tolino Update 15.2.0 post by Ohmugin
More information about finding the passwords: Tolino 16.0 password crack attempts.
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:
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.
If you want to switch the book shop (away from Thalia to buecher.de or so), modify ePub.xml the following way:
<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.
Published on 2020-09-27 in android, blackbox, hardware, 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.
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.
There are two settings that determine if adb root
works,
and they are documented in
android-core/adb/daemon/main.cpp:
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.
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.
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.
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:
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.
You can find my Tolino 12.2.0 boot image with the patched adbd in my binary file archive.
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.
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.
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 android, blackbox, hardware, tolino
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:
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 :/
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.
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.
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
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:
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 ...
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.
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 android, blackbox, hardware, tolino
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.
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.comDear 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
With the OUYA server not available anymore, the OUYA cannot be used anymore:
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.
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 :/
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!
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.
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.
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.
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.
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:
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.
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.
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...
Also on: Twitter, ouya.world.
Published on 2019-11-25 in blackbox, http, ouya
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 :/
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 :/
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.
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.
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.
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 blackbox, ouya
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.
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!
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
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...|
Immediately after the end of an entry, the next entry follows.
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
Just looking through the file tree gives us some yet unknown information:
The radio has an "iperf" web interface that has a "iperf Command" text field and the following buttons:
The buttons use the netremote.test.iperf API.
I don't yet know which commands can be use here.
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!
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 blackbox, hardware