Full Code of shanyungyang/esxi-unlocker for AI

master 9f3483ef6780 cached
11 files
25.9 KB
7.5k tokens
12 symbols
1 requests
Download .txt
Repository: shanyungyang/esxi-unlocker
Branch: master
Commit: 9f3483ef6780
Files: 11
Total size: 25.9 KB

Directory structure:
gitextract_zmf1ry0m/

├── .gitattributes
├── .github/
│   └── workflows/
│       ├── main.yml
│       └── manual.yml
├── .gitignore
├── LICENSE
├── esxi-build.py
├── esxi-install.sh
├── esxi-smctest.sh
├── esxi-uninstall.sh
├── etc/
│   └── rc.local.d/
│       └── unlocker.py
└── readme.md

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitattributes
================================================
# Recommedations from GitHub help pages
# Set the default behavior, in case people don't have core.autocrlf set.
* text=auto

# Declare files that will always have CRLF line endings on checkout.
*.cmd text eol=crlf

# Declare files that will always have LF line endings on checkout.
*.sh text eol=lf
*.py text eol=lf

# Declare files that are binary on checkout.
*.exe binary

================================================
FILE: .github/workflows/main.yml
================================================
# This is a basic workflow to help you get started with Actions

name: CI

# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch
on:
  push:
#     branches: [ master ]
    tags:
      - '[0-9].[0-9]+.[0-9]+'
  pull_request:
    branches: [ master ]
    tags:
    - '[0-9].[0-9]+.[0-9]+'

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2
      - name: Set env
        run: echo "RELEASE_VERSION=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV

      # Runs a few command using the runners shell
      - name: Run a multi-line script
        run: | 
          # pwd debug
          tar -cvzf unlocker.tgz etc
          tar -cvzf esxi-unlocker-${{ env.RELEASE_VERSION }}.tgz *.*
          # ls -al more debug

      # release automation.gz
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
        with:
          tag_name: ${{ github.ref }}.${{ github.run_number }}
          release_name: Release ${{ env.RELEASE_VERSION }}
          draft: true
          prerelease: true
      - name: Upload Release Asset
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./readme.md
          asset_name: readme.md
          asset_content_type: text/markdown 
      - name: Upload Release Asset2
        id: upload-release-asset2
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./esxi-unlocker-${{ env.RELEASE_VERSION }}.tgz
          asset_name: esxi-unlocker-${{ env.RELEASE_VERSION }}.tgz
          asset_content_type: application/zip 


================================================
FILE: .github/workflows/manual.yml
================================================
# This is a basic workflow to help you get started with Actions

name: Manual CI

# Controls when the action will run. Triggers the workflow on push or pull request
# events but only for the master branch

on:
  workflow_dispatch:
    inputs:
      semver:
        description: 'Version'
        required: true

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  # This workflow contains a single job called "build"
  build:
    # The type of runner that the job will run on
    runs-on: ubuntu-latest

    # Steps represent a sequence of tasks that will be executed as part of the job
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2

      # Runs a few command using the runners shell
      - name: Run a multi-line script
        run: | 
          # pwd debug
          tar -cvzf unlocker.tgz etc
          tar -cvzf esxi-unlocker-${{ github.event.inputs.semver }}.tgz *.*
          # ls -al more debug

      # release automation.gz
      - name: Create Release
        id: create_release
        uses: actions/create-release@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, you do not need to create your own token
        with:
          tag_name: ${{ github.event.inputs.semver }}
          release_name: Release ${{ github.event.inputs.semver }}
#           body: |
#             Changes in this Release
#             - First Change
#             - Second Change
          draft: true
          prerelease: true
      - name: Upload Release Asset
        id: upload-release-asset
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./readme.md
          asset_name: readme.md
          asset_content_type: text/plain 
      - name: Upload Release Asset2
        id: upload-release-asset2
        uses: actions/upload-release-asset@v1
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          upload_url: ${{ steps.create_release.outputs.upload_url }}
          asset_path: ./esxi-unlocker-${{ github.event.inputs.semver }}.tgz
          asset_name: esxi-unlocker-${{ github.event.inputs.semver }}.tgz
          asset_content_type: application/zip 


