Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Eilam E.Reversing.Secrets of reverse engineering.2005

.pdf
Скачиваний:
65
Добавлен:
23.08.2013
Размер:
8.78 Mб
Скачать

Deciphering File Formats 221

00401A9A

ADD ESI,98

00401AA0 SUB DWORD PTR SS:[ESP+14],1

00401AA5

JNZ SHORT cryptex.00401A70

00401AA7

MOV ECX,SS:[ESP+10]

00401AAB

MOV ESI,DS:[ECX]

00401AAD

TEST ESI,ESI

00401AAF

JE SHORT cryptex.00401ACC

00401AB1

MOV EDX,SS:[ESP+20]

00401AB5

MOV EAX,SS:[ESP+1C]

00401AB9

PUSH EDX

00401ABA

PUSH ESI

00401ABB

PUSH EAX

00401ABC

CALL cryptex.00401030

00401AC1

ADD ESP,0C

00401AC4

TEST ESI,ESI

00401AC6

MOV SS:[ESP+10],EAX

00401ACA

JNZ SHORT cryptex.00401A60

00401ACC

POP EDI

00401ACD

POP ESI

00401ACE

POP EBP

00401ACF

MOV EAX,EBX

00401AD1

POP EBX

00401AD2

ADD ESP,8

00401AD5

RETN

 

 

Listing 6.6 (continued)

This function starts out with a familiar sequence that reads the Cryptex header into memory. This is obvious because it is reading 0x28 bytes from offset 0 in the file. It then proceeds to call into a function at 00401030, which, upon stepping into it, looks quite important. Listing 6.7 provides a disassembly of the function at 00401030.

00401030

PUSH ECX

 

00401031

PUSH ESI

 

00401032

MOV ESI,SS:[ESP+C]

 

00401036

PUSH EDI

 

00401037

MOV EDI,SS:[ESP+14]

 

0040103B

MOV ECX,1008

 

00401040

LEA EAX,DS:[EDI-1]

 

00401043

MUL ECX

 

00401045

ADD EAX,28

 

00401048

ADC EDX,0

 

0040104B

PUSH 0

; Origin = FILE_BEGIN

0040104D

MOV SS:[ESP+18],EDX

;

 

 

 

Listing 6.7 A disassembly of Cryptex’s cluster decryption function. (continued)

222 Chapter 6

00401051

LEA EDX,SS:[ESP+18]

;

00401055

PUSH EDX

; pOffsetHi

00401056

PUSH EAX

; OffsetLo

00401057

PUSH ESI

; hFile

00401058

CALL DS:[<&KERNEL32.SetFilePointer>]

0040105E

PUSH 0

; pOverlapped = NULL

00401060

LEA EAX,SS:[ESP+C]

;

00401064

PUSH EAX

; pBytesRead

00401065

PUSH 1008

; BytesToRead = 1008 (4104.)

0040106A

PUSH cryptex.00405050

; Buffer = cryptex.00405050

0040106F

PUSH ESI

; hFile

00401070

CALL DS:[<&KERNEL32.ReadFile>]

00401076

TEST EAX,EAX

 

00401078

JE SHORT cryptex.004010CB

 

0040107A

MOV EAX,SS:[ESP+18]

 

0040107E

TEST EAX,EAX

 

00401080

MOV DWORD PTR SS:[ESP+14],1008

00401088

JE SHORT cryptex.004010C2

 

0040108A

LEA ECX,SS:[ESP+14]

 

0040108E

PUSH ECX

 

0040108F

PUSH cryptex.00405050

 

00401094

PUSH 0

 

00401096

PUSH 1

 

00401098

PUSH 0

 

0040109A

PUSH EAX

 

0040109B

CALL DS:[<&ADVAPI32.CryptDecrypt>]

004010A1

TEST EAX,EAX

 

004010A3

JNZ SHORT cryptex.004010C2

 

004010A5

CALL DS:[<&KERNEL32.GetLastError>]

004010AB

PUSH EDI

; <%d>

004010AC

PUSH cryptex.004030E8

; format = “ERROR: Unable to

 

 

decrypt block from cluster %d.”

004010B1

