|
7 | 7 |
|
8 | 8 | import argparse |
9 | 9 |
|
| 10 | + |
10 | 11 | @winsdkapi(cc=STDCALL) |
11 | 12 | def hook_chkstk(ql, addr, params): |
12 | | - return ql.arch.regs.rax |
| 13 | + return ql.arch.regs.rax |
| 14 | + |
13 | 15 |
|
14 | 16 | def parse_args(): |
15 | | - parser = argparse.ArgumentParser(description="decrypts a ClepV4 encrypted device key.") |
16 | | - parser.add_argument("--license", required=True, help="base64 encoded encrypted device license (Required length: 4094)") |
17 | | - parser.add_argument("--smbios", required=True, help="base64 encoded SMBIOS system struct.") |
18 | | - parser.add_argument("--driveser", required=True, help="base64 encoded null-terminated root drive serial number.") |
19 | | - args = parser.parse_args() |
20 | | - return (args.license, args.smbios, args.driveser) |
21 | | - |
22 | | -if __name__ == '__main__': |
23 | | - enc_license_b64, smbios_b64, driveser_b64 = parse_args() |
24 | | - encrypted_device_license = b64decode(enc_license_b64) |
25 | | - smbiosSystem = b64decode(smbios_b64) |
26 | | - driveSer = b64decode(driveser_b64) |
27 | | - |
28 | | - assert len(encrypted_device_license) == 4094, "Error: Encrypted Device License length mismatch. (Expected: 4094)" |
29 | | - |
30 | | - max_smbios = 256 |
31 | | - max_driveser = 64 |
32 | | - max_tpminfo = 901 |
33 | | - |
34 | | - if (len(smbiosSystem) > max_smbios): |
35 | | - smbiosSystem = smbiosSystem[:max_smbios] |
36 | | - |
37 | | - if (len(driveSer) > max_driveser): |
38 | | - driveSer = driveSer[:max_driveser] |
39 | | - |
40 | | - ql = Qiling(["./clipsp.sys"], ".\\x8664_windows", libcache=True, console=False) |
41 | | - |
42 | | - ql.os.set_api("__chkstk", hook_chkstk) |
43 | | - |
44 | | - clep_vault_func_pattern = b"\x4C\x8B\xDC\x49\x89\x4B\x08" |
45 | | - clep_vault_size = 0x4e |
46 | | - clep_vault_func = ql.mem.search(clep_vault_func_pattern, begin=0x1c0000000, end=0x1d0000000)[0] |
47 | | - if clep_vault_func is None: |
48 | | - print("Error: Failed to find vault function using pattern.") |
49 | | - exit() |
50 | | - |
51 | | - # Gets the cached request memory location through a bit of trickery: |
52 | | - # Basically pattern matching a part of a vault function, navigating to the lea request opcode, then reading the operand for the offset |
53 | | - clep_request_pattern = b"\xC6\x45\x00\x19\x8A\x45\x00\x8B\x04\x24\x48\x83\xEC\x10\x8B\x04\x24\x8B\x04\x24\x48\x83\xEC\x50\x48\x8D\x4C\x24\x20\x8B\x01\x41\x0F\x10\x02\x33\xC0\x48\x8D\x59\x0F\x48\x83\xE3\xF0\xF3\x0F\x7F\x43\x28" |
54 | | - clep_request_opcode_offset = 0x4e # actually 0x4b, but + 3 to get the operand directly |
55 | | - clep_request_func = ql.mem.search(clep_request_pattern, begin=0x1c0000000, end=0x1d0000000)[0] |
56 | | - if clep_request_func is None: |
57 | | - print("Error: Failed to find request function using pattern.") |
58 | | - exit() |
59 | | - |
60 | | - clep_request_opcode = clep_request_func + clep_request_opcode_offset |
61 | | - clep_request_ptr_offset = struct.unpack("<I", ql.mem.read(clep_request_opcode, 4))[0] |
62 | | - clep_request_ptr = clep_request_opcode + clep_request_ptr_offset + 4 # To offset the operand size |
63 | | - |
64 | | - req_version = clep_request_ptr |
65 | | - req_smbios = req_version + 4 |
66 | | - req_driveser = req_smbios + max_smbios |
67 | | - req_tpmstatus = req_driveser + max_driveser |
68 | | - req_tpminfo = req_tpmstatus + 1 |
69 | | - req_istogo = req_tpminfo + max_tpminfo |
70 | | - req_debuggerEnabled = req_istogo + 1 |
71 | | - req_debuggerAttached = req_debuggerEnabled + 4 |
72 | | - req_remdata = req_debuggerAttached + 4 |
73 | | - |
74 | | - ql.mem.write(req_version, struct.pack("<I", 0x4)) |
75 | | - ql.mem.write(req_smbios, smbiosSystem) |
76 | | - ql.mem.write(req_driveser, driveSer) |
77 | | - |
78 | | - ql.mem.write(req_tpmstatus, struct.pack("<B", 0x0)) |
79 | | - ql.mem.write(req_istogo, struct.pack("<B", 0x0)) |
80 | | - |
81 | | - ql.mem.write(req_debuggerEnabled, struct.pack("<I", 0x7ffe02d4)) |
82 | | - ql.mem.write(req_debuggerAttached, struct.pack("<I", 0x7ffe02d4)) |
83 | | - |
84 | | - try: |
85 | | - ql.os.KUSER_SHARED_DATA |
86 | | - except: |
87 | | - # We are running in a Qiling version that does not have KUSER_SHARED_DATA, set debugger var manually |
88 | | - ql.mem.map(0x7ffe02d4 // 4096*4096, 4096) |
89 | | - ql.mem.write(0x7ffe02d4, struct.pack("<I", 0x00000010)) # 0x0 - Debugger not enabled | 0x10 - Debugger not attached |
90 | | - |
91 | | - pb_secret = ql.os.heap.alloc(32) |
92 | | - license_buffer = ql.os.heap.alloc(len(encrypted_device_license)) |
93 | | - ql.mem.write(license_buffer, encrypted_device_license) |
94 | | - |
95 | | - # sp_cache = 0x1C0043600 --- If needed this could also be done through pattern matching, but we can just use our own buffer |
96 | | - sp_cache = ql.os.heap.alloc(64) |
97 | | - |
98 | | - #clep_vault_v4_begin = 0x1C000B234 |
99 | | - #clep_vault_v4_end = 0x1C000B282 |
100 | | - |
101 | | - ql.arch.regs.rcx = 0x0 |
102 | | - ql.arch.regs.rdx = license_buffer + 4 |
103 | | - ql.arch.regs.r8 = pb_secret |
104 | | - ql.arch.regs.r9 = license_buffer + 516 |
105 | | - |
106 | | - ql.stack_write(0x30, sp_cache) |
107 | | - |
108 | | - ql.run(begin=clep_vault_func, end=clep_vault_func + clep_vault_size) |
109 | | - |
110 | | - buffer = ql.mem.read(pb_secret, 16) |
111 | | - print(binascii.hexlify(buffer).decode("utf-8")) |
| 17 | + parser = argparse.ArgumentParser( |
| 18 | + description="decrypts a ClepV4 encrypted device key." |
| 19 | + ) |
| 20 | + parser.add_argument( |
| 21 | + "--license", |
| 22 | + required=True, |
| 23 | + help="base64 encoded encrypted device license (Required length: 4094)", |
| 24 | + ) |
| 25 | + parser.add_argument( |
| 26 | + "--smbios", required=True, help="base64 encoded SMBIOS system struct." |
| 27 | + ) |
| 28 | + parser.add_argument( |
| 29 | + "--driveser", |
| 30 | + required=True, |
| 31 | + help="base64 encoded null-terminated root drive serial number.", |
| 32 | + ) |
| 33 | + args = parser.parse_args() |
| 34 | + return (args.license, args.smbios, args.driveser) |
| 35 | + |
| 36 | + |
| 37 | +if __name__ == "__main__": |
| 38 | + enc_license_b64, smbios_b64, driveser_b64 = parse_args() |
| 39 | + encrypted_device_license = b64decode(enc_license_b64) |
| 40 | + smbiosSystem = b64decode(smbios_b64) |
| 41 | + driveSer = b64decode(driveser_b64) |
| 42 | + |
| 43 | + assert len(encrypted_device_license) == 4094, ( |
| 44 | + "Error: Encrypted Device License length mismatch. (Expected: 4094)" |
| 45 | + ) |
| 46 | + |
| 47 | + max_smbios = 256 |
| 48 | + max_driveser = 64 |
| 49 | + max_tpminfo = 901 |
| 50 | + |
| 51 | + if len(smbiosSystem) > max_smbios: |
| 52 | + smbiosSystem = smbiosSystem[:max_smbios] |
| 53 | + |
| 54 | + if len(driveSer) > max_driveser: |
| 55 | + driveSer = driveSer[:max_driveser] |
| 56 | + |
| 57 | + ql = Qiling(["./clipsp.sys"], ".\\x8664_windows", libcache=True, console=False) |
| 58 | + |
| 59 | + ql.os.set_api("__chkstk", hook_chkstk) |
| 60 | + |
| 61 | + clep_vault_func_pattern = b"\x4c\x8b\xdc\x49\x89\x4b\x08" |
| 62 | + clep_vault_size = 0x4E |
| 63 | + clep_vault_func = ql.mem.search( |
| 64 | + clep_vault_func_pattern, begin=0x1C0000000, end=0x1D0000000 |
| 65 | + )[0] |
| 66 | + if clep_vault_func is None: |
| 67 | + print("Error: Failed to find vault function using pattern.") |
| 68 | + exit() |
| 69 | + |
| 70 | + # Gets the cached request memory location through a bit of trickery: |
| 71 | + # Basically pattern matching a part of a vault function, navigating to the lea request opcode, then reading the operand for the offset |
| 72 | + # tuple of (pattern, offset to address) |
| 73 | + patterns_to_try = [ |
| 74 | + # actually 0x4b, but + 3 to get the operand directly |
| 75 | + ( |
| 76 | + b"\xc6\x45\x00\x19\x8a\x45\x00\x8b\x04\x24\x48\x83\xec\x10\x8b\x04\x24\x8b\x04\x24\x48\x83\xec\x50\x48\x8d\x4c\x24\x20\x8b\x01\x41\x0f\x10\x02\x33\xc0\x48\x8d\x59\x0f\x48\x83\xe3\xf0\xf3\x0f\x7f\x43\x28", |
| 77 | + 0x4E, |
| 78 | + ), |
| 79 | + (b"\xc6\x45\x00\x19\x0f\xb6\x45", 0x50), |
| 80 | + ] |
| 81 | + |
| 82 | + clep_request_ptr = 0 |
| 83 | + |
| 84 | + for pattern, offset in patterns_to_try: |
| 85 | + results = ql.mem.search(pattern, begin=0x1C0000000, end=0x1D0000000) |
| 86 | + if len(results) == 0: |
| 87 | + continue |
| 88 | + |
| 89 | + if len(results) != 1: |
| 90 | + print( |
| 91 | + "Error: Ambiguous request function references found: " |
| 92 | + + str(hex(x) for x in results) |
| 93 | + ) |
| 94 | + continue |
| 95 | + |
| 96 | + clep_request_opcode = results[0] + offset |
| 97 | + clep_request_ptr_offset = struct.unpack( |
| 98 | + "<I", ql.mem.read(clep_request_opcode, 4) |
| 99 | + )[0] |
| 100 | + |
| 101 | + clep_request_ptr = ( |
| 102 | + clep_request_opcode + clep_request_ptr_offset + 4 |
| 103 | + ) # To offset the operand size |
| 104 | + |
| 105 | + break |
| 106 | + |
| 107 | + if clep_request_ptr == 0: |
| 108 | + print("Error: Failed to find request location using pattern.") |
| 109 | + exit() |
| 110 | + |
| 111 | + req_version = clep_request_ptr |
| 112 | + req_smbios = req_version + 4 |
| 113 | + req_driveser = req_smbios + max_smbios |
| 114 | + req_tpmstatus = req_driveser + max_driveser |
| 115 | + req_tpminfo = req_tpmstatus + 1 |
| 116 | + req_istogo = req_tpminfo + max_tpminfo |
| 117 | + req_debuggerEnabled = req_istogo + 1 |
| 118 | + req_debuggerAttached = req_debuggerEnabled + 4 |
| 119 | + req_remdata = req_debuggerAttached + 4 |
| 120 | + |
| 121 | + ql.mem.write(req_version, struct.pack("<I", 0x4)) |
| 122 | + ql.mem.write(req_smbios, smbiosSystem) |
| 123 | + ql.mem.write(req_driveser, driveSer) |
| 124 | + |
| 125 | + ql.mem.write(req_tpmstatus, struct.pack("<B", 0x0)) |
| 126 | + ql.mem.write(req_istogo, struct.pack("<B", 0x0)) |
| 127 | + |
| 128 | + ki_debugger_addr = 0x7FFE02D4 |
| 129 | + |
| 130 | + ql.mem.write(req_debuggerEnabled, struct.pack("<I", ki_debugger_addr)) |
| 131 | + ql.mem.write(req_debuggerAttached, struct.pack("<I", ki_debugger_addr)) |
| 132 | + |
| 133 | + try: |
| 134 | + ql.os.KUSER_SHARED_DATA |
| 135 | + except Exception: |
| 136 | + # We are running in a Qiling version that does not have KUSER_SHARED_DATA, set debugger var manually |
| 137 | + ql.mem.map(ki_debugger_addr // 4096 * 4096, 4096) |
| 138 | + ql.mem.write( |
| 139 | + ki_debugger_addr, struct.pack("<I", 0x00000010) |
| 140 | + ) # 0x0 - Debugger not enabled | 0x10 - Debugger not attached |
| 141 | + |
| 142 | + pb_secret = ql.os.heap.alloc(32) |
| 143 | + license_buffer = ql.os.heap.alloc(len(encrypted_device_license)) |
| 144 | + ql.mem.write(license_buffer, encrypted_device_license) |
| 145 | + |
| 146 | + # sp_cache = 0x1C0043600 --- If needed this could also be done through pattern matching, but we can just use our own buffer |
| 147 | + sp_cache = ql.os.heap.alloc(64) |
| 148 | + |
| 149 | + # clep_vault_v4_begin = 0x1C000B234 |
| 150 | + # clep_vault_v4_end = 0x1C000B282 |
| 151 | + |
| 152 | + ql.arch.regs.rcx = 0x0 |
| 153 | + ql.arch.regs.rdx = license_buffer + 4 |
| 154 | + ql.arch.regs.r8 = pb_secret |
| 155 | + ql.arch.regs.r9 = license_buffer + 516 |
| 156 | + |
| 157 | + ql.stack_write(0x30, sp_cache) |
| 158 | + |
| 159 | + ql.run(begin=clep_vault_func, end=clep_vault_func + clep_vault_size) |
| 160 | + |
| 161 | + buffer = ql.mem.read(pb_secret, 16) |
| 162 | + print(binascii.hexlify(buffer).decode("utf-8")) |
0 commit comments