1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
|
# Copyright 2023 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""
Parses .apiset section from a DLL (e.g. ApiSetSchema.dll) to determine which
functions are "exported" on a given Windows version by apisets. Only supports
version 6 (Windows 10+) of the schema.
This script is not run automatically as we do not ship a specimen ApiSetSchema
with the build.
vpython3 .\chrome\test\delayload\generate_supported_apisets.py \
--dll c:\Windows\System32\ApisetSchema.dll \
--out-file .\chrome\test\delayload\supported_apisets_10.0.10240.inc
See: https://www.geoffchappell.com/studies/windows/win32/apisetschema/index.htm
"""
import argparse
import hashlib
import os
import sys
import struct
import ctypes
USE_PYTHON_3 = f'This script will only run under python3.'
# Assume this script is under chrome\test\delayload
_SCRIPT_DIR = os.path.dirname(__file__)
_ROOT_DIR = os.path.join(_SCRIPT_DIR, os.pardir, os.pardir, os.pardir)
_PEFILE_DIR = os.path.join(_ROOT_DIR, 'third_party', 'pefile_py3')
sys.path.insert(1, _PEFILE_DIR)
import pefile
def from_utf16(data, start, length):
return data[start:start + length].decode("utf-16-le")
class StructHelper(ctypes.Structure):
def __init__(self, data):
super(StructHelper, self).__init__()
ctypes.memmove(ctypes.addressof(self), data, ctypes.sizeof(self))
class ApiSetNamespaceEntryV6(StructHelper):
_fields_ = [
('Flags', ctypes.c_uint32),
('NameOffset', ctypes.c_uint32),
('NameLength', ctypes.c_uint32),
('HashedLength', ctypes.c_uint32),
('ValueOffset', ctypes.c_uint32),
('ValueCount', ctypes.c_uint32)
]
class ApiSetNamespaceV6(StructHelper):
_fields_ = [
('Version', ctypes.c_uint32),
('Size', ctypes.c_uint32),
('Flags', ctypes.c_uint32),
('Count', ctypes.c_uint32),
('EntryOffset', ctypes.c_uint32),
('HashOffset', ctypes.c_uint32),
('HashFactor', ctypes.c_uint32)
]
class ApiSetSchemaV6:
def __init__(self, data):
self._data = data
self._apisets = []
header = ApiSetNamespaceV6(self._data)
self._flag = header.Flags
self._version = header.Version
self._load_apisets(header.EntryOffset, header.Count)
@property
def apisets(self):
return self._apisets
def _load_apisets(self, offset, count):
for _ in range(count):
entry = ApiSetNamespaceEntryV6(
self._data[offset:offset + ctypes.sizeof(ApiSetNamespaceEntryV6)])
entry_name = from_utf16(self._data, entry.NameOffset, entry.NameLength)
self._apisets.append(entry_name)
offset += ctypes.sizeof(ApiSetNamespaceEntryV6)
def parse_apiset_names(data):
version = struct.unpack("B", data[0:1])[0]
if version != 6:
raise Exception(f'Unsupported schema version: {version}')
apiset_schema = ApiSetSchemaV6(data)
return apiset_schema.apisets
def get_file_version(pe):
for fileinfo in pe.FileInfo[0]:
if fileinfo.Key.decode() == 'StringFileInfo':
for st in fileinfo.StringTable:
for entry in st.entries.items():
if entry[0] == b'FileVersion':
return entry[1].decode('utf8')
raise Exception("FileVersion not found in dll")
def read_apiset_section(filename):
pe = pefile.PE(filename)
product_version = get_file_version(pe)
for section in pe.sections:
if section.Name == b'.apiset\0':
return (product_version, section.get_data())
raise Exception(".apiset section not Found")
# apiset_name: api-ms-win-core-synch-l1-2-0.dll
# -> (api-ms-win-core-synch-l1-2, 0)
def apiset_dll_to_version(apiset_name):
last_dash = apiset_name.rindex('-')
apiset_maj_min = apiset_name[0:last_dash]
apiset_subversion = apiset_name[last_dash+1:]
return (apiset_maj_min, apiset_subversion)
def main():
parser = argparse.ArgumentParser(
description=__doc__, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('--dll',
metavar='FILE_NAME',
help='Dll with a .apiset section')
parser.add_argument('--out-file',
default='apisets.inc',
metavar='FILE_NAME',
help='path to write .inc file to')
args, _ = parser.parse_known_args()
shasum = hashlib.sha256(open(args.dll, 'rb').read()).hexdigest()
dll_basename = os.path.basename(args.dll)
(dll_version, data) = read_apiset_section(args.dll)
apiset_names = parse_apiset_names(data)
# Only keep api- entries (skip ext- as these are for kernel modules).
apiset_names = filter(lambda s: s.startswith("api-"), apiset_names)
apiset_entries = [apiset_dll_to_version(s) for s in apiset_names]
with open(args.out_file, 'w', encoding='utf8') as f:
f.write(f'// Generated from {dll_basename}\n')
f.write(f'// FileVersion: {dll_version}\n')
f.write(f'// sha256: {shasum}\n')
f.write(',\n'.join([f'{{"{e[0]}", {e[1]}}}' for e in apiset_entries]))
f.write('\n')
if __name__ == '__main__':
sys.exit(main())
|