CALL DS:[<&MSVCR71.printf>]

 

004010B7

ADD ESP,8

 

004010BA

PUSH 1

; status = 1

004010BC

CALL DS:[<&MSVCR71.exit>]

 

004010C2

POP EDI

 

004010C3

MOV EAX,cryptex.00405050

 

004010C8

POP ESI

 

004010C9

POP ECX

 

004010CA

RETN

 

004010CB

POP EDI

 

004010CC

XOR EAX,EAX

 

004010CE

POP ESI

 

004010CF

POP ECX

 

004010D0

RETN

 

 

 

 

Listing 6.7 (continued)

Deciphering File Formats 223

This function starts out by reading a fixed size (4,104-byte) chunk of data from the archive file. The interesting thing about this read operation is how the starting address is calculated. The function receives a parameter that is multiplied by 4,104, adds 0x28, and is then used as the file offset from where to start reading. This exposes an important detail about the internal organization of Cryptex files: they appear to be divided into data blocks that are 4,104 bytes long. Adding 0x28 to the file offset is simply a way to skip the file header. The second parameter that this function takes appears to be some kind of a block number that the function must read.

After the data is read into memory, the function proceeds to decrypt it using the CryptDecrypt function. As expected, the data-length parameter (which is the sixth parameter passed to this function) is again hard-coded to 4104. It is interesting to look at the error message that is printed if this function fails. It reveals that this function is attempting to read and decrypt a cluster, which is probably just a fancy name for what I classified as those fixed-sized data blocks. If CryptDecrypt is successful, the function simply returns to the caller while returning the address of the newly decrypted block.

Analyzing a File Entry

Since you’re working under the assumption that the block that was just read is the archive’s file directory or some other part of its header, your next step is to take the decrypted block and attempt to study it and establish how it’s structured. The following memory dump shows the contents of the decrypted block I obtained while trying to list the files in the Test1.crx archive created earlier.

00405050

00

00

00

00

02

00

00

00 .......

00405058

01

00

00

00

52

EB DD 0C

...Rë_.

00405060

D4

CB 55 D9 A4 CD E1 C6

ÔËUÙ¤ÍáÆ

00405068

96

6C

9C

3C

61

73

74

65

–lœ<aste

00405070

72

69

73

6B

73

2E

74

78

risks.tx

00405078

74

00

00

00

00

00

00

00

t.......

As you can see, you’re in the right place; the memory block contains the file name asterisks.txt, which you encrypted into this archive earlier. The question now is: What is in those 28 bytes that precede the file name? One thing to do right away is to try and view the data in a different way. In the preceding dump I used an ASCII view because I wanted to be able to see the file name string, but it might be easier to make out other fields by using a 32-bit view on this entry. The following are the first 28 bytes viewed as a sequence of 32-bit hexadecimal numbers:

00405050

00000000

00000002

00000001

0CDDEB52

00405060

D955CBD4

C6E1CDA4

3C9C6C96

 

224 Chapter 6

With this view, you can immediately see a somewhat improved picture. The first three DWORDs are obviously some kind of 32-bit fields. The last four DWORDs are not as obvious, and seem to be some kind of a random 16-byte sequence. This is easy to tell because they do not contain text (you would have seen that in the previous dump), and they are not pointers or offsets into the file (the numbers are far too large, and some of them are not 32-bit aligned). This is a classic case where stepping into the code that deciphers this data should really simplify the process of deciphering the file format.

The code that actually reads the file table and displays the file list is shown in Listing 6.6 and is actually quite simple to analyze because the fields that it reads are both printed into the screen, so it’s very easy to tell what they stand for. Let’s go back to that code sequence and see what it’s doing with this file entry.

00401A60

MOV ESI,SS:[ESP+10]

00401A64

ADD ESI,8

00401A67

MOV DWORD PTR SS:[ESP+14],1A

00401A6F

NOP

00401A70

MOV EAX,DS:[ESI]

00401A72

TEST EAX,EAX

00401A74

JE SHORT cryptex.00401A9A

00401A76

MOV EDX,EAX

00401A78

SHL EDX,0A

00401A7B

