quarta-feira, 17 de dezembro de 2008

Como utilizar o TCPDUMP


Como utilizar o tcpdump

Colaboração: Ricardo Iramar dos Santos
Data de Publicação: 14 de outubro de 2008
--> Eu estava pensando em escrever aqui uma definição para o tcpdump mas tenho a certeza que está descrita aqui http://en.wikipedia.org/wiki/Tcpdump é o suficiente para a introdução desta documentação.
Esta documentação não irá descrever como instalar o tcpdump, até mesmo porque a maioria das distribuições linux possuem binários ou é extremamente fácil a sua instalação. Em todo caso você pode visitar http://www.tcpdump.org que com certeza vai encontrar algo a respeito.
No final desta documentação você estará apto a usar o tcpdump com filtros como: endereço ip ou porta de origem ou destino, endereço de rede, tipo de protocolo e até mesmo filtrar especificando uma flag do cabeçalho TCP.

1. Utilização

O tcpdump necessariamente não precisa de um parâmetro para ser executado, você só precisa estar como root para poder executá-lo pois o tcpdump irá precisar colocar a sua interface em promiscuous mode. Calma! Isso não tem nada ver com a promiscuidade que você esta pensando, para saber do que se trata visite http://en.wikipedia.org/wiki/Promiscuous_mode.
Matando dois coelhos com uma única tijolada vamos ver um exemplo do tcpdump sem parâmetros e aproveitamos para entender sua saída padrão também:
  smith ~ # tcpdump
 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
 listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
 ...
 21:02:02.579320 IP 192.168.0.1.1921 > smith.zion.ssh: S 4011627552:4011627552(0) win 64512 <>
 21:02:02.579462 IP smith.zion.ssh > 192.168.0.1.1921: S 2154231898:2154231898(0) ack 4011627553 win 5840 <>
 21:02:02.579707 IP 192.168.0.1.1921 > smith.zion.ssh: . ack 1 win 64512
 21:02:02.623121 IP smith.zion.ssh > 192.168.0.1.1921: P 1:21(20) ack 1 win 5840
 21:02:02.623576 IP 192.168.0.1.1921 > smith.zion.ssh: P 1:29(28) ack 21 win 64492
 ...
 327 packets captured
 1038 packets received by filter
 266 packets dropped by kernel
Como não informamos qual interface queríamos monitorar ele pegou a primeira disponível, no meu caso a eth0. Podemos notar isso no trecho listening on eth0. Para selecionar a interface na qual se deseja capturar os pacotes basta usar o parâmetro "-i nome_da_interface", como por exemplo "-i eth1".
Na saída padrão os campos estão separados por um espaço em branco. Como pode ser facilmente notado, o primeiro campo é o que eles chamam de timestamp, para nós é o horário em que o pacote foi capturado.
O segundo campo informa que o tipo do pacote ethernet foi capturado, no nosso caso é o ip. Poderia ser também ip6, arp, rarp, atalk, aarp, decnet, sca, lat, mopdl, moprc, iso, stp, ipx ou netbeui, mas isso não tem a mínima importância agora.
O terceiro campo são dois em um, famoso "endereço_de_origem.porta_de_origem". No nosso caso, para o primeiro pacote descrito no exemplo, o endereço de origem é "192.168.0.1" e a porta de origem "1921".
Bem, o quarto campo não é bem um campo mas somente um sinal para indicar o sentido do pacote. Desta forma não tem como a gente confundir origem com destino.
Idêntico ao terceiro o quinto campo é "endereço_de_destino.porta_de_destino". Como não especificamos nenhum parâmetro o tcpdump converteu o endereço de destino para o nome smith.zion e a porta "22" para ssh. Se você não quiser que o tcpdump fique convertendo os endereços e portas basta utilizar o parâmetro "-n". Esse campo sempre termina com ":", não sei a sua utilização mas deve ter uma razão lógica para isso, acho que só perguntando para os desenvolvedores do tcpdump.
O quinto campo é referente ao bit de controle, no caso do primeiro pacote "S" quer dizer que é um pacote do tipo SYN (Synchronize). Esse campo poderia ser "R" (Reset), "F" (Finish), "P" (Push), etc. Para saber mais detalhadamente leia a RFC 793 (TRANSMISSION CONTROL PROTOCOL).
Os demais campos não nos interessa neste momento.
Agora que você já sabe o básico da saída padrão vamos ver um exemplo bem básico. Digamos que você queira pegar somente os pacotes que estão sendo enviados ou recebidos para www.gooogle.com.br. Para isso basta utilizar a expressão "host" seguido do "nome do host" que deseja filtrar.
  smith ~ # tcpdump -i eth0 host www.google.com.br
 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
 listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
 10:18:11.483790 IP 192.168.0.2.49286 > br-in-f104.google.com.http: S 3414458403:3414458403(0) win 5840 <>
 10:18:11.495372 IP br-in-f104.google.com.http > 192.168.0.2.49286: S 2198482267:2198482267(0) ack 3414458404 win 5672 <>
 10:18:11.495555 IP 192.168.0.2.49286 > br-in-f104.google.com.http: . ack 1 win 46 <>
