Packet Capture em Java com JPCAP

PCAP (Packet Capture) consiste numa API para a captura de pacotes de rede. Em sistemas baseados em Unix o Pcap é implementado na biblioteca LibPcap, no caso dos sistemas Windows este encontra-se implementado na biblioteca WinPcap.

Estas bibliotecas permitem que software consiga capturar e filtrar pacotes que viagem pela rede, enviar pacotes e até listar todas as interfaces de rede existentes no sistema bem como obter informações sobre os mesmos, tais como o nome, IP ou MAC.

É ainda possível guardar os pacotes capturados num ficheiro e mais tarde voltar esses pacotes a partir do ficheiro onde foram guardados. Tais recursos são muito utilizados em ferramentas de monitorização e análise de rede como por exemplo packet sniffers, network monitors, network intrusion detection and traffic-generators.

Jpcap é no fundo uma biblioteca intermédia entre o Java e as bibliotecas LibPcap e WinPcap, permite ao programador criar aplicações Java utilizando as funcionalidades destas bibliotecas. Existem bibliotecas semelhantes para as mais diversas linguagens tais como Python, Ruby, .NET, Perl.

Ao longo deste artigo iremos abordar alguns dos principais recursos desta biblioteca tais como:

  • Interfaces de rede disponíveis no sistema.
  • Preparar a interface de rede para captura.
  • Capturar pacotes da interface de rede.
  • Aplicar filtros à captura de pacotes de rede.
  • Gravar pacotes de rede capturados em ficheiros.
  • Ler pacotes de rede gravados em ficheiros.

Para isso iremos criar pequenas aplicações, exemplos simples que demonstram como utilizar cada um dos recursos mencionados acima.

Mas antes de tudo vamos precisar de preparar o nosso ambiente, para isso precisar do Jpcap que podemos encontrar em

http://netresearch.ics.uci.edu/kfujii/jpcap/doc/download.html

Aqui encontramos versões para vários sistemas operativos, podemos utilizar uma versão de instalação como é o caso do Windows ou Linux ou podemos utilizar a versão código-fonte, esta parte deixo ao critério de cada um, não existem vantagens ao nível de desenvolvimento.

Para instalar basta seguir os passos descritos na página http://netresearch.ics.uci.edu/kfujii/jpcap/doc/install.html

Depois da instalação vamos por mão à obra!

Interfaces de rede disponíveis no sistema

Nesta sessão vamos ver como é simples obter a lista de interfaces de rede que se encontram disponíveis no nosso sistema, para além disso vamos ver ainda como é fácil retirar alguma informação destas interfaces.

Para isso vamos ver um pequeno programa que demonstra como, usando o Jpcap, podemos obter todos estes dados de uma forma bastante simples e rápida.

import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.NetworkInterfaceAddress;

/**
 *
 * @author magician
 */
public class JpcapInterfaces {

    /**
     * Dado um array de bytes retorna a String equivalente.
     * @param input - Array de bytes.
     * @return String com a representação textual do array de bytes.
     */
    public static String hex2String(byte [] input){
        String output = "";
        for (int i = 0; i < input.length-1; i++) {
            output += Integer.toHexString(input[i] & 0xff) + ":";
        }
        output += Integer.toHexString(input[input.length-1] & 0xff);
        return output;
    }

    public static void main(String args[]) {

        //Obtém a lista de interfaces de rede no sistema.
        NetworkInterface[] interfaces = JpcapCaptor.getDeviceList();

        for (NetworkInterface ni : interfaces) {
            System.out.println("-------------------------------------------------");
            //Nome da interface.
            System.out.println("Nome: " + ni.name);
            //Descrição da interface caso exista.
            System.out.println("Descrição: " + ni.description);
            //DataLink da interface.
            System.out.println("Nome da DataLink : " + ni.datalink_name);
            //Descrição do DataLink caso exista.
            System.out.println("Descrição da DataLink : " + ni.datalink_description);
            //MAC Address da interface.
            System.out.println("MAC Address: "+ hex2String(ni.mac_address));

            for(NetworkInterfaceAddress a : ni.addresses){
                //Endereço de IP da interface.
                System.out.println("IP: " + a.address.getHostAddress());
                //Endereço de Broadcast da interface.
                System.out.println("BroadCast: " + a.broadcast);
                //Mascara de SubRede.
                System.out.println("SubNet: " + a.subnet.getHostAddress());
                //Em caso de ligações P2P o endereço de destino.
                System.out.println("Destinho P2P: " + a.destination);
            }
            System.out.println("-------------------------------------------------\n");
        }
    }
}

