summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorh0t_max <h0t_max@hotmail.com>2022-07-19 00:02:21 +0200
committerh0t_max <h0t_max@hotmail.com>2022-07-19 00:02:21 +0200
commitea3381947943b8cd33f8edad41b4f32ea52af49e (patch)
tree618003ecf55c7eb7893915bf8345c05a063ee4c6
downloadMicrocodeDecryptor-ea3381947943b8cd33f8edad41b4f32ea52af49e.tar
MicrocodeDecryptor-ea3381947943b8cd33f8edad41b4f32ea52af49e.tar.gz
MicrocodeDecryptor-ea3381947943b8cd33f8edad41b4f32ea52af49e.tar.bz2
MicrocodeDecryptor-ea3381947943b8cd33f8edad41b4f32ea52af49e.tar.lz
MicrocodeDecryptor-ea3381947943b8cd33f8edad41b4f32ea52af49e.tar.xz
MicrocodeDecryptor-ea3381947943b8cd33f8edad41b4f32ea52af49e.tar.zst
MicrocodeDecryptor-ea3381947943b8cd33f8edad41b4f32ea52af49e.zip
-rw-r--r--README.md134
-rw-r--r--XuUnp.py78
-rw-r--r--dec_uUpd_Atom_apl.py109
-rw-r--r--dec_uUpd_xu_Atom_glp.py161
-rw-r--r--pic/XuRT.pngbin0 -> 176851 bytes
-rw-r--r--pic/swsec-xucode-fig1.pngbin0 -> 45966 bytes
6 files changed, 482 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..4e4b723
--- /dev/null
+++ b/README.md
@@ -0,0 +1,134 @@
+# **Disclaimer**
+
+**All information is provided for educational purposes only. Follow these instructions at your own risk. Neither the authors nor their employer are responsible for any direct or consequential damage or loss arising from any person or organization acting or failing to act on the basis of information contained in this page.**
+
+# Description
+At the beginning of 2020, we discovered the Red Unlock technique that allows extracting [Intel Atom Microcode](https://en.wikipedia.org/wiki/Intel_Microcode). We were able to research the internal structure of the microcode and then x86 instruction implementation. Also, we recovered a format of microcode updates, algorithm and the encryption key used to protect the microcode (see [RC4](https://en.wikipedia.org/wiki/RC4)).
+
+# FAQ
+1. Can I make a custom microcode update?
+
+No, you can't. Only decryption is supported, because microcode has an RSA signature for integrity protection.
+
+2. What kind CPUs are supported?
+
+Microcode format depends from CPU generation. We've extracted keys for Intel Gemini Lake (Goldmont Plus microarchitecture) and Intel Apolo Lake (Goldmont microarchitecture) generation. See [List of Supported CPUs](#list-of-supported-cpus)
+
+3. How you had extracted the keys?
+
+Using vulnerabilities in Intel TXE we had activated undocumented debugging mode called red unlock and extracted dumps of microcode directly from the CPU. We found the keys and algorithm inside.
+
+4. Where can I find more information about it?
+
+See our talks [Chip Red Pill: How We Achived to Execute Arbitrary Microcode Inside Inttel Atom CPUs](https://www.youtube.com/watch?v=V1nJeV0Uq0M&ab_channel=OffensiveCon) from OffensiveCon22.
+
+
+# Python
+All our scripts are written on Python. We recommend using Python 3. The scripts require pycryptodome packet. To install pycryptodome, run the following command:
+
+```
+pip3 install pycryptodome
+```
+
+# Decrypting Microcode Update
+Run the script **dec_uUpd_Atom_apl.py** for Apolo Lake CPUs:
+
+```
+C:\microcode>py -3 dec_uUpd_Atom_apl.py cpu506C9_plat03_ver00000040_2020-02-27_PRD_2FC3D618_.bin
+Hash matched at length 0x3E00 (15872)
+Data entropy: 6.898306
+```
+
+
+Run the script **dec_uUpd_xu_Atom_glp.py** for Gemini Lake CPUs:
+
+```
+C:\microcode>py -3 dec_uUpd_xu_Atom_glp.py cpu706A8_plat01_ver00000018_2020-06-09_PRD_1D9A5DFE.bin
+Patch hash matched: enc size: 0x3480
+Patch data entropy: 6.438890
+XuCode hash matched: size: 0xed40
+XuCode data entropy: 7.752765
+```
+
+
+
+# Unpacking decrypted XuCode (for Goldmont Plus only)
+
+Run the script **XuUnp.py** for Goldmont Plus CPUs:
+
+```
+C:\microcode>py -3 XuUnp.py cpu706A8_plat01_ver00000018_2020-06-09_PRD_1D9A5DFE.bin.xu.dec
+Processing cpu706A8_plat01_ver00000018_2020-06-09_PRD_1D9A5DFE.bin.xu.dec
+. ELF at 0x28
+. Parking: 0xD58+944
+. XuRT: 0x16EC+D404
+. Decompressing 54276 -> 101448, be patient...
++ Unpacked OK
+```
+![screenshot](pic/XuRT.png)
+
+# About XuCode
+XuCode is the software part of SGX technology, there are ELF-files integrated in microcode update.
+![screenshot](pic/swsec-xucode-fig1.png)
+
+
+See: [XuCode: An Innovative Technology for Implementing Complex Instruction Flows](https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/secure-coding/xucode-implementing-complex-instruction-flows.html)
+
+
+
+# List of Supported CPUs
+
+The microcode update can be decrypted for following CPUs:
+
+| Processor Model | Microarchitecture | SGX / XuCode support |
+| ------ | ------ | ------ |
+| Pentium J4205 | Goldmont, Apollo Lake | - |
+| Celeron J3455 | Goldmont, Apollo Lake | - |
+| Celeron J3355 | Goldmont, Apollo Lake | - |
+| Pentium N4200 | Goldmont, Apollo Lake | - |
+| Celeron N3450 | Goldmont, Apollo Lake | - |
+| Celeron N3350 | Goldmont, Apollo Lake | - |
+| Atom x7 E3940 | Goldmont, Apollo Lake | - |
+| Atom x5 E3930 | Goldmont, Apollo Lake | - |
+| Atom x7 A3960 | Goldmont, Apollo Lake | - |
+| Atom x7 A3950 | Goldmont, Apollo Lake | - |
+| Atom x5 A3940 | Goldmont, Apollo Lake | - |
+| Atom x5 A3930 | Goldmont, Apollo Lake | - |
+| Atom C3958 | Goldmont, Denverton | - |
+| Atom C3955 | Goldmont, Denverton | - |
+| Atom C3858 | Goldmont, Denverton | - |
+| Atom C3850 | Goldmont, Denverton | - |
+| Atom C3830 | Goldmont, Denverton | - |
+| Atom C3808 | Goldmont, Denverton | - |
+| Atom C3758 | Goldmont, Denverton | - |
+| Atom C3750 | Goldmont, Denverton | - |
+| Atom C3708 | Goldmont, Denverton | - |
+| Atom C3558 | Goldmont, Denverton | - |
+| Atom C3538 | Goldmont, Denverton | - |
+| Atom C3508 | Goldmont, Denverton | - |
+| Atom C3338 | Goldmont, Denverton | - |
+| Atom C3308 | Goldmont, Denverton | - |
+| Pentium Silver J5005 | Goldmont Plus, Gemini Lake | + |
+| Celeron J4105 | Goldmont Plus, Gemini Lake | + |
+| Celeron J4005 | Goldmont Plus, Gemini Lake | + |
+| Pentium Silver N5000 | Goldmont Plus, Gemini Lake | + |
+| Celeron N4100 | Goldmont Plus, Gemini Lake | + |
+| Celeron N4000 | Goldmont Plus, Gemini Lake | + |
+| Pentium Silver N5030 | Goldmont Plus, Gemini Lake Refresh | + |
+| Celeron N4120 | Goldmont Plus, Gemini Lake Refresh | + |
+| Celeron N4020 | Goldmont Plus, Gemini Lake Refresh | + |
+
+
+
+# Research Team
+
+Mark Ermolov ([@\_markel___][1])
+
+Maxim Goryachy ([@h0t_max][2])
+
+Dmitry Sklyarov ([@_Dmit][3])
+
+
+[1]: https://twitter.com/_markel___
+[2]: https://twitter.com/h0t_max
+[3]: https://twitter.com/_Dmit \ No newline at end of file
diff --git a/XuUnp.py b/XuUnp.py
new file mode 100644
index 0000000..dc84408
--- /dev/null
+++ b/XuUnp.py
@@ -0,0 +1,78 @@
+import struct, sys, os, binascii
+
+class bitsReader(object):
+ def __init__(self, ab):
+ self.bits = int(binascii.hexlify(ab[::-1]), 16)
+ self.o = 0
+ def get(self, n=1):
+ self.bits, v = divmod(self.bits, 1<<n)
+ return v
+
+def decompress(ab):
+ cbUnp, = struct.unpack_from("<L", ab)
+ print(". Decompressing %d -> %d, be patient..." % (len(ab), cbUnp))
+ br = bitsReader(ab[4:])
+ r = bytearray()
+ while (br.bits):
+ flag = br.get(1)
+ if flag:
+ ncp = br.get(4)
+ if ncp < 3: ncp += 16
+ offs = br.get(14)
+ ocp = len(r) - offs
+ for i in range(ncp): r.append(r[ocp+i])
+ else:
+ r.append(br.get(8))
+ if len(r) != cbUnp: print("? Got %d instead of %d..." % (len(r), cbUnp))
+ else: print("+ Unpacked OK")
+ return bytes(r)
+
+class Elf64_Shdr(object):
+ fmt = struct.Struct("<LLQQQQLLQQ")
+ def __init__(self, ab, shoff, iSec):
+ self.sh_name, self.sh_type, self.sh_flags, self.sh_addr, self.sh_offset, self.sh_size, self.sh_link, self.sh_info, self.sh_addralign, self.sh_entsize = \
+ self.fmt.unpack_from(ab, shoff + iSec*self.fmt.size)
+
+def process(fn):
+ print("Processing %s" % fn)
+ with open(fn, "rb") as fi: ab = fi.read()
+ oELF = ab.find(b'\x7FELF\2\1\1\0')
+ if oELF < 0:
+ print("- Can't find top ELF")
+ return
+ print(". ELF at 0x%X" % oELF)
+ e_ident, e_type, e_machine, e_version, e_entry, e_phoff, e_shoff, e_flags, e_ehsize, e_phentsize, e_phnum, e_shentsize, e_shnum, e_shstrndx = \
+ struct.unpack_from("<16sHHLQQQLHHHHHH", ab, oELF)
+ names = Elf64_Shdr(ab, oELF + e_shoff, e_shstrndx)
+ oNames = oELF + names.sh_offset
+
+ nPARKING, nXURT = b".PARKING\0", b".XURT\0"
+ sPARKING, sXURT = None,None
+
+ for iSec in range(e_shnum):
+ sec = Elf64_Shdr(ab, oELF + e_shoff, iSec)
+ oN = oNames+sec.sh_name
+ if ab[oN:oN+len(nPARKING)] == nPARKING: sPARKING = sec
+ if ab[oN:oN+len(nXURT)] == nXURT: sXURT = sec
+
+ base,ext = os.path.splitext(fn)
+ if not os.path.isdir(base): os.mkdir(base)
+ with open(os.path.join(base, "topELF.bin"), "wb") as fo: fo.write(ab[oELF:])
+
+ if sPARKING:
+ print(". Parking: 0x%X+%X" % (oELF+sPARKING.sh_offset, sPARKING.sh_size))
+ with open(os.path.join(base, "Parking.bin"), "wb") as fo: fo.write(ab[oELF+sPARKING.sh_offset:oELF+sPARKING.sh_offset+sPARKING.sh_size])
+ else: print("- Can't find .PARKING")
+
+ if sXURT:
+ print(". XuRT: 0x%X+%X" % (oELF+sXURT.sh_offset, sXURT.sh_size))
+ packed = ab[oELF+sXURT.sh_offset:oELF+sXURT.sh_offset+sXURT.sh_size]
+# with open(os.path.join(base, "XuRT.sect"), "wb") as fo: fo.write(packed)
+ plain = decompress(packed)
+ with open(os.path.join(base, "XuRT.bin"), "wb") as fo: fo.write(plain)
+ else: print("- Can't find .XURT")
+
+def main(argv):
+ for fn in argv[1:]: process(fn)
+
+if __name__=="__main__": main(sys.argv)
diff --git a/dec_uUpd_Atom_apl.py b/dec_uUpd_Atom_apl.py
new file mode 100644
index 0000000..2fce8d9
--- /dev/null
+++ b/dec_uUpd_Atom_apl.py
@@ -0,0 +1,109 @@
+import math, sys, struct, hashlib
+from Crypto.Cipher import ARC4
+
+def ROR32(v, nBit): return ((v >> nBit) | (v << (32 - nBit))) & 0xFFFFFFFF
+
+class my_SHA256(object):
+ k = [
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+ ]
+ def __init__(self, h=None):
+ self.h = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19] if h is None else h[:]
+ self.w = [0]*64
+
+ def transform(self, blk):
+ w = self.w
+ w[:16] = struct.unpack_from(">16L", blk) # assert len(blk) == 0x40
+ for i in range(16, 64): # Extend the first 16 words into the remaining 48 words
+ s0 = ROR32(w[i-15], 7) ^ ROR32(w[i-15], 18) ^ (w[i-15] >> 3)
+ s1 = ROR32(w[i-2], 17) ^ ROR32(w[i- 2], 19) ^ (w[i-2] >> 10)
+ w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFF
+ a,b,c,d,e,f,g,h = self.h
+
+ for i in range(64): # Compression function main loop
+ S1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25)
+ ch = (e & f) ^ ((0xFFFFFFFF ^ e) & g)
+ temp1 = (h + S1 + ch + self.k[i] + w[i]) & 0xFFFFFFFF
+ S0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22)
+ maj = (a & b) ^ (a & c) ^ (b & c)
+ temp2 = (S0 + maj) & 0xFFFFFFFF
+
+ h = g
+ g = f
+ f = e
+ e = (d + temp1) & 0xFFFFFFFF
+ d = c
+ c = b
+ b = a
+ a = (temp1 + temp2) & 0xFFFFFFFF
+
+ # Add the compressed chunk to the current hash value:
+ self.h[0] = (self.h[0] + a) & 0xFFFFFFFF
+ self.h[1] = (self.h[1] + b) & 0xFFFFFFFF
+ self.h[2] = (self.h[2] + c) & 0xFFFFFFFF
+ self.h[3] = (self.h[3] + d) & 0xFFFFFFFF
+ self.h[4] = (self.h[4] + e) & 0xFFFFFFFF
+ self.h[5] = (self.h[5] + f) & 0xFFFFFFFF
+ self.h[6] = (self.h[6] + g) & 0xFFFFFFFF
+ self.h[7] = (self.h[7] + h) & 0xFFFFFFFF
+
+ return self.get(True)
+
+ def get(self, le=False):
+ return struct.pack("<8L" if le else ">8L", *self.h)
+
+def calcEntropy(data):
+ entropy = 0.0
+ for x in range(256):
+ p_x = float(data.count(b"%c" % x))/len(data)
+ if p_x > 0: entropy -= p_x*math.log(p_x, 2)
+ return entropy
+
+
+aX = [0x9db2770e, 0x5d76919e, 0x994866a2, 0xab13688b] # Secret
+abX = struct.pack("<4L", *aX)
+
+hPub_need = bytes.fromhex("a1b4b7417f0fdcdb0feaa26eb5b78fb2cb86153f0ce98803f5cb84ae3a45901d") # Hash of Modulus
+
+def s2i(ab):
+ return int(ab[::-1].hex(), 16)
+
+def process(fn):
+ fmt = struct.Struct("96s32s256s4s256s")
+ with open(fn, "rb") as fi:
+ hdr30 = fi.read(0x30) # Top header
+ hdr, nonce, modulus, exponent, signature = fmt.unpack(fi.read(fmt.size))
+ enc = fi.read() # Everything else is encrypted data
+ n,e,ct = s2i(modulus), s2i(exponent), s2i(signature)
+ pt = bytes.fromhex(("%0512X" % pow(ct, e, n)))
+ rsa_pad = b"\x00\x01%s\x00" % (b"\xFF"*221) # Padding for 32 bytes of data
+ assert pt.startswith(rsa_pad) # Naive padding check
+ signed_hash = pt[-32:][::-1]
+
+ hPub = hashlib.sha256(modulus).digest()
+ assert hPub == hPub_need
+
+ buf = abX + nonce + abX
+ mh = my_SHA256()
+ k = b"".join(mh.transform(buf) for i in range(8))
+ rc4 = ARC4.new(k)
+ rc4.encrypt(b'\x00'*0x200) # Skip 0x200 bytes
+ dec = rc4.encrypt(enc)
+ with open(fn + ".dec", "wb") as fo: fo.write(dec)
+ for cc in range(0, len(dec), 64):
+ h = hashlib.sha256(hdr + nonce + dec[:cc]).digest()
+ if h == signed_hash:
+ print("Hash matched at length 0x%X (%d)" % (cc, cc))
+ print("Data entropy: %f" % calcEntropy(dec[:cc]))
+
+def main(argv):
+ if len(argv) > 1: process(argv[1])
+
+if __name__=="__main__": main(sys.argv)
diff --git a/dec_uUpd_xu_Atom_glp.py b/dec_uUpd_xu_Atom_glp.py
new file mode 100644
index 0000000..8331ec2
--- /dev/null
+++ b/dec_uUpd_xu_Atom_glp.py
@@ -0,0 +1,161 @@
+import math, sys, struct, hashlib
+from Crypto.Cipher import ARC4
+
+def ROR32(v, nBit): return ((v >> nBit) | (v << (32 - nBit))) & 0xFFFFFFFF
+
+class my_SHA256(object):
+ k = [
+ 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,
+ 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,
+ 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,
+ 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,
+ 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,
+ 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,
+ 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,
+ 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2,
+ ]
+ def __init__(self, h=None):
+ self.h = [0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19] if h is None else h[:]
+ self.w = [0]*64
+
+ def transform(self, blk):
+ w = self.w
+ w[:16] = struct.unpack_from(">16L", blk) # assert len(blk) == 0x40
+ for i in range(16, 64): # Extend the first 16 words into the remaining 48 words
+ s0 = ROR32(w[i-15], 7) ^ ROR32(w[i-15], 18) ^ (w[i-15] >> 3)
+ s1 = ROR32(w[i-2], 17) ^ ROR32(w[i- 2], 19) ^ (w[i-2] >> 10)
+ w[i] = (w[i-16] + s0 + w[i-7] + s1) & 0xFFFFFFFF
+ a,b,c,d,e,f,g,h = self.h
+
+ for i in range(64): # Compression function main loop
+ S1 = ROR32(e, 6) ^ ROR32(e, 11) ^ ROR32(e, 25)
+ ch = (e & f) ^ ((0xFFFFFFFF ^ e) & g)
+ temp1 = (h + S1 + ch + self.k[i] + w[i]) & 0xFFFFFFFF
+ S0 = ROR32(a, 2) ^ ROR32(a, 13) ^ ROR32(a, 22)
+ maj = (a & b) ^ (a & c) ^ (b & c)
+ temp2 = (S0 + maj) & 0xFFFFFFFF
+
+ h = g
+ g = f
+ f = e
+ e = (d + temp1) & 0xFFFFFFFF
+ d = c
+ c = b
+ b = a
+ a = (temp1 + temp2) & 0xFFFFFFFF
+
+ # Add the compressed chunk to the current hash value:
+ self.h[0] = (self.h[0] + a) & 0xFFFFFFFF
+ self.h[1] = (self.h[1] + b) & 0xFFFFFFFF
+ self.h[2] = (self.h[2] + c) & 0xFFFFFFFF
+ self.h[3] = (self.h[3] + d) & 0xFFFFFFFF
+ self.h[4] = (self.h[4] + e) & 0xFFFFFFFF
+ self.h[5] = (self.h[5] + f) & 0xFFFFFFFF
+ self.h[6] = (self.h[6] + g) & 0xFFFFFFFF
+ self.h[7] = (self.h[7] + h) & 0xFFFFFFFF
+
+ return self.get(True)
+
+ def get(self, le=False):
+ return struct.pack("<8L" if le else ">8L", *self.h)
+
+def calcEntropy(data):
+ entropy = 0.0
+ for x in range(256):
+ p_x = float(data.count(b"%c" % x))/len(data)
+ if p_x > 0: entropy -= p_x*math.log(p_x, 2)
+ return entropy
+
+
+aX = [0xff062483, 0x9c7f0b6b, 0x2c5c83a4, 0x1e266274] # Secret
+abX = struct.pack("<4L", *aX)
+
+hPub_need = bytes.fromhex("c2e63058bef5bef84cac5319aabbac4095dd11d5f46a51cb0cfbe4641dd87a21") # Hash of Modulus
+
+def s2i(ab):
+ return int(ab[::-1].hex(), 16)
+
+def process(fn):
+ fmt = struct.Struct("96s32s256s4s256s")
+ with open(fn, "rb") as fi:
+ hdr30 = fi.read(0x30) # Top header
+ hdr, nonce, modulus, exponent, signature = fmt.unpack(fi.read(fmt.size))
+ _, patch_size_dw = struct.unpack_from("<28sL", hdr)
+ enc_size = (patch_size_dw * 4) - 0x284
+ enc = fi.read(enc_size) # Encrypted data of size calculated from total patch size (0x1c offset)
+ patch_other = fi.read()
+ n,e,ct = s2i(modulus), s2i(exponent), s2i(signature)
+ pt = bytes.fromhex(("%0512X" % pow(ct, e, n)))
+ rsa_pad = b"\x00\x01%s\x00" % (b"\xFF"*221) # Padding for 32 bytes of data
+ assert pt.startswith(rsa_pad) # Naive padding check
+ signed_hash = pt[-32:][::-1]
+
+ hPub = hashlib.sha256(modulus).digest()
+ assert hPub == hPub_need
+
+ buf = abX + nonce + abX
+ mh = my_SHA256()
+ k = b"".join(mh.transform(buf) for i in range(8))
+ rc4 = ARC4.new(k)
+ rc4.encrypt(b'\x00'*0x200) # Skip 0x200 bytes
+ dec = rc4.encrypt(enc)
+ with open(fn + ".dec", "wb") as fo: fo.write(dec)
+
+ h = hashlib.sha256(hdr + nonce + dec).digest()
+ hash_match = h == signed_hash
+ if hash_match:
+ print("Patch hash matched: enc size: 0x%04x" % enc_size)
+ else:
+ print("Patch hash not matched!: enc size: 0x%04x\n hash: %s\n signed_hash: %s" % \
+ (enc_size, h.hex(), signed_hash.hex()))
+
+ print("Patch data entropy: %f" % calcEntropy(dec))
+
+ if not hash_match:
+ print("XuCode is not found in patch (hash mismatch)!")
+ return
+
+ xu_dec_cmd_offset = -1
+ for dec_off in range(len(dec) - 4, 0, -1):
+ if dec[dec_off: dec_off+4] == b"\xfe\xff\xff\xff":
+ xu_dec_cmd_offset = dec_off - 0x35
+ break
+ if xu_dec_cmd_offset < 0:
+ print("XuCode is not found in patch (decode command is not found)!: xu_dec_cmd_offset: 0x%x" \
+ % xu_dec_cmd_offset)
+ return
+
+ xu_dec_cmd_id, xu_offset, xu_size, xu_hash = struct.unpack_from("<BLL32s", dec, xu_dec_cmd_offset)
+ if xu_dec_cmd_id != 0x14:
+ print("XuCode is not found in patch (invalid decode command id)!: xu_dec_cmd_offset: 0x%04x: cmd id: 0x%02x" \
+ % (xu_dec_cmd_offset, xu_dec_cmd_id))
+ return
+
+ patch_size = patch_size_dw * 4
+ assert(xu_size >= 0x20 and xu_offset >= patch_size and xu_offset + xu_size <= patch_size + len(patch_other))
+
+ xu_offset_other = xu_offset - patch_size
+ xu_nonce = patch_other[xu_offset_other: 0x20]
+ xu_data = patch_other[xu_offset_other + 0x20: xu_offset_other + xu_size]
+
+ xu_mh = my_SHA256()
+ xu_buf = abX + xu_nonce + abX
+ xu_k = b"".join(xu_mh.transform(xu_buf) for i in range(8))
+ xu_rc4 = ARC4.new(xu_k)
+ xu_rc4.encrypt(b'\x00'*0x200)
+ xu_dec = xu_rc4.encrypt(xu_data)
+ with open(fn + ".xu.dec", "wb") as fo: fo.write(xu_dec)
+
+ xu_h = hashlib.sha256(xu_nonce + xu_dec).digest()
+ if xu_h == xu_hash:
+ print("XuCode hash matched: size: 0x%04x" % xu_size)
+ else:
+ print("XuCode hash not matched!: size: 0x%04x\n hash: %s\n signed_hash: %s" % \
+ (xu_size, xu_h.hex(), xu_hash.hex()))
+
+ print("XuCode data entropy: %f" % calcEntropy(xu_dec))
+
+def main(argv):
+ if len(argv) > 1: process(argv[1])
+
+if __name__=="__main__": main(sys.argv)
diff --git a/pic/XuRT.png b/pic/XuRT.png
new file mode 100644
index 0000000..db1688a
--- /dev/null
+++ b/pic/XuRT.png
Binary files differ
diff --git a/pic/swsec-xucode-fig1.png b/pic/swsec-xucode-fig1.png
new file mode 100644
index 0000000..6cd030b
--- /dev/null
+++ b/pic/swsec-xucode-fig1.png
Binary files differ