Files
vmware-workstation/unlocker.py
ytshih ca56de4987
All checks were successful
Build package / build-package (push) Successful in 5m11s
Initial commit
2025-07-10 08:03:14 +08:00

388 lines
12 KiB
Python

#!/usr/bin/env python3
"""
vSMC Header Structure
Offset Length Struct Type Description
----------------------------------------
0x00/00 0x08/08 Q ptr Offset to key table
0x08/08 0x04/4 I int Number of private keys
0x0C/12 0x04/4 I int Number of public keys
vSMC Key Data Structure
Offset Length Struct Type Description
----------------------------------------
0x00/00 0x04/04 4s int Key name (byte reversed e.g. #KEY is YEK#)
0x04/04 0x01/01 B byte Length of returned data
0x05/05 0x04/04 4s int Data type (byte reversed e.g. ui32 is 23iu)
0x09/09 0x01/01 B byte Flag R/W
0x0A/10 0x06/06 6x byte Padding
0x10/16 0x08/08 Q ptr Internal VMware routine
0x18/24 0x30/48 48B byte Data
"""
import codecs
import os
import re
import struct
import sys
if sys.version_info < (3, 6):
sys.stderr.write('You need Python 3.6 or later\n')
sys.exit(1)
# Setup imports depending on whether IronPython or CPython
if sys.platform in ('cli', 'win32'):
# noinspection PyUnresolvedReferences
from winreg import *
def bytetohex(data):
return ''.join('{:02X} '.format(c) for c in data)
def joinpath(folder, filename):
return os.path.join(folder, filename)
def printkey(i, offset, smc_key, smc_data):
print(str(i + 1).zfill(3)
+ ' ' + hex(offset)
+ ' ' + smc_key[0][::-1].decode('UTF-8')
+ ' ' + str(smc_key[1]).zfill(2)
+ ' ' + smc_key[2][::-1].replace(b'\x00', b' ').decode('UTF-8')
+ ' ' + '{0:#0{1}x}'.format(smc_key[3], 4)
+ ' ' + hex(smc_key[4])
+ ' ' + bytetohex(smc_data))
def set_bit(value, bit):
return value | (1 << bit)
def clear_bit(value, bit):
return value & ~(1 << bit)
def test_bit(value, bit):
return value & bit
E_CLASS64 = 2
E_SHT_RELA = 4
# noinspection PyUnusedLocal
def patchelf(f, oldoffset, newoffset):
f.seek(0)
magic = f.read(4)
if not magic == b'\x7fELF':
raise Exception('Magic number does not match')
ei_class = struct.unpack('=B', f.read(1))[0]
if ei_class != E_CLASS64:
raise Exception('Not 64bit elf header: ' + ei_class)
f.seek(40)
e_shoff = struct.unpack('=Q', f.read(8))[0]
f.seek(58)
e_shentsize = struct.unpack('=H', f.read(2))[0]
e_shnum = struct.unpack('=H', f.read(2))[0]
e_shstrndx = struct.unpack('=H', f.read(2))[0]
print('e_shoff: 0x{:x} e_shentsize: 0x{:x} e_shnum:0x{:x} e_shstrndx:0x{:x}'.format(e_shoff, e_shentsize,
e_shnum, e_shstrndx))
for i in range(0, e_shnum):
f.seek(e_shoff + i * e_shentsize)
e_sh = struct.unpack('=LLQQQQLLQQ', f.read(e_shentsize))
e_sh_name = e_sh[0]
e_sh_type = e_sh[1]
e_sh_offset = e_sh[4]
e_sh_size = e_sh[5]
e_sh_entsize = e_sh[9]
if e_sh_type == E_SHT_RELA:
e_sh_nument = int(e_sh_size / e_sh_entsize)
print('RELA at 0x{:x} with {:d} entries'.format(e_sh_offset, e_sh_nument))
for j in range(0, e_sh_nument):
f.seek(e_sh_offset + e_sh_entsize * j)
rela = struct.unpack('=QQq', f.read(e_sh_entsize))
r_offset = rela[0]
r_info = rela[1]
r_addend = rela[2]
if r_addend == oldoffset:
r_addend = newoffset
f.seek(e_sh_offset + e_sh_entsize * j)
f.write(struct.pack('=QQq', r_offset, r_info, r_addend))
print('Relocation modified at: ' + hex(e_sh_offset + e_sh_entsize * j))
def patchkeys(f, key):
# Setup struct pack string
key_pack = '=4sB4sB6xQ'
smc_old_memptr = 0
smc_new_memptr = 0
# Do Until OSK1 read
i = 0
while True:
# Read key into struct str and data byte str
offset = key + (i * 72)
f.seek(offset)
smc_key = struct.unpack(key_pack, f.read(24))
smc_data = f.read(smc_key[1])
# Reset pointer to beginning of key entry
f.seek(offset)
if smc_key[0] == b'SKL+':
# Use the +LKS data routine for OSK0/1
smc_new_memptr = smc_key[4]
print('+LKS Key: ')
printkey(i, offset, smc_key, smc_data)
elif smc_key[0] == b'0KSO':
# Write new data routine pointer from +LKS
print('OSK0 Key Before:')
printkey(i, offset, smc_key, smc_data)
smc_old_memptr = smc_key[4]
f.seek(offset)
f.write(struct.pack(key_pack, smc_key[0], smc_key[1], smc_key[2], smc_key[3], smc_new_memptr))
f.flush()
# Write new data for key
f.seek(offset + 24)
smc_new_data = codecs.encode('bheuneqjbexolgurfrjbeqfthneqrqcy', 'rot_13')
f.write(smc_new_data.encode('UTF-8'))
f.flush()
# Re-read and print key
f.seek(offset)
smc_key = struct.unpack(key_pack, f.read(24))
smc_data = f.read(smc_key[1])
print('OSK0 Key After:')
printkey(i, offset, smc_key, smc_data)
elif smc_key[0] == b'1KSO':
# Write new data routine pointer from +LKS
print('OSK1 Key Before:')
printkey(i, offset, smc_key, smc_data)
smc_old_memptr = smc_key[4]
f.seek(offset)
f.write(struct.pack(key_pack, smc_key[0], smc_key[1], smc_key[2], smc_key[3], smc_new_memptr))
f.flush()
# Write new data for key
f.seek(offset + 24)
smc_new_data = codecs.encode('rnfrqbagfgrny(p)NccyrPbzchgreVap', 'rot_13')
f.write(smc_new_data.encode('UTF-8'))
f.flush()
# Re-read and print key
f.seek(offset)
smc_key = struct.unpack(key_pack, f.read(24))
smc_data = f.read(smc_key[1])
print('OSK1 Key After:')
printkey(i, offset, smc_key, smc_data)
# Finished so get out of loop
break
# elif smc_key[0] == b'WPPK':
# # Set the old SMC password as an addtional marker that vmx has been patched
# # This has no functional value except for checking status
# print('KPPW Key Before:')
# printkey(i, offset, smc_key, smc_data)
# f.seek(offset + 24)
# f.write('SpecialisRevelio'.encode('UTF-8'))
#
# # Re-read and print key
# f.seek(offset)
# smc_key = struct.unpack(key_pack, f.read(24))
# smc_data = f.read(smc_key[1])
# print('KPPW Key After:')
# printkey(i, offset, smc_key, smc_data)
else:
pass
i += 1
return smc_old_memptr, smc_new_memptr
def patchsmc(name, sharedobj):
with open(name, 'r+b') as f:
smc_old_memptr = 0
smc_new_memptr = 0
# Read file into string variable
vmx = f.read()
print('File: ' + name + '\n')
# Setup hex string for vSMC headers
# These are the private and public key counts
smc_header_v0 = b'\xF2\x00\x00\x00\xF0\x00\x00\x00'
smc_header_v1 = b'\xB4\x01\x00\x00\xB0\x01\x00\x00'
# Setup hex string for #KEY key
key_key = b'\x59\x45\x4B\x23\x04\x32\x33\x69\x75'
# Setup hex string for $Adr key
adr_key = b'\x72\x64\x41\x24\x04\x32\x33\x69\x75'
# Find the vSMC headers
smc_header_v0_offset = vmx.find(smc_header_v0) - 8
smc_header_v1_offset = vmx.find(smc_header_v1) - 8
# Find '#KEY' keys
smc_key0 = vmx.find(key_key)
smc_key1 = vmx.rfind(key_key)
# Find '$Adr' key table
smc_adr = vmx.find(adr_key)
# Print vSMC0 tables and keys
print('appleSMCTableV0 (smc.version = "0")')
print('appleSMCTableV0 Address : ' + hex(smc_header_v0_offset))
print('appleSMCTableV0 Private Key #: 0xF2/242')
print('appleSMCTableV0 Public Key #: 0xF0/240')
if (smc_adr - smc_key0) != 72:
print('appleSMCTableV0 Table : ' + hex(smc_key0))
smc_old_memptr, smc_new_memptr = patchkeys(f, smc_key0)
elif (smc_adr - smc_key1) != 72:
print('appleSMCTableV0 Table : ' + hex(smc_key1))
smc_old_memptr, smc_new_memptr = patchkeys(f, smc_key1)
print()
# Print vSMC1 tables and keys
print('appleSMCTableV1 (smc.version = "1")')
print('appleSMCTableV1 Address : ' + hex(smc_header_v1_offset))
print('appleSMCTableV1 Private Key #: 0x01B4/436')
print('appleSMCTableV1 Public Key #: 0x01B0/432')
if (smc_adr - smc_key0) == 72:
print('appleSMCTableV1 Table : ' + hex(smc_key0))
smc_old_memptr, smc_new_memptr = patchkeys(f, smc_key0)
elif (smc_adr - smc_key1) == 72:
print('appleSMCTableV1 Table : ' + hex(smc_key1))
smc_old_memptr, smc_new_memptr = patchkeys(f, smc_key1)
print()
# Find matching RELA record in .rela.dyn in ESXi ELF files
# This is temporary code until proper ELF parsing written
if sharedobj:
print('Modifying RELA records from: ' + hex(smc_old_memptr) + ' to ' + hex(smc_new_memptr))
patchelf(f, smc_old_memptr, smc_new_memptr)
# Tidy up
f.flush()
f.close()
def patchbase(name):
# Patch file
print('GOS Patching: ' + name)
f = open(name, 'r+b')
# Entry to search for in GOS table
# Should work for Workstation 12-15...
darwin = re.compile(
b'\x10\x00\x00\x00[\x10|\x20]\x00\x00\x00[\x01|\x02]\x00\x00\x00\x00\x00\x00\x00'
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
# Read file into string variable
base = f.read()
# Loop through each entry and set top bit
# 0xBE --> 0xBF (WKS 12/13)
# 0x3E --> 0x3F (WKS 14+)
for m in darwin.finditer(base):
offset = m.start()
f.seek(offset + 32)
flag = ord(f.read(1))
flag = set_bit(flag, 0)
# flag = chr(flag)
f.seek(offset + 32)
f.write(bytes([flag]))
print('GOS Patched flag @: ' + hex(offset))
# Tidy up
f.flush()
f.close()
print('GOS Patched: ' + name)
def patchvmkctl(name):
# Patch file
print('smcPresent Patching: ' + name)
f = open(name, 'r+b')
# Read file into string variable
vmkctl = f.read()
applesmc = vmkctl.find(b'applesmc')
f.seek(applesmc)
f.write(b'vmkernel')
# Tidy up
f.flush()
f.close()
print('smcPresent Patched: ' + name)
# noinspection PyUnresolvedReferences
def main():
# Work around absent Platform module on VMkernel
if os.name == 'nt' or os.name == 'cli':
osname = 'windows'
else:
osname = os.uname()[0].lower()
# vmwarebase = ''
vmx_so = False
# Setup default paths
if osname == 'linux':
# TODO: Dynamically find Linux installation
# Path the install is stored in /etc/vmware/config
# libdir = "/usr/lib/vmware"
vmx_path = '/usr/lib/vmware/bin/'
vmx = joinpath(vmx_path, 'vmware-vmx')
vmx_debug = joinpath(vmx_path, 'vmware-vmx-debug')
vmx_stats = joinpath(vmx_path, 'vmware-vmx-stats')
vmx_so = True
if os.path.isfile('/usr/lib/vmware/lib/libvmwarebase.so/libvmwarebase.so'):
vmwarebase = '/usr/lib/vmware/lib/libvmwarebase.so/libvmwarebase.so'
else:
vmwarebase = '/usr/lib/vmware/lib/libvmwarebase.so.0/libvmwarebase.so.0'
elif osname == 'windows':
reg = ConnectRegistry(None, HKEY_LOCAL_MACHINE)
key = OpenKey(reg, r'SOFTWARE\Wow6432Node\VMware, Inc.\VMware Workstation')
vmwarebase_path = QueryValueEx(key, 'InstallPath')[0]
vmx_path = QueryValueEx(key, 'InstallPath64')[0]
vmx = joinpath(vmx_path, 'vmware-vmx.exe')
vmx_debug = joinpath(vmx_path, 'vmware-vmx-debug.exe')
vmx_stats = joinpath(vmx_path, 'vmware-vmx-stats.exe')
vmwarebase = joinpath(vmwarebase_path, 'vmwarebase.dll')
else:
print('Unknown Operating System: ' + osname)
return
# Patch the vmx executables skipping stats version for Player
patchsmc(vmx, vmx_so)
patchsmc(vmx_debug, vmx_so)
if os.path.isfile(vmx_stats):
patchsmc(vmx_stats, vmx_so)
# Patch vmwarebase for Workstation and Player
patchbase(vmwarebase)
if __name__ == '__main__':
main()