Com o exemplo acima podemos ver como é simples obter as interfaces de rede disponíveis no sistema bem como todos os seus dados, alguns deles até bastante importantes como o Nome, IP, MAC e SubNet, dados esses que podem ser utilizados mais tarde das mais diversas formas. O resultado obtido após a execução desta pequena aplicação será algo como

Nome: eth0
Descrição: null
Nome da DataLink : EN10MB
Descrição da DataLink : Ethernet
MAC Address: 0:16:36:b0:5c:d7
IP: 192.168.2.183
BroadCast: /192.168.2.255
SubNet: 255.255.255.0
Destinho P2P: null
IP: fe80:0:0:0:216:36ff:feb0:5cd7
BroadCast: null
SubNet: ffff:ffff:ffff:ffff:0:0:0:0
Destinho P2P: null
-------------------------------------------------

-------------------------------------------------
Nome: any
Descrição: Pseudo-device that captures on all interfaces
Nome da DataLink : LINUX_SLL
Descrição da DataLink : Linux cooked
MAC Address: 0:0:0:0:0:0
-------------------------------------------------

-------------------------------------------------
Nome: lo
Descrição: null
Nome da DataLink : EN10MB
Descrição da DataLink : Ethernet
MAC Address: 0:0:0:0:0:0
IP: 127.0.0.1
BroadCast: null
SubNet: 255.0.0.0
Destinho P2P: null
IP: 0:0:0:0:0:0:0:1
BroadCast: null
SubNet: ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
Destinho P2P: null
-------------------------------------------------

Este output corresponde a um sistema Linux, noutros sistemas podem aparecer diferenças nomeadamente nos nomes dos dispositivos. Por exemplo, pode aparecer algo como \Device\NPF_{C3F5996D-FB82-4311-A205-25B7761897B9} ao invés do simples eth0.

Para que as nossas aplicações consigam utilizar o jpcap correctamente estas devem ser executadas com permissões de admin, caso contrário é possível que não sejam obtidos resultados, uma vez que o sistema pcap necessita dessas permissões para realizar as operações sobre as interfaces.

Preparar a interface de rede para captura

Para preparar a captura de pacotes de rede basta utilizar o método static openDevice da classe JpcapCaptor, este método abre a interface escolhida e prepara a captura de pacotes. Abaixo podemos ver como usar este método

    NetworkInterface[] interfaces = JpcapCaptor.getDeviceList();

    //openDevice(NetworkInterface interface, int snaplen, boolean promics, int to_ms)
    JpcapCaptor captor = JpcapCaptor.openDevice(interfaces[0], 65535, false, 20);

Como podemos ver são passados quatro argumentos ao método openDevice, o primeiro argumento é a interface a ser utilizada na captura, neste caso vamos utilizar o primeiro interface da lista ou seja o “eth0”. O segundo argumento corresponde ao numero de máximo bytes a serem capturados de cada vez, o terceiro argumento irá dizer se a captura será em modo promiscuo (true) ou não (false). O modo promiscuo permite capturar pacotes de rede mesmo que a sua origem ou destino não seja a da interface aberto para captura. O modo não promiscuo apenas captura pacotes cujo destino ou origem se a interface escolhida. O último argumento corresponde ao timeout em milissegundos dado para a captura de pacotes.

Capturar pacotes da interface de rede

Vamos agora passar à captura dos pacotes, o processo pode ser feito de duas formas diferentes mas que atingem o mesmo fim. Podemos utilizar a forma callback ou one-by-one.

