关于系统调用 ioctl 和 hostID 操作的问题

Question on syscall ioctl and hostID manipulation

我正在寻找一种方法来使用我的 MAC 地址并对其进行修改,我偶然发现了以下有效的 ANSI-C 代码片段,但我不确定如何进行。在代码片段中,我为我想问的问题添加了要点。

/*
 * 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]!='[=10=]') break;
            ptr[i] = val;
            p+=3;
        }
    }
    return ret; 
}

所以,我的第一个问题 [1]: 语句unsigned char *ptr = argp + 18;中的'addition'的目的是什么?我们如何得出这个数字?例如,在查看 strace ifconfig 之后,我可以看到使用以下参数调用了 ioctl:

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

其中5是文件描述符,SIOCGIFHWADDR是对应get/set硬件地址的flag,其余是argp。但是,我们究竟如何迭代这个 struct 数据?

获取 .so 文件并执行以下操作后,可以测试代码的功能,例如:

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

那么网口的mac地址确实改变了。但这到底是如何生效的呢?

MAC地址实际上并没有改变;相反,这个 returns 是试图检索地址的 ifconfig 等本地进程的假地址。目前尚不清楚为什么这会很有价值。进出电线的东西不会受到影响。

LD_PRELOAD 用于从标准 C 库中“挂钩”ioctl 函数。请参阅 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(对于 Linux,或您自己的等效内容 OS)。代码作者大概查看了OS上struct ifreq的二进制布局,确定MAC地址位于struct的第18字节开始,然后戳出想要的fake地址转换为从该位置开始的 6 个字节,以有点笨拙的方式将十六进制转换为二进制。


要查看 18 的来源,这是我在我的系统上所做的:

  • 如上面的手册页所述,struct ifreq<net/if.h> 中定义为(省略无关成员):
struct ifreq {
    char ifr_name[IFNAMSIZ]; /* Interface name */
    union {
        // ...
        struct sockaddr ifr_hwaddr;
        // ...
    };
};

<net/if.h>IFNAMSIZ 定义为 IF_NAMESIZE,后者定义为 16。所以那里有 16 个字节。接下来,struct sockaddr<sys/socket.h> 中定义,或者更确切地说在 <bits/socket.h> 中定义,后者包括,如:

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

__SOCKADDR_COMMON宏在<bits/sockaddr.h>中定义为:

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

所以它扩展到 sa_family_t sa_family;。在 <bits/sockaddr.h> 的上方我们有

typedef unsigned short int sa_family_t;

现在 unsigned short int 在我的系统上是 16 位类型,所以是 2 个字节。 sa_data 成员将在那之后立即开始,因为 char 数组不需要任何填充。因此 16 + 2 = 18.

另一种方法是编写一个简单的程序来打印出 offsetof(struct ifreq, ifr_hwaddr) + offsetof(struct sockaddr, sa_data) 的值。这也在我的系统上打印 18。


如果您想实际更改接口的硬件 MAC 地址,那更多的是系统管理问题,而不是编程问题;在 http://superuser.com 或适合您的操作系统的网站上询问。