Back in 2022 I was speaking at BSides Sofia about hacking set-top boxes and decrypting IPTV streams. After the talk I met Alex Stanev who told me about the wpa-sec project he was running. The project is about collecting WPA handshakes and cracking them in distributed manner. I got interested and Alex was kind enough to share a lot of his research with me. It turns out that a lot of routers are using some crappy algorithms for generating pseudo-random default Wi-Fi passwords. These algorithms usually take device-bound constants like serial number, MAC address, SSID, etc for input. Most of the time the generated password looks “random” and “secure” and people don’t bother changing it. It is printed along with the default SSID on a sticker on the back of the router.
Finding such algorithms is a lot of fun and I immediately started looking for routers with seemingly random default Wi-Fi passwords. One of the first candidates was the Smartcom Ralink router which was being deployed by two of the largest telecoms in Bulgaria - A1 and Vivacom. They were giving it to their customers for free and according to Smartcom’s press release they have deployed over 200,000 units.
This is an example sticker from Smartcom router deployed by A1 with default password 6dc0b69c
:
And this is an example sticker from Smartcom router deployed by Vivacom with default password fb7958c1
:
In both cases the default password is a hex string of length 8 and most probably it is generated by the same algorithm. I opened the router and immediately found a UART header on the PCB. Then I connected my USB-to-serial adapter (old Arduino) to it and I got a nice root shell:
After dumping the root filesystem I found what I was looking for in the /bin/ralink_init
binary.
There is a show_wifi_pass()
function which Ghidra decompiled to this:
void show_wifi_pass(char *param_1) {
...
const char *local_80 = "SmartcomWifi";
...
if (param_1 == (char *)0x0) {
flash_read_serial(&local_e0);
}
else {
strncpy((char *)&local_e0,param_1,0x1f);
}
uVar3 = 0;
while( true ) {
sVar1 = strlen((char *)&local_e0);
if (sVar1 <= uVar3) break;
iVar4 = tolower((int)*(char *)((int)&local_e0 + uVar3));
*(char *)((int)&local_e0 + uVar3) = (char)iVar4;
uVar3 = uVar3 + 1;
}
snprintf(acStack_c0,0x3f,"%s%s",&local_e0,local_80);
MD5Init(auStack_70);
sVar1 = strlen(acStack_c0);
MD5Update(auStack_70,acStack_c0,sVar1);
MD5Final(local_f0,auStack_70);
iVar4 = 0;
pbVar2 = local_f0;
do {
iVar4 = iVar4 + 1;
printf("%02x",(uint)*pbVar2);
pbVar2 = local_f0 + iVar4;
} while (iVar4 < 4);
return;
}
They take the serial number and append the string SmartcomWifi
to it.
Then they calculate the MD5 hash of the resulting string and take the first 4 bytes of it.
Here is a simplified version of the algorithm in pseudo code:
function show_wifi_pass(serial) {
if (serial == NULL) serial = flash_read_serial()
serial = tolower(serial)
pwd = serial + "SmartcomWifi"
pwd_md5 = md5(pwd)
print(pwd_md5[0:8])
}
A quick test with the serial numbers from the stickers confirms that this is the algorithm used to generate the default Wi-Fi password:
$ echo -n 'dea15fb8SmartcomWifi' | md5sum | cut -c -8
6dc0b69c
$ echo -n 'de7ddbb8SmartcomWifi' | md5sum | cut -c -8
fb7958c1
The serial number is the last 4 octets of the MAC address of the router with some offset.
We have reported the vulnerability to Smartcom, A1 and Vivacom and we have received CVE-2025-22936. This is the timeline of the disclosure:
- 2 Jan 2025: Vulnerability reported to Smartcom, A1, Vivacom
- 2 Jan 2025: Vivacom acknowledges the vulnerability
- 6 Jan 2025: Smartcom states the products in question are EoL/EoD, there will be no fix
- 9 Jan 2025: 2nd reminder to A1 sent, no response
- 5 Feb 2025: Public disclosure
Based on information, collected in wpa-sec, at least 69% of A1 and 54% of Vivacom clients are using AP default ESSID and password. If you are one of them, change your Wi-Fi password immediately.