Skip to content
KitploitKITPLOIT
OutilsExploitsBlog
Log in
Soumettre
OutilsExploitsBlog
Soumettre

Outils de Hacking, PenTest et Cybersécurité pour votre Arsenal de Sécurité !

Kitploit est un répertoire d'outils de hacking, de cybersécurité et de pentesting. Découvrez les dernières mises à jour des projets pour trouver des vulnérabilités, analyser des systèmes, automatiser les tests et renforcer votre sécurité.

··Flux·Contact·Confidentialité·© 2026 Kitploit

Répertoire d'outils

Catégories

Voir toutes les catégories
Loading categories
HallWatch — Détecteur en mode utilisateur qui capture les appels système indirects. Intercepte Hell's Hall, Tartarus' Gate, RecycledGate, les appels système VEH et bien d'autres. | Kitploit
Outils/GitHubGitHub/zypherion-technologies/hallwatch
Outils DéfensifsAnalyse de MalwareAnalyse de BinairesRenseignement sur les MenacesRéponse aux Incidents
GitHubzypherion-technologies/hallwatch

HallWatch

Détecteur en mode utilisateur qui capture les appels système indirects. Intercepte Hell's Hall, Tartarus' Gate, RecycledGate, les appels système VEH et bien d'autres.

Populaires

Voir tout →

Découvrez les outils les plus utilisés par notre communauté.

Explorer tous les outils

Parcourez notre collection d'outils

Voir tous les outils →
Voir le dépôt
871010il y a 3 moisVérifié par Kitploit
Partager

HallWatch

License: GPL v3 Website Discord Telegram X

Copyright (C) 2026 Adam Zypherion <[email protected]> — sous licence GPL-3.0.


Les appels système indirects sont simples une fois que vous en avez vu un. Le chargeur parcourt ntdll, lit le SSN dans le prologue de Nt*, trouve le 0F 05 deux octets plus loin, définit lui-même r10/rax/rdx/r8/r9, et jmp directement sur ces deux octets. L'appel système se produit depuis l'intérieur de ntdll. Votre hook sur l'export kernel32 n'est jamais touché. Votre hook sur l'export ntdll n'est jamais touché. [RSP] au moment du SYSCALL pointe vers la page RWX du chargeur, mais rien dans l'appel ne semble inhabituel de l'extérieur.

HellHall proc
    mov  r10, rcx
    mov  eax, dwSSN
    jmp  qword ptr [qAddr]
    ret
HellHall endp

Les variantes ont des noms. Tartarus' Gate et RecycledGate utilisent l'instruction syscall appartenant à un stub mais le SSN d'un autre, donc même si vous enregistrez le nom du stub que votre hook rapporte, c'est un mensonge. VEH syscall déclenche intentionnellement une violation d'accès et utilise son propre VEH pour réécrire le contexte afin que RIP atterrisse sur l'instruction syscall de ntdll avec les registres déjà préparés. Hell's Gate n'utilise pas du tout ntdll. Le chargeur écrit 0F 05 dans sa propre page RWX et s'exécute à partir de là.

La réponse en mode noyau (KM) est un pilote, je pourrais éventuellement m'y mettre et publier un projet, mais ce n'est pas prévu de sitôt :D


PAGE_GUARD semble devoir fonctionner. Marquez la page qui contient les octets syscall avec PAGE_GUARD, interceptez le STATUS_GUARD_PAGE_VIOLATION dans un VEH, inspectez, redirigez vers un trampoline privé, définissez le drapeau de piège, exécutez pas à pas, réarmez la page. Un piège par appel Nt, peu importe comment le chargeur y est arrivé. Cela fonctionne contre tout sauf Hell's Gate.

Le problème est que l'OS ne coopère pas. PAGE_GUARD est un coup unique, qu'est-ce que cela signifie ? chaque fois qu'il se déclenche, le bit est effacé et vous devez le remettre. Votre gestionnaire effectue des appels système (NtProtect pour remettre la garde, NtContinue pour reprendre) et ces appels système ont des stubs et ces stubs sont sur la page que vous venez de protéger. J'ai contourné la plupart de cela avec des stubs d'appel système privés construits sur une page séparée que nous possédions (allouer RWX, écrire mov r10,rcx; mov eax,SSN; syscall; ret, verrouiller RX, ne jamais toucher à ceux de ntdll), mais c'était fragile. Chaque version mineure de Windows changeait le timing. Chaque thread que l'échantillon engendrait était une autre course contre le worker d'intégrité qui reconstruisait la garde. Le succès de la démo était d'environ 30 à 50 pour cent sur dix exécutions sur la même machine.

