March 22, 2015

How to Bypass MAC Address License Validation

Obvious disclaimer: Using the information below to circumvent the license validation mechanism of a commercially licensed software product is almost certainly illegal in your city, state, nation, tribe, or religiously fundamental militia.

Let’s say you’re a software developer putting the finishing touches on a product that’s due to go to market shortly. The product is licensed on a per-machine basis, and you’re responsible for designing and implementing the license validation mechanism.

You are in an unenviable situation. It is 2015, and the concept of a machine has evolved from a static, hardware-based entity into an ephemeral, virtual one. In all likelihood, your customers will run your product on x86 virtual machines rather than AIX appliances. The VMs may be onsite, they may be hosted by a third-party service provider, or both. One thing is certain: basing your license validation mechanism on the static properties of an inherently ephemeral, virtual machine will introduce you to a world of hurt. When your customers replace an old VM with a new one, the new VM will, in all likelihood, have a different MAC address, and may have a different IP. If you base your license validation mechanism on MAC addresses, you will face a stream, maybe even a flood, of requests by customers to associate a new MAC address with an existing license. Those requests will take time and money to process and validate.

What’s more, MAC addresses can be spoofed at the operating system level. If your license validation routine checks for a static MAC address attached to a license, and I buy a license for MAC address ea:04:89:05:02:58, I would not be able to run your software on my Debian machine possessing the following network interfaces:

eth0      Link encap:Ethernet  HWaddr 43:87:35:96:2e:75
          inet addr:  Bcast:  Mask:
          inet6 addr: 2601:9:500:a0e:3e97:eff:fe9b:3e71/64 Scope:Global
          inet6 addr: fe80::3e97:eff:fe9b:3e71/64 Scope:Link
          RX packets:521 errors:0 dropped:0 overruns:0 frame:0
          TX packets:541 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:205515 (200.6 KiB)  TX bytes:72870 (71.1 KiB)
          Interrupt:20 Memory:f1500000-f1520000 

lo        Link encap:Local Loopback  
          inet addr:  Mask:
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:356 errors:0 dropped:0 overruns:0 frame:0
          TX packets:356 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:184321 (180.0 KiB)  TX bytes:184321 (180.0 KiB)

This machine has a single Ethernet interface with the usual loopback interface, about as simple as it gets. The eth0 interface has a MAC address of 43:87:35:96:2e:75 burned into it; assuming no bugs in your code, your software will refuse to start when I run it on this machine. But if I modify the eth0 entry in my /etc/network/interfaces file to the following:

allow-hotplug eth0
iface eth0 inet dhcp
  hwaddress ether ea:04:89:05:02:58 

and bring the eth0 interface down and back up, ifconfig will show that eth0’s MAC address has been changed accordingly:

eth0      Link encap:Ethernet  HWaddr ea:04:89:05:02:58  
          inet addr:  Bcast:  Mask:
          inet6 addr: 2601:9:500:a0e:e804:89ff:fe05:258/64 Scope:Global
          inet6 addr: fe80::e804:89ff:fe05:258/64 Scope:Link
          RX packets:4824 errors:0 dropped:0 overruns:0 frame:0
          TX packets:4147 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:3758601 (3.5 MiB)  TX bytes:559613 (546.4 KiB)
          Interrupt:20 Memory:f1500000-f1520000 

Now if I try to run your software, license validation will succeed. But depending on the circumstances, this may cause more problems for me than it solves, notwithstanding questions of legality. If I spin up multiple machines with the MAC address overridden in this fashion, and these machines are on the same network, the entry for ea:04:89:05:02:58 in the router’s MAC table will change continually as each machine sends frames across the network. Thus, only one machine will ever be reachable at a time.

Furthermore, some enterprise environments maintain a whitelist of MAC addresses and associated metadata; although implementations of this whitelist and associated software vary, the point is usually to prevent any unknown NICs, or duplicate (spoofed) NICs, from ever achieving network connectivity.

At this point, I could admit defeat, open a support case with you, and request that the license I bought have the associated MAC address changed. This likely forces me to wait a few hours at best and a day or more at worst. This isn’t a big deal if I’m required to do this on an infrequent basis, but if I have to do this often, it will introduce significant delays to my otherwise instant provisioning and configuration procedures.

