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 ') ================================================ 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 j857wYAQcWZgvIEQ/tcQqzxreUJgFHwJl6D2TN9BuSkQ +soGw/YVdGIRJiAs5SMmv1ihW37H1Fa9+/1w2Vgt14Ag ``` 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 [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 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 . 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 [input file name [output file name]] ``` `` 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'.*', f'\n{table}\n', readme_text, 1, flags=re.S))