Monday, August 17, 2009

Hackers Disassembling 1.1.4.2(Method 1: Searching Directly for the Entered Password in Memory)

Method 1: Searching Directly for the Entered Password in Memory


Storing a password as plain text in the program's body is more of an exception than rule. Hackers are hardly needed if the password can be seen with the naked eye. Therefore, protection developers try to hide it in every possible way. (We'll discuss how they do this later.) Taking into account the size of modern applications, a programmer may place the password in an unremarkable file stuffed with "dummies" — strings that look like a password, but are not. It's unclear what is fake and what isn't, especially because in a project of average size, there may be several hundreds, or even thousands, of suitable strings.

Let's approach the problem from the opposite side — let's not search for the original password, which is unknown to us, but rather for the string that we've fed to the program as the password. Then, let's set a breakpoint on it, and proceed in the same manner as before. The break will follow the watching call. We'll quit the matching procedure, correct JMP, and…

Let's take another look at the simple.c source code that we're cracking.

for (;;)
{
printf ("Enter password:") ;
fgets (&buff[0], PASSWORD_SIZE, stdin) ;

if (strcmp (&buff[0], PASSWORD) )
printf ("Wrong password\n") ;
else break;
if (++count>2) return -1;
}

Notice that the user-supplied password is read into buff, and compared to the reference password. If no match is made, the password again is requested from the user — but buff isn't cleared before the next attempt. From this, we can see that, if, upon receiving the message Wrong password, we open the debugger and walk through it with a context search, we may find buff.

So, let's begin. Let's start simple.exe, enter any password that comes to mind (KPNC Kaspersky ++, for example), ignore the Wrong cry and press + — the key combination for calling SoftIce. We needn't search blindly: Windows NT/9x isn't Windows 3.x or MS-DOS, with a common address space for all processes. Now, to keep one process from inadvertently intruding on another, each is allotted address space for its exclusive use. For example, process A may have the number 0x66 written at address 23:0146660, process B may have 0x0 written at the same address, 23:0146660, and process C may have a third value. Each process — A, B, or C — won't even suspect the existence of the others (unless it uses special resources for interprocessor communication).

You can find a more detailed consideration of all these issues in books by Helen Custer and Jeffrey Richter. Here, we're more worried about another problem: The debugger called by pressing the + key combination emerges in another process (most likely in Idle), and a context search over memory gives no results. We need to manually switch the debugger to the necessary address space.

From the documentation that comes with SoftIce, you may know that switching contexts is performed by the ADDR command, with either the process name truncated to eight characters or its PID. You can get that with another command — PROC. In cases where the process name is syntactically indistinguishable from a PID — "123", for example — we have to use the PID (the second column of digits in the PROC report).

:addr simple

Now, let's try the addr simple command. Nothing happens. Even the registers remain the same! Don't worry; the word "simple" is in the lower-right corner, identifying the current process. Keeping the same register values is just a bug in SoftIce. It ignores them, and only switches addresses. This is why tracing a switched program is impossible. Searching, however, is another matter.

:s 23:0 L -1 "KPNC Kaspersky"

The first argument after s is the search start address, written as selector:offset. In Windows 2000, selector 23 is used address data and the stack. In other operating systems, the selector may differ. We can find it by loading any program, and then read the contents of the DS register.

In general, starting a search from a zero offset is silly. According to the memory map, the auxiliary code is located there, and will unlikely contain the required password. However, this will do no harm, and will be much faster than trying to figure out the program load address and where to start the search. The third argument — L-1 — is the length of the area to search, where -1 means search until successful. Note that we are not searching for the entire string, but only for part of it (KPNC Kaspersky, not KPNC Kaspersky++). This allows us to get rid of false results. SoftIce likes to display references to its own buffers containing the search template. They are always located above 0x80000000, where no normal password ever lives. Nevertheless, it'll be more demonstrative if just the string we need is found using an incomplete substring.

Pattern found at 0023:00016E40 (00016E40)

We found at least one occurrence. But what if there are more of them in memory? Let's check this by issuing s commands until the message Pattern not found is received, or until the upper search address of 0x80000000 is exceeded.