SUB EDX,EAX

00401A7D

ADD EDX,EDX

00401A7F

LEA ECX,DS:[ESI+14]

00401A82

ADD EDX,EDX

00401A84

PUSH ECX

00401A85

SHR EDX,0A

This sequence starts out by loading ESI with the newly decrypted block’s starting address, adding 8 to that, and reading a 32-bit member at that address into EAX. If you go back to the previous memory dump, you’ll see that the third DWORD contains 00000001. At this point, the code confirms that EAX is nonzero, and proceeds to perform an interesting series of arithmetic operations on it.

First, EDX is shifted left by 0xA (10) bits, then the original value (from EAX) is subtracted from the result. At that point, the value of EDX is added to itself (which is the equivalent of multiplying it by two). This operation is performed again in 00401A82, and is followed by a right-shift of 0xA (10) bits. Now let’s go over these operations step by step and try to determine their purpose.

1.EDX is shifted left by 10, which is equivalent to edx = edx × 1,024.

2.The original number at EAX is subtracted from EDX. This means that instead of 1,024, you have essentially performed edx = edx × 1,024 – edx, which is the equivalent of edx = edx × 1,023.

Deciphering File Formats 225

3.EDX is then added to itself, twice. This is equivalent of edx = edx × 4, which means that so far you’ve essentially calculated edx = edx × 4,092.

4.Finally, EDX is shifted back right by 10 bits, which is the equivalent of dividing by 1,024. The final formula is edx = edx × 4092 ÷ 1024.

You might be wondering why Cryptex didn’t just use the MUL instruction to multiply EDX by 4,092 and then apply the DIV instruction to divide the result by 1,024. The answer is that such code would run far more slowly than the one we’ve just analyzed. MUL and DIV are both relatively slow instructions, whereas ADD, SUB, and the shifting instructions are much faster. It is important to note that this sequence reveals an interesting fact about Cryptex: It was most likely compiled using some kind of an optimize-for-fast-code switch, rather than with an optimize-for-small-code switch. That’s because using the direct arithmetic instructions for division and multiplication would have produced smaller, yet slower, code. The compiler was clearly aiming at the generation of high-performance code, even at the cost of a somewhat larger executable.

The result of this little arithmetic sequence goes right into the printf call that prints the current file entry. This is quite illuminating because it tells you exactly what Cryptex was trying to calculate in the preceding arithmetic sequence: the file size. In fact, it is quite obvious that because the file size is printed in kilobytes, the final division by 1,024 simply converts the file size from bytes to kilobytes. The question now is, what was that original number and why was Cryptex multiplying it by 4,092? Well, it would seem that the file size is maintained by using some kind of fixed block size, which is probably somehow related to the cluster you saw earlier while decrypting the buffer. The problem is that the cluster you were dealing with earlier was 4,104 bytes long, and here you’re dealing with clusters that are only 4,092 bytes long. The difference is not clear at this point.

The original number of clusters that you multiplied was taken from offset +8 in the current file entry structure, so you know that offset +8 contains the file size in clusters. This raises the question of where does Cryptex store the actual file size? It would not be possible to accurately recover encrypted files without creating them with the exact size they had originally. Therefore Cryptex must also maintain the accurate file size somewhere in the archive file.

Other than the file size, the printf call also takes the file name, which is easily obtained by taking the address of offset +14 from ESI. Keep in mind that ESI was incremented by 8 earlier, so this is actually offset +1C from the original data structure, which matches what you saw in our data dump, where the string started at offset +1C.

After printing the file name and size, the program loops back to print the next entry. To reach the next item, Cryptex increments ESI by 0x98 bytes (152 in decimal), which is clearly the length of each entry. This indicates that there is a fixed number of characters reserved for each file name. Since you know

226Chapter 6

that the string starts at offset +14 in the structure, you can assume that there aren’t any additional data entries after it in the structure, which would mean that the maximum length of a file name in Cryptex is 152 – 20, or 132 bytes.

