Sunday, April 17, 2016

GNU macchanger <= 1.6.0 Heap Buffer Overflow

I was poking around the GNU macchanger source code the other day for fun and happened across this little gem. Unfortunately modern heap-corruption protections make exploitation extremely difficult, if not impossible (to the best of my knowledge), and even then, you would only be able to gain the privileges of the current user. But what the heck, let’s dive in anyway! A little throwback to the glory days of stack smashing.

In case you aren’t aware, GNU macchanger is a simple program written in C which alters the MAC address of a given network interface. How it does this is beyond the scope of this blog-post, but it can be useful if you need to, for instance, troll sysadmins by bypassing their impenetrable MAC-address-whitelist. Let me also shout out to macchanger’s author, Alvaro Lopez, for the fantastic work. Over-all, macchanger is a very useful bit of code, and I can’t thank you enough. I will be submitting a patch to resolve this issue as soon as I’m done typing here. Anyone wishing to download a copy of the code can do so at:

The issue exists in the file netinfo.c:38-58, in the function mc_net_info_new(). Relevant code…

net_info_t *
mc_net_info_new (const char *device)
        net_info_t *new = (net_info_t *) malloc (sizeof(net_info_t));
        new->sock = socket (AF_INET, SOCK_DGRAM, 0);

        if (new->sock<0) {
                perror ("[ERROR] Socket");
                return NULL;

        strcpy (new->dev.ifr_name, device);

        if (ioctl(new->sock, SIOCGIFHWADDR, &new->dev) < 0) {
                perror ("[ERROR] Set device name");
                return NULL;
        return new;

I could spend the next 10 paragraphs explaining to you why strcpy()’ing arbitrary data into a fixed length character array is a bad idea, but AlephOne beat me to it back in Phrack 49 (1996). So instead let’s just take a closer look at what’s getting clobbered.

This function accepts one const char * argument called device. This value will be taken from argv[argc-1] (the last command line argument), and represents the network interface macchanger should be working with.

This function declares one local variable of type net_info_t * called new. net_info_t is a struct, defined in netinfo.h:33-36. Relevant code…

typedef struct {
        int sock;
        struct ifreq dev;
} net_info_t;

IF_NAMESIZE is previously define as 16 on line 32 of the same file. This ultimately means that new->dev.ifr_name (which strcpy() is going to use as the copy destination) will only be 16 bytes, whereas device, (our command line argument), can be arbitrarily long. And that leads to large quantities of fun. :) Relevant…

andrew@WOPR ~ % macchanger BBBBBBBBBBBBBBBB
*** buffer overflow detected ***: macchanger terminated
======= Backtrace: =========
======= Memory map: ========
00400000-00403000 r-xp 00000000 fe:01 2292415                            /usr/bin/macchanger
00602000-00603000 r--p 00002000 fe:01 2292415                            /usr/bin/macchanger
00603000-00604000 rw-p 00003000 fe:01 2292415                            /usr/bin/macchanger
022e8000-0238d000 rw-p 00000000 00:00 0                                  [heap]
7f9e476b7000-7f9e476cd000 r-xp 00000000 fe:01 2246088                    /usr/lib/
7f9e476cd000-7f9e478cc000 ---p 00016000 fe:01 2246088                    /usr/lib/
7f9e478cc000-7f9e478cd000 rw-p 00015000 fe:01 2246088                    /usr/lib/
7f9e478cd000-7f9e47a71000 r-xp 00000000 fe:01 2231574                    /usr/lib/
7f9e47a71000-7f9e47c71000 ---p 001a4000 fe:01 2231574                    /usr/lib/
7f9e47c71000-7f9e47c75000 r--p 001a4000 fe:01 2231574                    /usr/lib/
7f9e47c75000-7f9e47c77000 rw-p 001a8000 fe:01 2231574                    /usr/lib/
7f9e47c77000-7f9e47c7b000 rw-p 00000000 00:00 0
7f9e47c7b000-7f9e47c9c000 r-xp 00000000 fe:01 2231549                    /usr/lib/
7f9e47e1c000-7f9e47e64000 rw-p 00000000 00:00 0
7f9e47e9a000-7f9e47e9b000 rw-p 00000000 00:00 0
7f9e47e9b000-7f9e47e9c000 r--p 00020000 fe:01 2231549                    /usr/lib/
7f9e47e9c000-7f9e47e9d000 rw-p 00021000 fe:01 2231549                    /usr/lib/
7f9e47e9d000-7f9e47e9e000 rw-p 00000000 00:00 0
7fffa1aca000-7fffa1aeb000 rw-p 00000000 00:00 0                          [stack]
7fffa1bfc000-7fffa1bfe000 r-xp 00000000 00:00 0                          [vdso]
7fffa1bfe000-7fffa1c00000 r--p 00000000 00:00 0                          [vvar]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
[1]    5575 abort (core dumped)  macchanger BBBBBBBBBBBBBBBB

Here's the patch...

--- /tmp/macchanger-1.6.0/src/netinfo.c 2013-03-16 17:56:37.000000000 -0500
+++ /tmp/macchanger-1.6.0-patched/src/netinfo.c 2014-08-23 16:25:31.929638204 -0500
@@ -47,6 +47,12 @@
        return NULL;
+   if (strlen(device) >= IFNAMSIZ) {
+       fprintf (stderr, "[ERROR] Device name too long\n");
+       free(new);
+       return NULL;
+   }
    strcpy (new->dev.ifr_name, device);
    if (ioctl(new->sock, SIOCGIFHWADDR, &new->dev) < 0) {
        perror ("[ERROR] Set device name");

Remember kids… Just say no to strcpy()