:s
Pattern found at 0023:0013FF18 (0013FF18)
:s
Pattern found at 0023:0024069C (0024069C)
:s
Pattern found at 0023:80B83F18 (80B83F18)

We have three! Isn't this too much? It would be silly to set all three breakpoints. In this case, four debug-processor registers will suffice, but even three breakpoints are enough to get us lost! What would we do if we found ten matches?

Let's think: Some matches likely result from reading the input via the keyboard and putting characters into the system buffers. This seems plausible. How can we filter out the "interference?"

The memory map will help: Knowing the owner of an area that possesses a buffer, we can say a lot about that buffer. By typing in map32 simple, we obtain approximately the following:

:map32 simple
Owner Obj Name Obj# Address Size Type
simple .text 0001 001B:00011000 00003F66 CODE RO
simple .rdata 0002 0023:00015000 0000081E IDATA RO
simple .data 0003 0023:00016000 00001E44 IDATA RW

Hurrah! One of the matches belongs to our process. The buffer at address 0x16E40 belongs to the data segment and is probably what we need. But we shouldn't be hasty; everything may not be as simple as it seems. Let's look for the address 0x16E40 in the simple.exe file. (Taking into account the reverse sequence of bytes, it'll be 40 6E 01 00.)

> dumpbin /SECTION:.data /RAWDATA simple.exe
RAW DATA #3
00016030: 45 6E 74 65 72 20 70 61 73 73 77 6F 72 64 3A 00 Enter password:.
00016040: 6D 79 47 4F 4F 44 70 61 73 73 77 6F 72 64 0A 00 myGOODpassword..
00016050: 57 72 6F 6E 67 20 70 61 73 73 77 6F 72 64 0A 00 Wrong password..
00016060: 50 61 73 73 77 6F 72 64 20 4F 4B 0A 00 00 00 00 Password OK.....
00016070: 40 6E 01 00 00 00 00 00 40 6E 01 00 01 01 00 00 @n......@n......
00016080: 00 00 00 00 00 00 00 00 00 10 00 00 00 00 00 00 ................

We found two of them there. Let's see what references the first one by looking for the substring 16070 in the decompiled code.

00011032: 68 70 60 01 00 push 16070h
00011037: 6A 64 push 64h ; Max. Password length (== 100 dec)
00011039: 8D 4D 98 lea ecx, [ebp-68h]
; The pointer to the buffer
; in which the password should be written
0001103C: 51 push ecx
0001103D: E8 E2 00 00 00 call 00011124 ; fgets
00011042: 83 C4 0C add esp, 0Ch ; Popping up three arguments

It should be clear where we are in the code, except for a mysterious pointer to 0x16070. In MSDN, where the prototype of the fgets function is described, we'll discover "the mysterious stranger" is a pointer to the FILE structure. (According to C convention, arguments are pushed onto the stack from right to left.) The first member of the FILE structure is the pointer to the buffer. (In the standard C library, the file input/output is buffered with a size of 4 KB by default.) Thus, the address 0x16E40 is a pointer to an auxiliary buffer, and we can cross it off the list of candidates.

Candidate No. 2 is 0x24069C. It falls outside the data segment. In general, it's not clear to whom it belongs. Remember the heap? Let's see what's there.

:heap 32 simple
Base Id Cmmt/Psnt/Rsvd Segments Flags Process
00140000 01 0003/0003/00FD 1 00000002 simple
00240000 02 0004/0003/000C 1 00008000 simple
00300000 03 0008/0007/0008 1 00001003 simple

That's it. We just need to clarify who allocated the memory — the system, or the programmer. The first thing that jumps out is the suspicious and strangely undocumented 0x8000 flag. We can find its definition in WINNT.H, but this won't be helpful unless it shows the system using the flag.

#define HEAP_PSEUDO_TAG_FLAG 0x8000

To be convinced, load any application into the debugger and give the command heap 32 proc_name. The system automatically allocates three areas from the heap — exactly like those in our case. This means that this candidate also has led nowhere.

One address remains: 0x13FF18. Does it remind you of anything? What was the ESP value while loading? It seems that it was 0x13FFC4. (Note that in Windows 9x, the stack is located in another place. Nevertheless, this reasoning also works for it: Just remember the stack location in your own operating system and know how to recognize it.)

Since the stack grows from the bottom up (i.e., from higher addresses to lower ones), the address 0x13FF18 is located on the stack. That's why it's similar to buffers. In addition, most programmers allocate buffers in local variables that, in turn, are allocated on the stack by the compiler.

Shall we try to set a breakpoint here?

:bpm 23:13FF18
:x
Break due to BPMB #0023:0013FF18 RW DR3 (ET = 369.65 microseconds)
MSR LastBranchFromIp = 0001144F
MSR LastBranchToIp = 00011156
001B:000110B0 mov eax, [edx]
001B:000110B2 cmp al, [ecx]
001B:000110B4 jnz 000110E4
001B:000110B6 or al, al
001B:000110B8 jz 000110E0
001B:000110BA cmp ah, [ecx+01]
001B:000110BD jnz 000110E4
001B:000110BF or ah, ah

We're in the body of the comparing procedure, which should be familiar. Let's display the values of the EDX and ECX pointers to find out what is being compared.

:d edx
0023:0013FF18 4B 50 4E 43 2D 2D 0A 00-70 65 72 73 6B 79 2B 2B KPNC Kaspersky++

:d ecx
0023:00016040 6D 79 47 4F 4F 44 70 61-73 73 77 6F 72 64 0A 00 myGOODpassword..

We've already discussed everything else that needs to be done. Let's quit the comparing procedure using the P RET command. Then, we need to find a branch, note its address, and correct the executable file. We're done.

You now are acquainted with one common way of cracking protection based on matching passwords. (Later, you'll see that this method is also suitable for cracking protection based on registration numbers.) Its main advantage is its simplicity. There are at least two drawbacks:

If the programmer clears the buffer after making a comparison, a search for the entered password will give nothing unless the system buffers remain. These are difficult to erase. However, it's also difficult to trace the password from system to local buffers!

With the abundance of auxiliary buffers, it can be difficult to find the "right" one. A programmer may allocate the password buffer in the data segment (a static buffer), on the stack (a local buffer), or on the heap. The programmer may even allocate memory using low-level VirtualAlloc calls. As a result, it sometimes appears necessary to go through all obtained occurrences.

Let's analyze another example: crackme01. It's the same as simple.exe except for its graphic user interface (GUI). Its key procedure looks like this:

Listing 5: The Source Code of the Key Procedure of crackme01


void CCrackme_01D1g: :OnOK()
{
char buff[PASSWORD_SIZE];
m_password.GetWindowText (&buff[0], PASSWORD_SIZE);
if (strcmp (&buff[0] , PASSWORD) )
{
MessageBox("Wrong password") ;
m_password.SetSel (0,-1,0) ;
return;
}
else
{

MessageBox ("Password OK");
}
CDialog: :OnOK() ;
}




Everything seems straightforward. Enter the password KPNC Kaspersky++ as usual, but before you press the OK button in response to the wrong password dialog, call the debugger and switch the context.

:s 23:0 L -1 'KPNC Kaspersky'
Pattern found at 0023:0012F9FC (0012F9FC)
:s
Pattern found at 0023:00139C78 (00139C78)

There are two occurrences, and both are on the stack. Let's begin with the first one. Set a breakpoint and wait for the debugger to emerge. The debugger's window does not make us wait long, but it shows some strange code. Press the key to quit. A cascade of windows follows, each less intelligible than the previous one.

We can speculate that the CCrackme_01D1g: :OnOK function is called directly when the OK button is pressed: It's allotted part of the stack for local variables, which is deallocated automatically when the function is exited. Thus, the local buffer with the password that we've entered exists only when it is checked, and then it is erased automatically. Our only bit of luck is the modal dialog, which tells us that we entered the wrong password. While it remains on the screen, the buffer still contains the entered password, which can be found in memory. But this does little to help us trace when this buffer will be accessed. We have to sort through the false windows one by one. At last, we see the string we seek in the data window and some intelligent code in the code window.


0023:0012F9FC 4B 50 4E 43 20 4B 61 73-70 65 72 73 6B 79 2B 2B KPNC
Kaspersky++
0023:0012FA0C 00 01 00 00 0D 00 00 00-01 00 1C C0 A8 AF 47 00
...G.
0023:0012FA1C 10 9B 13 00 78 01 01 00-F0 3E 2F 00 00 00 00 00
...x...>/...
0023:0012FA2C 01 01 01 00 83 63 E1 77-F0 AD 47 00 78 01 01 00
...c.w..G.x...

001B:004013E3 8A10 mov d1, [eax]
001B:004013E5 8A1E mov b1, [esi]
001B:004013E7 8ACA mov c1, d1
001B:004013E9 3AD3 cmp d1, b1
001B:004013EB 751E jnz 0040140B
001B:004013ED 84C9 test c1, c1
001B:004013EF 7416 jz 00401407
001B:004013F1 8A5001 mov dl, [eax+01]

Let's see where ESI points.

:d esi
0023:0040303C 4D 79 47 6F 6F 64 50 61-73 73 77 6F 72 64 00 00 MyGoodPassword..

All that remains is to patch the executable file. Here, more difficulties are waiting for us. First, the compiler has optimized the code, inserting the strcmp code instead of calling it. Second, it's swarming with conditional jumps! It will take a lot of work to find what we need. Let's approach the problem in a scientific way by viewing the disassembled code, or, to be more exact, its key fragment that compares the passwords:

>dumpbin /DISASM crackme_01.exe
004013DA: BE 3C 30 40 00 mov esi, 40303Ch
0040303C: 4D 79 47 6F 6F 64 50 61 73 73 77 6F 72 64 00 MyGoodPassword

A pointer to the reference password was placed in the ESI register.

004013DF: 8D 44 24 10 lea eax, [esp+10h]

A pointer to the user-supplied password was placed in the EAX register.

004013E3: 8A 16 mov d1, byte ptr [esi]
004013E5: 8A 1E mov b1, byte ptr [esi]
004013E7: 8A CA mov c1, d1
004013E9: 3A D3 cmp d1, b1

A comparison was made to the first character.

004013EB: 75 1E jne 0040140B ←---(3)---→ (1)

If the first character didn't match, a jump was made. Further checking would be pointless.

004013ED: 84 C9 test cl, cl

Did the first character equal zero?

004013EF: 74 16 je 00401407 ---→ (2)

If so, we reached the end of line and the passwords would be identical.

004013F1: 8A 50 01 mov d1, byte ptr [eax+1]
004013F4: 8A 5E 01 mov b1, byte ptr [esi+1]
004013F7: 8A CA mov c1, d1
004013F9: 3A D3 cmp d1, b1

The next pair of characters were checked.

004013FB: 75 0E jne 0040140B ---→ (1)

If they were not equal, the check was stopped.

004013FD: 83 C0 02 add eax, 2
00401400: 83 C6 02 add esi, 2

The next two characters were examined

00401403: 84 C9 test c1, c1

Did we reach the end of line?

00401405: 75 DC jne 004013E3 -→ (3)

No, we didn't. Matching was continued.

00401407: 33 C0 xor eax, eax ←---(2)
00401409: EB 05 jmp 00401410 ---→ (4)

This shows EAX was cleared (strcmp returns zero if successful) and quit.

0040140B: 1B C0 sbb eax, eax ←---(3)
0040140D: 83 D8 FF sbb eax, 0FFFFFFFFh

This branch is executed when the passwords don't match. EAX was set to a nonzero value. (Guess why.)

00401410: 85 C0 test eax, eax ←---(4)

If EAX equaled zero, a check was made.

00401412: 6A 00 push 0
00401414: 6A 00 push 0

Something was placed on the stack.

00401416: 74 38 je 00401450<<<<---→(5)

A jump was made somewhere.

00401418: 68 2C 30 40 00 push 40302Ch
0040302C: 57 72 6F 6E 67 20 70 61 73 73 77 6F 72 64 00 .Wrong password

Aha! "Wrong password." (The code that follows isn't of interest; it's just displaying error messages.)

Now that we understand the algorithm, we can crack it (for example, by replacing the conditional jump in line 0x401416 with an short unconditional jump, such as 0xEB).

0 comments:

Post a Comment