Ctrl K
ComunidadGuías…

test_verify_win7_bundle.py

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

Archivo original ↗Preguntar a KiBot
"""Contrôles PE Win7 sans construire ni charger de DLL native."""

from __future__ import annotations

import tempfile
import unittest
from contextlib import redirect_stdout
from io import StringIO
from pathlib import Path
from types import SimpleNamespace
from unittest import mock

try:
    import verify_win7_bundle as verification
except ModuleNotFoundError as erreur:
    if erreur.name != "pefile":
        raise
    raise unittest.SkipTest("pefile absent : contrôle réservé à l'environnement de build")


def _imports(dll, *symboles):
    return SimpleNamespace(
        dll=dll,
        imports=[SimpleNamespace(name=nom) for nom in symboles],
    )


def _pe(imports=(), differes=(), machine=verification.AMD64):
    return SimpleNamespace(
        FILE_HEADER=SimpleNamespace(Machine=machine),
        DIRECTORY_ENTRY_IMPORT=imports,
        DIRECTORY_ENTRY_DELAY_IMPORT=differes,
        parse_data_directories=mock.Mock(),
        close=mock.Mock(),
    )


class LecturePETests(unittest.TestCase):
    def test_interface_historique_et_imports_differes(self):
        pe = _pe(
            [_imports(b"KERNEL32.dll", b"GetProcAddress")],
            [_imports(b"BCRYPTPRIMITIVES.DLL", b"ProcessPrng")],
        )
        with mock.patch.object(verification.pefile, "PE", return_value=pe):
            resultat = verification._lire_pe(Path("native.pyd"))
        self.assertEqual(resultat, (
            verification.AMD64, {"kernel32.dll", "bcryptprimitives.dll"}
        ))
        pe.close.assert_called_once_with()
        pe.parse_data_directories.assert_called_once_with(directories=[
            verification.pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_IMPORT"],
            verification.pefile.DIRECTORY_ENTRY[
                "IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT"
            ],
        ])

    def test_symboles_uniques_et_ordinals_ignores(self):
        entree = _imports(b"KERNEL32.dll", b"CreateFile2", None)
        pe = _pe([entree], [entree])
        with mock.patch.object(verification.pefile, "PE", return_value=pe):
            machine, dlls, symboles = verification._lire_pe_details(Path("x"))
        self.assertEqual(machine, verification.AMD64)
        self.assertEqual(dlls, {"kernel32.dll"})
        self.assertEqual(symboles, {("kernel32.dll", "CreateFile2")})

    def test_fichier_non_pe_conserve_le_resultat_historique(self):
        with mock.patch.object(
            verification.pefile, "PE",
            side_effect=verification.pefile.PEFormatError("pas un PE"),
        ):
            self.assertEqual(verification._lire_pe(Path("x")), (None, set()))

    def test_ferme_le_pe_meme_si_la_lecture_echoue(self):
        pe = _pe()
        pe.parse_data_directories.side_effect = RuntimeError("lecture")
        with mock.patch.object(verification.pefile, "PE", return_value=pe):
            with self.assertRaisesRegex(RuntimeError, "lecture"):
                verification._lire_pe_details(Path("x"))
        pe.close.assert_called_once_with()


class VerificationBundleTests(unittest.TestCase):
    def setUp(self):
        temporaire = tempfile.TemporaryDirectory()
        self.addCleanup(temporaire.cleanup)
        self.bundle = Path(temporaire.name)
        interne = self.bundle / "_internal"
        (interne / "certifi").mkdir(parents=True)
        (self.bundle / "blink2video.exe").touch()
        (interne / "python38.dll").touch()
        (interne / "windows7-build.txt").touch()
        (interne / "certifi" / "cacert.pem").write_bytes(b"x" * 100_000)
        self.native = interne / "native.pyd"
        self.native.touch()

    def _verifier(self, pe):
        def lire(chemin, **kwargs):
            return pe if Path(chemin) == self.native else _pe()

        with mock.patch.object(verification.pefile, "PE", side_effect=lire):
            with redirect_stdout(StringIO()) as sortie:
                erreurs = verification.verifier(self.bundle)
        return erreurs, sortie.getvalue()

    def test_chaque_api_interdite_normale_ou_differee(self):
        for symbole in verification.API_POST_WIN7:
            for differe in (False, True):
                with self.subTest(symbole=symbole, differe=differe):
                    dll = (b"BCRYPTPRIMITIVES.DLL" if symbole == "ProcessPrng"
                           else b"KERNEL32.dll")
                    entree = _imports(dll, symbole.encode("ascii"))
                    pe = _pe(differes=[entree]) if differe else _pe([entree])
                    erreurs, sortie = self._verifier(pe)
                    self.assertEqual(len(erreurs), 1, erreurs)
                    self.assertIn(str(self.native.relative_to(self.bundle)),
                                  erreurs[0])
                    self.assertIn(dll.decode().lower() + "!" + symbole,
                                  erreurs[0])
                    self.assertNotIn("OK :", sortie)

    def test_api_sets_et_kernelbase_sont_controles(self):
        for dll in (b"API-MS-Win-Core-Synch-l1-2-0.dll", b"KernelBase.dll",
                    b"EXT-MS-Win-Kernel32-Synch-l1-1-0.dll"):
            with self.subTest(dll=dll):
                erreurs, _ = self._verifier(_pe([
                    _imports(dll, b"WaitOnAddress")
                ]))
                self.assertEqual(len(erreurs), 1, erreurs)
                self.assertIn(dll.decode().lower() + "!WaitOnAddress",
                              erreurs[0])

    def test_getprocaddress_et_chaines_dynamiques_ne_sont_pas_des_imports(self):
        # Ces noms peuvent exister comme chaînes pour un GetProcAddress
        # optionnel : seules les tables PE ont autorité pour ce garde-fou.
        self.native.write_bytes(b"ProcessPrng\x00WaitOnAddress\x00CreateFile2")
        erreurs, sortie = self._verifier(_pe([
            _imports(b"KERNEL32.dll", b"GetProcAddress", b"LoadLibraryW",
                     b"GetSystemTimeAsFileTime")
        ]))
        self.assertEqual(erreurs, [])
        self.assertIn("OK :", sortie)

    def test_homonyme_dans_une_dll_applicative_autorise(self):
        erreurs, _ = self._verifier(_pe([
            _imports(b"application.dll", b"WaitOnAddress", b"ProcessPrng")
        ]))
        self.assertEqual(erreurs, [])

    def test_dll_interdite_reste_interdite_meme_par_ordinal(self):
        erreurs, _ = self._verifier(_pe(differes=[
            _imports(verification.DLL_INTERDITE.upper().encode(), None)
        ]))
        self.assertEqual(len(erreurs), 1, erreurs)
        self.assertIn(verification.DLL_INTERDITE, erreurs[0])
        self.assertIn(str(self.native.relative_to(self.bundle)), erreurs[0])

    def test_architecture_reste_controlee(self):
        erreurs, _ = self._verifier(_pe(machine=0x14C))
        self.assertEqual(len(erreurs), 1, erreurs)
        self.assertIn("non x86-64", erreurs[0])


if __name__ == "__main__":
    unittest.main()