Ctrl K
ComunidadGuías…

verify_win7_bundle.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/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())