Thursday, October 16, 2014

Scapy Primer - Part II

Obviously if we create a packet, the next logical step would be to send that packet over the network to a destination. This is a necessary step, especially when attempting to analyze the response to a crafted packet. Since crafting packets allows us to deviate from protocol and RFC standards, an unexpected or unique response may be received. This is a useful technique when it comes to fingerprinting and troubleshooting a host. There are other useful applications when analyzing the response to these crafted packets as well.

Packet Trasmission

A number of functions are available for transmitting packets. The following functions are the most noteworthy and useful.

send()

The send() function is used to send a packet over a network. This function is typically used when we do not want to store the response for further use in the script or program.

     >>> packet = IP(dst="10.10.10.1")/TCP(dport=80)
     >>> ans = send(packet)
     .
     Sent 1 packets.
     >>> ans
     >>>

Notice that no value is returned as a response to the sent packet so the result of printing the variable 'ans' is an empty one.

Let's use tcpdump to get a more detailed picture of what is happening on the network when we use the send() function.


We receive a SYN/ACK as expected since destination port 80 is open on host 10.10.10.1. The host that initiated the conversation, 192.168.93.136, responds to the SYN/ACK with a RST packet. This is due to Scapy making the initial request and not the Linux Kernel. Since this is the case the host does not expect to receive a SYN/ACK, thus it responds with a RST packet.

sr()

The sr() function is very much similar to send(). The difference between the two functions is that sr() will return an answer to the stimulus packet that has been sent. For example, if we were to send an ICMP request to a destination, this function would listen and store the responses within a list that can be enumerated. Unanswered packets will also be returned using sr().

    >>> packet = IP(dst="10.10.10.1")/TCP(dport=80)/"Open Port"
    >>> ans,unsans = sr(packet)
    Begin emission:
    .Finished to send 1 packets.
    *
    Received 2 packets, got 1 answers, remaining 0 packets
    >>> ans
    <Results: TCP:1 UDP:0 ICMP:0 Other:0>
    >>> ans[0]
    (<IP  frag=0 proto=tcp dst=10.10.10.1 |<TCP  dport=http |<Raw  load='Open Port' |>>>,   
    <IP  version=4L ihl=5L tos=0x0 len=40 id=31 flags= frag=0L ttl=64 proto=tcp   
    chksum=0x5a98 src=10.10.10.1 dst=10.0.2.15 options=[] |<TCP  sport=http dport=ftp_data 
    seq=448001 ack=2 dataofs=5L reserved=0L flags=A window=65535 chksum=0xb94d 
    urgptr=0 |<Padding  load='\x00\x00\x00\x00\x00\x00' |>>>)
    >>> packet = IP(dst=["10.10.10.101","10.10.10.251"])/ICMP()/"Ping"
    >>> ans,uans = sr(packet)
    Begin emission:
    .Finished to send 2 packets.
    **
    Received 3 packets, got 2 answers, remaining 0 packets
    >>> ans
    <Results: TCP:0 UDP:0 ICMP:2 Other:0>
    >>> uans
    <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>
    
