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

Eilam E.Reversing.Secrets of reverse engineering.2005

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

Breaking Protections 401

After KERNEL32.DLL is loaded, Defender goes through the familiar sequence of allocating a random address in memory and produces the same name checksum/RVA table from all the KERNEL32.DLL exports. After the copied module is ready for use the function makes one other call to NtDelay Execution for good luck and then you get to another funny jump that skips 30 bytes or so. Dumping the memory that immediately follows the CALL instruction as text reveals the following:

00404138

44 65

66

65

6E

64

65

72

Defender

00404140

20 56

65

72

73

69

6F

6E

Version

00404148

20 31

2E

30

20

2D

20

57

1.0 - W

00404150

72 69

74

74

65

6E

20

62

ritten b

00404158

79 20

45

6C

64

61

64

20

y Eldad

00404160

45 69

6C

61

6D

 

 

 

Eilam

Finally, you’re looking at something familiar. This is Defender’s welcome message, and Defender is obviously preparing to print it out. The CALL instruction skips the string and takes us to the following code.

00404167 PUSH DWORD PTR SS:[ESP]

0040416A CALL Defender.004012DF

The code is taking the “return address” pushed by the CALL instruction and pushes it into the stack (even though it was already in the stack) and calls a function. You don’t even have to look inside this function (which is undoubtedly full of indirect calls to copied KERNEL32.DLL code) to know that this function is going to be printing that welcome message that you just pushed into the stack. You just step over it and unsurprisingly Defender prints its welcome message.

Reencrypting the Function

Immediately afterward you have yet another call to 6DEF20NtDelay Execution and that brings us to what seems to be the end of this function. OllyDbg shows us the following code:

004041E2

MOV EAX,Defender.004041FD

004041E7

MOV DWORD PTR DS:[4034D6],EAX

004041ED

MOV DWORD PTR SS:[EBP-8],0

004041F4

JMP Defender.00403401

004041F9

LODS DWORD PTR DS:[ESI]

004041FA

DEC EDI

004041FB

ADC AL,0F2

004041FD

POP EDI

004041FE

POP ESI

004041FF

POP EBX

00404200

LEAVE

00404201

RETN

402 Chapter 11

If you look closely at the address that the JMP at 004041F4 is going to you’ll notice that it’s very far from where you are at the moment—right at the beginning of this function actually. To refresh your memory, here’s the code at that location:

00403401 CMP DWORD PTR SS:[EBP-8],0

00403405 JE SHORT Defender.0040346D

You may or may not remember this, but the line immediately preceding 00403401 was setting [EBP-8] to 1, which seemed a bit funny considering it was immediately checked. Well, here’s the answer—there is encrypted code at the end of the function that sets this variable to zero and jumps back to that same position. Since the conditional jump is taken this time, you land at 40346D, which is a sequence that appears to be very similar to the decryption sequence you studied in the beginning. Still, it is somewhat different, and observing its effect in the debugger reveals the obvious: it is reencrypting the code in this function.

There’s no reason to get into the details of this logic, but there are several details that are worth mentioning. After the encryption sequence ends, the following code is executed:

004034D0 MOV DWORD PTR DS:[406008],EAX

004034D5 PUSH Defender.004041FD

004034DA POP EBX

004034DB JMP EBX

The first line saves the value in EAX into a global variable. EAX seems to contain some kind of a checksum of the encrypted code. Also, the PUSH, POP, JMP sequence is the exact same code that originally jumped into the decrypted code, only it has been modified to jump to the end of the function.

Back at the Entry Point

After the huge function you’ve just dissected returns, the entry point routine makes the traditional call into NtDelayExecution and calls into another internal function, at 404202. The following is a full listing for this function:

00404202

MOV EAX,DWORD PTR DS:[406004]

00404207

MOV ECX,EAX

00404209

MOV EAX,DWORD PTR DS:[EAX]

0040420B

JMP SHORT Defender.00404219

0040420D

CMP EAX,66B8EBBB

00404212

JE SHORT Defender.00404227

00404214

ADD ECX,8

00404217

MOV EAX,DWORD PTR DS:[ECX]

Breaking Protections 403

00404219

TEST EAX,EAX

0040421B

JNZ SHORT Defender.0040420D

