Friday, August 21, 2009

Hackers Disassembling 1.1.4.3(Method 2: Setting a Breakpoint at the Password Input Function)

Method 2: Setting a Breakpoint at the Password Input Function

We can't call the previous method of directly searching for the entered password elegant or practical. Why should we search for the password, stumbling over irregularly scattered buffers, when we can place a breakpoint directly on the function that reads it? Will it be easier to guess which function the developer used?

The operation can be performed with one of just a few functions. Looking them up won't take a lot of time. In particular, editable field contents often are read with GetWindowTextA or, less frequently, with GetDlgItemTextA.

Since we're talking about windows, let's start our GUI crackme01 example and set a breakpoint at the GetWindowTextA function ("bpx GetWindowTextA"). Since this is a system function, the breakpoint will be global (i.e., it will affect all running applications). Therefore, close all unneeded programs. If you set the breakpoint before starting crackme01, you'll get several false windows because the system reads the window contents when displaying the dialog.

Let's enter KPNC Kaspersky++ as usual, then press the key. The debugger will show up instantly.

USER32!GetWindowTextA
001B: 77E1A4E2 55 push ebp
001B: 77E1A4E3 8BEC mov ebp, esp
001B: 77E1A4E5 6AFF push FF
001B: 77E1A4E7 6870A5E177 push 77E1A570
001B: 77E1A4EC 68491DE677 push 77E61D49
001B: 77E1A4F1 64A100000000 mov eax, fs: [00000000]
001B: 77E1A4F7 50 push eax

Many hacking manuals recommend that we immediately quit the function with P RET, saying there's no need to analyze it. But, we needn't hurry! We should clarify where the entered string is located and set a breakpoint at it. Let's look at the arguments the function accepts and the sequence in which it accepts them. (If you don't remember, view the SDK documentation.)

int GetWindowText (
HWND hWnd, // Handle to window or control with text
LPTSTR lpString, // Address of buffer for text
int nMaxCount // Maximum number of characters to copy
) ;

If a program is written in C, it may seem that the arguments are written on the stack according to the C convention. Nothing of the kind! All Windows API functions are called according to the Pascal convention, regardless of the language in which the program is written. Thus, arguments are pushed on the stack from left to right, and the last argument onto the stack is the return address. In 32-bit Windows, all arguments and the return address occupy a double word (4 bytes). Therefore, to reach the pointer to the string, you need to add 8 bytes to the stack's top pointer register, or ESP (one double word for nMaxCount, and another one for lpString). This is represented more clearly in Fig. 3.



Figure 3: The stack when calling GetWindowText
In SoftIce, you can display the contents of a specified address using the * operator. (See the debugger documentation for more details.)

:d * (esp+8)
0023:0012F9FC 1C FA 12 00 3B 5A E1 77-EC 4D E1 77 06 02 05 00 ... . ;Z.w.M.w... .
0023:0012FA0C 01 01 00 00 10 00 00 00-01 00 2A C0 10 A8 48 00 ... ... ... . *...H.
0023:0012FA1C 10 9B 13 00 0A 02 04 00-E8 3E 2F 00 00 00 00 00 ... ... ... >/ ... . .
0023:0012FA2C 01 02 04 00 83 63 E1 77-08 DE 48 00 0A 02 04 00 ... . . c.w. .H... . .

The buffer is filled with garbage because the string hasn't been read yet. Let's quit the function with P RET and see what happens. (Note that it will be impossible to use d *esp+8; after we exit the function, its arguments will be pushed off the stack.)

: p ret
:d 0012F9FC
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 80 10 A8 48 00 ..............H.
0023:0012FA1C 10 9B 13 00 0A 02 04 00-E8 3E 2F 00 00 00 00 00 .........>/.....
0023:0012FA2C 01 02 04 00 83 63 E1 77-08 DE 48 00 0A 02 04 00 .....c.w..H.....

This is the buffer we need. Set a breakpoint and wait for the debugger window to show up. Look! (Do you recognize the comparing procedure?) After the first try, we are where we want to be.

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

This is wonderful! Elegantly, quickly, beautifully — and without any false hits — we defeated the protection.

This method is universal; we'll take advantage of it many times. It simply requires us to determine the key function and set a breakpoint at it. In Windows, all attempts to read a password (calls to a key file, to the registry, etc.) are reduced to calls of API functions. There are many, but the number is finite and known beforehand.

0 comments:

Post a Comment