Categories: C/C++

libpcap/

При отладке программ бывает удобно посмотреть, что они передают по сети. Для решения этой задачи существует множество программ-снифферов. Проблема в том, что они бывают не слишком удобны. Так, tcpdump всегда выводит IP- и TCP-заголовки пакетов, а мне они не всегда интересны. Этого недостатка лишен tcpflow, однако он не работает с UDP и имеет больно уж широкий вывод в консоль. Столкнувшись с этим в очередной раз, я решил написать маленький сниффер, который был бы удобен лично мне. Оказалось, что пишутся снифферы очень просто.

Примечание: Кое-кто из вас может помнить, что однажды я уже писал свой сниффер . Существенный недостаток этого сниффера заключается в том, что он имеет графический интерфейс и написан на Perl . Я же в последнее время предпочитаю консольные утилиты, а из языков — либо Python , либо чистый C .

Итак, код сниффера получилcя следующим:

/*
* eaxsniff — libpcap usage example
* (c) Aleksander Alekseev 2016 | http://remontka.com/
*/

#include <pcap/pcap.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include «include/net_headers.h»

#include <string.h>
#include <stdio.h>
#include <stdlib.h>

#define UNUSED(x) ((void)(x))

#define PRINT_BYTES_PER_LINE 16

static void
print_data_hex ( const uint8_t * data , int size )
{
int offset = 0 ;
int nlines = size / PRINT_BYTES_PER_LINE ;
if ( nlines * PRINT_BYTES_PER_LINE < size )
nlines ++;

printf ( »        » ) ;

for ( int i = 0 ; i < PRINT_BYTES_PER_LINE ; i ++ )
printf ( «%02X » , i ) ;

printf ( » n n » ) ;

for ( int line = 0 ; line < nlines ; line ++ )
{
printf ( «%04X    » , offset ) ;
for ( int j = 0 ; j < PRINT_BYTES_PER_LINE ; j ++ )
{
if ( offset + j >= size )
printf ( »   » ) ;
else
printf ( «%02X » , data [ offset + j ] ) ;
}

printf ( »   » ) ;

for ( int j = 0 ; j < PRINT_BYTES_PER_LINE ; j ++ )
{
if ( offset + j >= size )
printf ( » » ) ;
else if ( data [ offset + j ] > 31 && data [ offset + j ] < 127 )
printf ( «%c» , data [ offset + j ] ) ;
else
printf ( «.» ) ;
}

offset += PRINT_BYTES_PER_LINE ;
printf ( » n » ) ;
}
}

static void
handle_packet ( uint8_t * user , const struct pcap_pkthdr * hdr ,
const uint8_t * bytes )
{
UNUSED ( user ) ;

struct iphdr * ip_header = ( struct iphdr * ) ( bytes +
sizeof ( struct ethhdr ) ) ;
struct sockaddr_in  source , dest ;

memset ( & source , 0 , sizeof ( source ) ) ;
memset ( & dest , 0 , sizeof ( dest ) ) ;
source. sin_addr . s_addr = ip_header -> saddr ;
dest. sin_addr . s_addr = ip_header -> daddr ;

char source_ip [ 128 ] ;
char dest_ip [ 128 ] ;

strncpy ( source_ip , inet_ntoa ( source. sin_addr ) , sizeof ( source_ip ) 1 ) ;
source_ip [ sizeof ( source_ip ) 1 ] = ;

strncpy ( dest_ip , inet_ntoa ( dest. sin_addr ) , sizeof ( dest_ip ) 1 ) ;
dest_ip [ sizeof ( dest_ip ) 1 ] = ;

int source_port = 0 ;
int dest_port = 0 ;
int data_size = 0 ;
int ip_header_size = ip_header -> ihl * 4 ;
char * next_header = ( char * ) ip_header + ip_header_size ;

if ( ip_header -> protocol == IP_HEADER_PROTOCOL_TCP )
{
struct tcphdr * tcp_header = ( struct tcphdr * ) next_header ;
source_port = ntohs ( tcp_header -> source ) ;
dest_port = ntohs ( tcp_header -> dest ) ;
int tcp_header_size = tcp_header -> doff * 4 ;
data_size = hdr -> len sizeof ( struct ethhdr )
ip_header_size tcp_header_size ;
}
else if ( ip_header -> protocol == IP_HEADER_PROTOCOL_UDP )
{
struct udphdr * udp_header = ( struct udphdr * ) next_header ;
source_port = ntohs ( udp_header -> source ) ;
dest_port = ntohs ( udp_header -> dest ) ;
data_size = hdr -> len sizeof ( struct ethhdr )
ip_header_size sizeof ( struct udphdr ) ;
}

if ( data_size > 0 )
{
printf ( » n %s:%d -> %s:%d, %d (0x%x) bytes n n » ,
source_ip , source_port , dest_ip , dest_port ,
data_size , data_size ) ;

int headers_size = hdr -> len data_size ;
print_data_hex ( bytes + headers_size , data_size ) ;
}
}

void
list_devs ( )
{
int errcode ;
pcap_if_t * alldevs , * currdev ;
char errbuff [ PCAP_ERRBUF_SIZE ] ;

errcode = pcap_findalldevs ( & alldevs , errbuff ) ;
if ( errcode != 0 )
{
fprintf ( stderr , «pcap_findalldevs failed: %s n » , errbuff ) ;
return ;
}

currdev = alldevs ;

while ( currdev )
{
printf ( «%s t %s n » , currdev -> name ,
currdev -> description ? currdev -> description :
«(no description)»
) ;
currdev = currdev -> next ;
}

if ( alldevs )
pcap_freealldevs ( alldevs ) ;
}

