Python: Layer-3 tunnel device traffic under client & server TCP sockets

# notes: PoC, single threaded, single client, single interface, single address, ipv4 over tcp
# 
# server: python tun.py -s 0.0.0.0 31337 key
#   ('[conn]:new', [socket._socketobject object at 0xb6b20c00], ('192.168.160.241', 64227))
#   ('[auth]:good', [socket._socketobject object at 0xb6b20c00])
# 
# client: python tun.py -c 1.2.3.4 31337 key
#   client: route add -host 1.2.3.4 [real gateway]
#   client: route add -net 0.0.0.0/0 10.0.0.1
#   client: host yahoo.com 4.2.2.1
#!/usr/bin/python

import os, sys
import ctypes, fcntl, struct
import select, socket, subprocess
import time, random, hashlib

def tclose(sobj):
	try:
		sobj.close()
	except:
		pass

def ksa4(k):
	s = []
	for i in range(0, 256):
		s.append(i)
	j = 0
	l = len(k)
	for i in range(0, 256):
		j = ((j + s[i] + ord(k[i % l])) % 256)
		t = s[i]; s[i] = s[j]; s[j] = t
	return (s, 0, 0)

def arc4(m, k):
	(s, i, j) = k
	l = len(m)
	o = ""
	for x in range(0, l):
		i = ((i + 1) % 256)
		j = ((j + s[i]) % 256)
		t = s[i]; s[i] = s[j]; s[j] = t
		c = s[(s[i] + s[j]) % 256]
		o += chr(ord(m[x]) ^ c)
	k = (s, i, j)
	return (o, k)

def set4(n, k):
	if (n == ""):
		n = (str(time.time()) + ":" + str(random.random()) + "-")
	s = ksa4(n + "s" + k)
	r = ksa4(n + "r" + k)
	(null, s) = arc4("x"*4096, s)
	(null, r) = arc4("x"*4096, r)
	return (n, s, r)

def client(h, p, n, k):
	f = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	f.connect((h, p))
	(enc, k) = arc4(n, k)
	f.send(n + enc)
	return (f, k)

# initialize some common variables
BUFFER_SIZE = 2000
PR_MODE = "client"
PR_HOST = "1.2.3.4"
PR_PORT = 31337
PR_SKEY = "key"
OS_TYPE = sys.platform
IF_CMD = ["/sbin/ifconfig", "tun0", "10.0.0.1", "10.0.0.2", "up"]

# process command line args usage: tun.py mode:[-c,-s] ip:[0.0.0.0] port:[4321] secret:[key]
if (sys.argv[1] == "-s"):
	PR_MODE = "server"
PR_HOST = sys.argv[2]
PR_PORT = int(sys.argv[3])
PR_SKEY = sys.argv[4]

# initialize the clients secret key state
last = 0
(nonce, skey, rkey) = set4("", PR_SKEY)

# define the remote connection socket descriptors - tcp client or tcp server
srv_fd = None
net_fd = None
if (PR_MODE == "client"):
	tmp = IF_CMD[2]
	IF_CMD[2] = IF_CMD[3]
	IF_CMD[3] = tmp
	(net_fd, skey) = client(PR_HOST, PR_PORT, nonce, skey)
else:
	srv_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
	srv_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
	srv_fd.bind(("0.0.0.0", PR_PORT))
	srv_fd.listen(1)

# determine how to bring up the tun0 interface - mac or linux
if (OS_TYPE == "darwin"):
	tun_fd = os.open("/dev/tun0", os.O_RDWR)
else:
	TUNSETIFF = 0x400454ca
	IFF_TUN = 0x0001
	IFF_TAP = 0x0002
	IFF_NO_PI = 0x1000
	
	tun_fd = os.open("/dev/net/tun", os.O_RDWR)
	ifr = struct.pack("16sH", "tun0", IFF_TUN | IFF_NO_PI)
	fcntl.ioctl(tun_fd, TUNSETIFF, ifr)
	
	IF_CMD.insert(3, "pointopoint")
subprocess.call(IF_CMD)

# main processing loop for network traffic tunneling
conns = []
while (1):
	#print("loop")
	
	# set the list of available socket descriptors to listen for
	fd_list = [tun_fd]
	if (srv_fd):
		fd_list.append(srv_fd)
	if (net_fd):
		fd_list.append(net_fd)
	for conn in conns:
		fd_list.append(conn)
	
	# select the available fds ready for reading
	rd = select.select(fd_list, [], [])
	
	# loop through each socket now to process each action
	for ready_fd in rd[0]:
		# if we are reading from our tun device then encrypt the message and write it to the network socket
		if (ready_fd == tun_fd):
			packet = os.read(tun_fd, BUFFER_SIZE)
			plen = len(packet)
			if ((plen > 0) and (net_fd)):
				(enc, skey) = arc4(packet, skey)
				smac = str(skey)
				hmac = hashlib.sha256(smac + enc + smac).digest()
				net_fd.send(enc + hmac)
		
		# if the server gets a new client connection then add it to a list to be authenticated next
		if (ready_fd == srv_fd):
			(conn, addr) = srv_fd.accept()
			print("[conn]:new",conn,addr)
			conns.append(conn)
		
		# if we get a network message decrypted and authenticated then write it to our tunnel device
		if (ready_fd == net_fd):
			data = net_fd.recv(BUFFER_SIZE)
			dlen = len(data)
			if (dlen > 0):
				try:
					temp = data[:-32]
					(dec, tkey) = arc4(temp, rkey)
					tmac = str(tkey)
					hmac = hashlib.sha256(tmac + temp + tmac).digest()
					if (hmac == data[-32:]):
						rkey = tkey
						os.write(tun_fd, dec)
					else:
						print("[hmac]:fail",hmac,data[-32:])
				except:
					dlen = 0
			if (dlen < 1):
				tclose(net_fd)
				net_fd = None
		
		# loop through any unauthenticated clients who have sent us data
		tmps = []
		conl = (len(conns) - 1)
		while (conl > -1):
			conn = conns[conl]
			
			# if the connection is ready
			if (ready_fd == conn):
				data = conn.recv(BUFFER_SIZE)
				dlen = len(data)
				indx = data.find("-")
				
				# if they sent us some actual data
				if ((dlen > 0) and (indx > -1)):
					# try to decrypt the nonce and verify an increase in the time
					try:
						temp = float(data.split(":")[0])
					except:
						temp = 0
					(tnonce, trkey, tskey) = set4(data[:indx+1], PR_SKEY)
					(dec, trkey) = arc4(data[indx+1:], trkey)
					
					# if the auth succeeds then setup the new key state and set the new client socket
					if ((temp > last) and (tnonce == dec)):
						print("[auth]:good",conn)
						
						nonce = tnonce
						skey = tskey
						rkey = trkey
						
						tclose(net_fd)
						net_fd = conn
						
						last = temp
						conns[conl] = None
					else:
						dlen = 0
				else:
					dlen = 0
				
				# if the auth failed then remove the client now
				if (dlen < 1):
					print("[auth]:fail",conns[conl])
					tclose(conns[conl])
					conns[conl] = None
			
			if (conns[conl]):
				tmps.append(conns[conl])
			conl -= 1
		
		conns = tmps[:]

2 thoughts on “Python: Layer-3 tunnel device traffic under client & server TCP sockets

Leave a comment