Criando Pacotes com Scapy

Como falamos anteriormente, pacotes de rede são construídos em camadas, e cada camada superior é encapsulada pela camada anterior. O jeito mais fácil de entendermos como montar um pacote no Scapy é utilizando o método command() em um pacote que já capturamos.

Método command()

Através deste método, conseguiremos entender melhor como é possível criar um pacote utilizando o Scapy, pois ele irá replicar os comandos necessários para que possamos realizar esta função.

Vamos novamente utilizar a função sniff() e capturar um pacote TCP desta vez, depois vamos descobrir como recriar este pacote no Scapy.

>>> pkt = sniff(count=3, filter='tcp', prn=Packet.summary)
Ether / IP / TCP 192.168.163.132:34710 > 69.163.224.44:https S
Ether / IP / TCP 69.163.224.44:https > 192.168.163.132:34710 SA / Padding
Ether / IP / TCP 192.168.163.132:34710 > 69.163.224.44:https A

Capturamos um Three Way Handshake do protocolo TCP, vamos agora descobrir como recriar o primeiro pacote deste procedimento, o TCP SYN.

>>> pkt[0].command()
"Ether(dst='00:50:56:ed:5e:7f', src='00:0c:29:28:a7:a4', type=2048)/IP(version=4, ihl=5, tos=0, len=60, id=107, flags=2, frag=0, ttl=64, proto=6, chksum=45140, src='192.168.163.132', dst='69.163.224.44', options=[])/TCP(sport=34710, dport=443, seq=2709857470, ack=0, dataofs=10, reserved=0, flags=2, window=29200, chksum=35371, urgptr=0, options=[('MSS', 1460), ('SAckOK', b''), ('Timestamp', (647246920, 0)), ('NOP', None), ('WScale', 7)])"

Não se assustem com a quantidade de parâmetros que obtivemos através do método command(), esses parâmetros estão todos preenchidos pois capturamos um pacote da rede, a criação é muito mais simples, pois o Scapy nos ajuda inferindo valores automaticamente, como veremos mais à frente.

Reparem que cada camada é chamada como se fosse uma função e como toda função, ela pode receber parâmetros.

A camada 2 representada pelo protocolo Ethernet, por exemplo, é iniciada com os parâmetros abaixo:

  • dst - MAC Address de destino
  • src - MAC Address de origem
  • type - 2048 (0x0800 em Hexadecimal)

Portanto, um pacote camada 2, cujo protocolo é Ethernet pode ser criado de maneira simples!

Ether(dst='00:50:56:ed:5e:7f', src='00:0c:29:28:a7:a4', type=2048)

Para realizarmos o encapsulamento de camadas no Scapy, utilizamos o carácter "/", tornando esta tarefa extremamente simples. Portanto, para um pacote IP, teremos o seguinte comando:

>>> ip_packet = Ether() / IP()

Montando o pacote em camadas

O objetivo é montarmos dois pacotes, pois queremos realizar um TCP Handshake com o blog.thepacketwizards.com, portanto vamos montar o pacote TCP SYN e o TCP ACK, uma vez que o pacote TCP SYNACK deverá ser enviado pelo nosso blog.

TCP Three-Way Handshake
TCP Handshake

Camada 2 - Ethernet

Sempre precisamos montar o pacote da camada inferior até a superior, portanto inicializaremos nosso pacote TCP SYN com a camada Ether().

>>> eth = Ether()

Lembram-se do método show2() que introduzimos na parte 4? Vamos utilizá-lo aqui para aprendermos quais são os valores padrões que o Scapy usa para o protocolo Ethernet.

>>> eth.show2()
WARNING: Mac address to reach destination not found. Using broadcast.
###[ Ethernet ]### 
  dst= ff:ff:ff:ff:ff:ff
  src= 00:0c:29:28:a7:a4
  type= 0x9000

Temos um warning, pois o Scapy na hora de popular os valores não conseguiu encontrar um MAC Address de destino, porém ele já populou o MAC Address de origem e também o type! Agora que já criamos a camada 2, podemos incluir mais uma camada no nosso pacote, o protocolo de camada 3, IP.

Camada 3 - Internet Protocol