int
main ( int argc , char * argv [ ] )
{
int res ;

if ( ( argc < 3 ) && ! ( ( argc == 2 ) &&
( strcmp ( argv [ 1 ] , «—list-devs» ) == 0 ) ) )
{
printf ( «Usage: %s device filter n »
»       %s —list-devs n » ,
argv [ 0 ] , argv [ 0 ] ) ;
printf ( «Example: %s eth0 ‘udp src or dst port 53’ n » , argv [ 0 ] ) ;
printf ( «%s n » , pcap_lib_version ( ) ) ;
return 1 ;
}

if ( argc == 2 )
{
list_devs ( ) ;
return 0 ;
}

const char * device = argv [ 1 ] ;
const char * filter = argv [ 2 ] ;
char errbuf [ PCAP_ERRBUF_SIZE ] ;

pcap_t * pcap = pcap_open_live ( device , 65535 , 1 , 100 , errbuf ) ;
if ( pcap == NULL )
{
fprintf ( stderr , «pcap_open_live failed: %s n » , errbuf ) ;
return 1 ;
}

struct bpf_program filterprog ;
res = pcap_compile ( pcap , & filterprog , filter , 0 ,
PCAP_NETMASK_UNKNOWN ) ;
if ( res != 0 )
{
fprintf ( stderr , «pcap_compile failed: %s n » ,
pcap_geterr ( pcap ) ) ;
pcap_close ( pcap ) ;
return 1 ;
}

res = pcap_setfilter ( pcap , & filterprog ) ;
if ( res != 0 )
{
fprintf ( stderr , «pcap_setfilter failed: %s n » ,
pcap_geterr ( pcap ) ) ;
pcap_close ( pcap ) ;
return 1 ;
}

printf ( «Listening %s, filter: %s… n » , device , filter ) ;

res = pcap_loop ( pcap , 1 , handle_packet , NULL ) ;
printf ( «pcap_loop returned %d n » , res ) ;

pcap_close ( pcap ) ;
return 0 ;
}

На мой взгляд, особого rocket science здесь не происходит, поэтому нудно расписывать, какая строчка что делает, я не стану. Вы можете найти все интересующие вас подробности в man 3 pcap . Интересно, что определение заголовков Ethernet-, IP-, TCP- и UDP-пакетов в Linux и FreeBSD сильно различается. Соответствующие им структуры и поля этих структур имеют разные названия, не говоря уже о том, что определение структур находится в разных файлах. Чтобы код был переносимым, мне пришлось самостоятельно описать эти структуры в заголовочном файле net_headers.h.

Пример использования сниффера:

$ sudo ./eaxsniff eth0 ‘udp src or dst port 53’
Listening eth0, filter: udp src or dst port 53…

В соседнем терминале:

$ dig @8.8.8.8 remontka.com

; <<>> DiG 9.10.3-P4-Ubuntu <<>> @8.8.8.8 remontka.com
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 51361
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;remontka.com.                IN  A

;; ANSWER SECTION:
remontka.com.         1026    IN  A   212.193.240.242

;; Query time: 24 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Fri Jul 29 11:44:01 MSK 2016
;; MSG SIZE  rcvd: 51

При этом в первом терминале увидим что-то вроде:

192.168.27.178:41787 -> 8.8.8.8:53, 35 (0x23) bytes

00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0000  C8 A1 01 20 00 01 00 00 00 00 00 01 03 65 61 78  … ………eax
0010  02 6D 65 0000 01 00 01 00 00 29 10 00 00 00 00  .me…….)…..
0020  00 00 00                                         …

8.8.8.8:53 -> 192.168.27.178:41787, 51 (0x33) bytes

00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F

0000  C8 A1 81 80 00 01 00 01 00 00 00 01 03 65 61 78  ………….eax
0010  02 6D 65 00 00 01 00 01 C0 0C 00 01 00 01 00 00  .me………….
0020  04 02 00 04 D4 C1 F0 F2 00 00 29 02 00 00 00 00  ……….)…..
0030  00 00 00                                         …

Похоже, работает! Полную версию исходников, а также инструкцию по их сборке, вы найдете в этом репозитории на GitHub . Понятно, что, помимо озвученной отладки приложений, libpcap открывает для нас безграничные возможности по изучению сетевых протоколов, а также всевозможному перехвату веб-форм, паролей и вот этого всего. Кроме того, вы можете подменять пакеты. Детали можно найти в man 3 pcap_inject .

Дополнение: Вас также могут заинтересовать статьи Изучаем Ethernet-фреймы с помощью осциллографа и Снифинг Ethernet-трафика с платой Throwing Star .

admin

Share
Published by
admin
Tags: C/C++

Recent Posts

Консоль удаленного рабочего стола(rdp console)

Клиент удаленного рабочего стола (rdp) предоставляет нам возможность войти на сервер терминалов через консоль. Что…

2 месяца ago

Настройка сети в VMware Workstation

В VMware Workstation есть несколько способов настройки сети гостевой машины: 1) Bridged networking 2) Network…

2 месяца ago

Логи брандмауэра Windows

Встроенный брандмауэр Windows может не только остановить нежелательный трафик на вашем пороге, но и может…

2 месяца ago

Правильный способ отключения IPv6

Вопреки распространенному мнению, отключить IPv6 в Windows Vista и Server 2008 это не просто снять…

2 месяца ago

Ключи реестра Windows, отвечающие за параметры экранной заставки

Параметры экранной заставки для текущего пользователя можно править из системного реестра, для чего: Запустите редактор…

2 месяца ago

Как управлять журналами событий из командной строки

В этой статье расскажу про возможность просмотра журналов событий из командной строки. Эти возможности можно…

2 месяца ago