Not wanting to deal with the deterimental consequences to my own operations as a result of your inflexible license validation scheme, I go back to the technical drawing board. Rather than override the MAC address on eth0 itself, I create a new MAC VLAN interface on my machine:

# ip link add link eth0 address ea:04:89:05:02:58 vlan0 type macvlan

I’ve named this new interface vlan0 and given it the MAC address that’s associated with the license I currently have. A MAC VLAN is conceptually layered atop an existing interface, and can have its own MAC address and IP address. Traffic on this particular interface will be sent to eth0. After bringing the new interface up:

# ip link set up dev vlan0

running ifconfig shows the newly added interface as follows:

vlan0     Link encap:Ethernet  HWaddr ea:04:89:05:02:58  
          inet6 addr: 2601:9:500:a0e:e804:89ff:fe05:258/64 Scope:Global
          inet6 addr: fe80::e804:89ff:fe05:258/64 Scope:Link
          RX packets:5 errors:0 dropped:0 overruns:0 frame:0
          TX packets:32 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0 
          RX bytes:670 (670.0 B)  TX bytes:5337 (5.2 KiB)

This interface is completely invisible to the outside network. The router doesn’t see it, and any enterprise MAC address filtering software will not see it. When frames are emitted on vlan0, they will in turn be emitted by eth0; when they are forwarded across the network, their source MAC address will be that of eth0, not of vlan0.

But how to emit frames on vlan0 in the first place? You will need to modify your IP routing table. Running ip route will show something along the lines of:

default via dev eth0 dev eth0  scope link  metric 1000 dev eth0  proto kernel  scope link  src 

These are the standard default gateway, zeroconf, and link-local routing entries. They are all associated with eth0. I can modify these entries to use vlan0 instead (note that order is important here: you must change the link-local entry before you change the default gateway entry, otherwise you will encounter an error):

# ip route change dev vlan0 proto kernel scope link src
# ip route change dev vlan0  scope link  metric 1000
# ip route change default via dev vlan0

A quick check of tcpdump -i vlan0 shows that traffic is now being routed through the interface successfully. Now, any application that queries for the system’s MAC address will be presented with the value ea:04:89:05:02:58, allowing me to use my current license on the machine without running into the problems caused by overriding the physical interface’s MAC address directly.

I present the narrative above not from the adversial perspective of an illicit consumer, but as a software developer that fails to see the efficacy of a license validation mechanism that uses MAC addresses. It is effectively pointless. The validation code has to be maintained, the licenses have to be generated and recorded, changes to the licenses have to be triaged by support personnel - but where are the benefits?

Support doesn’t benefit, because there’s nothing to be gained from having paying customers submit their licenses with every SR. Customers certainly don’t benefit, as license files are yet another resource for them to monitor and acquire. That leaves the billing department. If the software license agreement stipulates that the company is to be paid by the customer for each per-machine license, I can see how that simplifies billing significantly. Yet the narrative above shows how easily falsifiable MAC addresses are, which means billing is in turn relying on a falsifiable metric for payment purposes.

Most, if not all, customers are going to play by the rules. The risk is that potential new customers with more modern computing environments and better technical discernibility will choose to take their business elsewhere. The days of per-CPU, per-core, and per-machine license agreements are rapidly fading, and for good reason. Usage of a licensed application cannot credibly be measured in CPUs or machines, especially in organizations with multiple replicated environments for development, testing, and production. Add in the historically new ability to dynamically expand and contract environment footprints in terms of machines and data centers, and per-machine licensing becomes uneconomical and impractical.

Per-machine licensing is indicative of short-sighted management, and leads to technical implementations like MAC address validation that completely fail to accomplish the goal of enforcing one license per machine in the first place. If licensing costs are to be tied to a metric, an auditable or puplicly disclosable value should be used, such as employee count, quarterly revenue, quarterly profit, etc. Adopting such a licensing scheme eliminates all of the inconveniences of per-machine licensing for both provider and consumer, along with the temptation to employ technical circumventions such as the one detailed here.