Ir para conteúdo
  • Cadastre-se
Entre para seguir isso  
gnoo

Análise de pacotes na prática [ Parte 3 ] - "TCP" - Raw Sockets python

Posts Recomendados

Saudações,

Este conteúdo está sujeito a erros de interpretação por parte da minha pessoa, se vires algum erro ou achas que tens algo a acrescentar deixa nos comentários para ser corrigido/adicionado...

Os conceitos de redes aqui apresentados foram retirados do livro:

 

 

antes de começar a falar sobre pacotes TCP, temos que fazer uma pequena alteração para complemetar o código anterior na função em que tratamos os dados de um pacote Ipv4, com uma linha de código que vai calcular o tamanho do pacote Ipv4 em bytes, valor esse que serve para fazer o fatiamento da carga que vai ser passada como argumento para a função, que trata os dados do pacote TCP, tendo também que retornar o valor da carga com o fatiamento referido.

Eu não vou fazer a alteração no código do post anterior, porque se a intenção for apenas analisar um pacote Ipv4 o código anterior faz essa tarefa, mas para analisar o pacote TCP tem que ser feita essa pequena alteração que vamos ver agora… Na linha  tamanho_header_bytes = (versao & 15) * 4 e retornando o valor da carga com o devido fatiamento, carga[tamanho_header_bytes:]

 

Alteração da Função trata dados pacote Ipv4

def dados_pacote_ipv4(carga):
    tupla_dados_ipv4 = struct.unpack("!BBHHHBBH4s4s", carga[:20])
    versao = tupla_dados_ipv4[0]
    header_len = versao >> 4
    tipo_servico = tupla_dados_ipv4[1]
    tamanho_total = tupla_dados_ipv4[2]
    identificacao = tupla_dados_ipv4[3]
    offset_fragmento = tupla_dados_ipv4[4]
    tempo_vida_ttl = tupla_dados_ipv4[5]
    protocolos = tupla_dados_ipv4[6]
    checksum_cabecalho = tupla_dados_ipv4[7]
    ip_origem = inet_ntoa(tupla_dados_ipv4[8])
    ip_destino = inet_ntoa(tupla_dados_ipv4[9])

    # Esta linha é a alteração efetuada do tópico anterior  
    tamanho_header_bytes = (versao & 15) * 4 # Cálculo Tamanho do header em bytes

    return versao, header_len, tipo_servico, + \
           tamanho_total, identificacao, offset_fragmento, + \
           tempo_vida_ttl, protocolos, checksum_cabecalho, ip_origem, ip_destino, carga[tamanho_header_bytes:] 

Agora segue o script do tópico anterior completo com as devidas alterações para análise do pacote TCP.

#coding:utf-8  
#!/usr/bin/env python3

from socket import *
import struct
import binascii

# AF_PACKET Familia protocolo (everywere)
# ntohs(3) - Permite capturar todos os pacotes com frames ethernet

def ethernet_frame(raw_dados):
    
    mac_destino, mac_fonte, protocolo = struct.unpack('! 6s 6s H', raw_dados[:14])

    return byte_to_hex_mac(mac_destino), byte_to_hex_mac(mac_fonte), htons(protocolo), raw_dados[14:]




def byte_to_hex_mac(mac_em_bytes):
    endereco = binascii.hexlify(mac_em_bytes).decode("ascii")
    return ":".join([endereco[i:i+2] for i in range(0,12,2)])


