Breaking Down A Python Reverse Shell One-Liner

Manipulating File Descriptors to Obtain Remote Code Execution

Alexis Rodriguez
May 13 · 4 min read

Hello, World! In this article, we will dissect the most popular Python reverse shell one-liner that is used in ethical hacking to obtain remote command execution on a target machine. We will go through the one-liner, line-by-line, to understand how we can use built-in Python modules and how we manipulate Linux file descriptors to achieve remote access to a machine. Let’s do it!

This is the one-liner we will discuss:

And this is the prettified version of the one-liner above that will we refer to throughout this article:

The Reverse Shell

Establishing a Connection

The very first objective of this Python one-liner is to establish a remote connection to the target machine using Python’s built-in socket module. If you want to know more about sockets, check out this article I wrote regarding creating a persistent Python backdoor in which I describe sockets in more detail. In short, line 5 in the snippet above is create an IPv4, TCP-based socket. The line that follows specifies the IP address and port to connect to which would be the attacker’s IP address and listening port.

Overwriting File Descriptors

Python’s os module allows us to interact with the underlying operating system. This reverse shell is only using one function from this library called dub2 . This function will duplicate the file descriptor provided as the first argument, which in this case is the file descriptor of the socket. The second argument to the dup2 function will be the value of the new file descriptor that will be created. The dup2 function is invoked three times in this Python reverse shell and the new file descriptors being created have the values of 0, 1, 2. Why these values?

When using the CLI, there are 3 data streams that are used to handle different types of data streams:

  • stdin — input data stream (this could be data from your keyboard or data piped from one command to another)
  • stdout — this is the output from a program that gets written to the console
  • stderr — error messages generated by a program and printed to the console

So by duplicating the socket’s file descriptor into the file descriptors 0, 1, and 2 for the current process, we are redirecting the stdin, stdout, and stderr data streams into the socket connection that was established with the attacker’s machine. The image below shows that the file descriptors 0, 1, and 2 for the process created on the target machine when executing the reverse shell are all redirected into the created socket:

Here’s what the file descriptors look like when you don’t duplicate the socket’s file descriptor (lines 7, 8, and 9) and overwrite the existing stdin, stdout, and stderr file descriptors:

As you can see, the stdin, stdout, and stderr file descriptors still point to their normal pty or pseudo-terminal, in this case /dev/pts/0 . This means that the shell that is spawned by our reverse shell will not be able to handle any of our command requests or send us back command output because this shell will run as if it were a normal shell on the target machine where stdin, stdout, and stderr are handled locally.

Spawning a Shell

To spawn a shell, this reverse shell is using python’s subprocess module which allows for the creation of “subprocesses”. From this module, the reverse shell is invoking the call function which is used to start a program as a subprocess. Because we are trying to get remote command execution with this Python code we need to run a shell program such as Bash that will allow us to execute system commands. The call function allows us to pass a program name to run, followed by any arguments for that program. Line 10 shows that we provide only one argument to the Bash shell which is the -i flag.

This makes the spawned shell an interactive one, meaning that the data to this shell will be read from and written to a terminal as opposed to taken in as program arguments.

Running the reverse shell should result in a connection back to the attacker machine and should enable the attacker to execute commands remotely on the target machine.


And there you have it! You can now continue to pop your Python reverse shells but also have a basic understanding of how they operate under the hood. If you enjoyed reading this article, please follow me on Medium for more cybersecurity-related articles and on GitHub for more cybersecurity-related programming projects.


Geek Culture

Proud to geek out. Follow to join our +500K monthly readers.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store