Patch TCP packets on the fly

tcp

Purpose

Some time ago I had an application (not web) on a laptop (not admin) which was connected with a remote server. After some research I can change a config file to set a new IP:PORT for the server. With this change, I can intercept the trafic and send it back to the server with tcpproxy.py tool. It help the use of wireshark to look at the traffic and to perfom action.

Some code

For this post I coded a client / server to replicate what I got.

client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import socket
import argparse
from itertools import cycle

default_login = 'user'
default_data = 'Hello server'
key = 'R9pRCDwWG8hw7BYg3wv3tZY'

parser = argparse.ArgumentParser()
parser.add_argument('-l','--login', default=default_login, help='user type [user] or admin')
parser.add_argument('-d','--data', default=default_data, help='data send to server')
args = parser.parse_args()

data_input = args.data
user_type = args.login

def xor(message,key):
cryptedMessage = ''.join(chr(ord(c)^ord(k)) for c,k in zip(message, cycle(key)))
return cryptedMessage


f = open("client_config.txt", "r")
server = f.readline()

ADRESSE = server.split(':')[0]
PORT = int(server.split(':')[1])

# Create a client socket
clientSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# Connect to the server
clientSocket.connect((ADRESSE,PORT))

# Send data to server
data = f"{user_type}|{data_input}"
data = xor(data,key)


clientSocket.send(data.encode())

# Receive data from server
dataFromServer = clientSocket.recv(1024)

# Print to the console
xor_output = dataFromServer.decode()

output = xor(xor_output,key)
if output.find('userType:1') == 0:
print("you are ADMIN! (userType:1)")

if output.find('userType:0') == 0:
print("you are user :/ (userType:0)")

client_config.txt

1
127.0.0.1:6781

server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import socket
from signal import signal, SIGINT
from sys import exit
from itertools import cycle

# xor key
key = 'R9pRCDwWG8hw7BYg3wv3tZY'

def xor(message,key):
cryptedMessage = ''.join(chr(ord(c)^ord(k)) for c,k in zip(message, cycle(key)))
return cryptedMessage

def handler(signal_received, frame):
# Handle any cleanup here
print('SIGINT or CTRL-C detected. Exiting gracefully')
exit(0)

signal(SIGINT, handler)

ADRESSE = '127.0.0.1'
PORT = 6781

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server.bind((ADRESSE, PORT))
server.listen()

while True:
client, adresseClient = server.accept()
print('Connection from ', adresseClient)
data_rcv = client.recv(1024)

if not data_rcv:
print('Reception error')

else:
print('Reception of:' + data_rcv.decode())
data = xor(data_rcv.decode(),key)
print(f'unxor {data}')
datas = data.split('|')
user = datas[0]

if user == 'admin':
response = 'userType:1;userRight:11111'
else:
response = 'userType:0:userRight:00000'

response = response + ';randomId:666;randomValue:nodata;someField:' + datas[1]
xor_response = xor(response,key)
n = client.send(xor_response.encode())

if (n != len(response)):
print('Send error')
else:
print('Send ok.')

Analyse

diagram

The information in packets is obfuscated.
By default the server listen on 6781.

If we look at the server output, in wireshark, we can notice some change in the same place :

tcp flow

  1. request from server when logged as user
  2. request from server when logged as admin

The idea is « what happen if we change the server answer to match a admin response from a user request ? »
We are going to try to do this :). Like I say before we can use tcpproxy.py to patch tcp server response.

Patch TCP packet

mitm diagram

So we edit the client_config.txt to set the tcpproxy listen port and run it.

./tcpproxy.py -ti 127.0.0.1 -tp 6781 -li 127.0.0.1 -lp 8888 -om hexdump -im hexdump

tcpproxy

View of hexdump from tcpproxy.py : first the user connection and after the admin.
tcpproxy.py have the possibility to add module.

patch.py (add in tcpproxy/proxymodules)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#!/usr/bin/env python3
import os.path as path
import re
from itertools import cycle

class Module:
def __init__(self, incoming=False, verbose=False, options=None):
# patch tcp flow
self.name = path.splitext(path.basename(__file__))[0]
self.description = 'Patch tcp'
self.incoming = incoming # incoming means module is on -im chain
self.start = 46
self.end = 66
self.str = b'\x09'
self.patch = b''
self.key = 'R9pRCDwWG8hw7BYg3wv3tZY'

def xor(self,data):
cryptedMessage = ''.join(chr(ord(c)^ord(k)) for c,k in zip(data, cycle(self.key)))
return cryptedMessage

def help(self):
return '\tPatch TCP and more'

def execute(self, data):
packets = bytearray(data)
out = self.xor(data.decode())
print(f"unxor data: {out}")

if bytes(packets[6:11]).hex() == '07327d0852': # test hex signature to detect user packet
print('> PATCH USER TO ADMIN')
packets[9] = 9 # patch user to admin

return packets

if __name__ == '__main__':
print ('This module is not supposed to be executed alone!')

No we run the command:

./tcpproxy.py -ti 127.0.0.1 -tp 6781 -li 127.0.0.1 -lp 8888 -om hexdump,patch

tcpproxy patch

  1. server response
  2. tcpproxy patch repsonse

patched

The client.py think we are admin \o/

demo time