def dados_pacote_ipv4(carga):
    tupla_dados_ipv4 = struct.unpack("!BBHHHBBH4s4s", carga[:20])
    versao = tupla_dados_ipv4[0]
    header_len = versao >> 4
    tipo_servico = tupla_dados_ipv4[1]
    tamanho_total = tupla_dados_ipv4[2]
    identificacao = tupla_dados_ipv4[3]
    offset_fragmento = tupla_dados_ipv4[4]
    tempo_vida_ttl = tupla_dados_ipv4[5]
    protocolos = tupla_dados_ipv4[6]
    checksum_cabecalho = tupla_dados_ipv4[7]
    ip_origem = inet_ntoa(tupla_dados_ipv4[8])
    ip_destino = inet_ntoa(tupla_dados_ipv4[9])

    # correção  
    tamanho_header_bytes = (versao & 15) * 4 # Cálculo Tamanho do header em bytes

    return versao, header_len, tipo_servico, + \
           tamanho_total, identificacao, offset_fragmento, + \
           tempo_vida_ttl, protocolos, checksum_cabecalho, ip_origem, ip_destino, carga[tamanho_header_bytes:] 
    


sock = socket(AF_PACKET, SOCK_RAW, ntohs(0x0003))    

while True:
    raw_dados, addr = sock.recvfrom(65536)
    mac_destino, mac_fonte, protocolo, carga_util = ethernet_frame(raw_dados)

    print("------- FRAME ETHERNET-----------")
    print("Endereço MAC destino : {}".format(mac_destino))
    print("Endereço MAC fonte : {}".format(mac_fonte))
    print("Protocolo : {}\n".format(protocolo))
    print("Carga útil -> {}\n".format(carga_util))

    # A variavel (protocolo) está a referir-se ao
    # tipo de frame
    # Se for 8 vem ai pacote IP \0/
    if protocolo == 8:
        """
        Para desempacotar os valores da função dados_pacote_ipv4
        com as variáveis aninhadas, necessáriamente elas têm que estar entre
        parênteses...
        """
        ( versao, header_len, tipo_servico,
        tamanho_total, identificacao, offset_fragmento,
        tempo_vida_ttl, protocolos, checksum_cabecalho,
         ip_origem, ip_destino, carga ) = dados_pacote_ipv4(carga_util)
        
        
        print("--------- HEADER IPV4----------")  
        print("Versão : {}".format(versao))
        print("Header_len : {}".format(header_len))
        print("Tipo Serviço : {}".format(tipo_servico))
        print("Tamanho Total : {}".format(tamanho_total))
        print("Identificação : {}".format(identificacao))
        print("Offset Fragmento : {}".format(offset_fragmento))
        print("Tempo vida TTL : {}".format(tempo_vida_ttl))
        print("Protocolo : {}".format(protocolos))
        print("checksum Cabeçalho : {}".format(checksum_cabecalho))
        print("IP Origem: {}".format(ip_origem))
        print("IP Destino: {}\n".format(ip_destino))
        print("Carga : {}\n".format(carga))

 

Agora que já temos as alterações feitas, antes de avançar e fazer a função que trata os dados do pacote TCP, vamos ver algumas caracteristicas desse mesmo pacote.

 

ATENÇÃO: O conteúdo que segue deve ser complementado com a leitura de um livro da especialidade

Formato do segmento TCP

Citar

A unidade de transferência entre o TCP nas duas máquinas é chamada de segmento. Os segmentos são trocados para se estabelecer conexões, transferir dados, enviar confirmações, anunciar tamanhos de janela e fechar conexões. Como o TCP utiliza o piggybacking, uma confirmação trafegando do computador A para o computador B pode trafegar no mesmo segmento dos dados atravessando do computador A para o computador B, embora a confirmação se refira aos dados enviados de B para A.*** Como a maioria dos protocolos, uma mensagem é dividida em duas partes conceituais: um cabeçalho que contém metadados e uma área de carga útil que leva dados. A Figura 11.7 mostra o formato do segmento TCP.

 

NOTA: O campo (bits de código) entenda-se, flags.

1319346694_Capturadeecr_2018-12-28_12-43-47.png.82a8f5862cb454ebccbe1a5b89c1b321.png

 

Citar