Mas cade www.google.com.br? Calma, o google tem vários servidores e br-in-f104.google.com é somente mais um deles. Mas acredite no tcpdump, ele não está mentindo e como geralmente queremos saber qual o ip e o número da porta vamos executar agora com o parâmetro "-n" para ver o que acontece.
  smith ~ # tcpdump -i eth0 -n host www.google.com.br
 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
 listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
 13:17:00.795873 IP 192.168.0.2.58605 > 209.85.193.104.80: S 4145938788:4145938788(0) win 5840 <>
 13:17:00.807891 IP 209.85.193.104.80 > 192.168.0.2.58605: S 4200328202:4200328202(0) ack 4145938789 win 5672 <>
 13:17:00.808066 IP 192.168.0.2.58605 > 209.85.193.104.80: . ack 1 win 46 <>
Agora não temos mais nomes somente números facilitando a leitura e entendimento. Você também pode usar ips na expressão ao invés do nome do host como por exemplo:
  smith ~ # tcpdump -i eth0 -n host 192.168.0.1
 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
 listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
 13:19:50.636347 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 59421, seq 1, length 64
 13:19:50.636652 IP 192.168.0.1 > 192.168.0.2: ICMP echo reply, id 59421, seq 1, length 64
 13:20:43.622909 IP 192.168.0.2.54940 > 192.168.0.1.23: S 3347765906:3347765906(0) win 5840 <>
 13:20:43.623236 IP 192.168.0.1.23 > 192.168.0.2.54940: R 0:0(0) ack 3347765907 win 0
 13:20:47.828000 IP 192.168.0.2.54941 > 192.168.0.1.23: S 3403459962:3403459962(0) win 5840 <>
 13:20:47.828321 IP 192.168.0.1.23 > 192.168.0.2.54941: R 0:0(0) ack 3403459963 win 0
 13:20:48.620885 arp who-has 192.168.0.1 tell 192.168.0.2
 13:20:48.621120 arp reply 192.168.0.1 is-at 00:19:5b:b6:ff:59
 13:21:01.091548 IP 192.168.0.2.44304 > 192.168.0.1.80: S 3619763307:3619763307(0) win 5840 <>
Mas eu queria somente o acesso via HTTP (porta 80). Sem problemas basta usar a expressão "host" em conjunto com "port" utilizando o operador "and", veja como:
  smith ~ # tcpdump -i eth0 -n host 192.168.0.1 and port 80
 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
 listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
 13:32:03.548794 IP 192.168.0.2.36993 > 192.168.0.1.80: S 1122631006:1122631006(0) win 5840 <>
 13:32:03.549074 IP 192.168.0.1.80 > 192.168.0.2.36993: S 12860500:12860500(0) ack 1122631007 win 5840 <>
 13:32:03.549136 IP 192.168.0.2.36993 > 192.168.0.1.80: . ack 1 win 5840