0040421D

XOR ECX,ECX

0040421F

PUSH Defender.0040322E

00404224

CALL ECX

00404226

RETN

00404227

MOV ECX,DWORD PTR DS:[ECX+4]

0040422A

ADD ECX,DWORD PTR DS:[406014]

00404230

JMP SHORT Defender.0040421F

This function performs another one of the familiar copied export table searches, this time on the copied KERNEL32 memory block (whose pointer is stored at 406004). It then immediately calls the found function. You’ll use the function index trick that you used before in order to determine which API is being called. For this you put a breakpoint on 404227 and observe the address loaded into ECX. You then subtract KERNEL32’s copied base address (which is stored at 406004) from this address and divide the result by 8. This gives us the current API’s index. You quickly run DUMPBIN /EXPORTS on

KERNEL32.DLL and find the API name: SetUnhandledExceptionFilter. It looks like Defender is setting up 0040322E as its unhandled exception filter. Unhandled exception filters are routines that are called when a process generates an exception and no handlers are available to handle it. You’ll worry about this exception filter and what it does later on.

Let’s proceed to another call to NtDelayExecution, followed by a call to another internal function, 401746. This function starts with a very familiar sequence that appears to be another decryption sequence; this function is also encrypted. I won’t go over the decryption sequence, but there’s one detail I want to discuss. Before the code starts decrypting, the following two lines are executed:

00401785 MOV EAX,DWORD PTR DS:[406008]

0040178A MOV DWORD PTR SS:[EBP-9C0],EAX

The reason I’m mentioning this is that the variable [EBP-9C0] is used a few lines later as the decryption key (the value against which the code is XORed to decrypt it). You probably don’t remember this, but you’ve seen this global variable 406008 earlier. Remember when the first encrypted function was about to return, how it reencrypted itself? During encryption the code calculated a checksum of the encrypted data, and the resulting checksum was stored in a global variable at 406008. The reason I’m telling you all of this is that this is an unusual property in this code—the decryption key is calculated at runtime. One side effect this has is that any breakpoint installed on encrypted code that is not removed before the function is reencrypted would change this checksum, preventing the next function from properly decrypting! Defender is doing as its name implies: It’s defending!

404 Chapter 11

Let’s proceed to investigate the newly decrypted function. It starts with two calls to the traditional NtDelayExecution . Then the function proceeds to call what appears to be NtOpenFile through the obfuscated interface, with the string “\??\C:” hard-coded right there in the middle of the code. After

NtOpenFile the function calls NtQueryVolumeInformationFile with the FileFsVolumeInformation information level flag. It then reads offset +8 from the returned data structure and stores it in the local variable

[406020]. Offset +8 in data structure FILE_FS_VOLUME_INFORMATION is