O cabeçalho, conhecido como cabeçalho TCP, consiste de pelo menos 20 octetos e deve conter mais se o segmento carregar opções. O cabeçalho tem a identificação esperada e informações de controle. Os campos PORTA DE ORIGEM e PORTA DE DESTINO contêm os números de porta TCP que identificam os programas aplicativos nas extremidades da conexão. O campo NÚMERO DE SEQUÊNCIA identifica a posição no fluxo de bytes do emissor dos dados no segmento. O campo NÚMERO DE CONFIRMAÇÃO identifica o número do octeto que a origem espera receber em seguida. Observe que o número de sequência se refere ao fluxo que segue na mesma direção do segmento, enquanto o número de confirmação refere-se ao fluxo que segue na direção oposta do segmento. O campo HLEN* contém um inteiro que especifica o tamanho do cabeçalho do segmento medido em múltiplos de 32 bits. Ele é necessário porque o campo OPÇÕES varia em tamanho, dependendo de quais opções foram incluídas. Assim, o tamanho do cabeçalho TCP varia dependendo

das opções selecionadas. O campo de 6 bits marcado como RESERVADO é reservado para uso futuro (outra seção descreve um uso proposto). Alguns segmentos transportam apenas uma confirmação, enquanto outros transportam dados. Já outros, ainda, transportam requisições para estabelecer ou fechar uma conexão. O TCP utiliza um campo de 6 bits rotulado como BITS DE CÓDIGO para determinar a finalidade e o conteúdo do segmento. Os seis bits dizem como interpretar outros campos no cabeçalho, de acordo com a tabela na Figura 11.8. O TCP anuncia quantos dados ele espera aceitar toda vez que envia um segmento, especificando seu tamanho de buffer no campo JANELA. O campo contém um inteiro não sinalizado de 16 bits na ordem de bytes padrão da rede. Anúncios de janela oferecem outro exemplo de piggybacking, pois acompanham todos os segmentos, incluindo aqueles transportando dados e também aqueles carregando apenas uma confirmação.

847612960_Capturadeecr_2018-12-29_16-42-42.png.9381140de2fdfd8996731cb0a1f21a2b.png

 

NOTA: de forma a compreender melhor os valores da variável ( flags ), que aparece na função que vamos fazer a seguir deves ler a matéria que segue neste link

https://www.manitonetworks.com/flow-management/2016/10/16/decoding-tcp-flags

 

Função para tratar os dados do cabeçalho TCP

def dados_pacote_tcp(carga):
    tupla_dados_tcp = struct.unpack('! HHLLBBHHH', carga[:20])
    porta_fonte = tupla_dados_tcp[0]
    porta_destino = tupla_dados_tcp[1]
    numero_sequencia = tupla_dados_tcp[2]
    numero_confirmacao = tupla_dados_tcp[3]

    # Esta linha recebe dois campos do cabeçalho ( HLEN & RESERVADO )
    hlen_e_reservado = tupla_dados_tcp[4]

    # Esta linha recebe campo HLEN 
    header_len = (hlen_e_reservado >> 4) * 4
    
    # variável flags
    flags = tupla_dados_tcp[5]
    
    flag_FIN = flags & 1
    flag_SYN = (flags >> 1) & 1
    flag_RST = (flags >> 2) & 1
    flag_PSH = (flags >> 3) & 1
    flag_ACK = (flags >> 4) & 1
    flag_URG = (flags >> 5) & 1
    flag_ECE = (flags >> 6) & 1
    flag_CWR = (flags >> 7) & 1

    window_size = tupla_dados_tcp[6]
    checksum = tupla_dados_tcp[7]
    urgent_pointer = tupla_dados_tcp[8]
    
    return porta_fonte, porta_destino ,numero_sequencia, + \
           numero_confirmacao, hlen_e_reservado, header_len, + \
           flags, flag_FIN, flag_SYN, flag_RST, flag_PSH, flag_ACK, + \
           flag_URG, flag_ECE, flag_CWR, window_size, checksum, urgent_pointer 

 

Script Completo....

#coding:utf-8  
#!/usr/bin/env python3

from socket import *
import struct
import binascii

