Wednesday, April 19, 2006

Recovering Netscape Communicator 4.7 POP3 passwords

Some people are still using Netscape Communicator 4.7, you know -- and they might have to recover forgotten POP3 passwords ...


Monitoring Netscape activity, it becomes pretty obvious that passwords are stored:
  • under the following registry key, of type REG_SZ:
HKCU\Software\Netscape\Netscape Navigator\biff\users\user name\servers\server name\password
  • in two files (prefs.js and liprefs.js), located inside the following directory:
C:\Program Files\Netscape\Users\user name\

For example, let's say that both values are :
  • =pGpFLmBAYsBTTdV
  • user_pref("mail.pop_password", "IqGGOfLNOzYScTc=");

Step 2: hand decryption

At first sight, it looks like file-stored passwords are relying on Base64 encoding (because of the = sign at the end). After a few tests, it is also noticeable that:
  • Original and encoded passwords have same length
  • Same passwords give same results
So we have unsalted, fixed-key encrypted passwords. Some people have already found that the encryption scheme is plain old XOR, so it is possible to recover the beginning of the XOR stream, by encrypting a known password and XOR-ing it with the result. First bytes can be found using a trivial Python script:

import base64
import operator
input = base64.b64decode("IqGGOfLNOzYScTc=")
key = "thisisatest"
print map(operator.xor, map(ord, input), map(ord, key))

[86, 201, 239, 74, 155, 190, 90, 66, 119, 2, 67]

However, this does not work for registry-stored passwords, and is totally unsatisfactory for the mind :)

Step 3 : binary analysis

So it's time to have a closer look at NETSCAPE.EXE ... The binary file is huge (over 5 MB), but not stripped - which is really cool.

After few minutes of browsing, it becomes pretty clear that MSG_SetPasswordForMailHost() is calling SECNAV_MungeString(), which is relying on the RC4 stream cipher. RC4_CreateContext() is initialized with a fixed key, namely:
  • 0xD0869CDEC6EEEB3E
Registry-stored passwords are encrypted with the same stream, but also reversed (using the strrev() function) and scrambled using the following table:

// To be used for characters in range [0x40 ... 0x7F]
unsigned char table[] = {
0x40, 0x4E, 0x4F, 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x41, 0x42,
0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x6E, 0x6F, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x61, 0x62,
0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F
Using Python Crypto Toolkit, it is now possible to test our results:

from Crypto.Cipher import ARC4
import base64
input = base64.b64decode("IqGGOfLNOzYScTc=")
key = "\xD0\x86\x9C\xDE\xC6\xEE\xEB\x3E"
print o.decrypt(input)


Job finished!


Gary Freedman said...
This comment has been removed by a blog administrator.
ulysse said...

Un gros coup d'Ethereal en écoutant que le port 110, c'est plus simple non?

newsoft said...

@Ulysse: dans la plupart des cas, oui. Mais quand la machine est saturée de spywares et ne démarre même plus, il est parfois utile de pouvoir récupérer l'information "offline" :)

Ulysse said...

Hum oui en effet, je n'avais pas pensé à ça.