Once this loop ends, an interesting thing takes place. The first member in the buffer you read and decrypted earlier is tested, and if it is nonzero, Cryptex calls the function at 00401030, the function from Listing 6.7 that reads and decrypts a data chunk that we analyzed earlier. The second parameter, which is used as a kind of cluster number (remember how the function multiplies that number by 4104?), is taken directly from that first member. Clearly the idea here is to read and decrypt another chunk of data and scan it for files. It looks likes the file list can span an arbitrary number clusters and is essentially implemented using a sort of cluster linked list. This brings up one question: Is the first cluster hard-coded to number one? Let’s take a look at the code that made the initial call to read the first file-list cluster, from Listing 6.6.

00401A1E

MOV EDX,DS:[406064]

00401A24

PUSH ECX

00401A25

PUSH EDX

00401A26

PUSH

ESI

00401A27

CALL

cryptex.00401030

The first-cluster index is taken from a global variable with a familiar address. It turns out that 00406064 is a part of the Cryptex header loaded into 00406058 just a few lines earlier. So, it looks like offset +0C in the Cryptex header contains the index of the first cluster in the file table.

Going back to Listing 6.7, after 00401030 returns, ESI is tested for a nonzero value again (even though it has already been tested and its value couldn’t have been changed), and if it is nonzero Cryptex loops back into the code that reads the file table. You now know that the first member in these file table clusters is the next cluster element that tells Cryptex which cluster contains the following file table entries, if any. Because the size of each file entry is fixed, there must also be a fixed number of entries in each cluster. Since a local variable at [ESP+14] is used for counting the remaining number of items in the current cluster, you easily find the instruction at 00401A67, which initializes this variable to 0x1A (26 in decimal), so you know that each cluster can contain up to 26 file entries.

Finally, it is important to pay attention to three lines in Listing 6.6 that we’ve so far ignored.

00401A70 MOV EAX,DS:[ESI]

00401A72 TEST EAX,EAX

00401A74 JE SHORT cryptex.00401A9A

It appears that a file entry must have a nonzero value in its offset +8 in order for Cryptex to actually pay attention to the entry. As we’ve recently established,

Deciphering File Formats 227

offset +8 contains the file size in clusters, so Cryptex is essentially checking for a nonzero file size. The fact that Cryptex supports skipping file entries indicates that it allows for holesin its file list, so when a file is deleted Cryptex simply marks its entry as deleted and doesn’t have to start copying any entries. When deleted entries are encountered they are simply ignored, as you can see here.

This is exactly the type of thing you probably wouldn’t see in a robust commercial security product. By not erasing these data blocks, Cryptex creates a slight security risk. Sure, the “deleted” clusters are probably still encrypted (they couldn’t be in plain text because Cryptex isn’t ever supposed to insert plaintext data into the archives!), but they might contain sensitive information. Suppose that you used Cryptex to send files to someone who had the password to your archive. Because deleted files might still be in the archive, you might actually be sending that person additional files you thought you had deleted!

Dumping the Directory Layout

So, what would you have to do in order to actually dump the file list in a Cryptex archive? It’s actually not that complicated. The following steps must be taken in order to correctly dump the list of files inside a Cryptex archive:

1.Initialize the Crypto API and open the archive file.

2.Verify the 8-byte header signature.

3.Calculate an SHA hash out of the typed password, and calculate an MD5 hash out of that.

4.Verify that the calculated MD5 hash matches the stored MD5 hash from the Cryptex header (at offset +18).

5.Produce a 3DES key using the SHA hash object.

6.Read the first file list cluster (whose index is stored in offset +0C in the Cryptex header) in the same manner as it is read in Cryptex (reading 4,104 bytes and decrypting them using our 3DES key).

7.Loop through those 152-bytes long entries and dump each entry’s name if its offset +8 (which is the file size in clusters) is nonzero.

8.Proceed to read and decrypt additional file-list clusters if they are present. List any entries within those clusters.

The actual code that implements the preceding sequence is relatively straightforward to implement. If you’d like to see what it looks like, it is available on this book’s Web site at www.wiley.com/go/eeilam.

228 Chapter 6

The File Extraction Process