VolumeSerialNumber (this information was also obtained at http:// undocumented.ntinternals.net).

This is a fairly typical copy protection sequence, in a slightly different flavor. The primary partition’s volume serial number is a good way to create com- puter-specific dependencies. It is a 32-bit number that’s randomly assigned to a partition when it’s being formatted. The value is retained until the partition is formatted. Utilizing this value in a serial-number-based copy protection means that serial numbers cannot be shared between users on different computers— each computer has a different serial number. One slightly unusual thing about this is that Defender is obtaining this value directly using the native API. This is typically done using the GetVolumeInformation Win32 API.

You’ve pretty much reached the end of the current function. Before returning it makes yet another call to NtDelayExecution, invokes RDTSC, loads the low-order word into EAX as the return value (to make for a garbage return value), and goes back to the beginning to reencrypt itself.

Parsing the Program Parameters

Back at the main entry point function, you find another call to NtDelay Execution which is followed by a call into what appears to be the final function call (other than that apparently useless call to IsDebuggerPresent) in the program entry point, 402082.

Naturally, 402082 is also encrypted, so you will set a breakpoint on 402198, which is right after the decryption code is done decrypting. You immediately start seeing familiar bits of code (if Olly is still showing you junk instead of code at this point, you can either try stepping into that code and see if automatically fixes itself or you can specifically tell Olly to treat these bytes as code by right-clicking the first line and selecting Analysis. During next analysis, treat selection as Command). You will see a call to NtDelayExecution, followed by a sequence that loads a new DLL: SHELL32.DLL. The loading is followed by the creation of the obfuscated module interface: allocating memory at a random address, creating checksums for each of the exported SHELL32.DLL names, and copying the entire code section into the newly allocated memory block. After all of this the program calls a KERNEL32.DLL that

Breaking Protections 405

has a pure user-mode implementation, which forces you to use the function index method. It turns out the API is GetCommandLineW. Indeed, it returns a pointer to our test command line.

The next call is to a SHELL32.DLL API. Again, a SHELL32 API would probably never make a direct call down into the kernel, so you’re just stuck with some long function and you’ve no idea what it is. You have to use the function’s index again to figure out which API Defender is calling. This time it turns out that it’s CommandLineToArgvW. CommandLineToArgvW performs parsing on a command-line string and returns an array of strings, each containing a single parameter. Defender must call this function directly because it doesn’t make use of a runtime library, which usually takes care of such things.

After the CommandLineToArgvW call, you reach an area in Defender that you’ve been trying to get to for a really long time: the parsing of the commandline arguments.

You start with simple code that verifies that the parameters are valid. The code checks the total number of arguments (sent back from CommandLine ToArgvW) to make sure that it is three (Defender.EXE’s name plus username and serial number). Then the third parameter is checked for a 16-character length. If it’s not 16 characters, defender jumps to the same place as if there aren’t three parameters. Afterward Defender calls an internal function, 401CA8 that verifies that the hexadecimal string only contains digits and letters (either lowercase or uppercase). The function returns a Boolean indicating whether the serial is a valid hexadecimal number. Again, if the return value is 0 the code jumps to the same position (40299C), which is apparently the “bad parameters” code sequence. The code proceeds to call another function (401CE3) that confirms that the username only contains letters (either lowercase or uppercase). After this you reach the following three lines:

00402994 TEST EAX,EAX

00402996 JNZ Defender.00402AC4

0040299C CALL Defender.004029EC

When this code is executed EAX contains the returns value from the username verification sequence. If it is zero, the code jumps to the failure code, at 40299C, and if not it jumps to 402AC4, which is apparently the success code. One thing to notice is that 4029EC again uses the CALL instruction to skip a string right in the middle of the code. A quick look at the address right after the CALL instruction in OllyDbg’s data view reveals the following:

004029A1

42

61

64

20

70

61

72

61

Bad para

004029A9

6D

65

74

65

72

73

21

0A

meters!.

004029B1

55

73

61

67

65

3A

20

44

Usage: D

004029B9

65

66

65

6E

64

65

72

20

efender

004029C1

3C

46

75

6C

6C

20

4E

61

<Full Na

406 Chapter 11

004029C9

6D

65

3E

20

3C

31

36

2D

me> <16-

004029D1

64

69

67

69

74

20

68

65

digit he

004029D9

78

61

64

65

63

69

6D

61

xadecima

004029E1

6C

20

6E

75

6D

62

65

72

l number

004029E9

3E

0A

00

 

 

 

 

 

>..

So, you’ve obviously reached the “bad parameters” message display code. There is no need to examine this code – you should just get into the “good parameters” code sequence and see what it does. Looks like you’re close!

Processing the Username

Jumping to 402AC4, you will see that it’s not that simple. There’s quite a bit of code still left to go. The code first performs some kind of numeric processing sequence on the username string. The sequence computes a modulo 48 on each character, and that modulo is used for performing a left shift on the character. One interesting detail about this left shift is that it is implemented in a dedicated, somewhat complicated function. Here’s the listing for the shifting function:

00401681

CMP CL,40

00401684

JNB SHORT Defender.0040169B

00401686

CMP CL,20

00401689

JNB SHORT Defender.00401691

0040168B

SHLD EDX,EAX,CL

0040168E

SHL EAX,CL

00401690

RETN

00401691

MOV EDX,EAX

00401693

XOR EAX,EAX

00401695

AND CL,1F

00401698

SHL EDX,CL

0040169A

RETN

0040169B

XOR EAX,EAX

0040169D

XOR EDX,EDX

0040169F

RETN

This code appears to be a 64-bit left-shifting logic. CL contains the number of bits to shift, and EDX:EAX contains the number being shifted. In the case of a full-blown 64-bit left shift, the function uses the SHLD instruction. The SHLD instruction is not exactly a 64-bit shifting instruction, because it doesn’t shift the bits in EAX; it only uses EAX as a “source” of bits to shift into EDX. That’s why the function also needs to use a regular SHL on EAX in case it’s shifting less than 32 bits to the left.

Breaking Protections 407

After the 64-bit left-shifting function returns, you get into the following code:

00402B1C

ADD EAX,DWORD PTR SS:[EBP-190]

00402B22

MOV ECX,DWORD PTR SS:[EBP-18C]

00402B28

ADC ECX,EDX

00402B2A

MOV

DWORD

PTR SS:[EBP-190],EAX

00402B30

MOV

DWORD

PTR SS:[EBP-18C],ECX

Figure 11.16 shows what this sequence does in mathematical notation. Essentially, Defender is preparing a 64-bit integer that uniquely represents the username string by taking each character and adding it at a unique bit position in the 64-bit integer.

The function proceeds to perform a similar, but slightly less complicated conversion on the serial number. Here, it just takes the 16 hexadecimal digits and directly converts them into a 64-bit integer. Once it has that integer it calls into 401EBC, pushing both 64-bit integers into the stack. At this point, you’re hoping to find some kind of verification logic in 401EBC that you can easily understand. If so, you’ll have cracked Defender!

Validating User Information

Of course, 401EBC is also encrypted, but there’s something different about this sequence. Instead of having a hard-coded decryption key for the XOR operation or read it from a global variable, this function is calling into another function (at 401D18) to obtain the key. Once 401D18 returns, the function stores its return value at [EBP-1C] where it is used during the decryption process.

len

Sum = ΣCn × 2Cn mod48 n = 0

Figure 11.16 Equation used by Defender to convert username string to a 64-bit value.

408 Chapter 11

Let’s step into this function at 401D18 to determine how it produces the decryption key. As soon as you enter this function, you realize that you have a bit of a problem: It is also encrypted. Of course, the question now is where does the decryption key for this function come from? There are two code sequences that appear to be relevant. When the function starts, it performs the following:

00401D1F MOV EAX,DWORD PTR SS:[EBP+8]

00401D22 IMUL EAX,DWORD PTR DS:[406020]

00401D29 MOV DWORD PTR SS:[EBP-10],EAX

This sequence takes the low-order word of the name integer that was produced earlier and multiplies it with a global variable at [406020]. If you go back to the function that obtained the volume serial number, you will see that it was stored at [406020]. So, Defender is multiplying the low part of the name integer with the volume serial number, and storing the result in [EBP10]. The next sequence that appears related is part of the decryption loop:

00401D7B

MOV EAX,DWORD PTR SS:[EBP+10]

00401D7E

MOV ECX,DWORD PTR SS:[EBP-10]

00401D81

SUB ECX,EAX

00401D83

MOV EAX,DWORD PTR SS:[EBP-28]

00401D86

XOR ECX,DWORD PTR DS:[EAX]

This sequence subtracts the parameter at [EBP+10] from the result of the previous multiplication, and XORs that value against the encrypted function! Essentially Defender is doing Key = (NameInt * VolumeSerial) – LOWPART(SerialNumber). Smells like trouble! Let the decryption routine complete the decryption, and try to step into the decrypted code. Here’s what the beginning of the decrypted code looks like (this is quite random—your milage may vary).

00401E32

PUSHFD

00401E33

AAS

00401E34

ADD BYTE PTR DS:[EDI],-22

00401E37

AND DH,BYTE PTR DS:[EAX+B84CCD0]

00401E3D

LODS BYTE PTR DS:[ESI]

00401E3E

INS DWORD PTR ES:[EDI],DX

It is quite easy to see that this is meaningless junk. It looks like the decryption failed. But still, it looks like Defender is going to try to execute this code! What happens now really depends on which debugger you’re dealing with, but Defender doesn’t just go away. Instead it prints its lovely “Sorry . . . Bad Key.” message. It looks like the top-level exception handler installed earlier is the one generating this message. Defender is just crashing because of the bad code in the function you just studied, and the exception handler is printing the message.

Breaking Protections 409

Unlocking the Code

It looks like you’ve run into a bit of a problem. You simply don’t have the key that is needed in order to decrypt the “success” path in Defender. It looks like Defender is using the username and serial number information to generate this key, and the user must type the correct information in order to unlock the code. Of course, closely observing the code that computes the key used in the decryption reveals that there isn’t just a single username/serial number pair that will unlock the code. The way this algorithm works there could probably be a valid serial number for any username typed. The only question is what should the difference be between the VolumeSerial * NameLowPart and the low part of the serial number? It is likely that once you find out that difference, you will have successfully cracked Defender, but how can you do that?

Brute-Forcing Your Way through Defender

It looks like there is no quick way to get that decryption key. There’s no evidence to suggest that this decryption key is available anywhere in Defender.EXE; it probably isn’t. Because the difference you’re looking for is only 32 bits long, there is one option that is available to you: brute-forcing. Brute-forcing means that you let the computer go through all possible keys until it finds one that properly decrypts the code. Because this is a 32-bit key there are only 4,294,967,296 possible options. To you this may sound like a whole lot, but it’s a piece of cake for your PC.

To find that key, you’re going to have to create a little brute-forcer program that takes the encrypted data from the program and tries to decrypt it using every key, from 0 to 4,294,967,296, until it gets back valid data from the decryption process. The question that arises is: What constitutes valid data? The answer is that there’s no real way to know what is valid and what isn’t. You could theoretically try to run each decrypted block and see if it works, but that’s extremely complicated to implement, and it would be difficult to create a process that would actually perform this task reliably.

What you need is to find a “token”—a long-enough sequence that you know is going to be in the encrypted block. This will allow you to recognize when you’ve actually found the correct key. If the token is too generic, you will get thousands or even millions of hits, and you’ll have no idea which is the correct key. In this particular function, you don’t need an incredibly long token because it’s a relatively short function. It’s likely that 4 bytes will be enough if you can find 4 bytes that are definitely going to be a part of the decrypted code.

You could look for something that’s likely to be in the code such as those repeated calls to NtDelayExecution, but there’s one thing that might be a bit easier. Remember that funny variable in the first function that was set to one and then immediately checked for a zero value? You later found that the

410Chapter 11

encrypted code contained code that sets it back to zero and jumps back to that address. If you go back to look at every encrypted function you’ve gone over, they all have this same mechanism. It appears to be a generic mechanism that reencrypts the function before it returns. The local variable is apparently required to tell the prologue code whether the function is currently being encrypted or decrypted. Here are those two lines from 401D18, the function you’re trying to decrypt.

00401D49 MOV DWORD PTR SS:[EBP-4],1

00401D50 CMP DWORD PTR SS:[EBP-4],0

00401D54 JE SHORT Defender.00401DBF

As usual, a local variable is being set to 1, and then checked for a zero value. If I’m right about this, the decrypted code should contain an instruction just like the first one in the preceding sequence, except that the value being loaded is 0, not 1. Let’s examine the code bytes for this instruction and determine exactly what you’re looking for.

00401D49

C745 FC 01000000

MOV DWORD PTR SS:[EBP-4],1

Here’s the OllyDbg output that includes the instruction’s code bytes. It looks like this is a 7-byte sequence—should be more than enough to find the key. All you have to do is modify the 01 byte to 00, to create the following sequence:

C7 45 FC 00 00 00 00

The next step is to create a little program that contains a copy of the encrypted code (which you can rip directly from OllyDbg’s data window) and decrypts the code using every possible key from 0 to FFFFFFFF. With each decrypted block the program must search for the token—that 7-byte sequence you just prepared . As soon as you find that sequence in a decrypted block, you know that you’ve found the correct decryption key. This is a pretty short block so it’s unlikely that you’d find the token in the wrong decrypted block.

You start by determining the starting address and exact length of the encrypted block. Both addresses are loaded into local variables early in the decryption sequence:

00401D2C PUSH Defender.00401E32

00401D31

POP EAX

00401D32 MOV DWORD PTR SS:[EBP-14],EAX

00401D35 PUSH Defender.00401EB6

00401D3A

POP EAX

00401D3B MOV DWORD PTR SS:[EBP-C],EAX