Post

Shake my hand

A great way to start a conversation is with a handshake.

We are given a server to connect via netcat:

We can send packets using emit and receive packets using recv.

We know our IP and the challenge IP and port. Given the challenge name, we must complete a TCP handshake.

The easiest way to do so is by using scapy. I also used pwntools for communication.

Helper functions will be provided at the end of the post, in the final exploit script.

In order to send a TCP packet, first you have to create an IP packet:

1
2
3
4
5
ip = IP(
    src=my_ip,
    dst="192.168.1.10",
    frag=0
)

The first component in a TCP handshake is the initial SYN packet. The client (us) sends a packet with the SYN flag. In scapy, this is done with the / operator between an IP packet and a TCP packet:

1
2
3
4
5
6
syn = ip / TCP(
    sport=1234,
    dport=9999,
    flags="S",
    seq=1000,
)

The server responded with a SYN-ACK packet (packet displayed using the .display() function in scapy):

Note the flags being set to S (SYN) and A (ACK).

Now we have to complete the handshake by sending an ACK packet:

1
2
3
4
5
6
7
ack = ip / TCP(
    sport=1234,
    dport=9999,
    flags="A",
    seq=syn_ack[TCP].ack,
    ack=syn_ack[TCP].seq + 1
)

However, we need to pay attention to the syn and ack fields. syn needs to be set to the ack of the SYN-ACK packet, and ack needs to be set to the seq of the SYN-ACK packet, plus one.

Immediately after we completed the handshake, if we recv again we receive something from the server:

The server asks us if we want to print the flag. Before responding, we have to make sure we have ack-ed the question packet.

1
2
3
4
5
6
7
question_ack = ip / TCP(
    sport=1234,
    dport=9999,
    flags="A",
    seq=server_question[TCP].ack,
    ack=server_question[TCP].seq + len(server_question[TCP].payload)
)

seq needs to be set to the last packet’s ack, and ack needs to be set to the last packet’s seq, while also adding the length of the data in the packet.

After this, we can actually send our response and wait for the server to reply. This packet has to have the ACK and PUSH flags set. The seq and ack are the same as for the packet where we ack-ed the question. This time, they are not reversed. syn is actually last packet’s syn, and same for ack.

1
2
3
4
5
6
7
data = ip / TCP(
    sport=1234,
    dport=9999,
    flags="PA",
    seq=question_ack[TCP].seq,
    ack=question_ack[TCP].ack
) / Raw("yes")

We got the flag:

Final exploit script:

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
from scapy.all import IP, TCP, Raw, raw, sr1
from base64 import b64encode, b64decode
from pwn import remote, context, log
import re
import time


# context.log_level = 'debug'


def send_packet(packet):
    packet.display()
    packet = raw(packet)
    packet = b64encode(packet).decode()
    io.sendline(f"emit {packet}".encode())
    io.recvuntil(b"> ")
    io.clean()


def receive_packet():
    io.sendline(b"recv")
    data = io.recvuntil(b"> ")
    io.clean()
    if b"empty" in data:
        log.info("no packet, trying again...")
        receive_packet()
        return
    packet = data.split(b"\n")[1].strip()
    log.success(f"Received packet: {packet}")
    packet = IP(b64decode(packet))
    packet.display()
    return packet


io = remote("shake-my-hand.chal.irisc.tf", 10501)
text = io.recvuntil(b"> ").decode()
io.clean()
my_ip = re.search(r"Your IP: (\d+\.\d+\.\d+\.\d+)", text).group(1)
log.success(f"{my_ip = }")

ip = IP(
    src=my_ip,
    dst="192.168.1.10",
    frag=0
)

syn = ip / TCP(
    sport=1234,
    dport=9999,
    flags="S",
    seq=1000,
)
print("Sending syn...")
send_packet(syn)

print("Receiving syn-ack...")
syn_ack = receive_packet()

ack = ip / TCP(
    sport=1234,
    dport=9999,
    flags="A",
    seq=syn_ack[TCP].ack,
    ack=syn_ack[TCP].seq + 1
)
print("Sending ack...")
send_packet(ack)

print("Getting server question...")
server_question = receive_packet()

question_ack = ip / TCP(
    sport=1234,
    dport=9999,
    flags="A",
    seq=server_question[TCP].ack,
    ack=server_question[TCP].seq + len(server_question[TCP].payload)
)
print("Sending question ack...")
send_packet(question_ack)

data = ip / TCP(
    sport=1234,
    dport=9999,
    flags="PA",
    seq=question_ack[TCP].seq,
    ack=question_ack[TCP].ack
) / Raw("yes")
print("Sending answer...")
send_packet(data)

server_response = receive_packet()

io.interactive()
This post is licensed under CC BY 4.0 by the author.