A forma callback passa pela utilização dos métodos processPacket ou loopPacket da classe JpcapCaptor e pela implementação da interface PacketReceiver. De uma forma resumida o que vamos fazer é criar uma classe que implementa a interface PacketReceiver onde implementamos o método receivePacket que irá conter o que deverá ser feito a cada pacote capturado. Depois de implementada a interface, usamos o método processPacket ou loopPacket, ambos atingem o mesmo fim com a diferença que no processPacket é compativel com timeout e modo non blocking.

A forma one-by-one é a mais “primitiva” e a mais flexível dado que é possível controlar todas as acções feitas sobre o pacote. Para isso utilizamos o método getPacket da classe JpcapCaptor, este método como o nome indica retorna um pacote capturado, assim apenas temos que criar um mecanismo que irá repetir o processo quantas vezes quisermos.

Vamos para isso ver um pequeno exemplo que captura 20 pacotes e imprime na consola o IP de origem/destino bem como as portas correspondentes e o tamanho do pacote.

import java.io.IOException;
import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.packet.Packet;
import jpcap.packet.TCPPacket;
import jpcap.packet.UDPPacket;

/**
 *
 * @author magician
 */
public class OpenInterface {

   public static void main(String args []){

        //Lista de interfaces de rede no sistema.
        NetworkInterface[] interfaces = JpcapCaptor.getDeviceList();

        try{
            //Abre a interface 0 da lista.
            JpcapCaptor captor = JpcapCaptor.openDevice(interfaces[0], 65535, false, 20);

            //Simples contador.
            int i = 0;
            Packet p = null;

            //Cliclo para capturar 20 pacotes.
            while(i < 20){
                //Captura um pacote.
                p = captor.getPacket();

                //Verifica se o pacote é do tipo TCPPacket
                if(p instanceof TCPPacket){
                    TCPPacket tcp = (TCPPacket) p;
                    System.out.println("SRC: " + tcp.src_ip.getHostAddress() + ":" + tcp.src_port +
                            "   \tDST: " + tcp.dst_ip.getHostAddress() +":" + tcp.dst_port +
                            "   \tSize = " + tcp.length + " bytes");

                }
                //Verifica se o pacote é do tipo UDPPacket
                else if(p instanceof UDPPacket){
                    UDPPacket udp = (UDPPacket) p;
                    System.out.println("SRC: " + udp.src_ip.getHostAddress() + ":" + udp.src_port +
                            "   \tDST: " + udp.dst_ip.getHostAddress() +":" + udp.dst_port +
                            "   \tSize = " + udp.length + " bytes");
                }
                i++;
            }

            //Fecha a captura de pacotes.
            captor.close();
        }
        catch(IOException io){
            System.out.println(io.getMessage());
        }
        catch(Exception e){
            System.out.println(e.getMessage());
        }
    }
}

Como podemos ver apenas foram processados os pacotes UDP e TCP, mas existem outros como por exemplo ARPPacket, DatalinkPacket, ICMPPacket entre outros. Este pequeno exemplo irá gerar um ouput semelhante ao que se segue a baixo sem os * que foram colocados por motivos óbvios de privacidade e segurança.

