NtClose Invalid Handle
Le malware ferme un handle délibérément invalide ; sous un débogueur, le noyau lève STATUS_INVALID_HANDLE, et capturer cette exception révèle un débogueur attaché.
Fermer un handle invalide se comporte différemment selon qu'un débogueur est attaché ou non. Lorsqu'un processus s'exécute sans débogueur, CloseHandle/NtClose sur un handle bidon renvoie simplement une erreur. Mais lorsqu'un débogueur est présent, le noyau lève une exception STATUS_INVALID_HANDLE (0xC0000008). Le malware enveloppe la fermeture dans un bloc try/except : si l'exception se déclenche, il en conclut qu'un débogueur est attaché.
C'est une vérification par pur effet de bord — aucune API de détection de débogueur n'est impliquée, ce qui la rend plus difficile à neutraliser avec des hooks d'API seuls.
Fonctionnement
Le code passe une valeur de handle invalide à NtClose (ou CloseHandle) à l'intérieur d'un gestionnaire d'exception. L'exécution du gestionnaire est le signal.
#include <windows.h>
typedef NTSTATUS (NTAPI *pfnNtClose)(HANDLE);
BOOL DebuggerByInvalidHandle(void)
{
HMODULE hNtdll = GetModuleHandleW(L"ntdll.dll");
pfnNtClose NtClose = (pfnNtClose)GetProcAddress(hNtdll, "NtClose");
__try {
NtClose((HANDLE)0xDEADBEEF); // invalid handle
}
__except (GetExceptionCode() == 0xC0000008 /* STATUS_INVALID_HANDLE */
? EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH) {
return TRUE; // debugger present
}
return FALSE;
}N'importe quelle valeur manifestement invalide fonctionne (0xDEADBEEF, un nombre impair, un handle déjà fermé). Le comportement distinctif est que l'exception n'est levée que lorsque le processus est débogué.
Détection & contournement
- Statique — Recherchez dans IDA/Ghidra les appels à
NtClose/CloseHandleavec un argument constant qui n'est pas un handle (p. ex.0xDEADBEEF) à l'intérieur d'un cadre d'exception structurée (__try/__except). La comparaisonGetExceptionCode() == 0xC0000008(constante0xC0000008) dans un filtre d'exception est révélatrice. - Dynamique — Dans x64dbg, posez un point d'arrêt sur
ntdll!NtCloseet inspectez l'argument handle ; une valeur fantaisiste confirme l'astuce. Surveillez l'exception de première chance0xC0000008— configurez le débogueur pour la transmettre à l'application afin que le gestionnaire ne l'interprète pas comme « débogueur présent ». - Patch / contournement — Laissez le débogueur transmettre l'exception à l'application (ne vous arrêtez pas dessus), ou patchez l'appel : NOP l'appel
NtClose, ou forcez le filtre__except/ le résultat pour que la branche « débogueur présent » ne soit jamais prise. Vous pouvez aussi hookerNtClosepour qu'il retourne proprement sur les handles invalides. - Outils — L'option « NtClose » de ScyllaHide intercepte l'appel et supprime l'exception parasite ; les paramètres d'exception de x64dbg (transmettre
0xC0000008à l'application) ; TitanHide au niveau noyau.