Now let's access the elements within this list. We would do this just like any other list in Python. The integer within the brackets represents the index of the stimuli/response pair we would like to access. In this example the first pair is an echo request (Type 8 Code 0) and the echo reply (Type 0) to the original request.

    >>> ans[0]
    (<IP  frag=0 proto=icmp dst=10.10.10.101 |<ICMP  |<Raw  load='Ping' |>>>, <IP  
    version=4L ihl=5L tos=0x0 len=32 id=55 flags= frag=0L ttl=63 proto=icmp chksum=0x5b29 
    src=10.10.10.101 dst=10.0.2.15 options=[] |<ICMP  type=echo-reply code=0 chksum=0x412f 
    id=0x0 seq=0x0 |<Raw  load='Ping' |<Padding  load='\x00\x00\x00\x00\x00\x00\x00\x00\x00
    \x00\x00\x00\x00\x00' |>>>>)

The second pair is the echo request and the ICMP destination unreachable/host unreachable from the gateway (Type 3 Code 1). The host 10.10.10.251 does not exist which is why this response is generated. We are only concerned with these responses for this illustration so the IP and ICMP errors can be ignored at this time.

    >>> ans[1]
    (<IP  frag=0 proto=icmp dst=10.10.10.251 |<ICMP  |<Raw  load='Ping' |>>>, <IP   
    version=4L ihl=5L tos=0xc0 len=60 id=56 flags= frag=0L ttl=63 proto=icmp 
    chksum=0x5a4c src=10.10.10.101 dst=10.0.2.15 options=[] |<ICMP  type=dest-unreach 
    code=host-unreachable chksum=0xdd1f unused=0 |<IPerror  version=4L ihl=5L tos=0x0 
    len=8192 id=256 flags=  frag=0L ttl=63 proto=icmp chksum=0x59c9 src=10.0.2.15
    dst=10.10.10.251 options=[]
    |<ICMPerror  type=echo-request code=0 chksum=0x392f id=0x0 seq=0x0 |<Raw  load='Ping'
    |>>>>>)


The list that is returned as a result of this function is actually multidimensional. We will only explore the first two dimensions but it should be noted that there are many more. Each dimension allows us to dive down deeper in to the packet. We will access the second dimension in the following example. This index relates to the particular transaction that we are interested in accessing within the pair. In this case we will be examining the second ICMP request that was sent to host 10.10.10.251.

 
   >>> ans[1][0]
    <IP  frag=0 proto=icmp dst=10.10.10.251 |<ICMP  |<Raw  load='Ping' |>>>
    >>> ans[1][0].type
    8
    >>> ans[1][0].code
    0

sr1()

The sr1() function is another variation of the send() function. This function listens for a single response to a packet that has been sent. This is quite useful if we are only concerned with the first response to a packet that has been sent. Unanswered packets are stored within a list as well.


     >>> packet = IP(dst="10.10.10.1")/TCP(dport=22,flags="S")

     >>> ans = sr1(packet)
     Begin emission:
     Finished to send 1 packets.
     *
     Received 1 packets, got 1 answers, remaining 0 packets

We define a packet that is destined for TCP port 22 (Secure Socket Layer) and has a TCP flag that is set to 'S' for SYN. This should initiate the beginning of the TCP handshake and elicit a SYN/ACK flag from the destination host if the port is open.

     >>> ans
     <IP  version=4L ihl=5L tos=0x0 len=44 id=86 flags= frag=0L ttl=64 proto=tcp
     chksum=0x5a5d src=10.10.10.1 dst=10.0.2.15 options=[] |<TCP  sport=ssh dport=ftp_data
     seq=10624001 ack=1 dataofs=6L reserved=0L flags=SA window=65535 chksum=0x5b2f
     urgptr=0 options=[('MSS', 1460)] |<Padding  load='\x00\x00' |>>>
     >>> ans[0][1]
     <TCP  sport=ssh dport=ftp_data seq=10624001 ack=1 dataofs=6L reserved=0L flags=SA
     window=65535 chksum=0x5b2f urgptr=0 options=[('MSS', 1460)] |<Padding  load='\x00\x00'
     |>>
     >>> ans[0][1].flags
     18L



Keep in mind that the variable 'ans' stores the response to the 'packet' that was sent. 'ans[0][1]' specifies the second layer in this response packet. In our case, this would be the TCP layer. When we request the 'flags' field we are presented with the integer '18'. This is the combined value of the TCP flags (ACK = 16, SYN = 2).


srp()

The srp() function is used when sending a layer 2 (data link layer) datagram packet and will listen for responses in much the same way as the previous function does.

     >>> datagram =  
     Ether(src="00:00:00:12:34:ab",dst="14:da:e9:01:3e:23")/IP(dst="10.10.10.101")
     >>> sendp(datagram)
     .
     Sent 1 packets.

This function must be used if the Ether() layer has been defined within a packet. If a function such as send() is used, a warning will be produced indicating that there is an issue.

     >>> send(datagram)
     WARNING: Mac address to reach destination not found. Using broadcast.
     .
     Sent 1 packets.

Now that we have a foundation for crafting and transmitting packets, we can look in to actually reading packets from a packet capture file. This is a tremendously helpful capability when attempting to reproduce activity such as an exploit or attack on a network. We will explore these techniques as well as a few other in next post.

No comments:

Post a Comment