verify_win7_bundle.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""Vérifie les garde-fous statiques du bundle legacy Windows 7."""
from __future__ import annotations
import argparse
import re
from pathlib import Path
import pefile
DLL_INTERDITE = "api-ms-win-core-path-l1-1-0.dll"
# Liste volontairement étroite : ce contrôle ne certifie pas toutes les API
# Windows. Les tables d'import PE (y compris delay-load) sont inspectées, pas
# les chaînes du binaire : une recherche optionnelle par GetProcAddress avec
# repli Win7 ne doit pas être confondue avec une dépendance obligatoire.
# ProcessPrng : https://github.com/pyca/cryptography/issues/10944
# Les autres API nécessitent Windows 8, sauf les descriptions de threads
# (Windows 10 1607). Références Microsoft, rubriques "Requirements" :
# https://learn.microsoft.com/windows/win32/api/sysinfoapi/nf-sysinfoapi-getsystemtimepreciseasfiletime
# https://learn.microsoft.com/windows/win32/api/ioapiset/nf-ioapiset-getoverlappedresultex
# https://learn.microsoft.com/windows/win32/api/fileapi/nf-fileapi-createfile2
# https://learn.microsoft.com/windows/win32/api/synchapi/nf-synchapi-waitonaddress
# https://learn.microsoft.com/windows/win32/api/synchapi/nf-synchapi-wakebyaddresssingle
# https://learn.microsoft.com/windows/win32/api/synchapi/nf-synchapi-wakebyaddressall
# https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-setthreaddescription
# https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-getthreaddescription
# https://learn.microsoft.com/windows/win32/api/processthreadsapi/nf-processthreadsapi-getcurrentthreadstacklimits
API_POST_WIN7 = frozenset({
"ProcessPrng",
"GetSystemTimePreciseAsFileTime",
"GetOverlappedResultEx",
"CreateFile2",
"WaitOnAddress",
"WakeByAddressSingle",
"WakeByAddressAll",
"SetThreadDescription",
"GetThreadDescription",
"GetCurrentThreadStackLimits",
})
AMD64 = 0x8664
RUNTIME_PYTHON = re.compile(r"python3\d+\.dll$", re.I)
def _lire_pe(chemin: Path) -> tuple:
"""Conserve l'interface historique (architecture, noms des DLL)."""
machine, dlls, _ = _lire_pe_details(chemin)
return machine, dlls
def _lire_pe_details(chemin: Path) -> tuple:
try:
pe = pefile.PE(str(chemin), fast_load=True)
except pefile.PEFormatError:
return None, set(), set()
try:
pe.parse_data_directories(
directories=[
pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_IMPORT"],
pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT"],
]
)
resultat = set()
symboles = set()
for attribut in ("DIRECTORY_ENTRY_IMPORT", "DIRECTORY_ENTRY_DELAY_IMPORT"):
for entree in getattr(pe, attribut, ()):
dll = entree.dll.decode("ascii", "replace").lower()
resultat.add(dll)
for symbole in entree.imports:
# Les imports par ordinal n'ont pas de nom. Ils ne sont
# pas résolus ici, faute de DLL de référence Windows 7.
if symbole.name is not None:
symboles.add((
dll, symbole.name.decode("ascii", "replace")
))
return int(pe.FILE_HEADER.Machine), resultat, symboles
finally:
pe.close()
def _api_post_win7(dll: str, symbole: str) -> bool:
# Ne pas accuser une fonction homonyme fournie par une DLL de l'application.
systeme = dll in {"kernel32.dll", "kernelbase.dll", "bcryptprimitives.dll"}
systeme = systeme or dll.startswith(("api-ms-win-", "ext-ms-win-"))
return systeme and symbole in API_POST_WIN7
def verifier(bundle: Path) -> list:
erreurs = []
executable = bundle / "blink2video.exe"
if not executable.is_file():
erreurs.append(f"exécutable absent : {executable}")
python38 = list(bundle.rglob("python38.dll"))
if len(python38) != 1:
erreurs.append(
f"python38.dll attendu une fois, trouvé {len(python38)} fois"
)
autres_python = [f for f in bundle.rglob("*.dll")
if RUNTIME_PYTHON.match(f.name)
and f.name.lower() != "python38.dll"]
if autres_python:
erreurs.append(
"runtime Python inattendu : "
+ ", ".join(str(f.relative_to(bundle)) for f in autres_python)
)
binaires = [f for f in bundle.rglob("*")
if f.is_file() and f.suffix.lower() in (".exe", ".dll", ".pyd")]
fautifs = []
mauvaises_architectures = []
for binaire in binaires:
machine, imports, symboles = _lire_pe_details(binaire)
if machine is None:
continue
if machine != AMD64:
mauvaises_architectures.append(str(binaire.relative_to(bundle)))
if DLL_INTERDITE in imports:
fautifs.append(str(binaire.relative_to(bundle)))
for dll, symbole in sorted(symboles):
if _api_post_win7(dll, symbole):
erreurs.append(
f"API post-Windows 7 importée : "
f"{binaire.relative_to(bundle)} : {dll}!{symbole}"
)
if mauvaises_architectures:
erreurs.append(
"binaire PE non x86-64 : " + ", ".join(mauvaises_architectures)
)
if fautifs:
erreurs.append(
f"import interdit {DLL_INTERDITE} : " + ", ".join(fautifs)
)
if not (bundle / "_internal" / "windows7-build.txt").is_file():
erreurs.append("marqueur windows7-build.txt absent du bundle")
racines = bundle / "_internal" / "certifi" / "cacert.pem"
if not racines.is_file() or racines.stat().st_size < 100_000:
erreurs.append("magasin de certificats certifi absent ou incomplet")
if not erreurs:
print(
f"OK : Python 3.8, marqueur Win7, racines TLS et {len(binaires)} "
f"binaires PE sans {DLL_INTERDITE} ni API post-Win7 de la liste."
)
return erreurs
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("bundle", type=Path)
args = parser.parse_args()
erreurs = verifier(args.bundle.resolve())
for erreur in erreurs:
print(f"ÉCHEC : {erreur}")
return 1 if erreurs else 0
if __name__ == "__main__":
raise SystemExit(main())