================================================
FILE: .gitignore
================================================
backup/
tools/
.idea/
samples/
tests/
build/
dist/
*.spec
*.pyc
*.tar
*.tgz
*.vmtar
*.vgz


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2018 David Parsons

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: esxi-build.py
================================================
#!/usr/bin/env python
"""
The MIT License (MIT)

Copyright (c) 2014-2018 Dave Parsons & Sam Bingner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the 'Software'), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
"""

import datetime
import subprocess
import sys


if sys.version_info < (2, 7):
    sys.stderr.write('You need Python 2.7 or later\n')
    sys.exit(1)

# TODO: Change for a new release
VERSION = '3.0.2'
FILEVER = '302'
FILENAME = 'esxi-unlocker-302.tgz'

TIMESTAMP = '{:%Y%m%d%H%M.%S}'.format(datetime.datetime.now())
TOUCH = 'touch -t ' + TIMESTAMP
GTARUNLOCKER = '/usr/local/bin/gtar czvf unlocker.tgz etc'
GTARDISTRIB = '/usr/local/bin/gtar czvf ' + FILENAME + \
              ' unlocker.tgz esxi-install.sh esxi-uninstall.sh esxi-smctest.sh readme.txt'


def main():

    # Timestamp files for release
    print('\nTimestamping files...')
    subprocess.call(TOUCH + ' readme.txt', shell=True)
    subprocess.call(TOUCH + ' esxi-install.sh', shell=True)
    subprocess.call(TOUCH + ' esxi-uninstall.sh', shell=True)
    subprocess.call(TOUCH + ' esxi-smctest.sh', shell=True)
    subprocess.call(TOUCH + ' etc', shell=True)
    subprocess.call(TOUCH + ' etc/rc.local.d', shell=True)
    subprocess.call(TOUCH + ' etc/rc.local.d/unlocker.py', shell=True)

    # Build the gzipped tar file unlocker.tgz
    print('\nCreating unlocker.tgz...')
    subprocess.call(GTARUNLOCKER, shell=True)
    subprocess.call(TOUCH + ' unlocker.tgz', shell=True)

    # Build the distribution file esxi-unlocker-VER.tgz
    print('\nCreating ' + FILENAME + '...')
    subprocess.call(GTARDISTRIB, shell=True)
    subprocess.call(TOUCH + ' ' + FILENAME, shell=True)


if __name__ == '__main__':
    if sys.platform == 'darwin':
        print('ESXi-Build for macOS')
        main()
    else:
        print('ESXi-Build only supported on macOS')


================================================
FILE: esxi-install.sh
================================================
#!/bin/sh
set -e
#set -x

echo VMware Unlocker 3.0.2
echo ===============================
echo Copyright: Dave Parsons 2011-18

# Ensure we only use unmodified commands
export PATH=/bin:/sbin:/usr/bin:/usr/sbin

echo Installing unlocker.tgz
BootModuleConfig.sh --verbose --add=unlocker.tgz
echo Success - please now restart the server!


================================================
FILE: esxi-smctest.sh
================================================
#!/bin/sh
grep -il \(c\)AppleComputerInc /bin/vmx*
vim-cmd hostsvc/hosthardware | grep smcPresent | cut -d ',' -f 1 | sed 's/^[ \t]*//'
esxcli system visorfs tardisk list | grep custom.vgz


================================================
FILE: esxi-uninstall.sh
================================================
#!/bin/sh
set -e
#set -x

echo VMware Unlocker 3.0.2
echo ===============================
echo Copyright: Dave Parsons 2011-18

# Ensure we only use unmodified commands
export PATH=/bin:/sbin:/usr/bin:/usr/sbin

