2009-04-09

Locating the code that executes upon a button press

It can be hard to find the code that executes when a button is pushed. For a dialog, you can break on creation of the dialog and find the callback parameter. The callback logic can be followed on WM_COMMAND (0x111). But development environments that do a lot of the GUI work for you can be a different situation. Often a message will pass through several handlers before arriving at the user mode code.

SetWindowsHookEx() lets a callback be notified of different events. When supplied WH_CALLWNDPROC it can be notified of messages before they go to the windows procedure. Moreover, the lParam you are sent is a CWPSTRUCT* which has a handle to the destination window. This can be given to GetWindowLongPtr() with GWL_WNDPROC to find what code gets called.

I know spy++ type tools do this type of thing already, but it was a learning experience to make a tool. It does exactly what is described above. Just give it a PID and it will enumerate all threads and install the monitoring hook. It actually injects a DLL which opens a pipe back to the control program to send it the caught messages:


Instead of complicating it with filters and stuff, just grep the output to capture what message you are looking for. For instance: msgspy.exe 924 | grep 111 to get your WM_COMMAND. Download this tool msgspy_v1.rar in filedump.

Many times the PROC given for the command will be some default function in user32 or comctl32 or something (eg: COMCTL32!Button_WndProc). In this case you can trace through until the code in your target is hit or use your debuggers facility to do this, if it exists (go to user code in Olly).

One difficulty in making this tool was that it would often return values like 0xFFFFF052 for the WndProc. On the MSDN page for CallWindowProc the strangeness is explained, "If this value is obtained by calling the GetWindowLong function with the nIndex parameter set to GWL_WNDPROC or DWL_DLGPROC, it is actually either the address of a window or dialog box procedure, or a special internal value meaningful only to CallWindowProc."

That sucks - this internal magic token is useless to us trying to find the code. I traced into user32!CallWindowProc to see what it did with these funny values. My CallWindowProc is actually typedef'd to CallWindowProcA. This calls CallWindowProcAorW which performs this test:
.text:7E429FD3         mov     eax, 0FFFF0000h
.text:7E429FD8         mov     ecx, esi                <-- WndProc
.text:7E429FDA         and     ecx, eax
.text:7E429FDC         cmp     ecx, eax
.text:7E429FDE         push    edi
.text:7E429FDF         mov     edi, [ebp+arg_8]
.text:7E429FE2         jz      special_value 
So what makes the specialty of these WndProc's is that their top 16 bits are set. Immediately into special_value, we get:
.text:7E42A99C         mov     dl, 7
.text:7E42A99E         mov     ecx, esi                <-- WndProc
.text:7E42A9A0         call    HMValidateHandleNoRip 
This HMValidateHandleNoRip function is way beyond me: it does some system call crap to get a struct in ebx and blah blah who cares. But what does with the return value is treats it like some type of structure and extracts the member 24 bytes inwards:
.text:7E42A9C6         mov     esi, [eax+18h]
And this is the real function pointer. The only way I could think of to get this feature in my code was to scan user32 for the function HMValidateHandleNoRip, having made a signature that consists of just the opcode (the operands are like wildcards). This blows because if user32 ever looks different (different service pack, different windows version?), the tool will need to fall back and only display the special tokens, unable to resolve them. Have a better idea? Please comment. Anyways so if it is found (which it does on my XP SP3 machine), it can be used to resolve the special tokens just fine.
typedef struct HMVALIDATESTRUCT
{
// filler to get to offset 0x18
UINT a, b, c, d, e, f;
UINT fptr;
} HMVALIDATESTRUCT, *PHMVALIDATESTRUCT;

typedef PHMVALIDATESTRUCT (__fastcall *PFN_HMVALIDATEHANDLENORIP)(WNDPROC, BYTE);

PFN_HMVALIDATEHANDLENORIP ValidateHandle = ScanForFunction(hUser32);

WndProc proc = GetWindowLongPtr(pcwp->hwnd, GWL_WNDPROC);

PHMVALIDATESTRUCT foo = ValidateHandle((WNDPROC) proc, 7);

spymsg.wndProc = foo->fptr;
Ugly but it works! You may find this tool useful for delphi apps or other programs made with workshop type development environments. A recent example I used it on is obnoxious's AutoIt crackme. May 11th, 2009 EDIT: zip now includes source!

3 comments:

  1. Just to let you know that under Vista (not tried this on XP) that it does not allow pipes or redirection so grep is useless. Wish you had given the source with this.

    ReplyDelete
  2. Well I'm glad it at least works on Vista! Was it able to resolve the HMValidateHandleNoRip() ?

    I will upload source now :)

    ReplyDelete
  3. This occurs because your module and the target don't have matching character sets. The Windows APIs therefore create a thunk to forward messages onto. Make sure you call the proper GetWindowLong... either GetWindowLongA or GetWindowLongW (or GetWindowLongPtrA, GetWindowLongPtrW).

    ReplyDelete

thanks for commenting!