Anyone who has had a chance to program in Windows knows that interaction with the operating system is based on messages. Practically all Windows API functions are high-level "wrappers" that send messages to Windows. The GetWindowTextA function, an analog of the WM_GETTEXT message, is not an exception.
Consequently, a developer doesn't need to call GetWindowTextA to get the text from an edit window; SendMessageA (hWnd, WM_GETTEXT, (LPARAM) &buff [0]) can be used. crack02 does just that. Try to load it and set a breakpoint at GetWindowTextA (GetDlgItemTextA). What happened? It didn't work. Developers use such tricks to lead novice hackers astray.
In this case, you could set a breakpoint at SendMessageA. However, setting a breakpoint at the WM_GETTEXT message is a more universal solution, which works regardless of how the window's contents are read.
In SoftIce, a special command sets a breakpoint on messages: BMSG. But isn't it more interesting to do it yourself?
As you probably know, each window has a special window procedure associated with it (i.e., for receiving and processing messages). You could find it and set a breakpoint. The HWND command gives information on the windows of the specified process.
:addr crack02
:hwnd crack02
Handle Class WinProc TID
Module
050140 #32770 (Dialog) 6C291B81 2DC
crack02
05013E Button 77E18721 2DC
crack02
05013C Edit 6C291B81 2DC
crack02
05013A Static 77E186D9 2DC
crack02
You can locate quickly the edit window with the window procedure address 0x6C291B81. Should you set a breakpoint? No, it's not time yet. Remember that the window procedure is called on more occasions than when the text is read. It would be better to set a breakpoint after you have filtered out all other messages. To begin, study the prototype of this function:
LRESULT CALLBACK WindowProc(
HWND hwnd, // Handle to window
UINT uMsg, // Message identifier
WPARAM wParam, // First message parameter
LPARAM lParam // Second message parameter
) ;
It's easy to calculate that, when calling the function, the uMsg argument (the message identifier) is offset by 8 bytes relative to the stack-top pointer, ESP. If the value at that position equals WM_GETTEXT (0xD), that is when you want to break!
Here, mention must be made of conditional breaks. Their syntax is considered in detail in the debugger documentation. Programmers familiar with C, however, should find the syntax concise and intuitive.
:bpx 6C291B81 IF (esp-->8)==WM_GETTEXT
:x
Now, quit the debugger. Enter any text as a password, such as Hello, and press the
Break due to BPX #0008:6C291B81 IF ((ESP-->8)==0xD) (ET=2.52 seconds)
You need to determine the address with the read string. The pointer to the buffer is transferred to the buffer through the lParam argument (see SDK for the description of WM_GETTEXT), and lParam itself is placed on the stack at an offset of 0x10 relative to ESP.
Return address ← ESP
hwnd ← ESP + 0x4
uMsg ← ESP + 0x8
wParam ← ESP + 0xC
lParam ← ESP + 0x10
Now, output this buffer to the data window, quit the window procedure with P RET, and… see the text Hello, which you just entered.
:d * (esp+10)
:p ret
0023:0012EB28 48 65 6C 6C 6F 00 05 00-0D 00 00 00 FF 03 00 00 Hello...........
0023:0012EB38 1C ED 12 00 01 00 00 00-0D 00 00 00 FD 86 E1 77 ...............w
0023:0012EB48 70 3C 13 00 00 00 00 00-00 00 00 00 00 00 00 00 p<..............
0023:0012EB58 00 00 00 00 00 00 00 00-98 EB 12 00 1E 87 E1 77 ...............w
:bpm 23:12EB28
Set the breakpoint given above. The debugger will show up at one "spontaneous" point. (It is obviously "nonuser" code because CS has a value of 0008.) Prepare to press the
0008:A00B017C 8A0A mov cl, [edx]
0008:A00B017E 8808 mov [eax], cl
0008:A00B0180 40 inc eax
0008:A00B0181 42 inc edx
0008:A00B0182 84C9 test cl, cl
0008:A00B0184 7406 jz A00B018C
0008:A00B0186 FF4C2410 dec dword ptr [esp+10]
0008:A00B018A 75F0 jnz A00B017C
Aha! The buffer is passed by value, not by reference. The system doesn't allow you to access the buffer directly; it only provides a copy. A character in this buffer, pointed to by the EDX register is copied to CL. (It is clear that EDX contains a pointer to this buffer; it caused the debugger to appear.) Then it's copied from CL to the [EAX] location, where EAX is some pointer (about which we can't yet say anything definite). Both pointers are incremented by one, and CL (the last character read) is checked for equality to zero. If the end of the string isn't reached, the procedure is repeated. If you have to watch two buffers at once, set one more breakpoint.
:bpm EAX
:x
The debugger soon pops up at the other breakpoint. You should recognize the comparing procedure. The rest is trivial.
001B:004013F2 8A1E mov bl, [esi]
001B:004013F4 8ACA mov cl, dl
001B:004013F6 3AD3 cmp dl, bl
001B:004013F8 751E jnz 00401418
001B:004013FA 84C9 test cl, cl
001B:004013FC 7416 jz 00401414
001B:004013FE 8A5001 mov dl, [eax+01]
001B:00401401 8A5E01 mov bl, [esi+01]
In Windows 9x, messages are processed somewhat differently than in Windows NT. In particular, the window procedure of the edit window is implemented in 16-bit code, with a nasty segment memory model: segment:offset. Addresses also are passed differently. What parameter contains the segment? To Addresses also are passed differently. What parameter contains the segment? To answer that question, look at SoftIce's breakpoint report:

The entire address fits in the lParam 32-bit argument — a 16-bit segment and 16-bit offset. Therefore, the breakpoint should look like this: bpm 28D7:0000.
0 comments:
Post a Comment