Ir para conteúdo

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


Visitante gnoo

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...

 

vamos ver como extrair e analisar dados de um cabeçalho ipv4, existe várias formas de fazer este tipo de operação, esta é a mais fácil fazer e a mais fácil de perceber para quem não tem muita prática ou simplesmente desconhece operadores Bitwise ( Bit-a-Bit ), essa sim, do ponto de vista técnico seria a mais indicada.

Os conceitos de redes aqui apresentados foram retirados do livro:

Capitulo 7

 

Qualquer das formas vamos usar um dos operadores Bitwise que se chama shift right, para extrair os valor do campo header lenght, e por curiosidade vou deixar uma tabela com os operadores bitwise para quem nunca viu, e serve também para se alguma vez encontrares um código que contenha esses operadores já sabes do que se trata, até porque na maioria das vezes os códigos são apenas expostos sem que se perceba ou haja indicação do que ali está e o que faz.

 

Tabela Operadores Bitwise:

2078417329_Capturadeecr_2018-12-21_15-07-05.png.fe74d986deb8820a97558d72f6518960.png

 

Pegando no código anterior apresentado neste tópico:

Se olharmos para o que já temos...foi montado um socket em “escuta”, que não está assente em nenhum protocolo em específico, e estamos a começar a capturar e analisar os frames ethernet que nos chegam e que saem do nosso sistema.

A variável protocolo que na realidade quer dizer tipo de frame, é o que nos vai dizer que tipo de pacotes estão a chegar e em que protocolo se deslocam.

Antes de continuar eu gostaria de dizer que fiz merda porque deveria ter começado pelo protocolo ARP que, mas agora continuamos pelo caminho que está a seguir e seja o que deus quiser.

Então como estava a dizer a variável protocolo diz-nos que tipo de pacote que nos chega e em que protocolo se desloca, neste caso eu já tinha definido que seria o 8, eu imagino que algumas pessoas poderão estar a perguntar, “mas porque o 8?”, “como é que ele chegou ao 8?”.

Isso é uma pergunta muito inteligente de fazer…

O 8, se repararem bem na função ethernet_frame os valores que nos está a retornar, temos lá outra variável protocolo que está a ser passada como argumento para a função htons() da lib socket, e se olharem com atenção no fim do script, está lá num comentário que diz o que essa função faz, vamos relembrar…

Citar

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.

 

Agora vamos ver um exemplo prático para perceber o que está a acontecer naquela função.

Se entrarem nesta página( que poderia ser outra qualquer ), que contém alguns dos tipos de ethernet que existem e estão definidos pela IEEE Registration Authority :

https://en.wikipedia.org/wiki/EtherType

No fim da página temos uma tabela com alguns dos tipos ethernet e os protocolos que representam:

1103101753_Capturadeecr_2018-12-21_15-57-53.png.4dcb9c42946a334ef1f130850d49bc47.png

Então se passarmos os valores no campo EtherType como argumentos na função htons, vai-nos devolver um valor inteiro, vamos ver o resultado…

866339127_Capturadeecr_2018-12-21_16-04-47.png.dbb0e803f084f18ecf69eb8cd977a0c4.png

 

Ok agora já sabemos de onde vem o tal 8, e com isso sabemos que vem de lá um pacote Ipv4, e é isso mesmo que vamos analisar em seguida, mas antes vamos ver algumas características deste tipo de pacote

 

O datagrama IP

Citar

Em uma rede física, a unidade de transferência é um frame que contém um cabeçalho e dados, e esse cabeçalho fornece informações, tais como a origem (física) e endereços de destino. A Internet chama sua unidade de transferência básica de datagrama Internet, geralmente abreviado datagrama IP.

 

Citar

o cabeçalho do datagrama contém metadados, tais como endereços de origem e destino e um campo de tipo que identifica o conteúdo do datagrama. A diferença, claro, é que o cabeçalho do datagrama contém endereços IP, enquanto o cabeçalho do frame contém os endereços de hardware.

1377392949_geraldatagram.png.b212e22ad576bbf9ccfb79336ecb722e.png

Formato do datagrama Ipv4

A Figura 7.3 mostra a disposição de campos em um datagrama Ipv4.

2079449658_ipv4header.png.5c093306601ccebb2990b9202678c787.png

Citar

o primeiro campo de 4 bits num datagrama (VERS) contém a versão do protocolo IP que foi utilizado para criar o datagrama. Assim, para o IPv4, o campo versão contém o valor 4. O campo é utilizado para verificar se o remetente, o receptor e quaisquer roteadores entre eles concordam com o formato do datagrama. Todo software IP é necessário para verificar o campo de versão antes de processar um datagrama para garantir que corresponde ao formato que o software espera. Veremos que, embora o cabeçalho do datagrama IPv6 seja diferente do cabeçalho IPv4, o IPv6 usa também os primeiros quatro bits para um número de versão, tornando possível para um roteador ou computador host distinguir entre as duas versões.

 

Citar

O campo de comprimento do cabeçalho (HLEN), também de 4 bits, dá o comprimento do cabeçalho do datagrama medido em palavras de 32 bits. Como veremos, todos os campos do cabeçalho têm comprimento fixo, exceto para as OPÇÕES DE IP e campos de preenchimento (PADDING) correspondentes. O cabeçalho do datagrama mais comum, que não contém opções e nenhum preenchimento, mede 20 octetos e tem um tamanho de campo de cabeçalho igual a 5. O campo TAMANHO TOTAL dá o comprimento do datagrama IP medido em octetos, incluindo octetos de cabeçalho e de carga útil. O tamanho da área de carga útil pode ser calculado subtraindo o comprimento de cabeçalho (trinta e duas vezes HLEN) do COMPRIMENTO TOTAL. Como o campo de COMPRIMENTO TOTAL é de 16 bits, o tamanho máximo possível de um datagrama IP é 2 16 ou 65.535 octetos

 