SRC: 7*.177.*05.30:1670         DST: 19*.16*.*.1*3:49153        Size = 40 bytes
SRC: 19*.16*.*.1*3:49153        DST: 7*.177.*05.30:1670         Size = 1119 bytes
SRC: 19*.16*.*.1*3:1900         DST: 19*.16*.*.176:1536         Size = 247 bytes
SRC: *3.*5*.40.166:51457        DST: 19*.16*.*.1*3:49153        Size = 88 bytes
SRC: *3.30.6.103:*74*           DST: 19*.16*.*.1*3:49153        Size = 40 bytes
SRC: 19*.16*.*.1*3:49153        DST: *3.30.6.103:*74*           Size = 328 bytes
SRC: 19*.16*.*.1*3:579*5        DST: *1.111.49.15*:33655        Size = 445 bytes
SRC: *4.1*3.19*.**4:60000       DST: 19*.16*.*.1*3:49794        Size = 58 bytes
SRC: 19*.16*.*.1*3:49794        DST: *4.1*3.19*.**4:60000       Size = 160 bytes
SRC: *7.93.3.*55:16*4           DST: 19*.16*.*.1*3:49153        Size = 40 bytes
SRC: 19*.16*.*.1*3:49153        DST: *7.93.3.*55:16*4           Size = 550 bytes
SRC: 91.*1.*50.*11:*7075        DST: 19*.16*.*.1*3:49153        Size = 73 bytes
SRC: 19*.16*.*.1*3:49153        DST: 91.*1.*50.*11:*7075        Size = 93 bytes
SRC: *0*.99.194.194:49*90       DST: 19*.16*.*.1*3:50043        Size = 58 bytes
SRC: 19*.16*.*.1*3:50043        DST: *0*.99.194.194:49*90       Size = 260 bytes
SRC: **.**7.*00.1*:60053        DST: 19*.16*.*.1*3:469**        Size = 313 bytes
SRC: 19*.16*.*.1*3:469**        DST: **.**7.*00.1*:60053        Size = 58 bytes
SRC: *17.13*.7*.9*:600*0        DST: 19*.16*.*.1*3:504*7        Size = 52 bytes
SRC: 19*.16*.*.1*3:504*7        DST: *17.13*.7*.9*:600*0        Size = 1410 bytes
SRC: 19*.16*.*.1*3:504*7        DST: *17.13*.7*.9*:600*0        Size = 306 bytes

Embora neste exemplo apenas tenhamos extraído estes dados é possível extrair ainda mais dados dos pacotes, como por exemplo os dados (data) enviados no ficheiro, headers, version, etc.. Iremos ver mais à frente um exemplo em que retiramos os dados enviados.

Aplicar filtros à captura de pacotes de rede

O Jpcap permite ainda a utilização de filtros de forma a limitar os pacotes capturados a um círculo restrito de possibilidades e não tudo o que passa pela interface como vimos até agora.

Vamos começar por ver a sintaxe utilizada pelo tcpdump que é a mesma aplicada ao Jpcap.

Filtragem por Host
Sintaxe Descrição Exemplo
host <host> Captura todos os pacotes que entram e saem do host host 192.168.1.100
src host <host> Captura todos os pacotes com origem no host src host 192.168.1.100
dst host <host> Captura todos os pacotes com o host como destino dst host 192.168.1.100
Filtragem por Porta
Sintaxe Descrição Exemplo
port <port> Captura todos os pacotes que entram ou saem da porta port 80
src port <port> Captura todos os pacotes com origem na porta src port 80
dst port <port> Captura todos os pacotes com a porta como destino dst port 80
Filtragem por Network
Sintaxe Descrição Exemplo
net <net> Captura todos os pacotes da rede <net> net 192.168
src net <net> Captura todos os pacotes que saem da rede <net> src net 192.168
dst net <net> Captura todos os pacotes que entram na rede <net> dst net 192.168
Filtragem por Protocolo
Sintaxe Descrição
ip Captura todos os pacotes de IP
arp Captura todos os pacotes ARP
rarp Captura todos os pacotes ARP inversos
tcp Captura todos os pacotes TCP
udp Captura todos os pacotes UDP
icmp Captura todos os pacotes ICPM
Combinação de filtros
Sintaxe Descrição Exemplo
not Negação not src net 192.168
and Concatenação tcp and src host 192.168.1.100
or Alternância port 80 or port 8080

Acima temos a sintaxe principal que é possível utilizar para criar filtros, como podemos ver podem ser criados filtros por host, port, network, tipo de pacote e podemos ainda fazer combinações de filtros utilizando operações lógicas. Vamos agora ver um exemplo da utilização de filtros e iremos também ver neste exemplo como é possível ver os dados que são enviados nos pacotes.

import java.io.IOException;
import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.packet.TCPPacket;

/**
 *
 * @author magician
 */
public class JpcapFilter {