Você pode concatenar quantos filtros forem precisos basta usar os operadores "and" ou "or" a cada novo filtro. Esses operadores também podem serem utilizados em conjunto com o operador "not" no caso de uma lógica inversa.Na man page do tcpdump (http://www.tcpdump.org/tcpdump_man.html) você irá encontrar todos os filtros possíveis, vale a pena dar uma olhada. Vejamos um outro exemplo:
  smith ~ # tcpdump -i eth0 -n dst host 192.168.0.1 and not port 80
 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
 listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
 10:50:37.376405 arp who-has 192.168.0.1 tell 192.168.0.2
 10:51:01.670855 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 57367, seq 1, length 64
 10:51:02.671324 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 57367, seq 2, length 64
 10:53:55.283902 IP 192.168.0.2.59910 > 192.168.0.1.23: S 3501945306:3501945306(0) win 5840 <>
Neste exemplo capturamos somente os pacotes com destino ao host 192.168.0.1 exceto com destino a porta 80. Perceba que por utilizarmos somente o filtro "dst host" foram capturados os pacotes somente em um sentido indicado por ">". Vejamos outro exemplo básico especificando o tipo de protocolo e a rede:
  smith ~ # tcpdump -i eth0 -n net 192.168 and icmp
 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
 listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
 11:21:48.518096 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 60206, seq 1, length 64
 11:21:48.518404 IP 192.168.0.1 > 192.168.0.2: ICMP echo reply, id 60206, seq 1, length 64
 11:21:49.517104 IP 192.168.0.2 > 192.168.0.1: ICMP echo request, id 60206, seq 2, length 64
Neste exemplo capturamos todos os pacotes icmp da rede 192.168.0.0/16. Perceba que para o filtro "net" deve ser especificado somente os octetos referentes a rede, não há como especificar mascaras intermediárias além de 32/16/8/0.
Agora o gran finale onde iremos aprender como capturar pacotes filtrando por uma flag específica do cabeçalho TCP. Para isso precisamos de uma teoria básica sobre cabeçalho TCP. Vejamos a estrutura do cabeçalho TCP:
   0                            15                              31
 -----------------------------------------------------------------
 |          source port          |       destination port        |
 -----------------------------------------------------------------
 |                        sequence number                        |
 -----------------------------------------------------------------
 |                     acknowledgment number                     |
 -----------------------------------------------------------------
 |  HL   | rsvd  |C|E|U|A|P|R|S|F|        window size            |
 -----------------------------------------------------------------
 |         TCP checksum          |       urgent pointer          |
 -----------------------------------------------------------------
Começando pelo octeto 0 o octeto que nos interessa é o 13 onde estão nossas famosas flags de controle.
   0             7|             15|             23|             31
 ----------------|---------------|---------------|----------------
 |  HL   | rsvd  |C|E|U|A|P|R|S|F|        window size            |
 ----------------|---------------|---------------|----------------
 |               |  13th octet   |               |               |
Vejamos este octeto mais de perto:
  |               |
 |---------------|
 |C|E|U|A|P|R|S|F|
 |---------------|
 |7   5   3     0|
Essas são as famosas flags TCP CWR, ECE, URG, ACK, PSH, RST, SYN e FIN apresentadas exatamente nesta ordem. Perceba que a significância dos bits cresce da direita para a esquerda. Agora que já sabemos onde as flags estão digamos que queremos pacotes somente com a flag SYN ativada.
  |C|E|U|A|P|R|S|F|
 |---------------|
 |0 0 0 0 0 0 1 0|
 |---------------|
 |7 6 5 4 3 2 1 0|
Traduzindo em binário nosso octeto 13 deve assumir o valor 00000010. Convertendo o mesmo para decimal:
  7     6     5     4     3     2     1     0
 0*2 + 0*2 + 0*2 + 0*2 + 0*2 + 0*2 + 1*2 + 0*2  =  2
Temos o número 2 em decimal. Agora como podemos falar para o tcpdump capturar somente os pacotes TCP que possuírem o octeto 13 com o valor decimal 2? Fácil, basta usar o filtro "tcp[13] == 2". Vejamos no exemplo abaixo:
  smith ~ # tcpdump -i eth0 -n tcp[13] == 2
 tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
 listening on eth0, link-type EN10MB (Ethernet), capture size 96 bytes
 11:46:10.978953 IP 192.168.0.2.34670 > 209.85.133.18.443: S 1113104022:1113104022(0) win 5840 <>
 11:46:20.362403 IP 192.168.0.2.47876 > 209.85.193.104.23: S 1282044372:1282044372(0) win 5840 <>
 11:46:25.040800 IP 192.168.0.2.54605 > 192.168.0.1.23: S 1360875102:1360875102(0) win 5840 <>
Neste exemplo capturamos todos os pacotes TCP com a flag SYN ativada (igual à 1), porém necessariamente as outras flags devem estar desativadas (igual à 0). Mas eu quero todos os pacotes com a flag SYN ativada independente do estado das outras flags, tem como fazer isso? O tcpdump só não faz café. Vamos exemplificar para ficar mais fácil. Temos um pacote do tipo SYN-ACK como segue abaixo:
  |C|E|U|A|P|R|S|F|
 |---------------|
 |0 0 0 1 0 0 1 0|
 |---------------|
 |7 6 5 4 3 2 1 0|
O filtro "tcp[13] == 2" não irá capturar o pacote pois neste caso o valor binário do octeto 13 é 00010010 (18 em decimal) e não 00000010 (2 em decimal). Agora que vem o pulo gato! Para que o tcpdump "olhe" somente para a flag SYN e descarte as demais você deve pedir a ele que faça um AND com o valor binário referente a flag SYN. Vejamos graficamente:
  00010010 (octeto 13 com as flags SYN-ACK)
 AND  00000010 (valor binário referente a flag SYN)
 --------
 =    00000010 (resultado do AND)
Perceba mesmo que todas as flags estivessem ativadas (1) ou desativadas (0) o resultado sempre seria 00000010. Traduzindo na língua do tcpdump o filtro ficaria da seguinte forma "tcp[13] & 2 == 2". Em outras palavras, você está dizendo o seguinte para o tcpdump: Pegue todos os pacotes TCP, faça um AND entre o valor decimal 2 com o valor do octeto 13 e me mostre somente os que resultarem o valor decimal 2.
Você pode utilizar está técnica para filtrar baseado em qualquer bit do cabeçalho TCP. Isso é extremamente útil em um trobleshooting onde se sabe exatamente os pacotes que se deseja capturar.

2. Conclusão

O tcpdump é uma ferramenta indispensável para um trobleshooting de rede. Como podemos ver ele é preciso como um bisturi onde podemos capturar exatamente o que pretendemos. O que vimos nesta documentação pode ser muito mais aproveitado com a leitura da man page do tcpdump em http://www.tcpdump.org/tcpdump_man.html.

3. Referências

0 comentários: