Keyspace List for WPA on Default Routers
#41
I downloaded the firmwares from that link and extracted them manually, then mounted the jffs2 image and pulled everything off. Maybe I needed root to pull off the other directories... who knows, but I'll see if I can get an actual 588 firmware Smile

I also just remembered that I had pulled another firmware off an arris device and found libarrispassword.so (I think that was the name?) and it had the POTD algorithm in it, but I didn't find any WPA algos. POTD is generated from the MAC address but I don't have all the RE skill set necessary.
#42
Pardon me if I sound ignorant, but looking through the source code will get you nowhere. Any half decent manufacturer will remove code used to generate "secret" values. This is where disassembling compiled firmware wins, because they have to include that code for the product to work, granted you can't just "decompile" and get the C code. I have an NVG589 on the way and some tools I can use to pull a live, unedited firmware (I've never actually tried before so let's hope I don't royally mess up). I'm lead to believe that the firmwares hosted online were intentionally stripped.

If you look hard enough, EVERY manufacturer keeps a backdoor hidden. I was recently looking at a SKY (U.K. ISP) router and someone pointed out that the last character of the passphrase (length 10) was predictable, and furthermore there were a set of 16 characters used instead of a full 26, reducing the keyspace MASSIVELY, yet making it look much larger.

Belkin broadcasted the serial number in the probe responses, which was used ((in some models) to generate the WPS pin. While the serial number doesn't seem to be broadcast in probe responses from the newer ATT gateways, they are used in the SSID. I doubt it will be as simple as taking those 7 characters and running some algorithm and getting a password/wps pin, but it very well may be a part of it... we won't know until we look.

The moral of the story is manufacturers (or governments?) always want a way in, so they just have to get more creative with the ways they hide it. Also, I could be completely wrong and it could just be burned into the NVRAM at the factory but we will just have to wait and see Smile
#43
(06-20-2017, 10:12 PM)fart-box Wrote: I'm afraid I have to agree with Mrfancypants when he said "it looks like these routers come with the 10-char SSID and the non-alphanumeric password burned directly into EEPROM at the factory", if for no other reason than that the WPA pass phrase is already printed right on the label when the router leaves the factory. It doesn't make sense that the router would need to "rebuild" the WPA pass phrase every time it does a hard re-boot.

That's not a bulletproof conclusion. Consider that the SSID is normally printed on the label too, but most firmwares I've seen so far also have code that generates the SSID inside firmware.

Your typical router has a tiny amount (kilobytes worth) of storage for things like the password and the wi-fi config. It's "natural" for it to have fields for:
* Serial number
* (Optionally) MAC address (sometimes serial and MAC are related)
* Model identifier
* Access code
* WPA passkey
...

If you try to do a full factory reset, it can do one of three things. It can either wipe the access code and the WPA passkey (leaving the device unsecured), or reset them to fixed values, or it can compute default values from the serial and/or MAC.

AT&T routers are a bit different, in that they *also* have, right in the platform chunk with the serial number:
* Default access code
* Default wireless key
* Default SSID

If you do a factory reset or use the box without changing defaults, they simply pull the values from the platform chunk and use them until you put in custom values.

Per the open-source headers, that may be the case for all firmware versions 9.x and 10.x (which is pretty much all firmwares we have):

/* Here begins the new fields introduced by 9x */
char serNumStr[MAX_SERNUM_STRLEN + 1];

/* Default "Wireless Network Key" (for AT&T, similar to access code) */
char wirelessKey[MAX_WIRELESS_KEY_STRLEN + 1];

/* Default "Wireless SSID" (for AT&T) */
char wirelessSSID[MAX_WIRELESS_SSID_STRLEN + 1];
ubyte allowFWUpdate;


Quote: "The Wi-Fi Network Name consists of “ATT” plus the last seven characters of the Wi-Fi Gateway’s serial number."

I'm pretty sure that's not true for most newer models, unless the gateway has a "secret" serial number in addition to the one printed on the label. Your typical 589 has a serial number that looks like "163150389796448" and is equal to its MAC address converted to decimal. Its SSID is mixed letters and digits.
Still, there may be something here.

Quote:I'm not sure why you guys are looking through older firmware for a solution, when clearly the NVG589 and NVG599 are the products we're interested in, but I'm sure you know what you're doing, and you're much better at this than I am.

I would expect the algorithm to be basically similar between 589s and older models, just with more obfuscation. If we could figure out how the WPA key was generated before firmware 9.x, that would be a big step forward.

Unfortunately, the only devices for which pre-9.x AT&T firmwares exist are apparently Motorola 2247-N8 and 2Wire 2701/3801. Motorola 2247-N8 uses a 20-hex WPA key that is generated from its access key using SHA1. I'm not sure yet if it generalizes to later versions. Motorola went 9.x with NVG510, I have firmware dated September 2011 (9.0.6h0d48) which looks very similar to their current offerings.
2701/3801s have been shipping with 10-digit keys for ages (since 2005 if not longer). Both are well hardened against reverse engineering. I had no luck reversing 2701s for multiple reasons, and I couldn't even find any 3801 firmwares to download. There has been least one successful attempt to dump flash images from either, but it's been so long that even the files are no longer available.
#44
(06-21-2017, 08:32 AM)mrfancypants Wrote: I would expect the algorithm to be basically similar between 589s and older models, just with more obfuscation. If we could figure out how the WPA key was generated before firmware 9.x, that would be a big step forward.

I wouldn't say this for sure, many times ISPs use reference code from the manufacturer and change the stuff that matters most to them, like the SSID (think 2WIREXXX vs ATTXXX). 2Wire supplied the gateways, but ATT changed the firmware a bit to make the device "unique" to ATT. They both kept the same 10 digit password. Same goes for MyCharterWifi/MySpectrumWifi and NETGEAR devices, both use the adj+noun+x digits, etc etc.
#45
No manufacturer pinouts on the 589 board, I'll have to test some of the pinouts and see if I can find a JTAG/UART interface up.
#46
I've finally worked out part of the algorithm for 589/599. Not enough to crack it (in fact, with what I worked out, it's totally possible that it's [effectively] uncrackable because they feed it from a RNG), but enough to understand how passwords are being constructed.

Consider the following. Actual parameters of a NVG599 off eBay:

SSID: ATTn3f64I2
Wireless key: nyrip9=c5bgv
Access key: 18?/72@@<3
Second SSID: vATTvb%g?<&c
Second wireless key: #h,t)0(ZUwI0

Looks random, right? Now watch:

Code:
ssid_charset='23456789ABCDEFGHIJKMNPQRSTUVWXYZabcdefghijkmnpqrstuvwxyz'
pw_charset='abcdefghijkmnpqrstuvwxyz23456789#%+=?'
ext_charset='!"#$%&\'()*+,-./:;<=?@[]_`{|}0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
def intpw(x):
   val=0
   for n in range(0,12):
           val+=pw_charset.find(x[n])*(37**(11-n))
   if (val%8)==7:
           val+=37**12
   return val
def intssid(x):
 val=0
 for n in range(0,7):
  val+=ssid_charset.find(x[n+3])*(56**(6-n))
 return val
def int_ext(x):
   val=0
   for n in range(0,len(x)):
           val+=ext_charset.find(x[n])*(90**(len(x)-1-n))
   return val

>>> '%x' % intssid('ATTn3f64I2')
'13c2a3ea400'
>>> '%x' % intpw('nyrip9=c5bgv')
'7a7b4bbbf4f69800'
>>> '%x' % int_ext("b%g?<&c")
'1f71654cac80'
>>> '%x' % int_ext("#h,t)0")
'3d6180c00'
>>> '%x' % int_ext("(ZUwI0")
'a98a65dc0'

I'll let you meditate on this for now and I'll explain later Smile (hint: consider positions of top and bottom set bits in '7a7b...')
#47
AHA!

Code:
>>> def gen_599_pw(x):
...     x=int(float(x*(2**32+2)))
...     pw=''
...     for n in range(0,12):
...             rem=x%37
...             pw=pw_charset[rem]+pw
...             x/=37
...     return pw
...
>>>
>>> gen_599_pw(0x7a7b4bbb)
'nyrip9=c5bgv'

The input value is an integer in between 0 and 0x7FFFFFFF (2.1 billion possibilities).

I haven't yet worked out where this number comes from (that one may be random or derived with a MD5/SHA hash, it does not look sequential or clearly related to either the serial, the MAC, or the install date), but, even not knowing the exact number, we cut the time to process all keyspace from thousands of years to a couple of hours.

I wonder if this rates a CERT vulnerability filing.

This will work for 599s, some 589s, and newest BGW210s. This will not work for 5268s or for older 589s. As a rule of thumb, it would work if the SSID starts with a letter (not counting 'ATT').

EDIT: there is an outstanding minor rounding-related problem. For x=0x5f03b1, it generates 'afw7b4vnych7' but I saw a device with 'afw7b4vnych%'. (0x...0be0762 is rounded down to ..0be0760, but, to get the correct pw, it needs to be rounded up to ..0be0764.) Should be pretty rare.

Quote: I wonder if anybody else has noticed an extra character at each end of the second SSID (making it twelve characters long instead of ten characters)

They add a letter 'v' to either side of 'ATT'. So it becomes 'vATTv.....', twelve characters. 'v' may stand for 'video'. AT&T U-Verse comes with optional TV service and this second network may be for connecting the wireless TV receiver to the router.

Quote:I think I read earlier in this thread that the router might regenerate it's SSID and wireless key during a hard reboot or after a firmware upgrade. So I have to ask, wouldn't that preclude the use of a random number generator (RNG)?

Yes, that's why AT&T routers maintain a separate copy of the "default wireless key" so they don't need to regenerate it on the spot.
#48
(06-29-2017, 09:24 PM)fart-box Wrote: I hope that you will explain this in great detail for the benefit of those of us who struggle with programing. I, for instance, got lost at your clue 'set bits', and I'm sure your post will be read by many others who might be even less skilled than I am.

Yeah, that may have been a bit too obscure Smile

Anyhow, what you should have noticed in my quote is that my functions produce _way_ too many trailing zeros. Three out of five generated numbers are multiples of 256. The odds of that happening by accident are astronomical.
That's a strong clue that I have reversed the right process and arrived at some intermediate parameter, which happens to be a multiple of 16 and usually a multiple of 256 for some internal reason (TBD).

You can collect more SSIDs and passwords and I can promise you that, 99 times out of a 100, my functions will produce numbers with trailing zeros. And it is going to happen _only_ with charsets as I defined them (even if you swap the letters block and the digits block in the password charset, the pattern will mostly disappear.)

What we've determined is that the generator produces a number N, repeatedly does charset[N % len(charset)] to produce chars, and then concatenates resulting chars. (Think of it as conversion of N into e.g. base 37 for the password, or base 56 for the first SSID.) It is not a big leap to presume that the number may be larger than necessary (probably a 64-bit) and some lead chars get lopped off to keep only as many as we need.

At this point it rates a "very curious pattern" but it's not yet terribly informative. We take the next step by looking, like I said, at set bits. Most of these values are too short (the first SSID, for example, takes 56^7 ~ 2^41 values, and there are millions of possible 64-bit ints that could produce it). The second password, conversely, is too long (90^12 ~ 2^78), which is why it's generated in two parts (first six letters and then second six letters). But the 1st password range is just right (37^12 ~ 2^63) and we can deduce the mysterious N exactly.

In my example we have 0x7a7b4bbbf4f69800, which has highest set bit at 62 and the lowest set bit at 11. Which by itself may not jog any thoughts (it didn't for me at first), but, as you look at more passwords, you might find 0x810c8c01021918, with top and bottom bits at 55 and 3 respectively.

Now, what do you know that has at most 52 significant bits? The answer is, "double-precision floating point value"! I have no idea why they'd drag floating point into what should be a pure integer problem, but they clearly did, and in process they left an opening through which one could methodically work their way in. (If they just did N%len(charset) on a random 64-bit int, there would be no patterns to latch on to.)

And now all that is left is no notice that the value that goes in (before conversion to double erases lowest bits) is not purely random either, but happens to be a multiple of 2^32+2.
#49
That's Python (2.x) and no, you don't need an environment or an IDE, you can even do this

https://repl.it/JMbi/0

I go for MSVC++ for computationally expensive stuff, but, for basic analysis, command-line Python works very well:

Code:
>>> pws=open('c:\\texts\\589-pws.txt','r').readlines()
>>> pws
['2a4w8c3c7#8x\n', '2c7p482e7w3=\n', (...)  '7w7z226?4r9f\n', '7x5e572?6k53\n']
>>> pws=[x.replace('\n','') for x in pws]
>>> pws[3]
'2k722y6f4u7z'
>>> len(pws)
52
>>> pws.sort()
>>> pws[0]
'2%3y249=5c2s'
>>> y=[x[-2:] for x in pws]
>>> y
['2s', '2s', '9x', '2a', '9p', '8x', '3=', '5p', '7z', '4q', '35', '48', '7z', '8+', '48', '6?', '22', '4y', '5t', '8u',
'35', '6n', '4y', '66', '66', '3v', '8k', '6?', '3m', '3v', '6w', '2a', '35', '5j', '5j', '97', '7h', '5%', '7z', '2s',
'35', '3=', '84', '3m', '6w', '3v', '97', '2s', '6n', '7r', '9f', '53']
>>> len(list(set(y)))
31
>>> pws_2=[x for x in pws if x[0]=='2']
>>> pws_2
['2%3y249=5c2s',...., '2u6r7%9#8=35']
>>> z=''.join(pws)
>>> z
'2%3y249=5c2s245%...x5e572?6k53'
>>> charset=list(set(z))
>>> charset
['#', '%', '+', '3', '2', '5', '4', '7', '6', '9', '8', '=', '?', 'a', 'c', 'b', 'e', 'd', 'g', 'f', 'i', 'h', 'k', 'j',
'm', 'n', 'q', 'p', 's', 'r', 'u', 't', 'w', 'v', 'y', 'x', 'z']
>>> len(charset)
37
>>> for c in charset:
...     print c, z.count(c)
...
# 12
% 7
+ 7
3 54
2 45
5 67
4 46
7 48
6 50
9 29
8 47
= 12
? 10
a 9
c 9
b 6
e 7
d 7
g 1
f 5
i 4
h 3
k 15
j 5
m 7
n 10
q 9
p 8
s 12
r 8
u 8
t 10
w 12
v 7
y 10
x 7
z 11
#50
(07-05-2017, 12:21 AM)fart-box Wrote: Thank you, Mrfancypants! Maybe I'm getting too old for this, but Python never even crossed my mind for some reason. Less than a minute after booting up Linux, your code was running perfectly, as advertised.

Now I'm trying to find some correlation between the output of your code and the actual passwords we're hoping to find. I'm sure you've already exhausted that route, but sometimes two heads are better than one.

One interesting note though. One of the passwords on that list you provided contained the lower case letter 'L', which was omitted from your character set. After inserting the letter 'l' into your character set, I tried running a couple dozen random passwords (from your list) through the revised code and all those trailing zero's went away.

I'll keep playing with this and I'll be sure to let you know if I find anything else of interest.

I'm pretty sure that lower case L was a typo, for this exact reason. It might have been an 'i'.