Citar

O campo PROTOCOLO é análogo ao campo de tipo em um frame de rede; o valor especifica qual protocolo de alto nível foi utilizado para criar a mensagem transmitida na área de CARGA ÚTIL do datagrama. Emessência, o valor do PROTOCOLO especifica o formato da área de CARGA ÚTIL. O mapeamento entre um protocolo de alto nível e o valor inteiro utilizado no campo PROTOCOLO deve ser administrado por uma autoridade central para garantir acordo em toda a Internet. O campo HEADER CHECKSUM garante a integridade dos valores do cabeçalho. O header checksum IP é formado tratando o cabeçalho como uma sequência de inteiros de 16 bits (em ordem de byte de rede), juntando-os, usando um complemento aritmético e, em seguida, obtendo do resultado o complemento. Para fins de cálculo do checksum, o campo HEADER CHECKSUM é assumido como contendo zeros.

É importante notar que o checksum só se aplica para valores no cabeçalho IP e para a carga. Separar os checksums para cabeçalhos e para cargas apresenta vantagens e desvantagens. Como o cabeçalho geralmente ocupa menos octetos do que a capacidade de carga, ter um checksum separado reduz o tempo de processamento em roteadores que só precisam calcular checksum de cabeçalho. A separação também permite que os protocolos de nível superior escolham seu próprioesquema de checksum para as mensagens que enviam. A principal desvantagem é que protocolos de nível superior são obrigados a adicionar seus próprios despercebida. Campos de ENDEREÇO IP DE ORIGEM e ENDEREÇO IP DE

DESTINO contêm os endereços IP de 32 bits do remetente do datagrama e do destinatário desejado. Embora o datagrama possa ser transmitido através de vários roteadores intermediários, os campos de origem e de destino nunca mudam; eles especificam os endereços IP da fonte original e de destino final.* Note que os endereços de roteadores intermediários não aparecem no datagrama. A ideia é fundamental para a concepção global é descrita a seguir. O campo de endereço de origem em um datagrama sempre se refere à fonte original, e o campo de endereço de destino refere-se ao último destino. O campo denominado CARGA DE DADOS, na Figura 7.3, mostra apenas o início da área do datagrama que transporta os dados. O tamanho da carga depende, é claro, do que está sendo enviado no datagrama. O campo OPÇÕES IP, discutido a seguir, é de comprimento variável. O campo PREENCHIMENTO depende das opções selecionadas. Ela representa os bits contendo zero, que podem ser necessários para garantir ao cabeçalhodo datagrama se estender a um múltiplo exato de 32 bits. (Lembre-se de que o campo de tamanho de cabeçalho é especificado em unidades de palavras de 32 bits.)

Bibliografia : Interligação de redes com TCP/IP  6ºed capitulo 7

 

Agora que já sabemos algumas da características deste pacote vamos fazer uma função para tratar estes dados.

Como já tinha dito anteriormente esta não é a forma mais eficiente de o fazer mas é a mais fácil de interpretar…

def dados_pacote_ipv4(carga):
    tupla_dados_ipv4 = struct.unpack("!BBHHHBBH4s4s", carga[:20])
    versao_e_HLEN = tupla_dados_ipv4[0]
    versao = versao_e_HLEN >> 4
    tamanho_header = (versao_e_HLEN & 15) * 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])

    return versao, tamanho_header, tipo_servico, + \
           tamanho_total, identificacao, offset_fragmento, + \
           tempo_vida_ttl, protocolos, checksum_cabecalho, ip_origem, ip_destino  
    

Esta linha ( versao = versao_e_HLEN >> 4 ) que foi definida na função anterior, e como tinhamos visto que iriamos usar o operador Bitwise Right shift, o que este operador faz é, avança 4 bits apanhando assim o campo versao, que de outra forma não seria possível...

Então para montar o código vamos usar a variável protocolo da função ethernet_frame, criando uma estrutura de decisão, com o seguinte raciocinio…

Se protocolo ( tipo ethernet ) for equivalente a 8 ( representa ipv4), então passamos a carga util do frame ethernet como argumento na função dados_pacote_ipv4”… e realizamos a saida de dados.

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_e_HLEN = tupla_dados_ipv4[0]
    versao = versao_e_HLEN >> 4
    tamanho_header = (versao_e_HLEN & 15) * 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])

    return versao, tamanho_header, tipo_servico, + \
           tamanho_total, identificacao, offset_fragmento, + \
           tempo_vida_ttl, protocolos, checksum_cabecalho, ip_origem, ip_destino  
    
    

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 ) = 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))  
        




"""
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.
"""

Num próximo episódio vamos estar a ver mais pacotes, estes agora que vêm na carga / payload, do pacote IP, é para isso que a função dados_pacote_ipv4 que definimos anteriormente tem uma variável protocolos que vai armazenar o valor do protocolo que lá vem para nós sabermos que dados temos que trabalhar….

 

Em seguida vamos fazer o TCP…

 

Abraço.

 

Link para o comentário
Compartilhar em outros sites

Arquivado

Este tópico foi arquivado e está fechado para novas respostas.

  • Quem Está Navegando   0 membros estão online

    • Nenhum usuário registrado visualizando esta página.
×
×
  • Criar Novo...