    public static void main(String args []){

        //Lista de interfaces de rede no sistema.
        NetworkInterface[] interfaces = JpcapCaptor.getDeviceList();

        try{
            //Abre a interface 0 da lista.
            JpcapCaptor captor = JpcapCaptor.openDevice(interfaces[0], 65535, false, 20);

            //Captura apenas pacotes TCP com origem no host 192.168.1.100 e que
            //tem como destino a porta 80 ou seja HTTP
            captor.setFilter("tcp and src host 192.168.1.100 and dst port 80", true);

            //Simples contador.
            int i = 0;

            //Cliclo para capturar 20 pacotes.
            while(i < 20){
                //Captura um pacote e converte para TCPPacket dado que apenas
                //a capturar pacotes TCP.
                TCPPacket p = (TCPPacket) captor.getPacket();

                //Gera o output com a informação sobre o pacote
                System.out.println("SRC: " + p.src_ip.getHostAddress() + ":" + p.src_port +
                        "   \tDST: " + p.dst_ip.getHostAddress() +":" + p.dst_port +
                        "   \tSize = " + p.length + " bytes");

                //Caso o pacote contenha dados este são impressos e o programa para.
                if(p.data.length > 0){
                    System.out.println(new String(p.data));
                    break;
                }
                i++;
            }

            //Fecha a captura de pacotes.
            captor.close();
        }
        catch(IOException io){
            System.out.println(io.getMessage());
        }
        catch(Exception e){
            System.out.println(e.getMessage());
        }
    }
}

O que o programa vai fazer assumindo que o nosso IP corresponde ao 192.168.1.100 é capturar todos os pacotes TCP que são enviados pelo nosso computador e que tem como destino a porta 80. De uma forma resumida vai capturar todos os pedidos HTTP feitos por nós. Na realidade não irá capturar todos mas apenas alguns, ou seja, irá capturar até encontrar um pacote que contenha dados e irá gerar um output semelhante ao que se segue.

SRC: 192.168.1.100:49323        DST: 66.102.9.147:80    Size = 60 bytes
SRC: 192.168.1.100:49323        DST: 66.102.9.147:80    Size = 52 bytes
SRC: 192.168.1.100:49323        DST: 66.102.9.147:80    Size = 715 bytes
GET / HTTP/1.1
Host: www.google.pt
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.11) Gecko/20071204 Ubuntu/7.10 (gutsy) Firefox/2.0.0.11
Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5
Accept-Language: pt-pt,pt;q=0.8,en;q=0.5,en-us;q=0.3
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive: 300
Connection: keep-alive
Cookie: NID=17=BNGeDayJAh13ZHF6XFJ2mwo5N3rNTRu6WaFDUYmbgAC4pyT0kzPnSKN0qKAz_ajH2WjpqDZ92DXTdjhNyiG_s-Xwd4Mwr;
PREF=ID=8ed9295:TM=197383:LM=213911:IG=8:S=T7z-r65IhJ

O exemplo acima mostra o pedido feito pelo Firefox ao servidor www.google.pt, como podemos ver trata-se de um pedido HTTP ao servidor.

Assim encerramos este artigo, neste momento estamos prontos a criar desde grandes aplicações a pequenos utilitários de análise de pacotes de rede.

Links

JPCAP Home

JPCAP API

JPCAP Samples

LibPcap/TCPDump

Winpcap

Autoria

Este artigo foi originalmente escrito por Fábio Correia (magician) para a 18ª Edição da Revista PROGRAMAR (Fevereiro de 2009).

Anúncios

One thought on “Packet Capture em Java com JPCAP

  1. Parabéns, estou estudando a respeito. A captura foi bem abordada. Agora estou estudando o envio. Pretendo fazer um sistema que o usuário movimenta-se, identifique a rede wifi desejada e envie pacotes a ela.

Deixe uma Resposta

Preencha os seus detalhes abaixo ou clique num ícone para iniciar sessão:

Logótipo da WordPress.com

Está a comentar usando a sua conta WordPress.com Terminar Sessão /  Alterar )

Google photo

Está a comentar usando a sua conta Google Terminar Sessão /  Alterar )

Imagem do Twitter

Está a comentar usando a sua conta Twitter Terminar Sessão /  Alterar )

Facebook photo

Está a comentar usando a sua conta Facebook Terminar Sessão /  Alterar )

Connecting to %s