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 ...

Step 1 : FILEMON and REGMON

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"
o=ARC4.new(key)
print o.decrypt(input)

thisisatest

Job finished!

Cryptozor & Steganozorus
or why dinosaurs disappeared

Introduction

Believe it or not, I have been told that some people rely on Cryptozor(us) software for file encryption.

Cryptozor(us) is from Thomas Nerrant. The author's primary web site seems down currently, but it is possible to find a working mirror: there are some available on [1][2][3].

First impression is that the GUI is ... poor, to say the least.

The software claims using PC1 and CARACACHS algorithms, which are mostly unknown from the community. This is often bad news ... for the software :)

First tries

First of all, let's try to encrypt a simple, plaintext file (such as "README.TXT"). The original file size is 15,331 bytes. A "README.TXT.CTZ" file is created on output. Here are the results using different passwords and the CARACACHS-128 algorithm:
  • File #1, input password "a", output file size 15,402 bytes.
  • File #2, input password "aa", output file size 15,402 bytes.
Let's compare both files using CrypTool. First, we can see that the output file distribution for #1 is far from being good:

But the really bad news is that file #2 has exactly the same distribution:

What kind of "encryption" algorithm could give such results ? Having a closer look, file #1 and #2 differ on byte ranges [0x00..0x1C] and [0x3C00..0x3C1F]. Both files are otherwise exactly the same !

At this point, we are pretty confident in the fact that the output file is "scrambled" with a fixed key, and that the encryption key is useless in the process of decryption. Now let's have a look at "CRYPTOZORUS.EXE".

Binary analysis

Having unpacked the file (upx -d cryptozorus.exe), we begin analysis. An idea to start with would be searching for a string reference to "!#? Bad Password ?#!". Such a reference can be found in the following piece of code (comments have been added later):

.text:0040314D case6_bad_password: ; CODE XREF: dispatch_sub+181j
.text:0040314D cmp ebx, 6
.text:00403150 jnz short case7_stopped
.text:00403152 mov word ptr [edi+10h], 50h
.text:00403158 mov edx, offset a?BadPassword? ; " !#? Bad Password ?#!"
.text:0040315D lea eax, [ebp+var_1C]
.text:00403160 call wrap_LStrFromPChar

The function at 0x00402F88 is clearly a switch/case related to the program's internal state. We call it "dispatch_sub()". When state == 6, "bad password" is displayed.

There are many references to dispatch_sub(), so it is better to trace the program with a debugger (namely OllyDbg) to find the caller on case 6. The result is pretty obvious:

.text:00408218 bad_password: ; CODE XREF: maybe_write_file+FFj
.text:00408218 push 6 ; 6 = BAD PASSWORD
.text:0040821A mov eax, [esi]
.text:0040821C mov edx, [eax]
.text:0040821E push edx
.text:0040821F call dispatch_sub

This piece of code is entered on the following condition:

.text:00408071 call test_password
.text:00408076 add esp, 8
.text:00408079 test al, al
.text:0040807B jz bad_password ; 6 = BAD PASSWORD

By replacing "jz bad_password" with "nop", it is possible to completly bypass password checking.

Conclusion

By patching 2 bytes inside the program file, we made a program that will decrypt any file without knowing the password. Total analysis time is under 20 minutes. Would you call that security software ?


PS. Having a closer look at test_password(), it seems that this function relies on the "CRYPTOZORUS_THOMASNERRANT.COM" string. The exact purpose of this string is left as an exercise to the reader. There are 2 possibilities:
  • This is the fixed encryption key;
  • This string, encrypted with user password, is used for password checking on decryption.