Patching à l'exécution : l'attaque que votre contrôle d'intégrité ne voit jamais
Une anti-altération qui hache la DLL sur le disque détecte un fichier patché — mais pas une méthode réécrite en mémoire à l'exécution. Des bibliothèques comme Harmony détournent vos méthodes sans toucher au fichier, et c'est ainsi que fonctionnent les contournements de licence et les cheats modernes. Voici comment fonctionne le patching en mémoire et ce qui le détecte vraiment.
La plupart des anti-altérations .NET font une chose : au démarrage, elles hachent l”assemblage et comparent le hachage à une valeur connue bonne, de sorte que si quelqu”un a édité la DLL pour supprimer un contrôle de licence, l”app le remarque et refuse de s”exécuter. C”est une défense réelle et utile — contre l”altération de fichiers. Mais il existe toute une classe d”attaque qu”elle ne peut pas voir, car elle ne touche jamais au fichier. La méthode est réécrite en mémoire, après que le runtime l”a déjà chargée et compilée par le JIT. Les octets sur le disque sont identiques ; le comportement, non.
La méthode que le contrôle de hachage n’inspecte jamais
Une méthode .NET passe par deux formes. Sur le disque, c”est de l”IL dans l”assemblage — c”est ce que couvre un hachage de fichier. À l”exécution, le JIT compile cet IL en code machine natif dans la mémoire du processus, et c”est ce code natif que le CPU exécute réellement. Le patching en mémoire vise la seconde forme. Il laisse la méthode se charger et se compiler par le JIT normalement — passant tout contrôle de fichier au chargement — puis réécrit la méthode compilée, ou redirige son point d”entrée, pour que les appels atterrissent ailleurs.
L”outil managé le plus populaire pour cela est Harmony. C”est une bibliothèque légitime et excellente (les mods de jeux en dépendent), mais la même capacité qui permet à un mod de changer le comportement d”un jeu permet à un attaquant de neutraliser un contrôle de licence. Un patch Harmony de votre garde ressemble à ceci :
// Attacker's patch assembly, loaded into your process (via a mod loader,
// an injected AppDomain, or a wrapper exe):
[HarmonyPatch(typeof(LicenseGate), nameof(LicenseGate.IsLicensed))]
static class Bypass
{
// A "prefix" runs before your method and can skip it entirely.
static bool Prefix(ref bool __result)
{
__result = true; // force IsLicensed() to return true
return false; // skip the original method body
}
}
var harmony = new Harmony("bypass");
harmony.PatchAll(); // detours LicenseGate.IsLicensed in memory
Après PatchAll(), chaque appel à LicenseGate.IsLicensed() renvoie true sans que votre code s”exécute. Harmony l”a fait en réécrivant la méthode compilée par le JIT avec un détour vers le prefix — aucun fichier n”a été modifié. Votre signature Authenticode est toujours valide. Votre SHA-256 de la DLL correspond toujours. Le contrôle d”intégrité au chargement passe, et la garde est grande ouverte.
Détectez l’outil, pas le fichier
Si vous ne pouvez pas attraper le changement en hachant le fichier, attrapez les conditions dont l”attaque a besoin. La réécriture en mémoire en .NET dépend de la présence de l”une de deux choses dans votre processus, et les deux sont observables au démarrage avec du code managé ordinaire.
Un profiler CLR. Le runtime expose une API de profiling/instrumentation qui peut réécrire l”IL au moment de sa compilation JIT — la manière sanctionnée et puissante de détourner des méthodes managées. Le runtime l”active via des variables d”environnement, donc leur présence est un signal fort que quelque chose est positionné pour réécrire vos méthodes :
static bool ProfilerAttached() =>
Environment.GetEnvironmentVariable("CORECLR_ENABLE_PROFILING") == "1" ||
Environment.GetEnvironmentVariable("COR_ENABLE_PROFILING") == "1";
Un framework de patching chargé dans le processus. Harmony doit être dans votre processus pour le patcher, et il est livré comme un assemblage spécifique (0Harmony). Vous n”avez pas besoin d”énumérer chaque assemblage — demandez au runtime si son type d”entrée se résout :
static bool HarmonyLoaded() =>
Type.GetType("HarmonyLib.Harmony, 0Harmony", throwOnError: false) != null
|| Type.GetType("Harmony.HarmonyInstance, 0Harmony", throwOnError: false) != null; // legacy 1.x
Exécutez les deux au démarrage (dans l”initialiseur de module, pour qu”elles se déclenchent avant votre propre code) et réagissez comme vous réagissez à tout autre signal d”altération — lever une exception, sortir ou déléguer à votre propre gestionnaire. Aucun contrôle n”est coûteux, et surtout ils sont managés et multiplateformes, donc la même garde fonctionne sous Windows, Linux, macOS et le runtime WASM.
Ce que cela vous apporte et ce que cela n’apporte pas
Soyez honnête sur le modèle de menace, car survendre une défense est la façon d”obtenir un faux sentiment de sécurité. Ces contrôles élèvent le coût d”un contournement par détour occasionnel — l”attaquant qui saisit Harmony et écrit un prefix de dix lignes doit désormais aussi vaincre un détecteur avant même que son patch ne s”exécute. Cela arrête la majorité opportuniste. Ils ne vainquent pas un attaquant déterminé : quelqu”un qui contrôle l”environnement peut renommer l”assemblage patcheur, patcher d”abord votre détecteur, ou attacher un profiler que le contrôle ne cherche pas. Il n”existe aucun contrôle en code managé qu”un attaquant suffisamment motivé avec un contrôle total de la machine ne puisse finir par retirer — la même réserve qui s”applique à toute protection côté client.
La bonne posture est la superposition. L”anti-altération par intégrité de fichier attrape l”attaque du binaire patché ; la détection de profiler et de patcheur attrape l”attaque du détour en mémoire ; et ce que les deux protègent — votre garde de licence — devrait lui-même être difficile à trouver et difficile à lire, pour qu”un attaquant ne puisse pas localiser facilement la méthode à patcher en premier lieu. Une garde enfouie dans du code aplati par flux de contrôle et virtualisé, qui renvoie une valeur alimentant une vraie logique plutôt qu”un net bool IsLicensed(), est bien plus difficile à détourner proprement qu”une unique méthode bien nommée. La détection vous dit qu”un outil d”attaque est présent ; l”obscurcissement rend la cible qu”il vise coûteuse à acquérir.
En résumé
Si votre histoire d”anti-altération est « je hache la DLL au démarrage », vous défendez une porte et en laissez une autre ouverte. Le patch en mémoire est la porte par laquelle les contournements de licence et les cheats modernes passent réellement, précisément parce qu”il laisse le fichier — et sa signature — impeccable. Ajoutez au démarrage la détection des deux choses dont un patch en mémoire a besoin (un hook de profiler, un patcheur chargé), réagissez à vos conditions, et combinez cela avec un obscurcissement qui rend la cible patchée difficile à trouver. Nebula injecte à la fois le contrôle d”intégrité de fichier et la détection de profiler/patcheur pour vous, de sorte que la build protégée surveille la porte que le hachage ne peut pas voir — sans que vous écriviez le moindre code de détection à la main.
Essayez Nebula.NET
Renforcez votre code .NET en quelques minutes — commencez avec l'édition gratuite.