CTF Writeup – Futuristic communications (misc)

Communication in the future is a big thing. With the rising amount of data in the internet new handshake techniques are required. Here is my implementation, where I can identify who did request which resources.

In this challenge we receive a PCAP file from a custom authentication procedure between a client device and a server. Our goal is to mimic the different handshake stages and once logged in, request the flag, as can be seen in the sniff.

The first stage is a standard TCP connection to port 1337 on the target IP which is given in the challenge description. Over this connection we receive a welcome message and two parameters – our generated client UUID and a port to connect to.

Next, we need to send a SYN packet to the received port number with the packet contents being our UUID (a kind of port knock). Server sends a RST/ACK for this SYN and sends two new parameters on the 1337 socket – a new port (we will call it future port) and an initial sequence number.

At this point we start exchanging messages over the future port using a TCP variant. The first packet is a SYN where the sequence number equals the one received previously, ack is zero as in TCP, and content is UUID as previously. In response there is both a RST/ACK which we can ignore and a SYN/ACK. The SYN/ACK is unusual because it mirrors our SEQ (TCP sequence number) to the server’s SEQ and sends an ACK which is a number slightly lower than the SEQ. The gap is 24 in the sample connection but on every connection, it is a randomized small number. In response to SYN/ACK we send an ACK with SEQ = received ACK, ACK = received SEQ, as per usual TCP.

Now we receive a command menu on the 1337 connection – there are three commands: “synchronize”, “privileged” and “flag”. The goal is to first synchronize with the server, then use “privileged” to elevate our permissions and finally send “flag” to receive the challenge flag.

In order to synchronize with the server, client needs to send “synchronize” messages until SEQ=ACK. In the future protocol, in the “synchronize” stage sequence number increments by 1 every packet, so we loop SEQ-ACK times and send “synchronize” as data in SYN packets, ACK stays the same (equals the received SEQ, which is our initial sent SEQ), SEQ increments. Once we have caught up to the future, SEQ=ACK, all that is left is to send “privileged” and “flag” commands to capture the flag. Both of them are again SYN packets, the content is the message name and SEQ=ACK. Finally, we receive the flag on the standard 1337 connection.

For the implementation we used scapy to send custom TCP SEQ and ACK values, tcpdump to capture the SYN/ACK and just pasted the seq and ack values from the capture into the script. Receiving the SYN/ACK in scapy was buggy presumably because scapy couldn’t identify the connection between our SYN and the SYN/ACK as it is a custom handshake, and layer 2 sniffing just didn’t work.

We ended up wasting several hours thinking our future SYN was incorrect because server didn’t send SYN/ACK. The problem was in fact that we were using a host behind NAT so after the RST/ACK we were no longer able to receive any packets destined to the NAT-ed port. Luckily a team member was able to run the python script from his public IP box.

future solution

from scapy.all import *<br>
import sys<br>
import socket<br>
from time import sleep<br>
import re

hostname = "31.22.123.60"<br>
port = 1337

while True:<br>
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)<br>
    sock.connect((hostname, port))<br>
    data_in = sock.recv(256)<br>
    sport=1234<br>
    token = data_in.split(b"\n")[1].split(b": ")[1]<br>
    tcp_port0 = int(data_in.split(b"\n")[2].split(b" ")[-1])<br>
    ip = IP(dst="31.22.123.60")<br>
    tcp = TCP(sport=23995,dport=tcp_port0, flags="S")<br>
    data = token<br>
    sleep(2)<br>
    send(ip/tcp/data)<br>
    data_in = sock.recv(256)<br>
    print(data_in)<br>
    data_in = sock.recv(256)<br>
    print(data_in)<br>
    #for i in range(0x10000):<br>
    #sport = i<br>
    tcp_port = int(data_in.split(b",")[1].split(b" ")[-1])<br>
    seq_number = int(data_in.split(b",")[2].split(b" ")[-1])<br>
    ip = IP(dst="31.22.123.60")<br>
    tcp = TCP(sport=37614,dport=tcp_port, flags="S", seq=seq_number)<br>
    print("token: {0},first tcp port:{1},hex:{2},second tcp port:{3},hex:{4}sequence number:{5}".format(data, tcp_port0, hex(tcp_port0),tcp_port, hex(tcp_port), seq_number))<br>
    sleep(2)<br>
    print ("Sending packet with SEQ as requested")<br>
    send(ip/tcp/data)<br>
    #packets=sniff(filter="host %s" % hostname, count=1)

line = input("Give me tcpdump: ")
m = re.search("seq (\d+), ack (\d+)", line)
if not m:
    exit(1)

seq=int(m.group(1))
ack=int(m.group(2))


#packet=packets[0][0][1][TCP]
tcp = TCP(sport=37614, dport=tcp_port, flags="A", seq=ack, ack=seq)
print ("Sending ACK")
send(ip/tcp/data)

data_in = sock.recv(256)
print("From 1337:", data_in)

for i in range(seq-ack):

    tcp = TCP(sport=37614, dport=tcp_port, flags="S", seq=ack+i, ack=seq)
    print ("Sending packet #%d synchronize"% i)
    send(ip/tcp/"synchronize")
    sleep(0.4)
    data_in = sock.recv(256)
    print("From 1337:", data_in)

sleep(0.5)
tcp = TCP(sport=37614, dport=tcp_port, flags="S", seq=seq, ack=seq)
print ("Sending packet privileged")
send(ip/tcp/"privileged")

data_in = sock.recv(256)
print(data_in)

tcp = TCP(sport=37614, dport=tcp_port, flags="S", seq=seq, ack=seq)
print ("Sending packet flag")
send(ip/tcp/"flag")

data_in = sock.recv(256)
print(data_in)
data_in = sock.recv(256)
print(data_in)

break