# AF_PACKET Familia protocolo (everywere)
# ntohs(3) - Permite capturar todos os pacotes com frames ethernet

def ethernet_frame(raw_dados):
    
    mac_destino, mac_fonte, protocolo = struct.unpack('! 6s 6s H', raw_dados[:14])

    return byte_to_hex_mac(mac_destino), byte_to_hex_mac(mac_fonte), htons(protocolo), raw_dados[14:]




def byte_to_hex_mac(mac_em_bytes):
    endereco = binascii.hexlify(mac_em_bytes).decode("ascii")
    return ":".join([endereco[i:i+2] for i in range(0,12,2)])


def dados_pacote_ipv4(carga):
    tupla_dados_ipv4 = struct.unpack("!BBHHHBBH4s4s", carga[:20])
    versao = tupla_dados_ipv4[0]
    header_len = versao >> 4
    tipo_servico = tupla_dados_ipv4[1]
    tamanho_total = tupla_dados_ipv4[2]
    identificacao = tupla_dados_ipv4[3]
    offset_fragmento = tupla_dados_ipv4[4]
    tempo_vida_ttl = tupla_dados_ipv4[5]
    protocolos = tupla_dados_ipv4[6]
    checksum_cabecalho = tupla_dados_ipv4[7]
    ip_origem = inet_ntoa(tupla_dados_ipv4[8])
    ip_destino = inet_ntoa(tupla_dados_ipv4[9])

    # correção  
    tamanho_header_bytes = (versao & 15) * 4 # Cálculo Tamanho do header em bytes

    return versao, header_len, tipo_servico, + \
           tamanho_total, identificacao, offset_fragmento, + \
           tempo_vida_ttl, protocolos, checksum_cabecalho, ip_origem, ip_destino, carga[tamanho_header_bytes:] 
    

def dados_pacote_tcp(carga):
    tupla_dados_tcp = struct.unpack('! HHLLBBHHH', carga[:20])
    porta_fonte = tupla_dados_tcp[0]
    porta_destino = tupla_dados_tcp[1]
    numero_sequencia = tupla_dados_tcp[2]
    numero_confirmacao = tupla_dados_tcp[3]

    # Esta linha recebe dois campos do cabeçalho ( HLEN & RESERVADO )
    hlen_e_reservado = tupla_dados_tcp[4]

    # Esta linha recebe campo HLEN 
    header_len = (hlen_e_reservado >> 4) * 4
    
    flags = tupla_dados_tcp[5]
    flag_FIN = flags & 1
    flag_SYN = (flags >> 1) & 1
    flag_RST = (flags >> 2) & 1
    flag_PSH = (flags >> 3) & 1
    flag_ACK = (flags >> 4) & 1
    flag_URG = (flags >> 5) & 1
    flag_ECE = (flags >> 6) & 1
    flag_CWR = (flags >> 7) & 1

    window_size = tupla_dados_tcp[6]
    checksum = tupla_dados_tcp[7]
    urgent_pointer = tupla_dados_tcp[8]
    
    return porta_fonte, porta_destino ,numero_sequencia, + \
           numero_confirmacao, hlen_e_reservado, header_len, + \
           flags, flag_FIN, flag_SYN, flag_RST, flag_PSH, flag_ACK, + \
           flag_URG, flag_ECE, flag_CWR, window_size, checksum, urgent_pointer 

sock = socket(AF_PACKET, SOCK_RAW, ntohs(0x0003))    

