Repository: Hagb/decryptBooxUpdateUpx
Branch: master
Commit: 2be0d1c2319c
Files: 10
Total size: 26.4 KB
Directory structure:
gitextract_p80nabgi/
├── .gitmodules
├── BooxKeyConvert.py
├── BooxKeys.csv
├── CONTRIBUTING.md
├── DeBooxUpx.py
├── LICENSE.txt
├── README.md
├── algorithm-zh_cn.md
├── ota_jni.py
└── update_readme_strings.py
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitmodules
================================================
[submodule "ExAndroidNativeEmu"]
path = ExAndroidNativeEmu
url = https://github.com/maiyao1988/ExAndroidNativeEmu.git
================================================
FILE: BooxKeyConvert.py
================================================
#!/usr/bin/env python3
try:
from Cryptodome.Cipher import DES
from Cryptodome.Hash import MD5
except ModuleNotFoundError:
from Crypto.Cipher import DES
from Crypto.Hash import MD5
from Crypto import version_info
if version_info[0] == 2:
raise SystemExit('Need either pycryptodome or pycryptodomex, NOT pycrypto!')
import sys
from base64 import b64decode
from base64 import b64encode
def decryptStr(tmpKey: bytes, string: str) -> str:
if len(string) != 44:
print('Key is not the correct length of 44')
return None
try:
data: bytes = b64decode(string)
except:
print('Key is not valid base64 encoding')
return None;
if len(data) != 33:
print('Base64 decoding of key returned length less than 33')
return None;
cypher = DES.new(tmpKey, DES.MODE_CFB, iv=b'\xff' * 8, segment_size=64)
data = cypher.decrypt(data)
try:
out: str = data.decode()
except:
print('Key did not decode correctly for this model')
return None
if out[32] != '\n': print('Decrypted key missing standard newline')
return out[:32]
def encryptStr(tmpKey: bytes, string: str) -> str:
cypher = DES.new(tmpKey, DES.MODE_CFB, iv=b'\xff' * 8, segment_size=64)
out: str = b64encode(cypher.encrypt(bytearray(string.upper()+'\n', 'ascii'))).decode()
return out
if __name__ == '__main__':
if len(sys.argv) >= 3:
model = sys.argv[1];
tmpKey: bytes = MD5.new((model * 2).encode()).digest()[:8]
for i in range(2, len(sys.argv)):
if i > 2: print()
x = sys.argv[i]
print(x)
y = decryptStr(tmpKey, x)
if y != None:
print(y)
z = encryptStr(tmpKey, y)
if z != x: print('Key does not "round-trip" correctly')
else: print('Usage: BooxKeyConvert.py <model> <settings> <upgrade>')
================================================
FILE: BooxKeys.csv
================================================
Name,Model,Key,IV
Darwin11-ru,DARWIN11,68954E31C8EA505B646641AF2015B63C,C00286616C61DB326065BE988D1C1F90
Darwin7,MC_DARWIN7,9272F672593B96AE388849C30EC9272F,7EEF6EF4C34231E4E16A4C688DBF7C4B
FLOW,FLOW,42F6E69DB0A176DC5FBA04DE2C4C0C7D,9C0F30369B5539A1E0696598638D4731
FLOWPro,FLOWPro,714C5E4655170DA6774E7E1DFCF78D9E,08A0369CAE896E52AF8694A2EF39C1EE
FLOWProMax,FLOW.Pro Max,6D0D6E2DBDF3D5D6C71B84E170E1E33A,11DFA3EAC57A65E2F9731DA5F278013C
GALILEO,GALILEO,F303E7819D87666785B5CEE6C172201D,103040EE4F225D67A607B25337E787B9
Galileo2-ru,GALILEO2,B510E6FBDCF05596BED85DF4EEA293CB,B5A3B196ABE1310018A45FED291B856A
Go103,Go103,7BDBEE7DB0CB42F3334C40FA67491C72,951CB9A08616E7E230386931AB090AEF
Go6,Go6,09743FA75F5B9899787665A8806085A3,D294C0832EACF2EEEE0E944AB5928784
Go7,Go7,DFEA378BF727024CD7121DCD5856228C,1B534CD3E821247C8BD39D4AF0301DAA
GoColor7,GoColor7,73AA85E6BAF08452FC583DA8A533642A,4F573D5A9AE3857F8D220C7CF6BE32A8
GoColor7_2,GoColor7_2,8B2A051FF85FFD266C3639F8AF47C6F8,30A4B381DB0E88188A065AF1B1C78A61
Gulliver-ru,MC_GULLIVER,0CBE6C03F589353182354085D8E20F31,7E34FC7B6E6FD0A39C0E43CBAF32FB52
JDRead2,JDRead2,E4CEA0CD7CDB7BBC79BC651D3A98F96F,22430CF10E1F604E88BF32F0B683BBF7
KANT,KANT,13BC5939158355469CE617D7733DF4CB,35EB192AF12EF49206C8D747A49F0EAC
KonTiki-ru,KON_TIKI,071C5480AFAF91DD0EEA6EA1F4FC8951,7BCF8715C94BDD5FB684E0F66B9F57B5
KonTiki2-ru,Kon_Tiki2,98AC5EB0D8D7A0A0B2E75B278DB8B288,745555DF150183D4C92AFBC4EDE58ED2
KonTiki5-ru,Kon_Tiki5,AAE96BDB70457A536D2123DCD301A5A5,4B64E4867EF473E7BEDC652A1B737C6C
Leaf,Leaf,2FA5F9484C5079FA25B89FD6A926F0FD,0CB5EBC30E284C71108ACC344026DFD6
Leaf2,Leaf2,4131D58DC6CB5D85BFD23D9F576CAB16,A60D8B8DEE2A494194F1AC6C80E850AC
Leaf2-cn,Leaf2_P,E95EE8726EC4B63A2241F38829C27FCC,8093A741A8A4C45E11690B72D78C59A1
Leaf3,Leaf3,6ABE99B43B928F351C83CDD5A2516A6E,1009E8860F53BB40483A83173F844198
Leaf3C,Leaf3C,59A002BBBF0C9AF21FE068AAB8712E39,ADCD327395970FD0B8AA0159AB057341
Leaf5,Leaf5,648C324326F16772931171609F1EC0F6,DFA378E2340EF490F5EB6C8EDBDB97B5
Leaf5C,Leaf5C,66C121409BAF575EBA9F56C6127F5DEF,84EC4607094D49AAAF3A3946000E3669
Livingstone-ru,LIVINGSTONE,CDA476FB48B341F03C3217EBDFCA1BFA,B642BDF244FCC321E1A62BBE0EE4C15A
Livingstone2-ru,LIVINGSTONE2,5517687D3994A7EB439DA99F87134B88,509E86C0948605D593A44D70143529C6
Livingstone3-ru,LIVINGSTONE3,80E2F29AA083CBFBB065DFBA1922F1CC,1D8FBDAE2AB78E1DD0D6008036DE5667
Lomonosov-ru,Lomonosov,D2F7E3354FC07C2521BF609D01FBC90F,0070D3F87818CF23DB5EE9798B8B0657
Max2,Max2,7B71BD797D7E3721D69F4449E12B4C22,38B2BB851DAF20E1BE6E7E3F349CABA0
Max2Pro,Max2Pro,8BEAEE4D037DAC8E2DB18458CDFA4A53,706038B3497224CCC2B81572EACAA780
Max3,Max3,0C3FF83E57334391A2D4171E99EAC1A9,982877D54100A07BBF205F31193B2D3D
MaxLumi,MaxLumi,3205089AF7E08108730ABFC237D94BE8,09FE14E89ABE38654112885A43352D30
MaxLumi2,MaxLumi2,25F86689E5FE3DEB2856983CCF21FB6D,14E98D0F357F7087D27D22406D7291E0
Note,Note,1D7A5202269D384B72F348CE25D55120,A0828A338A06FF2CD3618B219D3E30E2
Note2,Note2,47363CF9416ADE0231B61D62DD8F4539,462B820F6C4B3BC72EE48FDFF0FA3E39
Note3,Note3,098C863EB6E1F8DC96720031B32EA200,EFC95EF6B02442EE518F2D2BD5594250
Note4,MC_Note4,182F38C6C9C292A0E4037730DDA1A509,F5852333D9EF9ADA35BA05824C0EAD65
Note5,Note5,FEACD5CEDBD266D7E84C560C4BE95511,2EFCE01264CD8DA64307434F887ED737
Note5Plus,Note5Plus,9377320B7032EDC0194DBA82AD9DDCB0,9B7422F02E481B143030F24F5F3736B0
NoteAir,NoteAir,AED5E8AE8AB08319EC791849310171C2,F62715B99809212131696C2FD7C6F75D
NoteAir2,NoteAir2,856C9D4ADCE8015F272357F5DDF08B71,879D69054D57C76152CF272A0C121006
NoteAir2P,NoteAir2P,1276ECC6636DA6AFCEF5D7F300E5C915,1F6CF46EC279E61EDB7FC150C3471972
NoteAir3,NoteAir3,54F4360CD7465F72D31957D9DAF017C7,40FE355773181D3B8D0B3E41B96D6998
NoteAir3C,NoteAir3C,4DEAED7F843CDCEDB384E6FC468C540E,964E8856F922178810AEC06E0D363512
NoteAir4C,NoteAir4C,CF26B9FD1C5F74A8170C24D389F9A92D,3BC02F669B2C772CC3F9A583F1FC3263
NoteAir5C,NoteAir5C,3829B3AEBF3600BD28AB88B887E7B1F0,1B16F54416A306113EA5B7B4CB5F65A3
NoteMax,NoteMax,D999393AEAA81119AC0F8C8C1EA11089,CD7894A509490BAF2BAA129686A083E4
NotePro,NotePro,F72D46836B5B1A0D1CB5BB27C6895A4F,2652833CDDC4C4CDD3BEC6F8D6AD3914
NoteS,NoteS,0B82F60DB060E404F752E9B91F0BE28B,363A003F2C54F1DA3412F7A9CC7DFDBE
NoteX,NoteX,8CC1464D7009076D571D4FF249F47B3D,2E3D8332638FED444BA24D5462C12046
NoteX2,NoteX2,00D996A8B45734B9E7943C0FF3333646,AE9ACFDF9C871C2B9458B2F2245035E0
NoteX3,NoteX3,40B1F5E5773AA90DC41ED62AA34EEF6D,C41B0F1CD0E88A0CC6CA22E1429AC4A3
NoteX3S,NoteX3S,37BD0706BCBCA53265083A292966FD4D,6EB0274D85707BA9729580E49D0890AA
NoteX3Pro,NoteX3Pro,7002375BDC1F2FE5FD29C3A0D3470883,C5334CAA3B27498621CFB7744F578797
NoteX5,NoteX5,16640A316BEE96785C799BC419D002AC,8CFF7D62605DE3809106B3136177C5CF
NoteX5Mini,NoteX5Mini,6E18EA16B323F39B1CE2CA6626843F17,1F709679CD98CDF5E01FC6974928E985
Note_Lite,Note_Lite,BD78142B675385319A5DD30AB786E743,5898BFC811708BC8DF67EEB6186F8752
Note_YDT,Note_YDT,A5BEEB144025D73FD90971E4906232BA,18870C4F426A494320868606DA2091BB
Nova,Nova,F03A6BBC306066AB1B3A5BE5AEC1D3F3,558CD7D43E17869A50FFFF6C72C26282
Nova2,Nova2,0AC6B91ABB5988ADE6313FD62F6EDB0B,3B9D6F6DE8D1795D3D265C0EFC9B59C7
Nova3,Nova3,6D89ABF9B380998958D8002897BBF650,7F1224824C0B41DAAE907ADE10828C58
Nova3Color,Nova3Color,796FD580A4BE82B6FEAC8CFDA676585B,AFB0DFEBD0A2D71F37F21231808BD1A9
Nova5,Nova5,96359CD7CCB6F86C38D808E2C4109B70,0F24CA95466CE4F0CE6477CE20EDCB5D
NovaAir,NovaAir,20A25EB3B9363810BCFFA9A2E1B55B0A,1B5380830BF637ECB7181C3926D5B479
NovaAir2,NovaAir2,88520228DF831062B6CB7AAED5166156,C0A91A092BC76A46F7AE6FDC4EED5C20
NovaAirC,NovaAirC,3E37242727DD67DF9D4EC08D936974E2,4DB17A985684B7319A349743B22BF41B
NovaAirS,NovaAirS,93E8EAF62AFAEA06D735CE5C07ABD390,083BB627BC2DD6290CD972874BC767F7
NovaPlus,NovaPlus,18C0438BE7CE881BC86BC6C84CEC096D,592328C13A578E053FA565A5B684294D
NovaPro,NovaPro,619A3C6290FEDA02418091AE2F39592F,C5DCEF361A60239DB4E8648DD9F7847A
P6,P6,E1C71F36EC30FFCC2FEC7501AD62FEC2,7B457D7E495B0B860755AFE362E9E5BF
PadMu3,PadMuAP3,A8BAA1F101DB9FC7CA0CD9FB04A97694,16ED3B30C6F251BA4267679433765E5F
Page,Page,25CC82D3A7B62D4F03321946F135037D,8D2BC33F82F6A14E04085574947687D0
Palma,Palma,0E729B7296ACCB86AE5A6C15374C4D02,BF694808B49ACAF49E73334E3A4B1163
Palma2,Palma2,EBF46CA428B21CD4012B17F6E37F132D,CE5D3FD02F712FE0E30F7DC31B0789E9
Palma2Pro,Palma2Pro,2B818F3CAD5200FB983C3C5867EA5DF5,006AAA200C7E8C905F2BFCE80E0A6156
Poke2,Poke2,C5E45730FBD665FC3857C6D78FD63E43,528DB4C57394F09D3C00E2D45E5C9641
Poke2Color,Poke2Color,D314CF54B76931B6309F83B2F037D419,C397190FD2E48874C1CFBE52A782AB59
Poke3,Poke3,3DC53116D8AE3DCCEAD99F53E08E1E35,42B996AB6E252DCA4EDBC668BA3E5A3A
Poke4,Poke4,E155E2D73EFC3EB70745B434BE203EFD,C3AC2D3955E32709F10511C3B662B27F
Poke4Lite,Poke4Lite,6C74952C3EA2E07244D6B3D91C222335,EB037125447814B0F4198E201A966B3D
Poke4S,Poke4S,CFD60D360BD91B04843F8B3090B2DA08,3595F7FCE8B4F2AD5804DEE46299281C
Poke5,Poke5,59B93DC6771B6740C266DD2AC851943C,4CF0CCAE9C4201B24D623221B9BA6673
Poke5P,Poke5P,550504B1DE28C363172040829BFFA408,26C4D62584DA0619AB972B66ACC3689A
Poke5S,Poke5S,AE0FB835F4A50D35C95FEC7700EEDF1D,321FFD1D14E8A2A7A422C683D152AF3B
Poke6,Poke6,DFA1B679E1089011FEB9C8455AD7E1D5,83BF5F0F69A76D42D742E044EA1BDACF
Poke_Pro,Poke_Pro,AA151AD6C888DFB010433F3B1EC676EC,FD83B38456D9C052F22414DF9A2C0FEC
Savi6,Savi6,C88A656DF7A4A4CD137892693622EFCE,21970EF67B88056928F827D9023C9B15
SP_NoteSL,SP_NoteS,3992B2A532F9ABDCB953EDAD7EC95FFF,12C928CF2C665AF83B50361C80B88297
SP_PokeL,SP_PokeL,6E01CB5A77A96F675561AE7CB7CAF6AC,DEAEF764C4290B4039EA7DDC2110A7B2
T10C,T10C,21B04F8458DCD400D6DD2FEDDF13BC16,C78066550FE7CE6CCAFB692FFFB0E3F5
Tab10,Tab10,C301F33490ABF2C9396E335D3258BD37,2B2B6159E3CA418F71D58966310B764F
Tab10C,Tab10C,3E3F7FB56DD7DF682D9F49070543B4F0,37E49117577D5BF42771F8E0DAAFBA73
Tab10CPro,Tab10CPro,D999393AEAA81119AC0F8C8C1EA11089,CD7894A509490BAF2BAA129686A083E4
Tab13,Tab13,1072DE10B43097036B98B9021F308FB0,F180C655AABF3EB02D9E45B9FD5379B2
Tab8,Tab8,ECC953580D49BAD70D9DBA526EC091D2,46085FF59CCE685B38FB651876E8964B
Tab8C,Tab8C,D7731639E43C1C421F25178D79A07031,A419E9D70B549920F91DD28733A51A46
TabMiniC,TabMiniC,3B0B382BDA19D0E9F0755C8B01B086DC,A602693F5AF19546BF7F40A523237E54
TabUltra,TabUltra,A01FF8E0F4D139FC75FAA49D61E12E9C,EC94D4BA3550734EA8C69DD726444DE3
TabUltraC,TabUltraC,2DB550268389D895AACB781AB3515DCB,13FDC3C394EAB6323BF42428CD275AA2
TabUltraCPro,TabUltraCPro,A41D77766544408A879E2796FA58FBEF,CE33186077354F08421AB9F0C2648A06
TabX,TabX,28E3BE85609F0D5CAA3796643512AAA2,8952832CBE65133CF531C978B7AC0696
Viking,Viking,F702F339BD6D31CB7F4252D6071A8C97,F51563B4B58AB1E673590E904F632361
ZYJ101IOE3,ZYJ101IOE3,21EE6EB8FEC5A5D10A2A3F5C73FB954B,F9898DEAC0E5DC9B0FB861D149194BDF
ZYJ103ION2,ZYJ103ION2,455521D97400F49CED0B7894CEF6720B,607C57A6F3333EC90A136263620C5B3D
================================================
FILE: CONTRIBUTING.md
================================================
# CONTRIBUTING
## New decryption keys
If you have a device for which no decryption keys are known and listed in [BooxKeys.csv](BooxKyes.csv) you can help by supplying them.
You can start an [Issue](https://github.com/Hagb/decryptBooxUpdateUpx/issues), but you'll still need to supply some information for someone else to extract the decryption keys.
You may be able to find the decryption keys yourself.
As the way these decryption keys are stored has changed over time you may have to use different approaches.
## Preliminaries
First you have to determine what the actual model of your device is called.
Yes, you know what you think is the model, but the actual model name usually does not have any spaces in it and may often have a surprising suffix.
ADB is a common and necessary tool for many operations on Android.
If you are not familiar with it, just Google "minimal ADB".
The simple command in an ADB shell `getprop ro.product.model` will tell you the real model name.
You can also check the fingerprints with `getprop | grep finger`.
## Decryption keys stored as resources
This was the oldest approach.
The app responsible for decrypting updates was usually in `/system/priv-app/OnyxOtaService/OnyxOtaService.apk`
You can download this app by `adb pull /system/priv-app/OnyxOtaService/OnyxOtaService.apk`.
Now you will have to disassemble the app using [Apktool](https://github.com/iBotPeaches/Apktool).
If this is beyond your pay grade, just start an issue and post the apk.
The strings that we are looking for are stored in res/values/strings.xml in the disassembled app.
``` xml
<string name="settings">j857wYAQcWZgvIEQ/tcQqzxreUJgFHwJl6D2TN9BuSkQ</string>
<string name="upgrade">+soGw/YVdGIRJiAs5SMmv1ihW37H1Fa9+/1w2Vgt14Ag</string>
```
As these are the "old style" strings they need to be converted to "new style" strings.
You can use [BooxKeyConverter.py](BooxKeyConverter.py) or, as always, just post an issue.
## Decryption keys stored in a library file
This is the newer approach and may have variants now and in the future.
You can easily see if this might be the case by the presence of a library file named `libota_jni.so`.
You can download this library by `adb pull /system/lib64/libota_jni.so` (for 64 bit) or `adb pull /system/lib/libota_jni.so` (for 32 bit).
As before, you can just start an issue and post this file if that's your limit as it gets a bit more complicated now.
### Decryption keys accessed by JNI method
The decryption keys may be extracted by calling JNI methods in libota_jni.so.
The two JNI methods are:
```
Java_com_onyx_android_onyxotaservice_RsaUtil_nativeGetSecretKey
Java_com_onyx_android_onyxotaservice_RsaUtil_nativeGetIvParameter
```
The Android application [GetBooxUpxKeysApp](https://github.com/Hagb/GetBooxUpxKeysApp) or the emulator utility [ota_jni.py](ota_jni.py) can extract the decryption keys.
If the extracted decryption keys are old style you can use [BooxKeyConverter.py](BooxKeyConverter.py) to convert them to new style.
### Decryption keys accessed by C++ call
The new style decryption keys may be extracted by calling C++ functions in `libota_jni.so`.
The two exported functions are:
```
getKeyString(void) _Z12getKeyStringv
getInitVectorString(void) _Z19getInitVectorStringv
```
There is not yet a released utility to extract the decryption keys.
### Decryption keys not accessible directly from the library file
There may not be any methods or functions to directly access the encryption keys.
Please post your libota_jni.so and we will see what we can do.
================================================
FILE: DeBooxUpx.py
================================================
#!/usr/bin/env python3
try:
from Cryptodome.Cipher import AES
except ModuleNotFoundError:
from Crypto.Cipher import AES
from Crypto import version_info
if version_info[0] == 2:
raise SystemExit('Need either `pycryptodome` or `pycryptodomex`,'\
' NOT `pycrypto`!')
import csv
class DeBooxUpx:
blockSize: int = 2**12 # 4KiB
def __init__(self,
KEY: str,
IV: str):
self.key: bytes = bytes.fromhex(KEY)
self.iv: bytes = bytes.fromhex(IV)
def deUpxStream(self, inputFile, outputFile):
block: bytes = b'1'
cipher = AES.new(self.key, AES.MODE_CFB, iv=self.iv, segment_size=128)
header_checked = False
while block:
block = inputFile.read(self.blockSize)
decrypted_block = cipher.decrypt(block)
if not header_checked:
if decrypted_block[:4] != b'\x50\x4b\x03\x04':
raise ValueError("The decrypted data seems not a zip package, "
"please ensure that the strings or model is correct.")
header_checked = True
outputFile.write(decrypted_block)
def deUpx(self, inputFileName: str, outputFileName: str):
inputFile = open(inputFileName, mode='rb', buffering=self.blockSize)
outputFile = open(outputFileName, mode='wb', buffering=self.blockSize)
self.deUpxStream(inputFile, outputFile)
inputFile.close()
outputFile.close()
def findKeyIv(path: str, name: str):
try:
with open(path) as file:
reader = csv.reader(file, delimiter=',')
line = 0
for row in reader:
if line > 0 and (row[0] == name or row[1] == name):
return(row)
line += 1
return None
except:
print(f'"{path}" not found')
sys.exit()
if __name__ == '__main__':
import sys
import os.path
if 2 <= len(sys.argv) <= 4:
csvPath = os.path.join(os.path.split(sys.argv[0])[0], 'BooxKeys.csv')
device_name = sys.argv[1]
updateUpxPath = "update.upx" if len(sys.argv) == 2 else sys.argv[2]
if len(sys.argv) == 4:
decryptedPath = sys.argv[3]
else:
basename = os.path.basename(updateUpxPath)
name, ext = os.path.splitext(basename)
decryptedPath = name + '.zip' if ext == '.upx' else basename + '.zip'
row = findKeyIv(csvPath, device_name)
if row is None:
print(f'No model named "{device_name}" found')
sys.exit()
decrypter = DeBooxUpx(row[2], row[3])
decrypter.deUpx(updateUpxPath, decryptedPath)
print(f"Saved decrypted file to {decryptedPath}")
else:
print('Usage:\npython DeBooxUpdate.py <device name> [input file name [output file name]]')
print('For supported devices see BooxKeys.csv')
================================================
FILE: LICENSE.txt
================================================
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
================================================
FILE: README.md
================================================
# decrypt Boox Update Upx
This project is aimed to help get the plain firmware packages of Boox devices, from the firmware packages named update.upx which are provided and encrypted by Onyx.
It has been tested only in [these Boox ereaders](./BooxKeys.csv). A few of models sold in China and most (if not all) of models sold in Russia are marked with suffixes `-cn`/`-ru`, **the firmwares for which don't use the same strings with their international versions!** SP\_NoteS is SuperStar (chaoxing) verison.
If your device hasn't been included, you could try to [get its keys](https://github.com/Hagb/decryptBooxUpdateUpx/blob/master/CONTRIBUTING.md). If you found it available for any other Boox ereader, please [submit the keys](CONTRIBUTING.md).
Note 1: There is also [another method to fetch decrypted `update.zip`](https://github.com/Hagb/decryptBooxUpdateUpx/issues/1) from [@shunf4](https://github.com/shunf4).
Note 2: There is [a way to get downloading url of latest firmware](https://github.com/Hagb/decryptBooxUpdateUpx/issues/2#issuecomment-704006389).
Note 3: `payload.bin` can be extracted with <https://github.com/cyxx/extract_android_ota_payload>.
Any other issue and pull request is also welcomed!
## How to run?
Python3 should be installed to run the script, and `pycryptodome` a dependency of the script should also be installed:
(BTW: in some environments, the following `pip` and `python` should be replaced with `pip3` and `python3`)
```bash
pip install pycryptodome
```
There are two components to this application: `DeBooxUpd.py` (the program) and `BooxKeys.csv` (the database of decryption keys).
These must be placed together in the same directory but the location does not matter.
For simplicity `update.upx` (the update to be decrypted) may be put in the same directory.
By default, `update.zip` (the decrypted update) will be generated in the current directory.
To run the application:
```bash
python DeBooxUpx.py <device model> [input file name [output file name]]
```
`<device model>` is required, and `[input file name [output file name]]` is optional. The input file will be `update.upx` and the output file will be saved in the current working directory, if not set in the arguments.
For a list of the currently supported models please refer to the file [BooxKeys.csv](BooxKeys.csv).
## Keys
Previously the raw strings extracted from Onyx software were used.
These strings were 44 alphanumeric characters long.
Since the newer Onyx models no longer use this level of obfuscation we've switched to using what the true gist of the keys are, a 32 character hexadecimal string.
Older Onyx models continue to be supported, although the key strings may appear to be unfamiliar.
## API
There is a python class `DeBooxUpx` in [DeBooxUpx.py](DeBooxUpx.py) to decrypt `update.upx`.
Following is a example of how to use this class to decrypt the `update.upx` using manual strings:
``` python3
from DeBooxUpx import DeBooxUpx
Key = "3DC53116D8AE3DCCEAD99F53E08E1E35"
IV = "42B996AB6E252DCA4EDBC668BA3E5A3A"
updateUpxPath = 'update.upx'
decryptedPath = 'update.zip'
decrypter = DeBooxUpx(Key, IV)
print('When updating, the device decrypt update package into', decrypter.path)
decrypter.deUpx(updateUpxPath, decryptedPath)
```
## Contributing
Refer to [CONTRIBUTING.md](CONTRIBUTING.md).
================================================
FILE: algorithm-zh_cn.md
================================================
# Onyx Boox update.upx 更新包解密算法
## 获取字符串 方式一:资源文件(仅在 Onyx Nova Pro 上测试过,以 Onyx Nova Pro 为例)
先将设备开启 adb 调试。
- MODEL 可将设备接上计算机后使用
```
adb shell getprop ro.product.model
```
来获取.(NovaPro 中得到的是`NovaPro`)
- 以下三个字符串:
先从设备中`pull`出`/system/app/OnyxOtaService/OnyxOtaService.apk`并用`apktool`工具解包,然后在其中查找`id`分别为`0x7f050000`、`0x7f050001`、`0x7f050002`的`name`值,再根据这三个`name`值查找得到三条`string`值,我们分别将它们记作 S1, S2, S3。
以 NovaPro 为例,以上 `name` 值首先在`res/values/public.xml`中找到,分别为
- `"settings"`
- `"upgrade"`
- `"local"`
接着在`res/values/strings.xml`中可找到其对应的`string`值即 S1、S2、S3 的值,分别为
- `"j857wYAQcWZgvIEQ/tcQqzxreUJgFHwJl6D2TN9BuSkQ"`
- `"+soGw/YVdGIRJiAs5SMmv1ihW37H1Fa9+/1w2Vgt14Ag"`
- `"lpsj9NJ8Kzv8jHb+OO8A5lxC+9Zhl243bFmDZYaF"`
- 在 Android 11 以上的设备上,这些字符串被移到了 `libota_jni.so`,在这种情况下,请查看安装该 APP [GetBooxUpxKeys](https://github.com/Hagb/GetBooxUpxKeysApp/releases) 来获得keys。或者参考 [22#issuecomment-964035840](https://github.com/Hagb/decryptBooxUpdateUpx/issues/22#issuecomment-964035840).
## 获取字符串 方式二:libota_jni.so
在 Android 11 以上的设备上(例如Poke4),Onyx Boox 将解密用的key移到了 shared library里。获取它的方法有很多,这里提供了一种模拟执行的方式来获取。
先将设备开启 adb 调试。
- MODEL 可将设备接上计算机后使用
```
adb shell getprop ro.product.model
```
来获取.(Poke4 中得到的是`Poke4`)
- 获取 libota_jni.so
32位机使用命令
```
adb pull /system/lib/libota_jni.so
```
64位机使用命令
```
adb pull /system/lib64/libota_jni.so
```
- 准备运行环境
将 submodule 克隆回来
```
git submodule init
git submodule update
```
安装python依赖
```
pip install unicorn capstone
```
- 模拟执行 libota_jni.so
将拖回来的 libota_jni.so 作为脚本的入参。下方指令使用的是 Poke4 中拖出来的 libota_jni.so,未测试其他机型。
```
python ota_jni.py test/libota_jni.so
```
输出内容示例,最后两行就是结果
```
python ota_jni.py test/libota_jni.so
ERROR:androidemu.internal.modules:=> Undefined external symbol:
ERROR:androidemu.internal.modules:=> Undefined external symbol: __cxa_finalize
ERROR:androidemu.internal.modules:=> Undefined external symbol: __register_atfork
ERROR:androidemu.internal.modules:=> Undefined external symbol: __cxa_atexit
ro.kernel.qemu was not found in system_properties dictionary.
libc.debug.malloc was not found in system_properties dictionary.
WARNING:root:File does not exist '/proc/stat'
WARNING:androidemu.internal.modules:libcrypto.so needed by libota_jni.so do not exist in vfs ExAndroidNativeEmu/vfs
WARNING:androidemu.internal.modules:libutils.so needed by libota_jni.so do not exist in vfs ExAndroidNativeEmu/vfs
----------------
STRING_SETTINGS uTKx3XVyRhlbI7hoHuy/NiYdwlWViPlc4EecZKYTThN/
STRING_UPGRADE vzDFqwIEMRfqTPUCV82ahjnz0hXfcZCIxRY8ljuLmTaf
```
## 解密算法
1. 求两份 MODEL 相连(如 MODEL 值为`NovaPro`,则该值为`MovaProNovaPro`)的 md5,(二进制)截取前 8 字节,作为临时密钥 tmpK;
2. Base64 解码 S1、S2、S3,分别得字符串 s1、s2、s3
3. 用 DES 算法 CFB 模式
- 初始化向量(initialization vector): 8 字节的 0xff
- 分段长度(segment size): 64 bits
- 密钥:上述临时密钥 tmpK
来解密 s1,得密钥 K 的 hex,进一步可求出密钥 K;
4. 用上述 3 的方法解密 s2 得初始化向量 IV 的 hex,从而可进一步得到初始化向量 IV;
5. 再用同样的方法解密 s3 得路径 P(这步可不做);
6. 以 AES 算法 CFB 模式
- 初始化向量: 上述 IV
- 分段长度: 128 bits
- 密钥: 上述 K
解密 update.upx 更新包,即可得到能够被 Recovery 读取的明文 zip 更新包。
================================================
FILE: ota_jni.py
================================================
import sys
sys.path.append('ExAndroidNativeEmu')
from unicorn import UcError
from unicorn.arm64_const import UC_ARM64_REG_PC
from androidemu.const import emu_const
from androidemu.emulator import Emulator
from androidemu.internal import elf_reader
from androidemu.java.classes.types import *
def main(so_path):
# 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"
)
try:
libtest = emulator.load_library(so_path)
print("----------------")
r = emulator.call_symbol(libtest, 'Java_com_onyx_android_onyxotaservice_RsaUtil_nativeGetSecretKey',
emulator.java_vm.jni_env.address_ptr)
pystr = emulator.java_vm.jni_env.get_local_reference(r).value.get_py_string()
print("STRING_SETTINGS", pystr)
r = emulator.call_symbol(libtest, 'Java_com_onyx_android_onyxotaservice_RsaUtil_nativeGetIvParameter',
emulator.java_vm.jni_env.address_ptr)
pystr = emulator.java_vm.jni_env.get_local_reference(r).value.get_py_string()
print("STRING_UPGRADE", pystr)
except UcError as e:
print("Exit at 0x%08X" % emulator.mu.reg_read(UC_ARM64_REG_PC))
emulator.memory.dump_maps(sys.stdout)
raise
if __name__ == '__main__':
if len(sys.argv) < 2:
print("python %s /path/to/libota_jni.so" % __file__)
exit(0)
main(sys.argv[1])
================================================
FILE: update_readme_strings.py
================================================
#!/usr/bin/env python3
from DeBooxUpx import boox_strings
import re
model_width = 10
with open('README.md') as readme:
readme_text = readme.read()
models = sorted(boox_strings.items(), key=lambda x: x[0])
table_header = f"""|{'': ^{model_width}}|{'MODEL': ^{model_width+2}}|{'STRING_SETTINGS': ^46}|{'STRING_UPGRADE': ^46}|{'STRING_LOCAL': ^42}|
|{'-'*(model_width)}|{'-'*(model_width+2)}|{'-'*46}|{'-'*46}|{'-'*42}|
"""
table = table_header + '\n'.join(
f"|{name: ^{model_width}}|{'`'+string['MODEL']+'`': ^{model_width+2}}|`{string['STRING_SETTINGS']}`|`{string['STRING_UPGRADE']}`|`{string.get('STRING_LOCAL') or ' '*40}`|"
for name, string in models)
with open('README.md', 'w') as readme:
readme.write(
re.sub(
r'<!--\(strings table begin\)-->.*<!--\(strings table end\)-->',
f'<!--(strings table begin)-->\n{table}\n<!--(strings table end)-->',
readme_text,
1,
flags=re.S))
gitextract_p80nabgi/ ├── .gitmodules ├── BooxKeyConvert.py ├── BooxKeys.csv ├── CONTRIBUTING.md ├── DeBooxUpx.py ├── LICENSE.txt ├── README.md ├── algorithm-zh_cn.md ├── ota_jni.py └── update_readme_strings.py
SYMBOL INDEX (8 symbols across 3 files)
FILE: BooxKeyConvert.py
function decryptStr (line 15) | def decryptStr(tmpKey: bytes, string: str) -> str:
function encryptStr (line 37) | def encryptStr(tmpKey: bytes, string: str) -> str:
FILE: DeBooxUpx.py
class DeBooxUpx (line 12) | class DeBooxUpx:
method __init__ (line 15) | def __init__(self,
method deUpxStream (line 21) | def deUpxStream(self, inputFile, outputFile):
method deUpx (line 35) | def deUpx(self, inputFileName: str, outputFileName: str):
function findKeyIv (line 42) | def findKeyIv(path: str, name: str):
FILE: ota_jni.py
function main (line 14) | def main(so_path):
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (29K chars).
[
{
"path": ".gitmodules",
"chars": 120,
"preview": "[submodule \"ExAndroidNativeEmu\"]\n\tpath = ExAndroidNativeEmu\n\turl = https://github.com/maiyao1988/ExAndroidNativeEmu.git\n"
},
{
"path": "BooxKeyConvert.py",
"chars": 1929,
"preview": "#!/usr/bin/env python3\ntry:\n from Cryptodome.Cipher import DES\n from Cryptodome.Hash import MD5\nexcept ModuleNotFo"
},
{
"path": "BooxKeys.csv",
"chars": 8634,
"preview": "Name,Model,Key,IV\nDarwin11-ru,DARWIN11,68954E31C8EA505B646641AF2015B63C,C00286616C61DB326065BE988D1C1F90\nDarwin7,MC_DARW"
},
{
"path": "CONTRIBUTING.md",
"chars": 3550,
"preview": "# CONTRIBUTING\n\n## New decryption keys\n\nIf you have a device for which no decryption keys are known and listed in [BooxK"
},
{
"path": "DeBooxUpx.py",
"chars": 2954,
"preview": "#!/usr/bin/env python3\ntry:\n from Cryptodome.Cipher import AES\nexcept ModuleNotFoundError:\n from Crypto.Cipher imp"
},
{
"path": "LICENSE.txt",
"chars": 501,
"preview": " DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE \r\n Version 2, December 2004 \r\n\r\n Copyright (C) 20"
},
{
"path": "README.md",
"chars": 3331,
"preview": "# decrypt Boox Update Upx\n\nThis project is aimed to help get the plain firmware packages of Boox devices, from the firmw"
},
{
"path": "algorithm-zh_cn.md",
"chars": 3242,
"preview": "# Onyx Boox update.upx 更新包解密算法\n\n## 获取字符串 方式一:资源文件(仅在 Onyx Nova Pro 上测试过,以 Onyx Nova Pro 为例)\n\n先将设备开启 adb 调试。\n\n- MODEL 可将设"
},
{
"path": "ota_jni.py",
"chars": 1819,
"preview": "import sys\n\nsys.path.append('ExAndroidNativeEmu')\n\nfrom unicorn import UcError\nfrom unicorn.arm64_const import UC_ARM64_"
},
{
"path": "update_readme_strings.py",
"chars": 965,
"preview": "#!/usr/bin/env python3\nfrom DeBooxUpx import boox_strings\nimport re\nmodel_width = 10\n\nwith open('README.md') as readme:\n"
}
]
About this extraction
This page contains the full source code of the Hagb/decryptBooxUpdateUpx GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (26.4 KB), approximately 10.4k tokens, and a symbol index with 8 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.