>>> ip = eth / IP(dst='blog.thepacketwizards.com')
>>> ip.show2()
###[ Ethernet ]### 
  dst= 00:50:56:ed:5e:7f
  src= 00:0c:29:28:a7:a4
  type= 0x800
###[ IP ]### 
     version= 4
     ihl= 5
     tos= 0x0
     len= 20
     id= 1
     flags= 
     frag= 0
     ttl= 64
     proto= hopopt
     chksum= 0xf0ec
     src= 192.168.163.132
     dst= 69.163.224.44
     \options  

Você deve ter percebido que utilizamos um domínio como parâmetro para a variável "dst" na função IP(), pois é, o Scapy nos da mais essa ferramenta, podemos utilizar um nome como parâmetro uma vez que o Scapy ao montar o pacote irá utilizar o DNS para traduzir este nome à um IP.

Utilizando novamente o método show2() podemos perceber que algumas coisas mudaram, por exemplo, a camada Ethernet foi preenchida com o MAC Address de destino e o parâmetro type também foi modificado de 0x0900 para 0x0800.

Quando o campo type do protocolo Ethernet é 0x0800, isso indica que o protocolo de camada superior será o IP, portanto conseguimos demonstrar que o Scapy consegue inferir as informações de cada camada dada as informações da camada superior. Isto facilita muito nosso trabalho na hora de criar nossos pacotes.

Vamos à nossa última camada para este pacote!

Camada 4 - Transport Control Protocol

>>> tcp = ip / TCP()
>>> tcp.show2()
###[ Ethernet ]### 
  dst= 00:50:56:ed:5e:7f
  src= 00:0c:29:28:a7:a4
  type= 0x800
###[ IP ]### 
     version= 4
     ihl= 5
     tos= 0x0
     len= 40
     id= 1
     flags= 
     frag= 0
     ttl= 64
     proto= tcp
     chksum= 0xf0d2
     src= 192.168.163.132
     dst= 69.163.224.44
     \options  
###[ TCP ]### 
        sport= ftp_data
        dport= http
        seq= 0
        ack= 0
        dataofs= 5
        reserved= 0
        flags= S
        window= 8192
        chksum= 0x582
        urgptr= 0
        options= []

Como acontece nas outras camadas, a camada 4 também possui valores padrões. Queremos modificar alguns desses valores, tais como a sequência, porta de origem e porta de destino.

Uma vez que o pacote já foi criado, podemos acessar as camadas tratando o objeto do pacote como um dicionário python. Vamos modificar a porta de origem para o valor 30001.

>>> tcp[TCP].sport = 30001

Sabemos que o nosso Blog possui serviço HTTPS, portanto vamos modificar também a porta de destino para 443, já aproveitando essa modificação, vamos colocar um valor aleatório para a sequência.

>>> tcp[TCP].dport = 443
>>> tcp[TCP].seq = RandInt()

Vamos olhar como ficou nosso pacote agora.

>>> tcp.show2()
###[ Ethernet ]### 
  dst= 00:50:56:ed:5e:7f
  src= 00:0c:29:28:a7:a4
  type= 0x800
###[ IP ]### 
     version= 4
     ihl= 5
     tos= 0x0
     len= 40
     id= 1
     flags= 
     frag= 0
     ttl= 64
     proto= tcp
     chksum= 0xf0d2
     src= 192.168.163.132
     dst= 69.163.224.44
     \options  
###[ TCP ]### 
        sport= 30001
        dport= https
        seq= 917576932
        ack= 0
        dataofs= 5
        reserved= 0
        flags= S
        window= 8192
        chksum= 0x3b64
        urgptr= 0
        options= []

Enfim, temos um pacote TCP SYN criado e já podemos iniciar nosso envio deste pacote para que possamos fazer o TCP Handshake com o servidor do blog.

Enviando e Recebendo Pacotes de rede com o Scapy

O Scapy possui algumas funções para enviarmos pacotes na rede.

Podemos não só enviar, como também receber pacotes, e com o Scapy esta tarefa se torna simples.

Enviando Pacotes

