755 lines
31 KiB
Python
755 lines
31 KiB
Python
#!/usr/bin/env python3
|
|
|
|
# Oneplus Decrypter (c) V 1.4 B.Kerler 2019-2022
|
|
# Licensed under MIT License
|
|
|
|
"""
|
|
Usage:
|
|
opscrypto.py --help
|
|
opscrypto.py encryptfile <filename>
|
|
opscrypto.py decryptfile <filename>
|
|
opscrypto.py decrypt <filename>
|
|
opscrypto.py encrypt <directory> [--projid=value] [--firmwarename=name] [--savename=out.ops] [--mbox=version]
|
|
|
|
Options:
|
|
--projid=value Set projid Example:18801
|
|
--mbox=version Set encryption key [default: 5]
|
|
--firmwarename=name Set firmware version Example:fajita_41_J.42_191214
|
|
--savename=name Set ops filename [default: out.ops]
|
|
|
|
"""
|
|
|
|
'''
|
|
{'--firmwarename': None,
|
|
'--help': False,
|
|
'--mbox': '5',
|
|
'--projid': None,
|
|
'--savename': 'out.ops',
|
|
'<directory>': None,
|
|
'<filename>': 'G',
|
|
'decrypt': True,
|
|
'decryptfile': False,
|
|
'encrypt': False,
|
|
'encryptfile': False}
|
|
'''
|
|
|
|
import shutil
|
|
|
|
import os
|
|
from struct import pack, unpack
|
|
import xml.etree.ElementTree as ET
|
|
import hashlib
|
|
from pathlib import Path
|
|
from queue import Queue
|
|
|
|
import mmap
|
|
|
|
|
|
def mmap_io(filename, mode, length=0):
|
|
if mode == "rb":
|
|
with open(filename, mode="rb") as file_obj:
|
|
return mmap.mmap(file_obj.fileno(), length=0, access=mmap.ACCESS_READ)
|
|
elif mode == "wb":
|
|
if os.path.exists(filename):
|
|
length = os.stat(filename).st_size
|
|
else:
|
|
with open(filename, "wb") as wf:
|
|
wf.write(length * b'\0')
|
|
wf.close()
|
|
with open(filename, mode="r+b") as file_obj:
|
|
return mmap.mmap(file_obj.fileno(), length=length, access=mmap.ACCESS_WRITE)
|
|
# mmap_obj.flush() on finish
|
|
|
|
|
|
key = unpack("<4I", bytes.fromhex("d1b5e39e5eea049d671dd5abd2afcbaf"))
|
|
|
|
# guacamoles_31_O.09_190820
|
|
mbox5 = [0x60, 0x8a, 0x3f, 0x2d, 0x68, 0x6b, 0xd4, 0x23, 0x51, 0x0c,
|
|
0xd0, 0x95, 0xbb, 0x40, 0xe9, 0x76, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x0a, 0x00]
|
|
# instantnoodlev_15_O.07_201103
|
|
mbox6 = [0xAA, 0x69, 0x82, 0x9E, 0x5D, 0xDE, 0xB1, 0x3D, 0x30, 0xBB,
|
|
0x81, 0xA3, 0x46, 0x65, 0xa3, 0xe1, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x0a, 0x00]
|
|
# guacamolet_21_O.08_190502
|
|
mbox4 = [0xC4, 0x5D, 0x05, 0x71, 0x99, 0xDD, 0xBB, 0xEE, 0x29, 0xA1,
|
|
0x6D, 0xC7, 0xAD, 0xBF, 0xA4, 0x3F, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x0a, 0x00]
|
|
|
|
sbox = bytes.fromhex("c66363a5c66363a5f87c7c84f87c7c84ee777799ee777799f67b7b8df67b7b8d" +
|
|
"fff2f20dfff2f20dd66b6bbdd66b6bbdde6f6fb1de6f6fb191c5c55491c5c554" +
|
|
"60303050603030500201010302010103ce6767a9ce6767a9562b2b7d562b2b7d" +
|
|
"e7fefe19e7fefe19b5d7d762b5d7d7624dababe64dababe6ec76769aec76769a" +
|
|
"8fcaca458fcaca451f82829d1f82829d89c9c94089c9c940fa7d7d87fa7d7d87" +
|
|
"effafa15effafa15b25959ebb25959eb8e4747c98e4747c9fbf0f00bfbf0f00b" +
|
|
"41adadec41adadecb3d4d467b3d4d4675fa2a2fd5fa2a2fd45afafea45afafea" +
|
|
"239c9cbf239c9cbf53a4a4f753a4a4f7e4727296e47272969bc0c05b9bc0c05b" +
|
|
"75b7b7c275b7b7c2e1fdfd1ce1fdfd1c3d9393ae3d9393ae4c26266a4c26266a" +
|
|
"6c36365a6c36365a7e3f3f417e3f3f41f5f7f702f5f7f70283cccc4f83cccc4f" +
|
|
"6834345c6834345c51a5a5f451a5a5f4d1e5e534d1e5e534f9f1f108f9f1f108" +
|
|
"e2717193e2717193abd8d873abd8d87362313153623131532a15153f2a15153f" +
|
|
"0804040c0804040c95c7c75295c7c75246232365462323659dc3c35e9dc3c35e" +
|
|
"3018182830181828379696a1379696a10a05050f0a05050f2f9a9ab52f9a9ab5" +
|
|
"0e0707090e07070924121236241212361b80809b1b80809bdfe2e23ddfe2e23d" +
|
|
"cdebeb26cdebeb264e2727694e2727697fb2b2cd7fb2b2cdea75759fea75759f" +
|
|
"1209091b1209091b1d83839e1d83839e582c2c74582c2c74341a1a2e341a1a2e" +
|
|
"361b1b2d361b1b2ddc6e6eb2dc6e6eb2b45a5aeeb45a5aee5ba0a0fb5ba0a0fb" +
|
|
"a45252f6a45252f6763b3b4d763b3b4db7d6d661b7d6d6617db3b3ce7db3b3ce" +
|
|
"5229297b5229297bdde3e33edde3e33e5e2f2f715e2f2f711384849713848497" +
|
|
"a65353f5a65353f5b9d1d168b9d1d1680000000000000000c1eded2cc1eded2c" +
|
|
"4020206040202060e3fcfc1fe3fcfc1f79b1b1c879b1b1c8b65b5bedb65b5bed" +
|
|
"d46a6abed46a6abe8dcbcb468dcbcb4667bebed967bebed97239394b7239394b" +
|
|
"944a4ade944a4ade984c4cd4984c4cd4b05858e8b05858e885cfcf4a85cfcf4a" +
|
|
"bbd0d06bbbd0d06bc5efef2ac5efef2a4faaaae54faaaae5edfbfb16edfbfb16" +
|
|
"864343c5864343c59a4d4dd79a4d4dd766333355663333551185859411858594" +
|
|
"8a4545cf8a4545cfe9f9f910e9f9f9100402020604020206fe7f7f81fe7f7f81" +
|
|
"a05050f0a05050f0783c3c44783c3c44259f9fba259f9fba4ba8a8e34ba8a8e3" +
|
|
"a25151f3a25151f35da3a3fe5da3a3fe804040c0804040c0058f8f8a058f8f8a" +
|
|
"3f9292ad3f9292ad219d9dbc219d9dbc7038384870383848f1f5f504f1f5f504" +
|
|
"63bcbcdf63bcbcdf77b6b6c177b6b6c1afdada75afdada754221216342212163" +
|
|
"2010103020101030e5ffff1ae5ffff1afdf3f30efdf3f30ebfd2d26dbfd2d26d" +
|
|
"81cdcd4c81cdcd4c180c0c14180c0c142613133526131335c3ecec2fc3ecec2f" +
|
|
"be5f5fe1be5f5fe1359797a2359797a2884444cc884444cc2e1717392e171739" +
|
|
"93c4c45793c4c45755a7a7f255a7a7f2fc7e7e82fc7e7e827a3d3d477a3d3d47" +
|
|
"c86464acc86464acba5d5de7ba5d5de73219192b3219192be6737395e6737395" +
|
|
"c06060a0c06060a019818198198181989e4f4fd19e4f4fd1a3dcdc7fa3dcdc7f" +
|
|
"4422226644222266542a2a7e542a2a7e3b9090ab3b9090ab0b8888830b888883" +
|
|
"8c4646ca8c4646cac7eeee29c7eeee296bb8b8d36bb8b8d32814143c2814143c" +
|
|
"a7dede79a7dede79bc5e5ee2bc5e5ee2160b0b1d160b0b1daddbdb76addbdb76" +
|
|
"dbe0e03bdbe0e03b6432325664323256743a3a4e743a3a4e140a0a1e140a0a1e" +
|
|
"924949db924949db0c06060a0c06060a4824246c4824246cb85c5ce4b85c5ce4" +
|
|
"9fc2c25d9fc2c25dbdd3d36ebdd3d36e43acacef43acacefc46262a6c46262a6" +
|
|
"399191a8399191a8319595a4319595a4d3e4e437d3e4e437f279798bf279798b" +
|
|
"d5e7e732d5e7e7328bc8c8438bc8c8436e3737596e373759da6d6db7da6d6db7" +
|
|
"018d8d8c018d8d8cb1d5d564b1d5d5649c4e4ed29c4e4ed249a9a9e049a9a9e0" +
|
|
"d86c6cb4d86c6cb4ac5656faac5656faf3f4f407f3f4f407cfeaea25cfeaea25" +
|
|
"ca6565afca6565aff47a7a8ef47a7a8e47aeaee947aeaee91008081810080818" +
|
|
"6fbabad56fbabad5f0787888f07878884a25256f4a25256f5c2e2e725c2e2e72" +
|
|
"381c1c24381c1c2457a6a6f157a6a6f173b4b4c773b4b4c797c6c65197c6c651" +
|
|
"cbe8e823cbe8e823a1dddd7ca1dddd7ce874749ce874749c3e1f1f213e1f1f21" +
|
|
"964b4bdd964b4bdd61bdbddc61bdbddc0d8b8b860d8b8b860f8a8a850f8a8a85" +
|
|
"e0707090e07070907c3e3e427c3e3e4271b5b5c471b5b5c4cc6666aacc6666aa" +
|
|
"904848d8904848d80603030506030305f7f6f601f7f6f6011c0e0e121c0e0e12" +
|
|
"c26161a3c26161a36a35355f6a35355fae5757f9ae5757f969b9b9d069b9b9d0" +
|
|
"178686911786869199c1c15899c1c1583a1d1d273a1d1d27279e9eb9279e9eb9" +
|
|
"d9e1e138d9e1e138ebf8f813ebf8f8132b9898b32b9898b32211113322111133" +
|
|
"d26969bbd26969bba9d9d970a9d9d970078e8e89078e8e89339494a7339494a7" +
|
|
"2d9b9bb62d9b9bb63c1e1e223c1e1e221587879215878792c9e9e920c9e9e920" +
|
|
"87cece4987cece49aa5555ffaa5555ff5028287850282878a5dfdf7aa5dfdf7a" +
|
|
"038c8c8f038c8c8f59a1a1f859a1a1f809898980098989801a0d0d171a0d0d17" +
|
|
"65bfbfda65bfbfdad7e6e631d7e6e631844242c6844242c6d06868b8d06868b8" +
|
|
"824141c3824141c3299999b0299999b05a2d2d775a2d2d771e0f0f111e0f0f11" +
|
|
"7bb0b0cb7bb0b0cba85454fca85454fc6dbbbbd66dbbbbd62c16163a2c16163a")
|
|
|
|
|
|
class QCSparse:
|
|
def __init__(self, filename):
|
|
self.rf = mmap_io(filename, "rb")
|
|
self.data = Queue()
|
|
self.offset = 0
|
|
self.tmpdata = bytearray()
|
|
|
|
self.major_version = None
|
|
self.minor_version = None
|
|
self.file_hdr_sz = None
|
|
self.chunk_hdr_sz = None
|
|
self.blk_sz = None
|
|
self.total_blks = None
|
|
self.total_chunks = None
|
|
self.image_checksum = None
|
|
|
|
self.info = print
|
|
self.debug = print
|
|
self.error = print
|
|
self.warning = print
|
|
|
|
def readheader(self, offset):
|
|
self.rf.seek(offset)
|
|
header = unpack("<I4H4I", self.rf.read(0x1C))
|
|
magic = header[0]
|
|
self.major_version = header[1]
|
|
self.minor_version = header[2]
|
|
self.file_hdr_sz = header[3]
|
|
self.chunk_hdr_sz = header[4]
|
|
self.blk_sz = header[5]
|
|
self.total_blks = header[6]
|
|
self.total_chunks = header[7]
|
|
self.image_checksum = header[8]
|
|
if magic != 0xED26FF3A:
|
|
return False
|
|
if self.file_hdr_sz != 28:
|
|
self.error("The file header size was expected to be 28, but is %u." % self.file_hdr_sz)
|
|
return False
|
|
if self.chunk_hdr_sz != 12:
|
|
self.error("The chunk header size was expected to be 12, but is %u." % self.chunk_hdr_sz)
|
|
return False
|
|
self.info("Sparse Format detected. Using unpacked image.")
|
|
return True
|
|
|
|
def get_chunk_size(self):
|
|
if self.total_blks < self.offset:
|
|
self.error(
|
|
"The header said we should have %u output blocks, but we saw %u" % (self.total_blks, self.offset))
|
|
return -1
|
|
header = unpack("<2H2I", self.rf.read(self.chunk_hdr_sz))
|
|
chunk_type = header[0]
|
|
chunk_sz = header[2]
|
|
total_sz = header[3]
|
|
data_sz = total_sz - 12
|
|
if chunk_type == 0xCAC1:
|
|
if data_sz != (chunk_sz * self.blk_sz):
|
|
self.error(
|
|
"Raw chunk input size (%u) does not match output size (%u)" % (data_sz, chunk_sz * self.blk_sz))
|
|
return -1
|
|
else:
|
|
self.rf.seek(self.rf.tell() + chunk_sz * self.blk_sz)
|
|
return chunk_sz * self.blk_sz
|
|
elif chunk_type == 0xCAC2:
|
|
if data_sz != 4:
|
|
self.error("Fill chunk should have 4 bytes of fill, but this has %u" % data_sz)
|
|
return -1
|
|
else:
|
|
return chunk_sz * self.blk_sz // 4
|
|
elif chunk_type == 0xCAC3:
|
|
return chunk_sz * self.blk_sz
|
|
elif chunk_type == 0xCAC4:
|
|
if data_sz != 4:
|
|
self.error("CRC32 chunk should have 4 bytes of CRC, but this has %u" % data_sz)
|
|
return -1
|
|
else:
|
|
self.rf.seek(self.rf.tell() + 4)
|
|
return 0
|
|
else:
|
|
self.debug("Unknown chunk type 0x%04X" % chunk_type)
|
|
return -1
|
|
|
|
def unsparse(self):
|
|
if self.total_blks < self.offset:
|
|
self.error(
|
|
"The header said we should have %u output blocks, but we saw %u" % (self.total_blks, self.offset))
|
|
return -1
|
|
header = unpack("<2H2I", self.rf.read(self.chunk_hdr_sz))
|
|
chunk_type = header[0]
|
|
chunk_sz = header[2]
|
|
total_sz = header[3]
|
|
data_sz = total_sz - 12
|
|
if chunk_type == 0xCAC1:
|
|
if data_sz != (chunk_sz * self.blk_sz):
|
|
self.error(
|
|
"Raw chunk input size (%u) does not match output size (%u)" % (data_sz, chunk_sz * self.blk_sz))
|
|
return -1
|
|
else:
|
|
# self.debug("Raw data")
|
|
data = self.rf.read(chunk_sz * self.blk_sz)
|
|
self.offset += chunk_sz
|
|
return data
|
|
elif chunk_type == 0xCAC2:
|
|
if data_sz != 4:
|
|
self.error("Fill chunk should have 4 bytes of fill, but this has %u" % data_sz)
|
|
return -1
|
|
else:
|
|
fill_bin = self.rf.read(4)
|
|
fill = unpack("<I", fill_bin)
|
|
# self.debug(format("Fill with 0x%08X" % fill))
|
|
data = fill_bin * (chunk_sz * self.blk_sz // 4)
|
|
self.offset += chunk_sz
|
|
return data
|
|
elif chunk_type == 0xCAC3:
|
|
data = b'\x00' * chunk_sz * self.blk_sz
|
|
self.offset += chunk_sz
|
|
return data
|
|
elif chunk_type == 0xCAC4:
|
|
if data_sz != 4:
|
|
self.error("CRC32 chunk should have 4 bytes of CRC, but this has %u" % data_sz)
|
|
return -1
|
|
else:
|
|
crc_bin = self.rf.read(4)
|
|
crc = unpack("<I", crc_bin)
|
|
# self.debug(format("Unverified CRC32 0x%08X" % crc))
|
|
return b""
|
|
else:
|
|
# self.debug("Unknown chunk type 0x%04X" % chunk_type)
|
|
return -1
|
|
|
|
def getsize(self):
|
|
self.rf.seek(0x1C)
|
|
length = 0
|
|
chunk = 0
|
|
while chunk < self.total_chunks:
|
|
tlen = self.get_chunk_size()
|
|
if tlen == -1:
|
|
break
|
|
length += tlen
|
|
chunk += 1
|
|
self.rf.seek(0x1C)
|
|
return length
|
|
|
|
def read(self, length=None):
|
|
if length is None:
|
|
return self.unsparse()
|
|
if length <= len(self.tmpdata):
|
|
tdata = self.tmpdata[:length]
|
|
self.tmpdata = self.tmpdata[length:]
|
|
return tdata
|
|
while len(self.tmpdata) < length:
|
|
self.tmpdata.extend(self.unsparse())
|
|
if length <= len(self.tmpdata):
|
|
tdata = self.tmpdata[:length]
|
|
self.tmpdata = self.tmpdata[length:]
|
|
return tdata
|
|
|
|
|
|
def gsbox(offset):
|
|
return int.from_bytes(sbox[offset:offset + 4], 'little')
|
|
|
|
|
|
def key_update(iv1, asbox):
|
|
d = iv1[0] ^ asbox[0] # 9EE3B5B1
|
|
a = iv1[1] ^ asbox[1]
|
|
b = iv1[2] ^ asbox[2] # ABD51D58
|
|
c = iv1[3] ^ asbox[3] # AFCBAFFF
|
|
e = gsbox(((b >> 0x10) & 0xff) * 8 + 2) ^ gsbox(((a >> 8) & 0xff) * 8 + 3) ^ gsbox((c >> 0x18) * 8 + 1) ^ \
|
|
gsbox((d & 0xff) * 8) ^ asbox[4] # 35C2A10B
|
|
|
|
h = gsbox(((c >> 0x10) & 0xff) * 8 + 2) ^ gsbox(((b >> 8) & 0xff) * 8 + 3) ^ gsbox((d >> 0x18) * 8 + 1) ^ \
|
|
gsbox((a & 0xff) * 8) ^ asbox[5] # 75CF3118
|
|
i = gsbox(((d >> 0x10) & 0xff) * 8 + 2) ^ gsbox(((c >> 8) & 0xff) * 8 + 3) ^ gsbox((a >> 0x18) * 8 + 1) ^ \
|
|
gsbox((b & 0xff) * 8) ^ asbox[6] # 6AD3F5C4
|
|
a = gsbox(((d >> 8) & 0xff) * 8 + 3) ^ gsbox(((a >> 0x10) & 0xff) * 8 + 2) ^ gsbox((b >> 0x18) * 8 + 1) ^ \
|
|
gsbox((c & 0xff) * 8) ^ asbox[7] # D99AC8FB
|
|
|
|
g = 8
|
|
|
|
for f in range(asbox[0x3c] - 2):
|
|
d = e >> 0x18 # 35
|
|
m = h >> 0x10 # cf
|
|
s = h >> 0x18
|
|
z = e >> 0x10
|
|
l = i >> 0x18
|
|
t = e >> 8
|
|
e = gsbox(((i >> 0x10) & 0xff) * 8 + 2) ^ gsbox(((h >> 8) & 0xff) * 8 + 3) ^ \
|
|
gsbox((a >> 0x18) * 8 + 1) ^ gsbox((e & 0xff) * 8) ^ asbox[g] # B67F2106, 82508918
|
|
h = gsbox(((a >> 0x10) & 0xff) * 8 + 2) ^ gsbox(((i >> 8) & 0xff) * 8 + 3) ^ \
|
|
gsbox(d * 8 + 1) ^ gsbox((h & 0xff) * 8) ^ asbox[g + 1] # 85813F52
|
|
i = gsbox((z & 0xff) * 8 + 2) ^ gsbox(((a >> 8) & 0xff) * 8 + 3) ^ \
|
|
gsbox(s * 8 + 1) ^ gsbox((i & 0xff) * 8) ^ asbox[g + 2] # C8022573
|
|
a = gsbox((t & 0xff) * 8 + 3) ^ gsbox((m & 0xff) * 8 + 2) ^ \
|
|
gsbox(l * 8 + 1) ^ gsbox((a & 0xff) * 8) ^ asbox[g + 3] # AD34EC55
|
|
g = g + 4
|
|
# a=6DB8AA0E
|
|
# b=ABD51D58
|
|
# c=AFCBAFFF
|
|
# d=51
|
|
# e=AC402324
|
|
# h=B2D24440
|
|
# i=CC2ADF24
|
|
# t=510805
|
|
return [(gsbox(((i >> 0x10) & 0xff) * 8) & 0xff0000) ^ (gsbox(((h >> 8) & 0xff) * 8 + 1) & 0xff00) ^
|
|
(gsbox((a >> 0x18) * 8 + 3) & 0xff000000) ^ gsbox((e & 0xff) * 8 + 2) & 0xFF ^ asbox[g],
|
|
(gsbox(((a >> 0x10) & 0xff) * 8) & 0xff0000) ^ (gsbox(((i >> 8) & 0xff) * 8 + 1) & 0xff00) ^
|
|
(gsbox((e >> 0x18) * 8 + 3) & 0xff000000) ^ (gsbox((h & 0xff) * 8 + 2) & 0xFF) ^ asbox[g + 3],
|
|
(gsbox(((e >> 0x10) & 0xff) * 8) & 0xff0000) ^ (gsbox(((a >> 8) & 0xff) * 8 + 1) & 0xff00) ^
|
|
(gsbox((h >> 0x18) * 8 + 3) & 0xff000000) ^ (gsbox((i & 0xff) * 8 + 2) & 0xFF) ^ asbox[g + 2],
|
|
(gsbox(((h >> 0x10) & 0xff) * 8) & 0xff0000) ^ (gsbox(((e >> 8) & 0xff) * 8 + 1) & 0xff00) ^
|
|
(gsbox((i >> 0x18) * 8 + 3) & 0xff000000) ^ (gsbox((a & 0xff) * 8 + 2) & 0xFF) ^ asbox[g + 1]]
|
|
|
|
|
|
def key_custom(inp, rkey, outlength=0, encrypt=False):
|
|
outp = bytearray()
|
|
inp = bytearray(inp)
|
|
pos = outlength
|
|
outp_extend = outp.extend
|
|
ptr = 0
|
|
length = len(inp)
|
|
if outlength != 0:
|
|
while pos < len(rkey):
|
|
if length == 0:
|
|
break
|
|
buffer = inp[pos]
|
|
outp_extend(rkey[pos] ^ buffer)
|
|
rkey[pos] = buffer
|
|
length -= 1
|
|
pos += 1
|
|
|
|
if length > 0xF:
|
|
for ptr in range(0, length, 0x10):
|
|
rkey = key_update(rkey, mbox)
|
|
if pos < 0x10:
|
|
slen = ((0xf - pos) >> 2) + 1
|
|
tmp = [rkey[i] ^ int.from_bytes(inp[pos + (i * 4) + ptr:pos + (i * 4) + ptr + 4], "little") for i in
|
|
range(0, slen)]
|
|
outp.extend(b"".join(tmp[i].to_bytes(4, 'little') for i in range(0, slen)))
|
|
if encrypt:
|
|
rkey = tmp
|
|
else:
|
|
rkey = [int.from_bytes(inp[pos + (i * 4) + ptr:pos + (i * 4) + ptr + 4], "little") for i in
|
|
range(0, slen)]
|
|
length = length - 0x10
|
|
if length != 0:
|
|
rkey = key_update(rkey, sbox)
|
|
j = pos
|
|
m = 0
|
|
while length > 0:
|
|
data = inp[j + ptr:j + ptr + 4]
|
|
if len(data) < 4:
|
|
data += b"\x00" * (4 - len(data))
|
|
tmp = int.from_bytes(data, 'little')
|
|
outp_extend((tmp ^ rkey[m]).to_bytes(4, 'little'))
|
|
if encrypt:
|
|
rkey[m] = tmp ^ rkey[m]
|
|
else:
|
|
rkey[m] = tmp
|
|
length -= 4
|
|
j += 4
|
|
m += 1
|
|
return outp
|
|
|
|
|
|
def extractxml(filename, key):
|
|
with mmap_io(filename, 'rb') as rf:
|
|
sfilename = os.path.join(filename[:-len(os.path.basename(filename))], "extract", "settings.xml")
|
|
filesize = os.stat(filename).st_size
|
|
rf.seek(filesize - 0x200)
|
|
hdr = rf.read(0x200)
|
|
xmllength = int.from_bytes(hdr[0x18:0x18 + 4], 'little')
|
|
xmlpad = 0x200 - (xmllength % 0x200)
|
|
rf.seek(filesize - 0x200 - (xmllength + xmlpad))
|
|
inp = rf.read(xmllength + xmlpad)
|
|
outp = key_custom(inp, key, 0)
|
|
if b"xml " not in outp:
|
|
return None
|
|
with mmap_io(sfilename, 'wb', xmllength) as wf:
|
|
wf.write(outp[:xmllength])
|
|
return outp[:xmllength].decode('utf-8')
|
|
|
|
|
|
def decryptfile(rkey, filename, path, wfilename, start, length):
|
|
sha256 = hashlib.sha256()
|
|
print(f"Extracting {wfilename}")
|
|
with mmap_io(filename, 'rb') as rf:
|
|
rf.seek(start)
|
|
data = rf.read(length)
|
|
if length % 4:
|
|
data += (4 - (length % 4)) * b'\x00'
|
|
outp = key_custom(data, rkey, 0)
|
|
sha256.update(outp[:length])
|
|
with mmap_io(os.path.join(path, wfilename), 'wb', length) as wf:
|
|
wf.write(outp[:length])
|
|
if length % 0x1000 > 0:
|
|
sha256.update(b"\x00" * (0x1000 - (length % 0x1000)))
|
|
return sha256.hexdigest()
|
|
|
|
|
|
def encryptsubsub(rkey, data, wf):
|
|
length = len(data)
|
|
if length % 4:
|
|
data += (4 - (length % 4)) * b'\x00'
|
|
outp = key_custom(data, rkey, 0, True)
|
|
wf.write(outp[:length])
|
|
return length
|
|
|
|
|
|
def encryptsub(rkey, rf, wf):
|
|
data = rf.read()
|
|
return encryptsubsub(rkey, data, wf)
|
|
|
|
|
|
def encryptfile(key, filename, wfilename):
|
|
print(f"Encrypting {filename}")
|
|
with mmap_io(filename, 'rb') as rf:
|
|
filesize = os.stat(filename).st_size
|
|
with mmap_io(wfilename, 'wb', filesize) as wf:
|
|
return encryptsub(key, rf, wf)
|
|
|
|
|
|
def calc_digest(filename):
|
|
with mmap_io(filename, 'rb') as rf:
|
|
data = rf.read()
|
|
sha256 = hashlib.sha256()
|
|
sha256.update(data)
|
|
if len(data) % 0x1000 > 0:
|
|
sha256.update(b"\x00" * (0x1000 - (len(data) % 0x1000)))
|
|
return sha256.hexdigest()
|
|
|
|
|
|
def copysub(rf, wf, start, length):
|
|
rf.seek(start)
|
|
rlen = 0
|
|
while length > 0:
|
|
if length < 0x100000:
|
|
size = length
|
|
else:
|
|
size = 0x100000
|
|
data = rf.read(size)
|
|
wf.write(data)
|
|
rlen += len(data)
|
|
length -= size
|
|
return rlen
|
|
|
|
|
|
def copyfile(filename, path, wfilename, start, length):
|
|
print(f"Extracting {wfilename}")
|
|
with mmap_io(filename, 'rb') as rf:
|
|
with mmap_io(os.path.join(path, wfilename), 'wb', length) as wf:
|
|
return copysub(rf, wf, start, length)
|
|
|
|
|
|
def encryptitem(key, item, directory, pos, wf):
|
|
try:
|
|
filename = item.attrib["Path"]
|
|
except:
|
|
filename = item.attrib["filename"]
|
|
if filename == "":
|
|
return item, pos
|
|
filename = os.path.join(directory, filename)
|
|
start = pos // 0x200
|
|
item.attrib["FileOffsetInSrc"] = str(start)
|
|
size = os.stat(filename).st_size
|
|
item.attrib["SizeInByteInSrc"] = str(size)
|
|
sectors = size // 0x200
|
|
if (size % 0x200) != 0:
|
|
sectors += 1
|
|
item.attrib["SizeInSectorInSrc"] = str(sectors)
|
|
with mmap_io(filename, 'rb') as rf:
|
|
rlen = encryptsub(key, rf, wf)
|
|
pos += rlen
|
|
if (rlen % 0x200) != 0:
|
|
sublen = 0x200 - (rlen % 0x200)
|
|
wf.write(b'\x00' * sublen)
|
|
pos += sublen
|
|
return item, pos
|
|
|
|
|
|
def copyitem(item, directory, pos, wf):
|
|
try:
|
|
filename = item.attrib["Path"]
|
|
except:
|
|
filename = item.attrib["filename"]
|
|
if filename == "":
|
|
return item, pos
|
|
filename = os.path.join(directory, filename)
|
|
start = pos // 0x200
|
|
item.attrib["FileOffsetInSrc"] = str(start)
|
|
|
|
size = os.stat(filename).st_size
|
|
item.attrib["SizeInByteInSrc"] = str(size)
|
|
sectors = size // 0x200
|
|
if (size % 0x200) != 0:
|
|
sectors += 1
|
|
item.attrib["SizeInSectorInSrc"] = str(sectors)
|
|
with mmap_io(filename, 'rb') as rf:
|
|
rlen = copysub(rf, wf, 0, size)
|
|
pos += rlen
|
|
if (rlen % 0x200) != 0:
|
|
sublen = 0x200 - (rlen % 0x200)
|
|
wf.write(b'\x00' * sublen)
|
|
pos += sublen
|
|
return item, pos
|
|
|
|
|
|
def main(args):
|
|
global mbox
|
|
print("Oneplus CryptTools V1.4 (c) B. Kerler 2019-2021\n----------------------------\n")
|
|
if args["decrypt"]:
|
|
filename = args["<filename>"].replace("\\", "/")
|
|
print(f"Extracting {filename}")
|
|
if args["outdir"]:
|
|
path = args["outdir"]
|
|
elif "/" in filename:
|
|
path = filename[:filename.rfind("/")]
|
|
else:
|
|
path = ""
|
|
path = os.path.join(path, "extract")
|
|
if os.path.exists(path):
|
|
shutil.rmtree(path)
|
|
os.mkdir(path)
|
|
else:
|
|
os.mkdir(path)
|
|
mbox = mbox5
|
|
xml = extractxml(filename, key)
|
|
if xml is not None:
|
|
print("MBox5")
|
|
else:
|
|
mbox = mbox6
|
|
xml = extractxml(filename, key)
|
|
if xml is not None:
|
|
print("MBox6")
|
|
else:
|
|
mbox = mbox4
|
|
xml = extractxml(filename, key)
|
|
if xml is not None:
|
|
print("MBox4")
|
|
else:
|
|
print("Unsupported key !")
|
|
exit(0)
|
|
root = ET.fromstring(xml)
|
|
for child in root:
|
|
if child.tag == "SAHARA":
|
|
for item in child:
|
|
if item.tag == "File":
|
|
wfilename = item.attrib["Path"]
|
|
start = int(item.attrib["FileOffsetInSrc"]) * 0x200
|
|
slength = int(item.attrib["SizeInSectorInSrc"]) * 0x200
|
|
length = int(item.attrib["SizeInByteInSrc"])
|
|
decryptfile(key, filename, path, wfilename, start, length)
|
|
elif child.tag == "UFS_PROVISION":
|
|
for item in child:
|
|
if item.tag == "File":
|
|
wfilename = item.attrib["Path"]
|
|
start = int(item.attrib["FileOffsetInSrc"]) * 0x200
|
|
# length = int(item.attrib["SizeInSectorInSrc"]) * 0x200
|
|
length = int(item.attrib["SizeInByteInSrc"])
|
|
copyfile(filename, path, wfilename, start, length)
|
|
elif "Program" in child.tag:
|
|
# if not os.path.exists(os.path.join(path, child.tag)):
|
|
# os.mkdir(os.path.join(path, child.tag))
|
|
# spath = os.path.join(path, child.tag)
|
|
for item in child:
|
|
if "filename" in item.attrib:
|
|
sparse = item.attrib["sparse"] == "true"
|
|
wfilename = item.attrib["filename"]
|
|
if wfilename == "":
|
|
continue
|
|
start = int(item.attrib["FileOffsetInSrc"]) * 0x200
|
|
slength = int(item.attrib["SizeInSectorInSrc"]) * 0x200
|
|
length = int(item.attrib["SizeInByteInSrc"])
|
|
sha256 = item.attrib["Sha256"]
|
|
copyfile(filename, path, wfilename, start, length)
|
|
csha256 = calc_digest(os.path.join(path, wfilename))
|
|
if sha256 != csha256 and not sparse:
|
|
print("Sha256 fail.")
|
|
else:
|
|
for subitem in item:
|
|
if "filename" in subitem.attrib:
|
|
wfilename = subitem.attrib["filename"]
|
|
sparse = subitem.attrib["sparse"] == "true"
|
|
if wfilename == "":
|
|
continue
|
|
start = int(subitem.attrib["FileOffsetInSrc"]) * 0x200
|
|
slength = int(subitem.attrib["SizeInSectorInSrc"]) * 0x200
|
|
length = int(subitem.attrib["SizeInByteInSrc"])
|
|
sha256 = subitem.attrib["Sha256"]
|
|
copyfile(filename, path, wfilename, start, length)
|
|
csha256 = calc_digest(os.path.join(path, wfilename))
|
|
if sha256 != csha256 and not sparse:
|
|
print("Sha256 fail.")
|
|
# else:
|
|
# print (child.tag, child.attrib)
|
|
print("Done. Extracted files to " + path)
|
|
exit(0)
|
|
elif args["encrypt"]:
|
|
if args["--mbox"] == "4":
|
|
mbox = mbox4
|
|
elif args["--mbox"] == "5":
|
|
mbox = mbox5
|
|
elif args["--mbox"] == "6":
|
|
mbox = mbox6
|
|
directory = args["<directory>"].replace("\\", "/")
|
|
settings = os.path.join(directory, "settings.xml")
|
|
# root = ET.fromstring(settings)
|
|
tree = ET.parse(settings)
|
|
root = tree.getroot()
|
|
outfilename = os.path.join(Path(directory).parent, args["--savename"])
|
|
projid = None
|
|
firmware = None
|
|
if os.path.exists(outfilename):
|
|
os.remove(outfilename)
|
|
with open(outfilename, 'wb') as wf:
|
|
pos = 0
|
|
for child in root:
|
|
if child.tag == "BasicInfo":
|
|
if "Project" in child.attrib:
|
|
projid = child.attrib["Project"]
|
|
if "Version" in child.attrib:
|
|
firmware = child.attrib["Version"]
|
|
if child.tag == "SAHARA":
|
|
for item in child:
|
|
if item.tag == "File":
|
|
item, pos = encryptitem(key, item, directory, pos, wf)
|
|
elif child.tag == "UFS_PROVISION":
|
|
for item in child:
|
|
if item.tag == "File":
|
|
item, pos = copyitem(item, directory, pos, wf)
|
|
elif "Program" in child.tag:
|
|
for item in child:
|
|
if "filename" in item.attrib:
|
|
item, pos = copyitem(item, directory, pos, wf)
|
|
else:
|
|
for subitem in item:
|
|
subitem, pos = copyitem(subitem, directory, pos, wf)
|
|
try:
|
|
configpos = pos // 0x200
|
|
with open(settings, 'rb') as rf:
|
|
data = rf.read()
|
|
rlength = len(data)
|
|
data += (0x10 - (rlength % 0x10)) * b"\x00"
|
|
rlen = encryptsubsub(key, data, wf)
|
|
if ((rlen + pos) % 0x200) != 0:
|
|
sublen = 0x200 - ((rlen + pos) % 0x200)
|
|
wf.write(b'\x00' * sublen)
|
|
pos += sublen
|
|
if args["--projid"] is None:
|
|
if projid is None:
|
|
projid = "18801"
|
|
else:
|
|
projid = args["--projid"]
|
|
|
|
if args["--firmwarename"] is None:
|
|
if firmware is None:
|
|
firmware = "fajita_41_J.42_191214"
|
|
else:
|
|
firmware = args["--firmwarename"]
|
|
magic = 0x7CEF
|
|
hdr = b""
|
|
hdr += pack("<I", 2)
|
|
hdr += pack("<I", 1)
|
|
hdr += pack("<I", 0)
|
|
hdr += pack("<I", 0)
|
|
hdr += pack("<I", magic)
|
|
hdr += pack("<I", configpos)
|
|
hdr += pack("<I", rlength)
|
|
hdr += bytes(projid, 'utf-8')
|
|
hdr += b"\x00" * (0x10 - len(projid))
|
|
hdr += bytes(firmware, 'utf-8')
|
|
hdr += b"\x00" * (0x200 - len(hdr))
|
|
wf.write(hdr)
|
|
with open(outfilename, 'rb') as rt:
|
|
with open("md5sum_pack.md5", 'wb') as wt:
|
|
mt = hashlib.md5()
|
|
mt.update(rt.read())
|
|
wt.write(bytes(mt.hexdigest(), 'utf-8') + b" " + bytes(os.path.basename(outfilename), 'utf-8'))
|
|
print("Done. Created " + outfilename)
|
|
except Exception as e:
|
|
print(e)
|
|
exit(0)
|
|
elif args["encryptfile"]:
|
|
filename = args["<filename>"].replace("\\", "/")
|
|
mbox = mbox5
|
|
encryptfile(key, filename, filename + ".enc")
|
|
print("Done.")
|
|
elif args["decryptfile"]:
|
|
filename = args["<filename>"].replace("\\", "/")
|
|
mbox = mbox5
|
|
fsize = os.stat(filename).st_size
|
|
decryptfile(key, filename, "", filename + ".dec", 0, fsize)
|
|
print("Done.")
|
|
else:
|
|
print("Usage:./opsdecrypt.py decrypt [filename.ops]")
|
|
exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|