Cryptex would not be worth much without having the ability to decrypt and extract files from its encrypted archive files. This is done using the x command, which simply creates a file with the same name as the original that was encoded (minus the file’s path) and decrypts the original data into it. Reversing the extraction process should provide you with a clearer view of the file list data layout and on how files are actually stored within archive files. The rather longish Listing 6.8 contains the Cryptex file extraction routine.

00401BB0

SUB ESP,70

 

00401BB3

MOV EAX,DS:[405020]

 

00401BB8

PUSH EBX

 

00401BB9

PUSH EDI

 

00401BBA

MOV EDI,SS:[ESP+84]

 

00401BC1

PUSH 0

 

00401BC3

MOV SS:[ESP+78],EAX

 

00401BC7

MOV EAX,SS:[ESP+80]

 

00401BCE

PUSH 0

 

00401BD0

PUSH EAX

 

00401BD1

PUSH EDI

 

00401BD2

CALL cryptex.00401670

 

00401BD7

MOV EDX,DS:[405048]

 

00401BDD

ADD ESP,10

 

00401BE0

LEA ECX,SS:[ESP+14]

 

00401BE4

PUSH ECX

 

00401BE5

PUSH 0

 

00401BE7

PUSH 0

 

00401BE9

PUSH 8003

 

00401BEE

PUSH EDX

 

00401BEF

MOV EBX,EAX

 

00401BF1

CALL DS:[<&ADVAPI32.CryptCreateHash>]

00401BF7

TEST EAX,EAX

 

00401BF9

JNZ SHORT cryptex.00401C11

 

00401BFB

PUSH cryptex.00403284

; /format = “Unable to verify the

 

 

file’s hash value!”

00401C00

CALL DS:[<&MSVCR71.printf>]

 

00401C06

ADD ESP,4

 

00401C09

PUSH 1

; /status = 1

00401C0B

CALL DS:[<&MSVCR71.exit>]

 

00401C11

PUSH EBP

 

00401C12

PUSH ESI

 

00401C13

PUSH 0

; /Origin = FILE_BEGIN

00401C15

PUSH 0

; |pOffsetHi = NULL

00401C17

PUSH 0

; |OffsetLo = 0

00401C19

PUSH EBX

; |hFile

00401C1A

CALL DS:[<&KERNEL32.SetFilePointer>]

 

 

 

Listing 6.8 A disassembly of Cryptex’s file decryption and extraction routine.

 

 

Deciphering File Formats 229

 

 

 

 

00401C20

PUSH 0

; /pOverlapped = NULL

 

00401C22

LEA EAX,SS:[ESP+24]

; |

 

00401C26

PUSH EAX

; |pBytesRead

 

00401C27

PUSH 28

; |BytesToRead = 28 (40.)

 

00401C29

PUSH cryptex.00406058

; |Buffer = cryptex.00406058

 

00401C2E

PUSH EBX

; |hFile

 

00401C2F

CALL DS:[<&KERNEL32.ReadFile>]

 

00401C35

MOV ESI,SS:[ESP+88]

 

 

00401C3C

XOR ECX,ECX

 

 

00401C3E

PUSH EDI

 

 

00401C3F

MOV SS:[ESP+71],ECX

 

 

00401C43

LEA EDX,SS:[ESP+70]

 

 

00401C47

PUSH EDX

 

 

00401C48

MOV SS:[ESP+79],ECX

 

 

00401C4C

LEA EAX,SS:[ESP+18]

 

 

00401C50

PUSH EAX

 

 

00401C51

MOV SS:[ESP+81],ECX

 

 

00401C58

MOV SS:[ESP+85],CX

 

 

00401C60

PUSH ESI

 

 

00401C61

PUSH EBX

 

 

00401C62

MOV DWORD PTR SS:[ESP+24],0

 

 

00401C6A

MOV SS:[ESP+28],ESI

 

 

00401C6E MOV BYTE PTR SS:[ESP+80],0

 

 

00401C76

MOV SS:[ESP+8F],CL

 

 

00401C7D

CALL cryptex.004017B0

 

 

00401C82

MOV EDI,SS:[ESP+24]

 

 

00401C86

PUSH 5C

; /c = 5C (‘\’)

 

00401C88

PUSH ESI

; |s

 

00401C89

