Eilam E.Reversing.Secrets of reverse engineering.2005
.pdfDeciphering 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)