É possível enviarmos pacotes tal como um Switch , utilizando apenas a camada 2 (Endereço MAC), ou tal como um Roteador, utilizando a camada 3 (Endereço IP).

Como criamos o pacote TCP SYN cuja camada inferior é a camada 2, iremos utilizar a função sendp() para enviá-lo. Esta função realiza o envio do pacote via camada 2.

>>> sendp(tcp)
.
Sent 1 packets.

Simples assim! Porém, para fazermos um TCP Handshake, que afinal é o objetivo deste tutorial, precisamos também receber um pacote de resposta.

Recebendo Pacotes

Apenas enviar o pacote já é por si só, muito interessante, pois como vimos, com o Scapy esta tarefa é muito fácil de ser executada.

Para receber o pacote, não é diferente. Basta utilizarmos a função srp(), esta função irá enviar e receber via camada 2.

Uma vez que estamos enviando um pacote TCP SYN, esperamos receber um pacote TCP ACK. O Scapy já sabe isso, portanto não precisamos fazer nada além de enviar nosso pacote com a função especificada.

>>> ans, uans = srp(tcp)
Begin emission:
.Finished sending 1 packets.
*
Received 2 packets, got 1 answers, remaining 0 packets
>>> ans
<Results: TCP:1 UDP:0 ICMP:0 Other:0>
>>> uans
<Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>

Passamos duas variáveis para desempacotar a saída da função srp(), isso se deve ao fato desta função nos retornar duas listas, uma lista de pacote que correspondem à resposta ao nosso envio e uma lista de pacotes capturados enquanto o Scapy esperava esta resposta.

Vamos olhar o pacote que enviamos e o pacote que recebemos como resposta.

# Pacote Enviado pelo Scapy
>>> ans[0][0].show()       
###[ Ethernet ]###         
  dst= 00:50:56:fa:d5:f8   
  src= 00:0c:29:28:a7:a4   
  type= 0x800              
###[ IP ]###               
     version= 4            
     ihl= None             
     tos= 0x0              
     len= None             
     id= 1                 
     flags=                
     frag= 0               
     ttl= 64               
     proto= tcp            
     chksum= None          
     src= 192.168.163.132  
     dst= 69.163.224.44    
     \options\             
###[ TCP ]###              
        sport= 30001       
        dport= https       
        seq= 3366717493    
        ack= 0             
        dataofs= None      
        reserved= 0        
        flags= S           
        window= 8192       
        chksum= None       
        urgptr= 0          
        options= []

# Pacote Recebido pelo Scapy    
>>> ans[0][1].show()            
###[ Ethernet ]###              
  dst= 00:0c:29:28:a7:a4        
  src= 00:50:56:fa:d5:f8        
  type= 0x800                   
###[ IP ]###                    
     version= 4                 
     ihl= 5                     
     tos= 0x0                   
     len= 44                    
     id= 18963                  
     flags=                     
     frag= 0                    
     ttl= 128                   
     proto= tcp                 
     chksum= 0x66bc             
     src= 69.163.224.44         
     dst= 192.168.163.132       
     \options\                  
###[ TCP ]###                   
        sport= https            
        dport= 30001            
        seq= 788811887          
        ack= 3366717494         
        dataofs= 6              
        reserved= 0             
        flags= SA               
        window= 64240           
        chksum= 0x4be6          
        urgptr= 0               
        options= [('MSS', 1460)]
###[ Padding ]###               
           load= '\x00\x00'     

Funcionou! Enviamos um pacote TCP SYN, ou seja, um pacote cuja camada superior é a camada TCP e cuja FLAG TCP é igual à S (SYN). Em retorno à este estimulo, recebemos um pacote TCP SYNACK, cujas FLAGs são S e A (SYN e ACK).

É importante repararmos nos valores de SEQ e ACK dos pacotes. O pacote TCP SYNACK possui o parâmetro ack igual ao parâmetro seq do TCP SYN com a adição de 1.

>>> tcp_syn = ans[0][0]
>>> tcp_synack = ans[0][1]
>>> tcp_syn[TCP].seq
3366717493
>>> tcp_synack[TCP].ack
3366717494

Para finalizarmos o TCP Handshake, ainda falta um passo. Precisamos enviar um pacote ACK em respota ao pacote SYNACK.