MOV SS:[ESP+34],ESI

; |

 

00401C8D

MOV ESI,DS:[<&MSVCR71.strchr>]

 

00401C93

MOV EBP,EAX

; |

 

00401C95

CALL ESI

; \strchr

 

00401C97

ADD ESP,1C

 

 

00401C9A

TEST EAX,EAX

 

 

00401C9C

JE SHORT cryptex.00401CB3

 

 

00401C9E

MOV EDI,EDI

 

 

00401CA0

ADD EAX,1

 

 

00401CA3

PUSH 5C

 

 

00401CA5

PUSH EAX

 

 

00401CA6

MOV SS:[ESP+20],EAX

 

 

00401CAA

CALL ESI

 

 

00401CAC

ADD ESP,8

 

 

00401CAF

TEST EAX,EAX

 

 

00401CB1

JNZ SHORT cryptex.00401CA0

 

 

00401CB3

TEST EBP,EBP

 

 

00401CB5

JNZ SHORT cryptex.00401CD2

 

 

00401CB7

MOV ECX,SS:[ESP+18]

 

 

00401CBB

PUSH ECX

; /<%s>

 

 

 

 

 

Listing 6.8 (continued)

230 Chapter 6

00401CBC

PUSH cryptex.004032B0

; |format = “File “%s” not found

 

 

in archive.”

00401CC1

CALL DS:[<&MSVCR71.printf>]

 

00401CC7

ADD ESP,8

 

00401CCA

PUSH 1

; /status = 1

00401CCC

CALL DS:[<&MSVCR71.exit>]

 

00401CD2

MOV ESI,SS:[ESP+14]

 

00401CD6

PUSH 0

; /hTemplateFile = NULL

00401CD8

PUSH 0

; |Attributes = 0

00401CDA

PUSH 2

; |Mode = CREATE_ALWAYS

00401CDC

PUSH 0

; |pSecurity = NULL

00401CDE

PUSH 0

; |ShareMode = 0

00401CE0

PUSH C0000000

; |Access = GENERIC_READ |

 

 

GENERIC_WRITE

00401CE5

PUSH ESI

; |FileName

00401CE6

CALL DS:[<&KERNEL32.CreateFileA>]

00401CEC

CMP EAX,-1

 

00401CEF

MOV SS:[ESP+14],EAX

 

00401CF3

JNZ SHORT cryptex.00401D13

 

00401CF5

CALL DS:[<&KERNEL32.GetLastError>]

00401CFB

PUSH EAX

; /<%d>

00401CFC

PUSH ESI

; |<%s>

00401CFD

PUSH cryptex.004032D4

; |format = “ERROR: Unable to

 

 

create file “%s” (Last

 

 

Error=%d).”

00401D02

CALL DS:[<&MSVCR71.printf>]

 

00401D08

ADD ESP,0C

 

00401D0B

PUSH 1

; /status = 1

00401D0D

CALL DS:[<&MSVCR71.exit>]

 

00401D13

MOV EDX,SS:[ESP+8C]

 

00401D1A

PUSH EDX

 

00401D1B

PUSH EBP

 

00401D1C

PUSH EBX

 

00401D1D

CALL cryptex.00401030

 

00401D22

TEST EDI,EDI

 

00401D24

MOV SS:[ESP+2C],EDI

 

00401D28

FILD DWORD PTR SS:[ESP+2C]

 

00401D2C

JGE SHORT cryptex.00401D34

 

00401D2E FADD DWORD PTR DS:[403BA0]

 

00401D34

FDIVR QWORD PTR DS:[403B98]

 

00401D3A

MOV EAX,SS:[ESP+24]

 

00401D3E

XORPS XMM0,XMM0

 

00401D41

MOV EBP,DS:[<&MSVCR71.printf>]

00401D47

PUSH EAX

 

00401D48

PUSH cryptex.00403308

; ASCII “Extracting “%.35s” - “

00401D4D

MOVSS SS:[ESP+24],XMM0

 

00401D53

FSTP DWORD PTR SS:[ESP+34]

 

00401D57

CALL EBP

 

 

 

 

Listing 6.8 (continued)