echo Uninstalling unlocker.tgz
BootModuleConfig.sh --verbose --remove=unlocker.tgz
echo Success - please now restart the server!


================================================
FILE: etc/rc.local.d/unlocker.py
================================================
#!/usr/bin/env python
"""
The MIT License (MIT)

Copyright (c) 2014-2018 Dave Parsons & Sam Bingner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the 'Software'), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

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 shutil
import struct
import subprocess
import sys

if sys.version_info < (2, 7):
    sys.stderr.write('You need Python 2.7 or later\n')
    sys.exit(1)


def bytetohex(data):
    if sys.version_info > (3, 0):
        # Python 3 code in this block
        return "".join("{:02X} ".format(c) for c in data)
    else:
        # Python 2 code in this block
        return "".join("{:02X} ".format(ord(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


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))
        # The name is not used.
        # 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

        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 only V1 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 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)


def main():

    # Stop the hostd service
    subprocess.call('/etc/init.d/hostd stop', shell=True)

    # Current folder
    currdir = os.getcwd()

    # Source files
    srcvmx = '/bin/vmx'
    srclib32 = '/lib/libvmkctl.so'
    srclib64 = '/lib64/libvmkctl.so'

    # Destination files currently tmp but may use scratch
    basefolder = '/tmp/'
    destfolder = joinpath(basefolder, 'unlocker')
    destvmx = joinpath(destfolder, 'bin/vmx')
    destlib32 = joinpath(destfolder, 'lib/libvmkctl.so')
    destlib64 = joinpath(destfolder, 'lib64/libvmkctl.so')

    # Remove files & folder if they exist
    if os.path.isdir(destfolder):
        shutil.rmtree(destfolder, True)

    # Create the base folder
    os.makedirs(destfolder)
    os.chdir(destfolder)

    # Patch the vmx executable
    os.makedirs(joinpath(destfolder, 'bin'))
    shutil.copy2(srcvmx, destvmx)
    patchsmc(destvmx, True)

    # Patch 32-bit libvmkctl to return Apple SMC present
    os.makedirs(joinpath(destfolder, 'lib'))
    if os.path.isfile(srclib32):
        shutil.copy2(srclib32, destlib32)
        patchvmkctl(destlib32)

    # Patch 64-bit libvmkctl to return Apple SMC present
    if os.path.isfile(srclib64):
        os.makedirs(joinpath(destfolder, 'lib64'))
        shutil.copy2(srclib64, destlib64)
        patchvmkctl(destlib64)

    # Build the gzipped tar file custom.tgz
    print('\nCreating custom.tgz...')
    subprocess.call('/bin/tar czvf custom.tgz bin lib lib64', shell=True)

    # Build the vmtar file custom.vmtar
    print('\nCreating custom.vmtar...')
    subprocess.call('/bin/vmtar -v -c custom.tgz -o custom.vmtar', shell=True)

    # Build the gzipped vmtar file custom.vgz
    print('\nCreating custom.vgz...')
    subprocess.call('/bin/gzip < custom.vmtar > custom.vgz', shell=True)
    subprocess.call('/bin/vmtar -v -t < custom.vgz', shell=True)

    # Load the tardisk
    subprocess.call('vmkramdisk custom.vgz', shell=True)

    # Return to script folder
    os.chdir(currdir)

    # Start the hostd service
    subprocess.call('/etc/init.d/hostd start', shell=True)


if __name__ == '__main__':
    # Check boot options specified
    bootoptions = subprocess.check_output(['/bin/bootOption', '-a'])
    if bootoptions.find(b'nounlocker') == -1:
        # Run unlocker code
        main()
    else:
        # Exit if "nounlocker" boot option specified
        print('Unlocker disabled by boot option')


================================================
FILE: readme.md
================================================
# macOS Unlocker V3.0.2 for VMware ESXi


## 1. Introduction


Unlocker 3 for ESXi is designed for VMware ESXi 6.5, 6.7 and 7.0

The patch code carries out the following modifications dependent on the product
being patched:

* Fix vmware-vmx to allow macOS to boot
* Fix libvmkctl to allow vSphere to control the guest

The code is written in Python as it makes the Unlocker easier to run and
maintain on ESXi.

>
> *IMPORTANT:*                                                                                                                           |
>                                                                           
> Always uninstall the previous version of the Unlocker before using a new  
> version. Failure to do this could render VMware unusable.                 

## 2. Installation

Copy the latest release file to the ESXi host datastore using scp or some other
data transfer system. If you want to use the source version (i.e. from GIT) see
"5. Building" fist.

Decompress the file from the ESXi console or via SSH:

    tar xzvf esxi-unlocker-xxx.tgz

(xxx - will be the version number, for example, 3.0.0)

Run the command from the terminal:

    ./esxi-install.sh

Finally reboot the server.

## 3. Uninstallation

Open the ESXi console or login via SSH and change to the folder where the files were extracted.

Run the command from the terminal:

    ./esxi-uninstall.sh

Finally reboot the server.

## 4. Notes

A. There is a command added called esxi-smctest.sh which can show if the patch is successful. It must be run from a
terminal or SSH session. The output should be:

/bin/vmx
smcPresent = true
custom.vgz     false   32486592 B

Note: The uncompressed size reported for custom.vgz will vary depending on the ESXi version.

B. The unlocker can be temporarily disabled during boot by editing the boot options and adding "nounlocker".

## 5. Building

If you want to use a version which is not available as a distribution (e.g. the code from "master" branch)
you need to first build the package.  You can build locally on a Mac or via a github workflow.

### Local Build

Checkout the repository:

    git clone https://github.com/shanyungyang/esxi-unlocker.git

(if you don't have git installed you can download ZIP archive from GitHub instead)

Enter the directory and build:
    
    cd esxi-unlocker
    ./esxi-build.py

If everything went correctly the ouput should be:

    ESXi-Build for macOS

    Timestamping files...

    Creating unlocker.tgz...
    etc/
    etc/rc.local.d/
    etc/rc.local.d/unlocker.py

    Creating esxi-unlocker-301.tgz...
    unlocker.tgz
    esxi-install.sh
    esxi-uninstall.sh
    esxi-smctest.sh
    readme.txt

The package you need to copy in the example above is esxi-unlocker-301.tgz (NOT unlocker.tgz!).

###  Github Build

#### Triggered

If you add a tag to any commit in the semver format `*.*.*` a triggered build will run and create a release.

#### Manual

Fork the repository, click on actions, select manual CI and then run.  

This will build a new draft release for you which you can upload to your esxi using curl or wget.

If you add a tag in the format `*.*.*` a triggered build will run without needing 

## 6. Thanks

Thanks to Zenith432 for originally building the C++ unlocker and Mac Son of Knife
(MSoK) for all the testing and support.

Thanks also to Sam B for finding the solution for ESXi 6 and helping me with
debugging expertise. Sam also wrote the code for patching ESXi ELF files and
modified the unlocker code to run on Python 3 in the ESXi 6.5 environment.

# History

26/09/18 3.0.0 - First release

01/05/20 3.0.1 - Fix for ESXi 7.0

10/18/20 3.0.2 - Fix for ESXi 7.0 U1 (7.0.1)

10/29/20 3.0.3 - Release process automated

(c) 2011-2018 Dave Parsons
Download .txt
gitextract_zmf1ry0m/

├── .gitattributes
├── .github/
│   └── workflows/
│       ├── main.yml
│       └── manual.yml
├── .gitignore
├── LICENSE
├── esxi-build.py
├── esxi-install.sh
├── esxi-smctest.sh
├── esxi-uninstall.sh
├── etc/
│   └── rc.local.d/
│       └── unlocker.py
└── readme.md
Download .txt
SYMBOL INDEX (12 symbols across 2 files)

FILE: esxi-build.py
  function main (line 47) | def main():

FILE: etc/rc.local.d/unlocker.py
  function bytetohex (line 56) | def bytetohex(data):
  function joinpath (line 65) | def joinpath(folder, filename):
  function printkey (line 69) | def printkey(i, offset, smc_key, smc_data):
  function set_bit (line 80) | def set_bit(value, bit):
  function clear_bit (line 84) | def clear_bit(value, bit):
  function test_bit (line 88) | def test_bit(value, bit):
  function patchelf (line 96) | def patchelf(f, oldoffset, newoffset):
  function patchkeys (line 141) | def patchkeys(f, key):
  function patchsmc (line 220) | def patchsmc(name, sharedobj):
  function patchvmkctl (line 294) | def patchvmkctl(name):
  function main (line 311) | def main():
Condensed preview — 11 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (28K chars).
[
  {
    "path": ".gitattributes",
    "chars": 375,
    "preview": "# Recommedations from GitHub help pages\n# Set the default behavior, in case people don't have core.autocrlf set.\n* text="
  },
  {
    "path": ".github/workflows/main.yml",
    "chars": 2448,
    "preview": "# This is a basic workflow to help you get started with Actions\n\nname: CI\n\n# Controls when the action will run. Triggers"
  },
  {
    "path": ".github/workflows/manual.yml",
    "chars": 2435,
    "preview": "# This is a basic workflow to help you get started with Actions\n\nname: Manual CI\n\n# Controls when the action will run. T"
  },
  {
    "path": ".gitignore",
    "chars": 90,
    "preview": "backup/\ntools/\n.idea/\nsamples/\ntests/\nbuild/\ndist/\n*.spec\n*.pyc\n*.tar\n*.tgz\n*.vmtar\n*.vgz\n"
  },
  {
    "path": "LICENSE",
    "chars": 1070,
    "preview": "MIT License\n\nCopyright (c) 2018 David Parsons\n\nPermission is hereby granted, free of charge, to any person obtaining a c"
  },
  {
    "path": "esxi-build.py",
    "chars": 2753,
    "preview": "#!/usr/bin/env python\n\"\"\"\nThe MIT License (MIT)\n\nCopyright (c) 2014-2018 Dave Parsons & Sam Bingner\n\nPermission is hereb"
  },
  {
    "path": "esxi-install.sh",
    "chars": 336,
    "preview": "#!/bin/sh\nset -e\n#set -x\n\necho VMware Unlocker 3.0.2\necho ===============================\necho Copyright: Dave Parsons 2"
  },
  {
    "path": "esxi-smctest.sh",
    "chars": 189,
    "preview": "#!/bin/sh\ngrep -il \\(c\\)AppleComputerInc /bin/vmx*\nvim-cmd hostsvc/hosthardware | grep smcPresent | cut -d ',' -f 1 | se"
  },
  {
    "path": "esxi-uninstall.sh",
    "chars": 341,
    "preview": "#!/bin/sh\nset -e\n#set -x\n\necho VMware Unlocker 3.0.2\necho ===============================\necho Copyright: Dave Parsons 2"
  },
  {
    "path": "etc/rc.local.d/unlocker.py",
    "chars": 12757,
    "preview": "#!/usr/bin/env python\n\"\"\"\nThe MIT License (MIT)\n\nCopyright (c) 2014-2018 Dave Parsons & Sam Bingner\n\nPermission is hereb"
  },
  {
    "path": "readme.md",
    "chars": 3760,
    "preview": "# macOS Unlocker V3.0.2 for VMware ESXi\n\n\n## 1. Introduction\n\n\nUnlocker 3 for ESXi is designed for VMware ESXi 6.5, 6.7 "
  }
]

About this extraction

This page contains the full source code of the shanyungyang/esxi-unlocker GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 11 files (25.9 KB), approximately 7.5k tokens, and a symbol index with 12 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!