|
| 1 | +import base64 |
| 2 | +import sys |
| 3 | +import os |
| 4 | +import subprocess |
| 5 | +import binascii |
| 6 | +from cryptography.hazmat.primitives.serialization import load_pem_private_key |
| 7 | +from cryptography.hazmat.backends import default_backend |
| 8 | +from cryptography.hazmat.primitives.asymmetric import ec |
| 9 | +from cryptography.hazmat.primitives import hashes, hmac |
| 10 | +from cryptography.exceptions import InvalidSignature |
| 11 | +from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes |
| 12 | + |
| 13 | +SEVTOOL = "Path to the sevtool binary that exports the KDF" |
| 14 | + |
| 15 | +def swap_bytes(data): |
| 16 | + data_int = int.from_bytes(data, 'little') |
| 17 | + return (data_int).to_bytes(len(data), 'big') |
| 18 | + |
| 19 | +def build_ecdsa_pubkey(qx_bytes, qy_bytes): |
| 20 | + """Build ecdsa public key from provided curve point. Returns an |
| 21 | + EllipticCurvePublicKey object. Point must be specified by qx and qy |
| 22 | + values in little-endian byte order. Curve defaults to NIST-P384. |
| 23 | + See: AMD SEV API ver. 0.22 Chapter 4.5 and Appendix C.2""" |
| 24 | + |
| 25 | + pubkey_qx = int.from_bytes(qx_bytes, 'little') |
| 26 | + pubkey_qy = int.from_bytes(qy_bytes, 'little') |
| 27 | + |
| 28 | + curve = ec.SECP384R1() # NIST-P384 |
| 29 | + pub_numbers = ec.EllipticCurvePublicNumbers(pubkey_qx, pubkey_qy, curve) |
| 30 | + |
| 31 | + ec_pubkey = pub_numbers.public_key(default_backend()) |
| 32 | + |
| 33 | + return ec_pubkey |
| 34 | + |
| 35 | +def derive_secret(secret,context,nonce): |
| 36 | + """Call the sevtool key key derivation function. |
| 37 | + Returns the derived key.""" |
| 38 | + # TODO: Implement in python |
| 39 | + if nonce is not None: |
| 40 | + out = subprocess.Popen([SEVTOOL,"--kdf",binascii.hexlify(secret),context,binascii.hexlify(nonce)],stdout=subprocess.PIPE) |
| 41 | + else: |
| 42 | + out = subprocess.Popen([SEVTOOL,"--kdf",binascii.hexlify(secret),context],stdout=subprocess.PIPE) |
| 43 | + |
| 44 | + return binascii.unhexlify(out.stdout.readlines()[0].rstrip()) |
| 45 | + |
| 46 | +# Read private PDH |
| 47 | +with open(sys.argv[1], 'rb') as f: |
| 48 | + pdh_priv = load_pem_private_key( |
| 49 | + f.read(), |
| 50 | + password=None, |
| 51 | + backend=default_backend()) |
| 52 | + |
| 53 | +# Read remote public PDH |
| 54 | +with open(sys.argv[2],'rb') as f: |
| 55 | + remote_pdh = f.read() |
| 56 | + remote_pdh = build_ecdsa_pubkey(remote_pdh[0x14:0x14+0x48], |
| 57 | + remote_pdh[0x5c:0x5c+0x48]) |
| 58 | + |
| 59 | +# Open encrypted memory file |
| 60 | +with open(sys.argv[3], 'rb') as f: |
| 61 | + # TODO don't rely on hard-coded constants |
| 62 | + |
| 63 | + f.seek(0x19d) |
| 64 | + policy = f.read(4) |
| 65 | + f.seek(0x1A1) |
| 66 | + pdh_len = f.read(4) |
| 67 | + |
| 68 | + # Seek to the beginning of the PDH |
| 69 | + f.seek(0x1A5) |
| 70 | + pdh_len = int.from_bytes(pdh_len,'big') |
| 71 | + # pdh = f.read(pdh_len) |
| 72 | + |
| 73 | + #Seek to the beginning of the session len |
| 74 | + f.seek(0x1A5 + pdh_len) |
| 75 | + session_len = int.from_bytes(f.read(4), 'big') |
| 76 | + |
| 77 | + # Read session data |
| 78 | + f.seek(0x1A5 + 4 + pdh_len) |
| 79 | + session_data = f.read(session_len) |
| 80 | + |
| 81 | + begin_chunk = 0x1A5 + 4 + pdh_len + session_len |
| 82 | + print(hex(begin_chunk)) |
| 83 | + |
| 84 | + |
| 85 | +# DH Static Unified Model - Section 2.2.2 AMD SEV API |
| 86 | +print("Deriving shared secret") |
| 87 | +shared_secret = pdh_priv.exchange(ec.ECDH(), remote_pdh) |
| 88 | + |
| 89 | +# Get session data |
| 90 | +nonce = session_data[:0x10] |
| 91 | +wrapped_tk = session_data[0x10:0x30] |
| 92 | +iv = session_data[0x30:0x40] |
| 93 | +hmac_tk = session_data[0x40:0x60] |
| 94 | +hmac_policy = session_data[0x60:0x80] |
| 95 | + |
| 96 | +print("Deriving master secret, key-encryption-key (KEK) and key-integrity-key (KIK)") |
| 97 | +master_secret = derive_secret(shared_secret,b'sev-master-secret',nonce) |
| 98 | +kek = derive_secret(master_secret,b'sev-kek',None) |
| 99 | +kik = derive_secret(master_secret,b'sev-kik',None) |
| 100 | + |
| 101 | +calc_hmac = hmac.HMAC(kik, hashes.SHA256(), backend=default_backend()) |
| 102 | + |
| 103 | +calc_hmac.update(wrapped_tk) |
| 104 | + |
| 105 | +# Sanity check: Verify that derived kik is correct. |
| 106 | +try: |
| 107 | + calc_hmac.verify(hmac_tk) |
| 108 | +except InvalidSignature: |
| 109 | + print("ERROR, couldn't verify using kik") |
| 110 | +else: |
| 111 | + print("Verified wrapped_tk using kik") |
| 112 | + |
| 113 | + |
| 114 | + |
| 115 | +aes = algorithms.AES(kek) |
| 116 | +decryptor = Cipher(aes,modes.CTR(iv),default_backend()).decryptor() |
| 117 | + |
| 118 | +tiktek = decryptor.update(wrapped_tk) + decryptor.finalize() |
| 119 | + |
| 120 | +# Get TIK and TEK |
| 121 | +tek = tiktek[:0x10] |
| 122 | +tik = tiktek[0x10:] |
| 123 | + |
| 124 | +calc_hmac_pol = hmac.HMAC(tik, hashes.SHA256(), backend=default_backend()) |
| 125 | +calc_hmac_pol.update(swap_bytes(policy)) |
| 126 | + |
| 127 | +# Sanity check: Verify that decrypted TIK is correct. |
| 128 | +try: |
| 129 | + calc_hmac_pol.verify(hmac_policy) |
| 130 | +except InvalidSignature: |
| 131 | + print("ERROR, couldn't verify policy using tik") |
| 132 | + sys.exit(-1) |
| 133 | +else: |
| 134 | + print("Verified policy using tik") |
| 135 | + |
| 136 | +print("Starting to decrypt...") |
| 137 | +out = open("./out",'ab') |
| 138 | + |
| 139 | +# Don't look at this code, please. It is hideous. |
| 140 | +with open(sys.argv[3],'rb') as f: |
| 141 | + while(True): |
| 142 | + # Read transport hdr |
| 143 | + f.seek(begin_chunk) |
| 144 | + hdr_size = int.from_bytes(f.read(4), 'big') |
| 145 | + if hdr_size != 0x34: |
| 146 | + break |
| 147 | + print("HDR size: %x at offset %x" % (hdr_size, f.tell())) |
| 148 | + f.seek(begin_chunk + 4) |
| 149 | + hdr = f.read(hdr_size) |
| 150 | + |
| 151 | + data_flags = hdr[:4] |
| 152 | + data_iv = hdr[4:20] |
| 153 | + data_mac = hdr[0x14:0x14+0x20] |
| 154 | + f.seek(begin_chunk + 4 + hdr_size) |
| 155 | + data_size = int.from_bytes(f.read(4), 'big') |
| 156 | + if data_size != 0x1000: |
| 157 | + break |
| 158 | + print("Data size: %x at offset: %x" % (data_size,f.tell())) |
| 159 | + |
| 160 | + f.seek(begin_chunk + 4 + hdr_size + 4) |
| 161 | + data = f.read(data_size) |
| 162 | + if len(data) != data_size: |
| 163 | + print("Couldn't read the full data chuck. data_size: %x len(data): %x" % (data_size, len(data))) |
| 164 | + print(" Offset in file: %x" % f.tell()) |
| 165 | + break |
| 166 | + if data_size == 0: |
| 167 | + print("data_size is zero") |
| 168 | + print(" Offset in file: %x" % f.tell()) |
| 169 | + break |
| 170 | + |
| 171 | + aes = algorithms.AES(tek) |
| 172 | + decryptor = Cipher(aes,modes.CTR(data_iv),default_backend()).decryptor() |
| 173 | + pos = 0 |
| 174 | + current_pos = f.tell() |
| 175 | + while True: |
| 176 | + f.seek(current_pos + pos) |
| 177 | + if f.read(8) == b'\x00\x00\x00\x34\x00\x00\x00\x00': # TODO: Don't assume a fixed header size of 0x34 |
| 178 | + break |
| 179 | + pos += 1 |
| 180 | + cur_pos = f.tell() |
| 181 | + f.seek(0,os.SEEK_END) |
| 182 | + if cur_pos >= f.tell(): |
| 183 | + break |
| 184 | + f.seek(cur_pos) |
| 185 | + begin_chunk = current_pos + pos |
| 186 | + print("Begin of new chuck at %x offset: %x" % (begin_chunk, f.tell())) |
| 187 | + |
| 188 | + out.write(decryptor.update(data) + decryptor.finalize()) |
| 189 | + |
| 190 | + |
| 191 | +out.close() |
0 commit comments