O pacote ACK possui três características, o parâmetro flags, precisa ser igual à 'A' (ACK), o parâmetro seq precisa ser igual ao parâmetro ack recebido no pacote TCP SYNACK e o parâmetro ack precisa ser igual ao parâmetro seq do pacote TCP SYNACK adicionado de 1.

>>> tcp_ack = Ether() / IP(dst='blog.thepacketwizards.com') / TCP(flags='A', seq = tcp_synack[TCP].ack, ack = tcp_synack[TCP].seq + 1)
>>> tcp_ack.show2()
###[ Ethernet ]### 
  dst= 00:50:56:fa:d5:f8
  src= 00:0c:29:28:a7:a4
  type= 0x800
###[ IP ]### 
     version= 4
     ihl= 5
     tos= 0x0
     len= 40
     id= 1
     flags= 
     frag= 0
     ttl= 64
     proto= tcp
     chksum= 0xf0d2
     src= 192.168.163.132
     dst= 69.163.224.44
     \options  
###[ TCP ]### 
        sport= ftp_data
        dport= http
        seq= 3366717494
        ack= 788811888
        dataofs= 5
        reserved= 0
        flags= A
        window= 8192
        chksum= 0xb51c
        urgptr= 0
        options= []

>>> 

Não podemos esquecer de alterar a porta de origem e destino! Pois elas precisam ser as mesmas que utilizamos no pacote TCP SYN.

>>> tcp_ack[TCP].dport = 443
>>> tcp_ack[TCP].sport = 30001

Agora que já aprendemos como montar cada pacote do TCP Handshake, que tal utilizarmos uma função que será mais adequada ao nosso objetivo para enviar e receber este pacote?

Função srp1()

A função srp1() envia um pacote e retorna apenas a resposta como saída, portanto ficará mais fácil montar o TCP Handshake desta maneira.

>>> SYN = Ether() / IP(dst='blog.thepacketwizards.com') / TCP(sport=30001, dport=443, seq=RandInt(), flags='S')
>>> SYNACK = srp1(SYN)
>>> ACK = Ether() / IP(dst='blog.thepacketwizards.com') / TCP(sport=30001, dport=443, seq=SYNACK[TCP].ack, ack=SYNACK[TCP].seq+1, flags='A')
>>> sendp(ACK)

Pronto! Conseguimos realizar nosso TCP Handshake!

Bônus

Como bônus, irei deixar aqui um código utilizando o Scapy como uma biblioteca python, além de enviar o pacote utilizando apenas a camada IP.

Fiquem à vontade para se divertirem com o código abaixo, apenas lembrem-se de executá-lo como root.

# Utilizar a biblioteca OS apenas para garantir que o Scapy se encontra no PATH do sistema
import os
os.sys.path.append('/home/thepacketwizards/PythonEnviroments/tutorial-scapy/lib/python3.6/site-packages')
# Importar todos os pacotes padroes do Scapy
from scapy.all import *

# Criar pacote TCP SYN
SYN = IP(dst='blog.thepacketwizards.com') / TCP(sport=30001, dport=443, flags='S', seq=RandInt())

# Enviar o pacote SYN e Receber o pacote SYNACK como resposta
SYNACK = sr1(SYN, timeout=4, verbose=False)

# Enviar o pacote ACK como resposta ao pacote SYNACK
if SYNACK:
    ACK = IP(dst='blog.thepacketwizards.com') / TCP(sport=30001, dport=443, flags='A', seq=SYNACK[TCP].ack, ack=SYNACK[TCP].seq + 1)
    send(ACK, verbose=False)

    tcp_handshake = [SYN, SYNACK, ACK]
    for pkt in tcp_handshake:
        print(pkt.summary())

Vejamos este script executado:

thepacketwizards@ubuntu:~/Desktop$ sudo python simple_tcp_handshake.py 

IP / TCP 192.168.163.132:30001 > Net('blog.thepacketwizards.com'):https S
IP / TCP 69.163.224.44:https > 192.168.163.132:30001 SA / Padding
IP / TCP 192.168.163.132:30001 > Net('blog.thepacketwizards.com'):https A