Statistics

Members: 1927
News: 293
Web Links: 1
Visitors: 3962314

Who's Online

We have 1 guest online
Damn Vulnerable LinuxDamn Vulnerable Linux (DVL) is a Linux-based (modified Damn Small Linux) tool for IT-Security & IT-Anti- Security and Attack & Defense. [CLICK HERE FOR MORE INFOS! ]

Featured Conference Video

T16-Recon2006-Joe_Stewart-OllyBonE.gif OllyBone - Semi-Automatic Unpacking on IA-32. View the conference video here!
Home arrow About/Disclaimer
WndProc, The Dirty Way
User Rating: / 1
PoorBest 
Written by Mammon   


I assume you all know what a WndProc is, and what you need it for. Let me give you a quick example of a WndProc:

WndProc PROC hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

        .IF uMsg == WM_DESTROY
INVOKE PostQuitMessage, NULL
.ELSE
INVOKE DefWindowProc, hWnd, uMsg, wParam, lParam
ret
.ENDIF
xor   eax, eax
ret

WndProc ENDP

This generates the following code:

    push  ebp                                   ; Create stack frame
mov   ebp, esp                              ; Why does MASM use 'leave',
; but not 'enter'?
cmp   dword ptr [ebp+0C], WM_DESTROY        ; ebp+0C is uMsg

jne @@notDestroy

push NULL
Call PostQuitMessage
jmp @@exitFromDestroy

@@notDestroy:

    push  [ebp+14]                              ; ebp+14 is lParam
push  [ebp+10]                              ; epb+10 is wParam
push  [ebp+0C]                              ; ebp+0C is uMsg
push  [ebp+08]                              ; ebp+08 is hWnd
Call  DefWindowProcA                        ; Let Windows handle the other
; messages
leave                                       ; Remove stack frame
ret   0010                                  ; Remove function arguments
; from stack and return

@@exitFromDestroy:

    xor   eax, eax                              ; Return 'FALSE'
leave                                       ; Remove stack frame
ret   0010                                  ; Remove function arguments
; from stack and return

Looks nice, and works fine... But, it builds a stack frame, even though we are not using local variables. And if you code in a good fashion, there almost never will be ...after all, this procedure is just a messagehandler, and to keep your code tidy, you will not put all the code in here, but in separate procedures, which you will call from here.

There's only one reason why MASM builds a stack frame for a function: The function has a prototype for a hll call. A hll call uses the stack to transfer its arguments.

So, all we have to do, is remove the prototype. That's easy: Just don't tell MASM that this function uses any arguments. This simple tweak will do the trick:

WndProc PROC

...
WndProc ENDP

The arguments will still be passed to the function, since that part of the code is in the Windows kernel, and has not changed. Be careful though: Since MASM does not know that there are arguments on the stack, it no longer cleans up the stack. You have to specify that yourself.

Now we have a slight problem: How can we access the arguments now? The answer is surprisingly easy: We create aliases for the addresses relative to the stack pointer (esp). MASM does the same, except that it uses the base pointer since it created a stack frame, and saved the original stack pointer in ebp.
Knowing that Windows hll calls always push the arguments in reverse order, and that the return address is stored on the stack aswell, we can devise these indices for our parameters:

hWnd EQU dword ptr [esp][4]
uMsg EQU dword ptr [esp][8]
wParam EQU dword ptr [esp][12]
lParam EQU dword ptr [esp][16]

There, now we can refer to the arguments as usual. There's 1 drawback however: Since the indices are relative to esp, they are only valid when esp is not touched. In other words: Don't try to push or pop anything and then use these arguments again. They can be used if you push some variables, then pop them again before you access any of these arguments again, because the stack pointer will be at the correct position again.

Let's say you need to use the stack again (eg. for an INVOKE), so the indices will be invalidated. You might think that the only option then is to save the stack pointer again, so we're back to the stack frame... It's an option, but not the best one. Namely, ebp is a non-volatile register, and needs to be saved and restored after use. But, there are more registers in the CPU, and most of them are volatile. How about using esi for example?

WndProc PROC

        mov   esi, esp
hWnd    EQU    dword ptr [esi][4]
uMsg    EQU    dword ptr [esi][8]
wParam  EQU    dword ptr [esi][12]
lParam  EQU    dword ptr [esi][16]
...

WndProc ENDP

And if you leave the stack as you found it (which should always be the case with decent code), you don't even need to restore esp again. If you got dirty and the stack still contains variables you don't want anymore, then this is enough for a clean exit:

WndProc PROC

        ...
mov   esp, esi
ret   4 * sizeof dword      ; As I mentioned earlier, we have to clean
; the stack ourselves.
; We had 4 dword arguments, so this does
; the trick

WndProc ENDP

Still less code, and thus faster than the original. And just as rigid. You have one register less to use during the WndProc, but as I said earlier, there shouldn't be too much code here, so should be able to spare the register.

Well, there's just 1 more thing that can be done with this tweaked WndProc. Namely, if you leave the stack as you found it, the arguments for the DefWindowProc are already in place, and the return address of our caller is there too.
So basically we can just jump to it without any further ado. The resulting WndProc that is equivalent to the original one will look like this then:

WndProc PROC

        hWnd    EQU    dword ptr [esp][4]
uMsg    EQU    dword ptr [esp][8]
wParam  EQU    dword ptr [esp][12]
lParam  EQU    dword ptr [esp][16]
.IF uMsg == WM_DESTROY
INVOKE PostQuitMessage, NULL
.ELSE
jmp  DefWindowProc
.ENDIF
xor   eax, eax
ret   4 * sizeof dword      ; Be sure to clean that stack!

WndProc ENDP

Yes, much shorter, and faster. Let's take a look at the generated code to get a better understanding of how much shorter it actually is:

cmp dword ptr [esp+08], WM_DESTROY jne @@noDestroy

push NULL
Call PostQuitMessage
jmp @@exitFromDestroy

@@noDestroy:
Jmp DefWindowProcA

@@exitFromDestroy
xor eax, eax
ret 0010

If you code it 'by hand' instead of with the .IF statement, there's another tweak we can pull, but the rest looks great, doesn't it?

Of course these stunts can be applied to other procedures as well. Be careful, and use them in good health.