Finalement, j'ai arrêté d'essayer de convaincre Windows que PAGE_GUARD devait se comporter comme je le voulais, et j'ai plutôt essayé de remplacer l'octet.


Si vous avez des difficultés à comprendre, regardez cette vidéo :)

https://github.com/user-attachments/assets/0cb670fd-e51b-413e-bf00-08f9297888ed


L'octet à l'instruction syscall est 0F 05. Le premier octet seul (0F) est le préfixe d'une famille d'opcodes à deux octets incluant SYSCALL, CPUID et RDTSC. En lui-même, il n'est pas exécutable ; le CPU a besoin du second octet pour décoder. Si vous remplacez le premier octet par 0xCC (INT3), la paire devient CC 05, que le CPU décode comme INT3 suivi d'un octet errant qu'il n'atteint jamais. Tout chemin de code qui atterrit sur cette adresse déclenche EXCEPTION_BREAKPOINT.

C'est tout le mécanisme. Notre VEH intercepte le point d'arrêt, recherche à quel stub appartient l'adresse (nous construisons la carte au moment de l'initialisation en énumérant les exports de ntdll), exécute trois vérifications sur celui qui s'est présenté, et définit Context->Rip sur un trampoline privé qui effectue le véritable appel système et retourne. L'octet reste CC. Le prochain appelant le rencontre de la même manière, donc pas de basculement de protection de page en avant et en arrière.

Pour être clair : oui, il s'agit de hook par remplacement d'octet. Ce n'est juste pas à l'octet que les gens entendent habituellement par "hook ntdll". Le hook EDR classique remplace les premiers octets du stub par un JMP <my_func> :

ntdll!NtAllocateVirtualMemory:
  E9 ?? ?? ?? ??       jmp my_hook     ; remplace "mov r10, rcx"
  ...
  0F 05                syscall
  C3                   ret

c'est exactement ce que les appels système indirects contournent. Le chargeur lit le SSN lui-même et saute directement sur le 0F 05, le prologue (et votre jmp) ne s'exécute jamais, le hook ne se déclenche jamais. Ce que nous faisons, c'est remplacer l'octet syscall lui-même :

ntdll!NtAllocateVirtualMemory:
  4C 8B D1             mov r10, rcx     ; non touché
  B8 18 00 00 00       mov eax, 18h     ; non touché
  CC 05                int3 / 05        ; était 0F 05, nous avons écrit CC sur le 0F
  C3                   ret

maintenant, peu importe comment vous êtes arrivé à cette adresse. via le prologue, via un saut indirect qui saute le prologue, via une réécriture de contexte VEH-syscall qui dépose RIP là, peu importe. Si le CPU exécute cet octet, il piège. Même famille de technique que le hook classique, emplacement différent, couverture complètement différente.

Le trampoline ressemble à ceci :

F3 0F 1E FA              endbr64
49 89 CA                 mov r10, rcx
B8 <stub SSN>            mov eax, ssn
0F 05                    syscall
C3                       ret

Les trois vérifications sont inchangées par rapport à la version PAGE_GUARD car c'étaient les bonnes trois vérifications ; elles avaient juste besoin d'un endroit fiable pour vivre.

Adresse de retour. [RSP] est l'endroit où l'appel système serait retourné. Pour un appel réel, c'est à l'intérieur de ntdll, kernel32, kernelbase, ou l'une des DLL runtime associées. Pour Hell's Hall, c'est à l'intérieur de la page RWX à partir de laquelle le chargeur s'exécute. Nous avons une courte liste de cibles de retour de confiance construite à l'initialisation en appelant GetModuleHandle pour ces noms et en lisant les plages .text de leurs en-têtes PE.

SSN. Le prologue du stub s'est déjà exécuté avant d'atteindre l'octet syscall, donc eax contient la valeur qui y a été chargée. Si le chargeur a fait un échange Tartarus, cette valeur ne correspondra pas au SSN que nous avons lu dans ce même stub lors de l'énumération. Nous enregistrons la divergence, et le trampoline écrit le bon SSN avant son propre syscall, de sorte que la fonction noyau qui s'exécute est celle qui appartient à l'octet plutôt que celle que le chargeur voulait. La technique est enregistrée et neutralisée dans la même étape.

Parcours de la pile. RtlVirtualUnwind à partir du contexte actuel, cinq trames vers le haut. Le RIP de chaque trame doit être à l'intérieur d'un module connu et doit avoir une entrée RUNTIME_FUNCTION. Les shellcodes et gadgets ROP échouent à cela même lorsque [RSP] lui-même semble fiable, ce qu'un chargeur peut simuler (il peut prédire approximativement où son appelant sera en mémoire et forger une adresse de retour crédible à cet endroit).

Télécharger l’outil