while True:
    raw_dados, addr = sock.recvfrom(65536)
    mac_destino, mac_fonte, protocolo, carga_util = ethernet_frame(raw_dados)

    print("------- FRAME ETHERNET-----------")
    print("Endereço MAC destino : {}".format(mac_destino))
    print("Endereço MAC fonte : {}".format(mac_fonte))
    print("Protocolo : {}\n".format(protocolo))
    print("Carga útil -> {}\n".format(carga_util))

    # A variavel (protocolo) está a referir-se ao
    # tipo de frame
    # Se for 8 vem ai pacote IP \0/
    if protocolo == 8:
        """
        Para desempacotar os valores da função dados_pacote_ipv4
        com as variáveis aninhadas, necessáriamente elas têm que estar entre
        parênteses...
        """
        ( versao, header_len, tipo_servico,
        tamanho_total, identificacao, offset_fragmento,
        tempo_vida_ttl, protocolos, checksum_cabecalho,
         ip_origem, ip_destino, carga ) = dados_pacote_ipv4(carga_util)
        
        
        print("--------- HEADER IPV4----------")  
        print("Versão : {}".format(versao))
        print("Header_len : {}".format(header_len))
        print("Tipo Serviço : {}".format(tipo_servico))
        print("Tamanho Total : {}".format(tamanho_total))
        print("Identificação : {}".format(identificacao))
        print("Offset Fragmento : {}".format(offset_fragmento))
        print("Tempo vida TTL : {}".format(tempo_vida_ttl))
        print("Protocolo : {}".format(protocolos))
        print("checksum Cabeçalho : {}".format(checksum_cabecalho))
        print("IP Origem: {}".format(ip_origem))
        print("IP Destino: {}\n".format(ip_destino))
        print("Carga : {}\n".format(carga))

        if protocolos == 6:
            (porta_fonte, porta_destino, numero_sequencia, numero_confirmacao,
             hlen_e_reservado, header_len, flags, flag_FIN, flag_SYN, flag_RST,
             flag_PSH, flag_ACK, flag_URG, flag_ECE, flag_CWR, window_size, checksum,
             urgent_pointer ) = dados_pacote_tcp(carga)

            print("--------- HEADER TCP----------")
            print("Porta Fonte : {}".format(porta_fonte))
            print("Porta Destino : {}".format(porta_destino))
            print("Número Sequência : {}".format(numero_sequencia))
            print("Número Confirmação : {}".format(numero_confirmacao))
            print("Hlen_e_reservado : {}".format(hlen_e_reservado))
            print("Header_len : {}".format(header_len))
            print("Flags : {}".format(flags))
            print("Flag FIN : {}".format(flag_FIN))
            print("Flag SYN : {}".format(flag_SYN))
            print("Flag RST : {}".format(flag_RST))
            print("Flag PSH : {}".format(flag_PSH))
            print("Flag ACK : {}".format(flag_ACK))
            print("Flag URG : {}".format(flag_URG))
            print("Flag ECE : {}".format(flag_ECE))
            print("Flag CWR : {}".format(flag_CWR))
            print("window size : {}".format(window_size))
            print("checksum : {}".format(checksum))
            print("urgent pointer : {}".format(urgent_pointer))

"""
socket.ntohs( x ) 
Converte inteiros positivos de 16 bits da rede para a ordem de bytes do host.
Nas máquinas em que a ordem de bytes do host é igual à ordem de bytes da rede,
isso é um não operacional; caso contrário, ele executará uma operação de troca
de 2 bytes.
------------------------------------------------------------------------------

socket.htons( x ) 
Converte inteiros positivos de 16 bits da ordem de bytes do host para a rede.
Nas máquinas em que a ordem de bytes do host é igual à ordem de bytes da rede,
isso é um não operacional; caso contrário, ele executará uma operação de troca
de 2 bytes.
"""

 

Em seguida vamos fazer o  UDP…

Abraço.

 

 

 

 

Editado por gnoo
  • l33t 1

Compartilhar este post


Link para o post
Compartilhar em outros sites

Crie uma conta ou entre para comentar

Você precisar ser um membro para fazer um comentário

Criar uma conta

Crie uma nova conta em nossa comunidade. É fácil!

Crie uma nova conta

Entrar

Já tem uma conta? Faça o login.

Entrar Agora
Entre para seguir isso  

  • Quem Está Navegando   0 membros estão online

    Nenhum usuário registrado visualizando esta página.

×