Home > Enterprise >  Question on syscall ioctl and hostID manipulation
Question on syscall ioctl and hostID manipulation

Time:10-03

I was looking for a way to play around with my MAC address and modify it and I bumped on the following snippet of ANSI-C code that works but I am not sure how. Inside the snippet, I have added points for the questions that I would like to ask.

/*
 * Spoof a MAC address with LD_PRELOAD
 *
 * If environment variable $MAC_ADDRESS is set in the form "01:02:03:04:05:06"
 * then use that value, otherwise use the hardcoded 'mac_address' in this file.
 *
 * Bugs: This currently spoofs MAC addresses for *all* interfaces.
 * It would be better to watch calls to socket() for the interfaces
 * you want and then only spoof ioctl calls to that file descriptor.
 */
#include <dlfcn.h>
#include <stdlib.h>
#include <stdio.h>

#define SIOCGIFHWADDR 0x8927

static unsigned char mac_address[6] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06};

int
ioctl(int d, int request, unsigned char *argp)
{
    static void *handle = NULL;
    static int (*orig_ioctl)(int, int, char*);
    int ret;
    char *p;

    // If this var isn't set, use the hardcoded address above
    p=getenv("MAC_ADDRESS");

    if (handle == NULL) {
        char *error;
        handle = dlopen("/lib/libc.so.6", RTLD_LAZY);
        if (!handle) {
            fputs(dlerror(), stderr);
            exit(EXIT_FAILURE);
        }
        orig_ioctl = dlsym(handle, "ioctl");
        if ((error = dlerror()) != NULL) {
            fprintf(stderr, "%s\n", error);
            exit(EXIT_FAILURE);
        }
    }
    
    ret = orig_ioctl(d, request, argp);
    
    if (request == SIOCGIFHWADDR) {
        unsigned char *ptr = argp   18; // [1] what is the  18 purpose?

        int i;
        for (i=0; i < 6; i  ) {
            if (!p) {
                ptr[i] = mac_address[i];
                continue;
            }
            int val = 0;
            if (p[0]>='0' && p[0]<='9') val |= (p[0]-'0')<<4;
            else if (p[0]>='a' && p[0]<='f') val |= (p[0]-'a' 10)<<4;
            else if (p[0]>='A' && p[0]<='F') val |= (p[0]-'A' 10)<<4;
            else break;
            if (p[1]>='0' && p[1]<='9') val |= p[1]-'0';
            else if (p[1]>='a' && p[1]<='f') val |= p[1]-'a' 10;
            else if (p[1]>='A' && p[1]<='F') val |= p[1]-'A' 10;
            else break;
            if (p[2]!=':' && p[2]!='\0') break;
            ptr[i] = val;
            p =3;
        }
    }
    return ret; 
}

So, my first question [1]: In the statement unsigned char *ptr = argp 18; what is the purpose of the 'addition'? How can we derive this number? After looking at strace ifconfig for example, i can see that the ioctl is called with the following arguments:

ioctl(5, SIOCGIFHWADDR, {ifr_name="eth0", ifr_hwaddr={sa_family=ARPHRD_ETHER, sa_data=00:15:5d:2f:7f:86}}) = 0

Where 5 is the file descriptor, SIOCGIFHWADDR is the flag that corresponds to get/set HW address and the rest is the argp. But, how exactly can we iterate over this struct of data ?

The functionality of the code can be tested after getting the .so file and doing something like e.g,:

export LD_PRELOAD="./hostid-spoof.so" # the compiled lib
export MAC_ADDRESS="11:22:33:44:55:66"
ifconfig

Then the mac addresses of the net interfaces are indeed changed. But how exactly does this take effect?

CodePudding user response:

The MAC address isn't actually changed; rather, this returns a fake address to local processes such as ifconfig that try to retrieve the address. It's not clear why that would be valuable. What goes in or out on the wire won't be affected.

The LD_PRELOAD serves to "hook" the ioctl function from the standard C library. See What is the LD_PRELOAD trick?. The call is first is chained along to the real ioctl function. If the command was SIOCGIFHWADDR, then we mess with the returned data. The argp parameter points to a struct ifreq; see the netdevice(7) man page (for Linux, or the equivalent on your own OS). The author of the code presumably looked at the binary layout of struct ifreq on this OS to determine that the MAC address is located starting at byte 18 of the struct, and then pokes the desired fake address into the 6 bytes starting at that location, converting hex to binary in a somewhat clumsy fashion.


To see where the 18 comes from, here's what I did on my system:

  • As explained by the man page above, struct ifreq is defined in <net/if.h> as (irrelevant members omitted):
struct ifreq {
    char ifr_name[IFNAMSIZ]; /* Interface name */
    union {
        // ...
        struct sockaddr ifr_hwaddr;
        // ...
    };
};

<net/if.h> defines IFNAMSIZ as IF_NAMESIZE which is defined as 16. So there's 16 bytes right there. Next, struct sockaddr is defined in <sys/socket.h>, or rather in <bits/socket.h> which the latter includes, as:

struct sockaddr
  {
    __SOCKADDR_COMMON (sa_);    /* Common data: address family and length.  */
    char sa_data[14];           /* Address data.  */
  };

The __SOCKADDR_COMMON macro is defined in <bits/sockaddr.h> as:

#define __SOCKADDR_COMMON(sa_prefix) \
  sa_family_t sa_prefix##family

so it expands to sa_family_t sa_family;. And just above in <bits/sockaddr.h> we have

typedef unsigned short int sa_family_t;

Now unsigned short int is a 16-bit type on my system, so that's 2 bytes. The sa_data member will start immediately after that, because a char array doesn't require any padding. Thus 16 2 = 18.

Another way would have been to write a simple program that prints out the value of offsetof(struct ifreq, ifr_hwaddr) offsetof(struct sockaddr, sa_data). That also prints 18 on my system.


If you want to actually change the hardware MAC address of your interface, that's more a question of system administration than programming; ask on http://superuser.com or an appropriate site for your operating system.

  • Related