Tab10CPro Strings
junrenshi opened this issue · 18 comments
I install GetBooxUpxKeys in a Boox Tab 10C Pro, and get outputs:
"MODEL" = "Tab10CPro",
"STRING_SETTINGS" = "D999393AEAA81119AC0F8C8C1EA11089",
"STRING_UPGRADE" = "CD7894A509490BAF2BAA129686A083E4"fingerprint: Onyx/Tab10CPro/Tab10CPro:11/2023-10-30_17-05_3.5_edb1c449c/4119:user/release-keys
Using the strings to decrypt update.upx results in error:
python3 DeBooxUpx.py Tab10CPro
Traceback (most recent call last):
File "/Users/shi/Downloads/decryptBooxUpdateUpx/DeBooxUpx.py", line 337, in
decrypter = DeBooxUpx(**boox_strings[device_name])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/shi/Downloads/decryptBooxUpdateUpx/DeBooxUpx.py", line 289, in init
self.decryptStr(tmpKey, STRING_SETTINGS))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/shi/Downloads/decryptBooxUpdateUpx/DeBooxUpx.py", line 296, in decryptStr
return cipher.decrypt(b64decode(string)).decode().strip()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
UnicodeDecodeError: 'utf-8' codec can't decode bytes in position 0-1: invalid continuation byte
It seems GetBooxUpxKeys cannot get correct strings for this new device.
You have (presumably) the Base64 decoded string.
The current setup uses Base64 strings
Post libota_jni.so and I'll check without Java.
Yes, they do look like hexadecimal numbers.
libota_ini.so is here:
libota_jni.tgz
I get the same results as you.
(The libjni_ota.so does not have those values as either binary or string directly in it. It's generated.)
But that is 32 hex digits, which is 16 bytes or 128 bits.
The strings we've been using have been 44 characters which can be evenly base64 converted to 33 bytes or 264 bits.
(Note that none of the strings end in '=', which means it's an even 4 char to 3 byte conversion.)
This new libota_jni.so has a third JNI function, Java_com_onyx_android_onyxotaservice_RsaUtil_nativeDecryptFile
So maybe things have changed radically. @Hagb
@junrenshi Could you please upload your /system/app/OnyxOtaService/OnyxOtaService.apk
? It might be needed to decompile this apk to try to find how it works.
The file is now moved to /sys/priv-app/OnyxOtaService/.
Here is the file:
OnyxOtaService.apk.tgz
Would you have a link for that update.upx?
I can't seem to find it.
Works nicely for me. :)
Poke3:/data/local/assets # unzip -l update.zip
Archive: update.zip
Length Date Time Name
--------- ---------- ----- ----
155 2009-01-01 00:00 payload_properties.txt
0 2009-01-01 00:00 apex_info.pb
678 2009-01-01 00:00 care_map.pb
615 2009-01-01 00:00 META-INF/com/android/metadata
1030 2009-01-01 00:00 META-INF/com/android/metadata.pb
1513 2009-01-01 00:00 META-INF/com/android/otacert
1556748068 2009-01-01 00:00 payload.bin
--------- -------
1556752059 7 files
Great! You did that. Can I do that as well?
That was my proof of concept, extracting without Python or Java.
I don't pretend to understand any of this encryption junk.
You can mod the stock DeBooxUpd to only work for the Tab10CPro.
@Hagb will have to figure out what to do going forward.
Just insert these two lines in DeBooxUpd, line 287, right after self.path: str =
self.key = b'\xd9\x99\x39\x3a\xea\xa8\x11\x19\xac\x0f\x8c\x8c\x1e\xa1\x10\x89'
self.iv = b'\xcd\x78\x94\xa5\x09\x49\x0b\xaf\x2b\xaa\x12\x96\x86\xa0\x83\xe4'
Great! It also works for me. Thanks a lot!
Hey guys,
Recently got my Note air 3C, after looking at both libota_jni.so
and OnyxOtaService.apk
, Boox seems to have changed how the decryption of the Upx files work.
Previously there was a function which returns the AES secret key (Java_com_onyx_android_onyxotaservice_RsaUtil_nativeGetSecretKey
) and AES IV (Java_com_onyx_android_onyxotaservice_RsaUtil_nativeGetIvParameter
), these were encrypted using DES (With the model name as the key, lol) and encoded using Base64.
Now for my new device with fingerprint: "Onyx/NoteAir3C/NoteAir3C:11/2023-10-17_09-55_3.5_a37b266eb/2482:user/test-keys"
, this is no longer the case.
Now there are two functions exported from the libota_jni.so dynamic library that return the direct AES secret key and AES IV.
These functions have mangled C++ names but are getKeyString
and getInitVectorString
This means that the fancy DES decryption is no longer needed but rather the parameters can be feed straight into AES to perform the decryption.
Here is a modified version of the script to extract the new private key/IV
@Hagb Feel free to use this code
from ExAndroidNativeEmu.androidemu.const import emu_const
from ExAndroidNativeEmu.androidemu.emulator import Emulator
from ExAndroidNativeEmu.androidemu.internal import elf_reader
from ExAndroidNativeEmu.androidemu.java.classes.types import *
from ExAndroidNativeEmu.androidemu.utils.memory_helpers import read_utf8
AES_SECRET_KEY_FUNCTION = 'getKeyString'
AES_IV_FUNCTION = 'getInitVectorString'
def call_function(so_path, function_name, *args):
# Initialize emulator
reader = elf_reader.ELFReader(so_path)
if reader.is_elf32():
emulator = Emulator(
vfs_root="ExAndroidNativeEmu/vfs",
arch=emu_const.ARCH_ARM32,
config_path="ExAndroidNativeEmu/emu_cfg/default.json"
)
else:
emulator = Emulator(
vfs_root="ExAndroidNativeEmu/vfs",
arch=emu_const.ARCH_ARM64,
config_path="ExAndroidNativeEmu/emu_cfg/default.json"
)
module = emulator.load_library(so_path)
function_symbol = [module.find_symbol(export) for export in module.symbols if function_name in export]
result = emulator.call_native(function_symbol[-1], *args)
return read_utf8(emulator.mu, result)
if __name__ == '__main__':
if len(sys.argv) < 2:
print("python %s /path/to/libota_jni.so" % __file__)
exit(0)
secret_key = call_function(sys.argv[1], AES_SECRET_KEY_FUNCTION)
iv = call_function(sys.argv[1], AES_IV_FUNCTION)
print(f"AES Secret key: {secret_key}")
print(f"AES Initialization vector: {iv}")
Below I have attached the corresponding binaries and screenshots of the new apk and library functions.
For now we can just backtrack to the strings:
'Tab10CPro': {
'MODEL': 'Tab10CPro',
'STRING_SETTINGS': 'on8l4AwDwIgVNi6AgrAqLzkc94XMx9O37CCELz23Fm+J',
'STRING_UPGRADE': 'pQIr4QYOsvxLqxXik9cIIzca+QzC2pa9U/hY8p/EAkur'
}
Update: continue in #70 for Note Air 3C, instead of polluting this issue.
from ExAndroidNativeEmu.androidemu.const import emu_const from ExAndroidNativeEmu.androidemu.emulator import Emulator from ExAndroidNativeEmu.androidemu.internal import elf_reader from ExAndroidNativeEmu.androidemu.java.classes.types import * from ExAndroidNativeEmu.androidemu.utils.memory_helpers import read_utf8 AES_SECRET_KEY_FUNCTION = 'getKeyString' AES_IV_FUNCTION = 'getInitVectorString' def call_function(so_path, function_name, *args): # Initialize emulator reader = elf_reader.ELFReader(so_path) if reader.is_elf32(): emulator = Emulator( vfs_root="ExAndroidNativeEmu/vfs", arch=emu_const.ARCH_ARM32, config_path="ExAndroidNativeEmu/emu_cfg/default.json" ) else: emulator = Emulator( vfs_root="ExAndroidNativeEmu/vfs", arch=emu_const.ARCH_ARM64, config_path="ExAndroidNativeEmu/emu_cfg/default.json" ) module = emulator.load_library(so_path) function_symbol = [module.find_symbol(export) for export in module.symbols if function_name in export] result = emulator.call_native(function_symbol[-1], *args) return read_utf8(emulator.mu, result) if __name__ == '__main__': if len(sys.argv) < 2: print("python %s /path/to/libota_jni.so" % __file__) exit(0) secret_key = call_function(sys.argv[1], AES_SECRET_KEY_FUNCTION) iv = call_function(sys.argv[1], AES_IV_FUNCTION) print(f"AES Secret key: {secret_key}") print(f"AES Initialization vector: {iv}")
This works perfectly on Note Air 3C, and the key I got is
AES Secret key: 4DEAED7F843CDCEDB384E6FC468C540E
AES Initialization vector: 964E8856F922178810AEC06E0D363512
I modified the sample code to
#!/usr/bin/env python3
from DeBooxUpx import DeBooxUpx
MODEL = "NovaPro"
STRING_SETTINGS = "j857wYAQcWZgvIEQ/tcQqzxreUJgFHwJl6D2TN9BuSkQ"
STRING_UPGRADE = "+soGw/YVdGIRJiAs5SMmv1ihW37H1Fa9+/1w2Vgt14Ag"
STRING_LOCAL = "lpsj9NJ8Kzv8jHb+OO8A5lxC+9Zhl243bFmDZYaF"
import sys
# Run with
# $ ./decrypt.py INPUT OUTPUT
updateUpxPath = sys.argv[1]
decryptedPath = sys.argv[2]
decrypter = DeBooxUpx(MODEL, STRING_SETTINGS, STRING_UPGRADE, STRING_LOCAL)
decrypter.key = bytes.fromhex("4DEAED7F843CDCEDB384E6FC468C540E")
decrypter.iv = bytes.fromhex("964E8856F922178810AEC06E0D363512")
print('When updating, the device decrypt update package into', decrypter.path)
decrypter.deUpx(updateUpxPath, decryptedPath)
and it decrypts the UPX file successfully.
Thank @RenateUSB and @hexomethyl ! I'm thinking that we could store these strings in boox_strings
dict of DeBooxUpx
like the older ones, and determine whether it is the AES keys or the DES-encrypted AES keys based on the length of the string?
@Hagb I think that there are a number of ways to do this:
- Use 32 character hex strings for everything. The old strings can be decrypted into new source
- Use 44 character base64 string for everything. The new strings can be encrypted into new source
- Use a mixture using the same slot names
- Use a mixture using new slot names (key, iv) for new style strings
I think that (1) is the best solution. It's less text. It's the way forward.
In the short term I think that (2) is fine for now.
See #62 for four new model strings of which two are "back encrypted".
@RenateUSB Thank you! The solution (1) and the code looks good to me.
Could you please submit a PR? Btw, Steam
in deUpxSteam
is a typo I made, which should be Stream
. You could fix it together.