by
CASIMIR
Part B
Software by PepSoft
(You might need to use FTP Search to find the software.)
Try searching for WXF.
This essay presents in-extenso the reverse-engineering and cracking of an encryption utility distributed by PEPSOFTWARE Ltd, WinXFiles(TM). Map of the essay:
Introduction Strategy Getting prepared PART A - REVERSING OF WINXFILES 1. First contact 2. Where is the fork? 3. The Check routine 4. The making of Check_string 5. The Remainder 6. Decryption process PART B - ANALYSIS AND BREAKING OF WINXFILES Method 1: let's suppose Tail = 000... Step 1: Solving Step 2: Checking password found Notes on Method 1 Method 2: Tail = garbage Step 1: How to find 2 characters from pwd Step 2: System(s) solving Step 3: Checking password found Bad luck! Notes on Method 2 PART C - 'C' SOURCE OF CRACKER
As with former cracks on Crypt-o-Text and Encrypt-It for Windows, we take advantage of the fact that WinXFiles uses ALWAYS the SAME structure to store original file name and password into Header (see below). Separators (| and \) are always there. And we also know that "sometimes" (not always, because of WXF's weird attitude) the Tail is filled with 0s.
First of all, let's sum up actions performed by WXF (chronological order):<---------------- 255 bytes --------------> file_name|password\00000000000000000000...0 <name_ > <pwd_ > <-------- TAIL --------> length length <---------------- HEADER ----------------->
It would have spared us a lot of search time, but anyway there are not too many combinations to try out. Look:
for each name_len { for each pwd_len { for each remainder { build Remain find a password (2 methods) check it if ok : STOP!!! We found good password. else : go on searching... } } }Depending on what WXF puts in Tail (0s or garbage), we can use 2 methods to find a password. We try method 1 first, if it doesn't work we try method 2.
If we SUPPOSE that Tail is filled up with zeros (instead of garbage, see Chapter 4, note 3), then cracking is very easy.
For instance: With name_len = 9 characters pwd_len = 8 characters remainder fixed to any value in [00 ; FE] we must find each character from password, i.e: Pwd[0],Pwd[1],... and Pwd[7].
KNOWN KNOWN UNKNOWN Header[0] + Remain[0] - Pwd[0] = ? F Header[1] + Remain[1] - Pwd[1] = ? I Header[2] + Remain[2] - Pwd[2] = ? L Header[3] + Remain[3] - Pwd[3] = ? E Header[4] + Remain[4] - Pwd[4] = ? N Header[5] + Remain[5] - Pwd[5] = ? A Header[6] + Remain[6] - Pwd[6] = ? M Header[7] + Remain[7] - Pwd[7] = ? E Header[8] + Remain[8] - Pwd[0] = ? Header[9] + Remain[9] - Pwd[1] = | <-------------- first separator Header[10] + Remain[10] - Pwd[2] = Pwd[0] P Header[11] + Remain[11] - Pwd[3] = Pwd[1] A Header[12] + Remain[12] - Pwd[4] = Pwd[2] S Header[13] + Remain[13] - Pwd[5] = Pwd[3] S Header[14] + Remain[14] - Pwd[6] = Pwd[4] W Header[15] + Remain[15] - Pwd[7] = Pwd[5] O Header[16] + Remain[16] - Pwd[0] = Pwd[6] R Header[17] + Remain[17] - Pwd[1] = Pwd[7] D Header[18] + Remain[18] - Pwd[2] = \ <-------------- second separator Header[19] + Remain[19] - Pwd[3] = 0 => Pwd[3] found Header[20] + Remain[20] - Pwd[4] = 0 => Pwd[4] found Header[21] + Remain[21] - Pwd[5] = 0 => Pwd[5] found Header[22] + Remain[22] - Pwd[6] = 0 => Pwd[6] found T Header[23] + Remain[23] - Pwd[7] = 0 => Pwd[7] found A Header[24] + Remain[24] - Pwd[0] = 0 => Pwd[0] found I Header[25] + Remain[25] - Pwd[1] = 0 => Pwd[1] found L Header[26] + Remain[26] - Pwd[2] = 0 => Pwd[2] found . . . . . . . . .We found successively 3°,4°,5°,6°,7°,0°,1° and 2° characters from password.
Remember we made assumptions on:
Tail begins here -> Header[19] + Remain[19] - Pwd[3] = 0 ? Header[20] + Remain[20] - Pwd[4] = 0 ? Header[21] + Remain[21] - Pwd[5] = 0 ? Header[22] + Remain[22] - Pwd[6] = 0 ? Header[23] + Remain[23] - Pwd[7] = 0 ? Header[24] + Remain[24] - Pwd[0] = 0 ? Header[25] + Remain[25] - Pwd[1] = 0 ? Header[26] + Remain[26] - Pwd[2] = 0 ? Header[27] + Remain[27] - Pwd[3] = 0 ? . . . . . . . . . Header[254] + Remain[254] - Pwd[6] = 0 ? Tail ends here -> Header[255] + Remain[255] - Pwd[7] = 0 ?We check if remainder R is correct:
Pwd[0]+Pwd[1]+Pwd[2]+Pwd[3]+Pwd[4]+Pwd[5]+Pwd[6]+Pwd[7] remaider of: ------------------------------------------------------- = R ? FFWe check if separators ( | and \ ) are present:
Header[9] + Remain[9] - Pwd[1] = | ? Header[18] + Remain[18] - Pwd[2] = \ ?We perform the same test as WXF with Check_string. If password is correct, we must have :
Header[10] + Remain[10] - Pwd[2] = Pwd[0] ? Header[11] + Remain[11] - Pwd[3] = Pwd[1] ? Header[12] + Remain[12] - Pwd[4] = Pwd[2] ? Header[13] + Remain[13] - Pwd[5] = Pwd[3] ? Header[14] + Remain[14] - Pwd[6] = Pwd[4] ? Header[15] + Remain[15] - Pwd[7] = Pwd[5] ? Header[16] + Remain[16] - Pwd[0] = Pwd[6] ? Header[17] + Remain[17] - Pwd[1] = Pwd[7] ?If a password passes every test, it means that it is the GOOD password! So we can stop searching and have some rest!
If WXF effectively filled the Tail with 0s, then this method works very well, you'll always find the good password in a very short time (search time < 30 sec on a P150 32MB).
If this method gives nothing, we use the second one.
Well, it won't be as easy as with Method 1, because this time we can NOT use the Tail. Instead, we'll use the separators ( | and \ ) to find out 2 *distinct* characters from password. Then we'll try to find out missing characters.
!!!Hey, YOU, if you're still reading this crap and plan to mail the author, use the word 'cactus' in your letter (don't worry, just for statistical purposes)!!!
For instance:
With name_len = 9 characters pwd_len = 8 characters remainder fixed to any value in [00 ; FE]We must find each character from password, i.e: Pwd[0],Pwd[1],... and Pwd[7].
We use separators in Header to guess 2 characters from password. We have (refer to Method 1, Step 1):
Header[9] + Remain[9] - Pwd[1] = | => Pwd[1] = (Header[ 9]+Remain[ 9]-0x7C) Header[18] + Remain[18] - Pwd[2] = \ => Pwd[2] = (Header[18]+Remain[18]-0x5C)Now we know Pwd[1] and Pwd[2].
OK, now take a deap breath, it gets a little harder {;-)
We are going to use following equations to find Pwd[0], Pwd[3], Pwd[4], Pwd[5], Pwd[6] and Pwd[7]:
Header[10] + Remain[10] - Pwd[2] = Pwd[0] | Header[11] + Remain[11] - Pwd[3] = Pwd[1] | Header[12] + Remain[12] - Pwd[4] = Pwd[2] | Header[13] + Remain[13] - Pwd[5] = Pwd[3] | ----> system S0 Header[14] + Remain[14] - Pwd[6] = Pwd[4] | Header[15] + Remain[15] - Pwd[7] = Pwd[5] | Header[16] + Remain[16] - Pwd[0] = Pwd[6] | Header[17] + Remain[17] - Pwd[1] = Pwd[7] |Those 8 equations are equivalent to system S0:
| P0 + P2 = H10 + R10 | P1 + P3 = H11 + R11 | P2 + P4 = H12 + R12 system S0 | P3 + P5 = H13 + R13 | P4 + P6 = H14 + R14 | P5 + P7 = H15 + R15 | P0 + P6 = H16 + R16 | P1 + P7 = H17 + R17
S1: -> given P1, we can calculate successively : P3,P5 and P7. Last equation is used for checking. | P1========>P3 = H11+R11 => P3=H11+R11-P1 | | | P3========>P5 = H13+R13 => P5=H13+R13-P3 system S1 | | | P5======>P7 = H15+R15 => P7=H15+R15-P5 | | | P1 P7 = H17+R17 check P1=H17+R17-P7? S2: -> given P2, we can calculate successively : P0,P4 and P6. Last equation is used for checking. | P0<=======P2 = H10+R10 => P0=H10+R10-P2 | | | P2=======>P4 = H12+R12 => P4=H12+R12-P2 system S2 | | | P4=======>P6 = H14+R14 => P6=H14+R14-P4 | | | P0 P6 = H16+R16 check P0=H16+R16-P6?So, by solving those 2 sub-systems above, we can recover the whole pwd.
As with Method 1, we made assumptions on:
For instance:
With name_len = 8 characters pwd_len = 6 characters remainder fixed to any value in [00 ; FE]We must find each character from password, i.e: Pwd[0],Pwd[1],... and Pwd[5].
KNOWN KNOWN UNKNOWN Header[0] + Remain[0] - Pwd[0] = ? F Header[1] + Remain[1] - Pwd[1] = ? I Header[2] + Remain[2] - Pwd[2] = ? L Header[3] + Remain[3] - Pwd[3] = ? E Header[4] + Remain[4] - Pwd[4] = ? N Header[5] + Remain[5] - Pwd[5] = ? A Header[6] + Remain[6] - Pwd[0] = ? M Header[7] + Remain[7] - Pwd[1] = ? E Header[8] + Remain[8] - Pwd[2] = | <-------------- first separator Header[9] + Remain[9] - Pwd[3] = Pwd[0] | Header[10] + Remain[10] - Pwd[4] = Pwd[1] P | Header[11] + Remain[11] - Pwd[5] = Pwd[2] W | Header[12] + Remain[12] - Pwd[0] = Pwd[3] D | ----> system S0 Header[13] + Remain[13] - Pwd[1] = Pwd[4] | Header[14] + Remain[14] - Pwd[2] = Pwd[5] | Header[15] + Remain[15] - Pwd[3] = \ <-------------- second separator Header[16] + Remain[16] - Pwd[4] = ? Header[17] + Remain[17] - Pwd[5] = ? T Header[18] + Remain[18] - Pwd[0] = ? A . . . I . . . L . . .Using separators we find Pwd[2] and Pwd[3]:
Header[8] + Remain[8] - Pwd[2] = | => Pwd[2] = (Header[ 8]+Remain[ 8]-0x7C) Header[15] + Remain[15] - Pwd[3] = \ => Pwd[3] = (Header[15]+Remain[15]-0x5C)This time, system S0 looks like this:
Header[9] + Remain[9] - Pwd[3] = Pwd[0] | Header[10] + Remain[10] - Pwd[4] = Pwd[1] P | Header[11] + Remain[11] - Pwd[5] = Pwd[2] W | Header[12] + Remain[12] - Pwd[0] = Pwd[3] D | ----> system S0 Header[13] + Remain[13] - Pwd[1] = Pwd[4] | Header[14] + Remain[14] - Pwd[2] = Pwd[5] | P0 + P3 = H9 + R9 | P1 + P4 = H10 + R10 | P2 + P5 = H11 + R11 system S0 | P0 + P3 = H12 + R12 | P1 + P4 = H13 + R13 | P2 + P5 = H14 + R14We can cut system S0 into 3 sub-systems S1, S2 and S3:
S1: -> given P3, we can calculate P0 with first equation. Second equation is used for checking. | P0<============P3 = H9 + R9 => P0=H9+R9-P3 | system S1 | | P0 P3 = H12 + R12 check P0=H12+R12-P3? S2: -> given P2, we can calculate P5 with first equation. Second equation is used for checking. | P2============>P5 = H11 + R11 => P5=H11+R11-P2 system S2 | | | P2 P5 = H14 + R14 check P5=H14+R14-P2?BUT for S3 : as we don't know P1 nor P4 we can't do anything. BTW, WXF will neither be able to check correctly input when there are 3 or more sub-systems.
For instance: with:
Pwd[2] = 36We can find various solutions to S3 (compatible with remainder value):
Pwd[3] = 36
S1 : Pwd[0] + Pwd[3] = 6C => Pwd[0] = 36 S2 : Pwd[2] + Pwd[5] = 6C => Pwd[5] = 36 S3 : Pwd[1] + Pwd[4] = 6C
Pwd[1] = 33 ; Pwd[4] = 39 Pwd[1] = 34 ; Pwd[4] = 38 etc...Try it: encrypt file SIX6.BMP using pwd 666666, then decrypt it using pwd 656676. WXF will let you in (!), but decryption will be erroneous.
If original text was: ABCDEFGHIJKL...
then you will get: A?CD?FG?IJ?L... ('?' means: wrong character)
I had fun playing with pictures too! When decrypted with a wrong pwd, you can observe weird (and sometimes beautiful) transformations. BTW, you need to edit file's header once decrypted, so a viewer can handle picture. Otherwise, you just get a 'Bad/Unknown Format' message when try to open picture.
Solvability depends on number of sub-systems you obtain:
By looking at various combinations of name_len/pwd_len, i found out that about 25% of files are auto-immune to Method 2 {:-( . Hey, don't cry, that means we gonna crack 3 files out of 4, not that bad! {:-)
In terms of solving speed, Method 2 is equivalent to Method 1 (search time < 30 sec on a P150 32MB). As Cracker try Method 1 first, then Method 2, the whole search time may not exceed one minute.
Click here for Part C. It includes C cracking source code and an executable.
Copyright December 1998 by Casimir.
Mail Casimir
Converted to hypertext by Joe Peschel December 16, 1998.