The content of the article
In this article, we will understand how to use Python to transfer messages between two computers connected to a network. This task is often encountered not only during application development, but also during a pentest or participation in CTF. Having penetrated into someone else's car, we somehow have to give her commands. That is why we need a reverse shell, or “reverse shell,” which we will write.
There are two low-level protocols over which data is transmitted in computer networks: UDP (User Datagram Protocol) and TCP (Transmission Control Protocol). Working with them is slightly different, so consider both.
UDP protocol Designed to transfer packets from one node to another without a delivery guarantee. A data packet usually consists of two parts. In one, control information, including sender and receiver data, as well as error codes. In the other, user information, that is, a message that is transmitted.
It is important here that UDP does not guarantee the delivery of a packet (more correctly, datagrams) to a specified node, since there is no communication channel between the sender and receiver. Errors and packet losses in networks happen all the time, and this is worth considering (or in certain cases, on the contrary, it’s not worth it).
TCP protocol also delivers messages, but at the same time ensures that the packet reaches the recipient safe and sound.
Let's move on to practice
We will write the code in modern Python 3. Together with Python, a set of standard libraries is supplied, from which we need the socket module. We connect it.
Then we will agree that we have a server and a client, where the client will usually be our computer, and the server will be remote. In reality, all these are conventions, and we can talk about any two computers (including virtual machines) or even just two processes running locally. The only important thing is that the code on different sides will be different.
On each side, first of all, create an instance of the socket class and set two constants (parameters) for it.
We use UDP
First, create a place to share data.
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
We have created an object
swhich is an instance of the socket class. To do this, we called a method from the socket module with the name
socket and passed him two parameters –
AF_INET means that the fourth version of the IP protocol is being used. You can use IPv6 if you wish. In the second parameter, for our purposes, we can specify one of two constants:
SOCK_STREAM. The first means that UDP will be used. The second is TCP.
Next, the code is different for the server side and the client side. Consider the server side first.
s.bind(('127.0.0.1', 8888))result = s.recv(1024)print('Message:', result.decode('utf-8'))s.close()
s.bind(('127.0.0.1', 8888)) means that we reserve on the server (that is, on our machine) the address 127.0.0.1 and port 8888. On it we will listen and receive packets of information. There are double brackets here, since the method
bind() a tuple of data is transmitted – in our case, consisting of a string with an address and a port number.
Only available ports can be reserved. For example, if a web server is already running on port 80, then it will bother us.
s It listens to the port we specified (8888) and receives data per kilobyte (therefore, we set the buffer size to 1024 bytes). If a datagram is sent to it, then the method reads the specified number of bytes and they fall into the variable
Next comes the familiar function
print()in which we display a message
Message: and decoded text. Since the data in
result – this is UTF-8 encoded text, we must interpret it by calling the method
And finally, the method call
close() needed to stop listening on port 8888 and release it.
Thus, the server side has the following form:
import sockets = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.bind(('127.0.0.1', 8888))result = s.recv(1024)print('Message:', result.decode('utf-8'))s.close()
Everything is much simpler here. To send the datagram we use the class method
socket (more precisely, our copy
', ('127.0.0.1', 8888))
The method has two parameters. The first is the message you are sending. The letter b in front of the text is needed to convert the characters of the text into a sequence of bytes. The second parameter is a tuple where the IP of the destination machine and the port that receives the datagram are indicated.
Thus, the client side will look something like this:
import sockets = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)s.sendto(b'
', ('127.0.0.1', 8888))
For testing, open two consoles, one will work as a server, the other as a client. In each run the corresponding program.
On the client side, we should not see anything, and this is logical, because we did not ask for any output.
For the test, we sent a message from one port to another port on our own machine, but if you run these scripts on different computers and specify the correct IP on the client side, then everything will work in exactly the same way.
We use TCP
It's time to get to know TCP. In the same way we create a class
s, but as the second parameter we will use the constant
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
Again, reserve the port on which we will receive packets:
Then a method unfamiliar to us appears
listen(). With it, we set up a queue for connected clients. For example, with the parameter
.listen(5) we create a limit on five connected and pending clients.
We make an endless loop in which we will process requests from each new client in the queue.
while 1: try: client, addr = s.accept() except KeyboardInterrupt: s.close() break else: result = client.recv(1024) print('Message:', result.decode('utf-8'))
Scary? Let's start in order. First we create an exception handler
KeyboardInterrupt (stopping the program from the keyboard) so that the server runs indefinitely until we press something.
accept() returns a pair of values that we put in two variables: in
addr will contain information about who was the sender, and
client will become an instance of the class
socket. That is, we have created a new connection.
Now let's look at these three lines:
except KeyboardInterrupt: s.close() break
In them, we stop listening and release the port only if we ourselves stop the program. If the interruption does not occur, then the block is executed.
else: result = client.recv(1024) print('Message:', result.decode('utf-8'))
Here we save user data in the variable result, and function
print() we display the message that the client sent to us (after turning the bytes into a Unicode string). As a result, the server side will look something like this:
import sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.bind(('127.0.0.1', 8888))s.listen(5)while 1: try: client, addr = s.accept() except KeyboardInterrupt: s.close() break else: result = client.recv(1024) print('Message:', result.decode('utf-8'))
With the client side, again, everything is simpler. After connecting the library and instantiating the class
s we are using the method
connect(), connect to the server and port on which messages are received:
Next, we send the data packet to the recipient using
At the end, stop listening and release the port:
The client code will look something like this:
import sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect(('127.0.0.1', 8888))s.send(b'
Run the server code and the client code in two different consoles. At the output, we should get about the same as with the UDP protocol.
Success! Congratulations: now you have great opportunities. As you can see, there’s nothing wrong with working with the network. And of course, do not forget that since we are experts in information security, we can add encryption to our protocol.
As the next exercise, you can try, for example, write a chat for several people, as in the screenshot.
Putting knowledge into practice
I participated in InnoCTF twice, and working with sockets in Python is very useful when solving hacking tasks. In fact, it all comes down to parsing incoming data from the InnoCTF server many times and correctly processing them. Data can be absolutely anything. Usually these are mathematical examples, different equations and so on.
To work with the server, I use the following code.
import sockettry: s = socket.socket(socket.AF_INET, spcket.SOCK_STREAM) s.connect(('
', )) while True: data = s.recv(4096) if not data: continue st = data.decode("ascii") # Здесь идет алгоритм обработки задачи, результаты работы которого должны оказаться в переменной result s.send(str(result)+'n'.encode('utf-8'))finally: s.close()
Here we save the byte data into a variable
data, and then convert them from ASCII encoding to a line
st = data.decode("ascii"). Now, in the st variable, we store what the server sent us. We can send an answer only by supplying a string variable to the input, so be sure to use the function
str(). At the end, she has a line break character –
n. Next, we all code in UTF-8 and using
send() send to the server. In the end, we definitely need to close the connection.
Making a full reverse shell
From training examples, we turn to the real task – the development of a reverse shell that will allow you to execute commands on the captured remote machine.
In this case, we need to add only a function call
subprocess. What it is? Python has a subprocess module that allows you to run processes in the operating system, manage and interact with them through standard input and output. As a simple example, we use subprocess to start notepad:
Here is the method
call() calls (starts) the specified program.
We turn to the development of the shell. In this case, the server side will be the attacking one (that is, our computer), and the client side will be the attacked machine. That is why the shell is called the reverse.
Continuation is available only to participants
Materials from the latest issues become available separately only two months after publication. To continue reading, you must become a member of the Xakep.ru community.
Join the Xakep.ru Community!
Membership in the community during the specified period will open you access to ALL Hacker materials, increase your personal cumulative discount and allow you to accumulate a professional Xakep Score!
I am already a member of Xakep.ru