WIN32 Api Hooks, The stub approach

advanced
Advanced reversing

by Lone Runner
30 October 1998


red

Well, well, well... another OUTSTANDING Fravia comes out of the web-void and gives us incredible reading material. I'm very happy to host this essay, a milestone on the difficult path to reverse engineering wizardry. Read, re-read, understand, re-understand, work on this, re-work on this... you get the pattern :-)


red
WIN32 Api Hooks, The stub approach
----------------------------------
(or How to change dinamically your Win32 system code)
(or also How to cook eggs on the back of your old grandma)

by Lone Runner/Aegis

I. Opening the fridge
---------------------

Ok, it has now been a while since i read articles on Fravia, and specially what is bound with the win32 api, beeing a lot interested with undocumented functions and how to take full control over what happens inside the win32 black box. I've decided today that i could take some time to write an small article on how i achieved the goal of controlling the API.

Of course you have softice for that, you can trace out all what you want but if you need to insert your code in here, its not as easy as it was in the good old dos with an resident code in int 3...

What about forcing the win32 to report all what it does, and maybe decide to prevent any action from the system... that could be interesting... well, applications are numerous, so i won't make a list.

Be carefull, if you're not experienced with system win32 code, you'll have big problems to debug anything here, or even get a working system at all. Touch at your own risks. It works for me, but i know what i do, if you think you prefer not to temper with system files, go play the NOP game with softice.

If you feel the guts, then here we go.

II. Finding eggs
----------------

One of the most known approach (microsoft gives an example to do this in the VisualC++ Cd) is to run the target process as a child, and then modify its function pointers to make them point to your code. Well that's a good approach but it has some drawbacks. For example you need to know the process id of the target process, for that you have to run the process as child. This can be a problem. Also you will only be able to intercept api for this child. But if this child has another child, he won't be hooked.

Our method, in contrast, will let all process run and go thru our own code (a bug here is deadly).
Of course, this method has drawbacks also, for example you have to replace a system dll. But this has very good points. You do intercept everything everytime so you can prevent everything in the win32 api to happen.

The base of the system is to make your own USER32.DLL to act as a stub for the real USER32.DLL which you renamed (for example to MSUS32.DLL), same goes with KERNEL32.DLL (-> MSKERN32.DLL)

But its not as easy as that, we will have to be carefull about some details.

III. Finding your grandma
-------------------------

My exemple will be on a project i had and then dropped. The aim of the game was to intercept the DefWindowProc function (quite often used) in all applications throughout the system. This was to change the default behaviour of windows (in my case, modify the window appearance by handling differently WM_NC* messages).

We will stub only USER32.DLL (where DefWindowProcA and DefWindowProcW reside). Our goal is to intercept only those two functions and make then chat using IPC with a user application to tell USER32.DLL to dynamically load (and unload) a DLL. This will be very usefull since once this is done and working, we'll be able not to reboot to change USER32.DLL between each compile (thanks!).

First, we must stub all functions of the original DLL, this means, all exported functions, not only documented ones, but all. For that, a nice MS utility called DUMPBIN will be perfect.

Here is the beginning of the output for user32.dll :
            ordinal hint   name

                  1    0   ActivateKeyboardLayout  (00015F25)
                  2    1   AdjustWindowRect  (0000ABC4)
                  3    2   AdjustWindowRectEx  (0002AA7A)
                  4    3   AnyPopup  (0003E44F)
                  5    4   AppendMenuA  (0001080C)
                  6    5   AppendMenuW  (00029A5A)
                  7    6   ArrangeIconicWindows  (0002EE3D)
                  8    7   AttachThreadInput  (000144FA)
                  9    8   BeginDeferWindowPos  (0001067E)
                 10    9   BeginPaint  (000023B4)
                 11    A   BringWindowToTop  (0000A7DC)
                 12    B   BroadcastSystemMessage  (0003F39D)
With this complete list, simply make a very basic C file :
//---------------------------------------------------------------------------
// Function ActivateKeyboardLayout
//---------------------------------------------------------------------------
__declspec ( naked ) void _my_ActivateKeyboardLayout(void)
{
__asm jmp far dword ptr ActivateKeyboardLayout;
}
//---------------------------------------------------------------------------
// Function AdjustWindowRect
//---------------------------------------------------------------------------
__declspec ( naked ) void _my_AdjustWindowRect(void)
{
__asm jmp far dword ptr AdjustWindowRect;
}
//---------------------------------------------------------------------------
etc...

a simple 4Dos script will be excellent for this task

The ( naked ) tag is VERY important. This will ensure that VC will not play with the stack nor with anything at the beginning and end of the function. Its kindof like the old __interrupt tag in BC/DOS.

We do this because we won't need to intercept most of the functions. Only DefWindowProc* will be declared with arguments. Others functions will only be passthrough so we avoid quite some work by not declaring arguments and using ( naked ), this ensure the stack is preserved and the original API function gets it right.

Then you need a .DEF file to create your DLL withe the right exports (we cannot name our functions the same as USER32.DLL since it's linked with our dll, but we can change export names). We'll get something like :
LIBRARY     USER33.DLL

EXPORTS
ActivateKeyboardLayout=_my_ActivateKeyboardLayout @1
AdjustWindowRect=_my_AdjustWindowRect @2
AdjustWindowRectEx=_my_AdjustWindowRectEx @3
AnyPopup=_my_AnyPopup @4
AppendMenuA=_my_AppendMenuA @5
AppendMenuW=_my_AppendMenuW @6
ArrangeIconicWindows=_my_ArrangeIconicWindows @7
AttachThreadInput=_my_AttachThreadInput @8
BeginDeferWindowPos=_my_BeginDeferWindowPos @9
BeginPaint=_my_BeginPaint @10
BringWindowToTop=_my_BringWindowToTop @11
BroadcastSystemMessage=_my_BroadcastSystemMessage @12
Notice USER33.DLL. I urge you not to name your C file USER32.C or you're going into deep troubles with the linker =)

We should give it a dedicated loading address to be good boys, so we'll add /base:0x65E70000 (for example) as parameter to the linker.

IV. Convincing your grandma to cooperate
----------------------------------------

The next problem is undocumented functions. imports for those functions do not exist in USER32.LIB so we'll have to either create a new LIB or load them with GetProcAddress. The first solution is of course the clean solution. But, who knows why, it did not work with me.
I had a clean new USER32.LIB generated from exports in the DLL, but it would not link.

So i had to go for the second solution, include a DllMain function and load exports by hand.

(*CascadeChildWindows)();
(*ClientThreadSetup)();
(*CreateDialogIndirectParamAorW)();
(*DdeGetQualityOfService)();
(*DeregisterShellHookWindow)();
(*DialogBoxIndirectParamAorW)();
(*DrawCaptionTempA)();

...
...

if (!UndocLoaded)
   {
    HInstance = hModule;
    hInstUser32 = LoadLibrary("MSUS32.DLL");

    (FARPROC)WCSToMBEx = GetProcAddress(hInstUser32, "WCSToMBEx");
    (FARPROC)MBToWCSEx = GetProcAddress(hInstUser32, "MBToWCSEx");
    (FARPROC)CascadeChildWindows = GetProcAddress(hInstUser32,
"CascadeChildWindows");
    (FARPROC)ClientThreadSetup = GetProcAddress(hInstUser32,
"ClientThreadSetup");
    (FARPROC)CreateDialogIndirectParamAorW =
GetProcAddress(hInstUser32, "CreateDialogIndirectParamAorW"
    (FARPROC)DdeGetQualityOfService = GetProcAddress(hInstUser32,
"DdeGetQualityOfService");
    (FARPROC)DeregisterShellHookWindow = GetProcAddress(hInstUser32,
"DeregisterShellHookWindow");
    (FARPROC)DialogBoxIndirectParamAorW = GetProcAddress(hInstUser32,
"DialogBoxIndirectParamAorW");
    (FARPROC)DrawCaptionTempA = GetProcAddress(hInstUser32,
"DrawCaptionTempA");
    (FARPROC)DrawCaptionTempW = GetProcAddress(hInstUser32,
"DrawCaptionTempW");
    ...
    ...
    UndocLoaded=-1;
   }
Repeat that for all undocumented functions and you're done.

So what do we have now ? a DLL that acts as stub for user32 functions, barely, it does nothing else for now. But will it work ? No.

Because inside USER33.DLL the real USER32 imports are still here. So what do we have to do is :

- Patch USER33.DLL to replace any "USER32.DLL" string with "MSUS32.DLL"
- Patch it again to replace "USER33.DLL" with "USER32.DLL"
- Reboot to DOS
- Rename USER32.DLL to MSUS32.DLL
- Copy USER33.DLL as the new system USER32.DLL

Thats all. Now we have a working stub.

V. Boiling the water
--------------------

But it does nothing. Okay :-)

Lets make something usefull with it.

To avoid too many reboots, the right choice is to write a simple IPC api to change our system code dynamically. What we'll need is :

- Tell our USER32.DLL the name of the DLL it'll have to load
- Tell our USER32.DLL to load the custom DLL and if successfull, redirect specified functions to our DLL code
- Tell our USER32.DLL to release the DLL and stop hooking (acting as other passthru functions)

A very simple way to do that is to make the USER32.DLL create a window that will be used for IPC using messages. For an arbitrary application it's easy to find a window with a specified name so that'll be our starting point.

Here is a minimal but working implementation :
char *DLLName[DLLMAX];
BOOL DWPHookOk=FALSE;
//---------------------------------------------------------------------------
LRESULT CALLBACK IPCWndProc(HWND hwnd, UINT message, WPARAM wParam,
LPARAM lParam)
{
switch (message)
       {
       case WM_COMMAND:
            switch (wParam)
                   {
                   case CMD_RESET_DLL_NAME:
                        *DLLName = 0;
                        break;
                   case CMD_CAT_DLL_NAME:
                        {
                        char *p = DLLName;
                        while (*p) p++;
                        if (p > DLLName + DLLMAX) break;
                        *p++ = (unsigned char)lParam;
                        *p = 0;
                        break;
                        }
                   case CMD_LOAD_DLL:
                        if (DWPHookOK)
                           UnloadDLL();
                        LoadDLL();
                        break;
                   case CMD_UNLOAD_DLL:
                        if (DWPHookOK)
                           UnloadDLL();
                        break;
                   }
       }

return DefWindowProc(hwnd, message, wParam, lParam);
}

//---------------------------------------------------------------------------
void LoadDLL(void)
{
DLLInstance = LoadLibrary(DLLName);
if (!DLLInstance) return;
(FARPROC)MyDefWindowProcW = GetProcAddress(DLLInstance,
"MyDefWindowProc");
if (MyDefWindowProcW == NULL)
   {
   FreeLibrary(DLLInstance);
   return;
   }
DWPHookOK = TRUE;
}

//---------------------------------------------------------------------------
void UnloadDLL(void)
{
DWPHookOK=FALSE;
FreeLibrary(DLLInstance);
}

DefWindowProc beeing split in two parts (A & W), we'll make a comon
function for both: 

//---------------------------------------------------------------------------
// Function DefWindowProcW
//---------------------------------------------------------------------------
LRESULT
WINAPI
_my_DefWindowProcW(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
return CommonDefWindowProc(hWnd, Msg, wParam, lParam, 1);
}

//---------------------------------------------------------------------------
// Function DefWindowProcA
//---------------------------------------------------------------------------
LRESULT
WINAPI
_my_DefWindowProcA(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
return CommonDefWindowProc(hWnd, Msg, wParam, lParam, 0);
}
Note that these functions are not ( Naked ), because we retrieve parameters so we need a stack management. Here is the common code :
//---------------------------------------------------------------------------
LRESULT WINAPI CommonDefWindowProc(HWND hWnd, UINT Msg, WPARAM wParam, 
LPARAM lParam, int Wide)
{

if (DisableIPC) // DisableIPC is a security flag. If set, no more hook
   return Wide ? DefWindowProcW(hWnd, Msg, wParam, lParam) :
DefWindowProcA(hWnd, Msg, wParam, lParam);

// If the IPC Window does not exists create it.

if (!IPCWindow)
   {
   WNDCLASS wc;
   
   memset(&wc,0,sizeof(wc));
   wc.lpfnWndProc = IPCWndProc;	
   wc.hInstance = HInstance;
   wc.lpszClassName = IPCWindowClass;
   wc.style = 0;

   RegisterClass(&wc);
   
   IPCWindow = CreateWindowEx(
      0,
      IPCWindowClass,
      "",
      WS_POPUP,
      0, 0,
      100,100,
      NULL,
      NULL,
      HInstance,
      NULL);
   
   // If failed, no need to retry, disable hooks
   if (!IPCWindow)
      {
      DisableIPC=TRUE;
      return DefWindowProcW(hWnd, Msg, wParam, lParam);
      }
   }

// If a receiver IPC window is found, tell it we're here
if (FirstCheck)
   {
   HWND MainIPC;
   FirstCheck=FALSE;

   MainIPC = FindWindow("EMainIPC", NULL);
   if (MainIPC)
      SendMessage(MainIPC, WM_COMMAND, CMD_IMHERE, 0);
   }

if (!DWPHookOK) // This flag is set once GetProcAddress is successfull,
see above
   return Wide ? DefWindowProcW(hWnd, Msg, wParam, lParam) :
DefWindowProcA(hWnd, Msg, wParam, lParam);
else
   return MyDefWindowProcW(hWnd, Msg, wParam, lParam, Wide);
}pre>

 VI. Finding out if eggs (and grandma) are ready.
------------------------------------------------

That's all, now our USER32.DLL with load its code and link dynamically to it at will. We just need to write a small application that will communication with it to tell it what to do:
#include <windows.h>

char ClassName[255];
char DllName[1024] = "C:\\DEV\\IPC2\\EFDll.DLL";

//---------------------------------------------------------------------------
// Tells an instance of USER32.DLL to load the DLL
void SendLoadDll(HWND hwnd)
{
char *p=DllName;

SendMessage(hwnd, WM_COMMAND, CMD_RESET_DLL_NAME, 0);
while (*p)
	{
	SendMessage(hwnd, WM_COMMAND, CMD_CAT_DLL_NAME, (long)(*p));
	p++;
	}
SendMessage(hwnd, WM_COMMAND, CMD_LOAD_DLL, 0);
}

//---------------------------------------------------------------------------
// Enumerate all instances of USER32.DLL
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
GetClassName(hwnd, ClassName, 254);

if (!strcmpi(ClassName, IPCWindowClass))
	lParam ? SendLoadDll(hwnd) : SendMessage(hwnd, WM_COMMAND,
CMD_UNLOAD_DLL, 0);

return TRUE;
}


//---------------------------------------------------------------------------
int WINAPI WinMain(HINSTANCE hInstance,	HINSTANCE hPrevInstance, LPSTR
lpCmdLine, int nCmdShow)
{
EnumWindows(EnumWindowsProc, 1);

// Dll is loaded, your app is here

//...
//...

// Your app has finished, unload the code now

EnumWindows(EnumWindowsProc, 0);
return 0;
}
VII. Taste eggs and grandma
---------------------------

Ok now you should know what's necessary to make your own dynamic stub to USER32.DLL or KERNEL32.DLL. I hope that it can be usefull to anybody. I had big fun making it, hope you can have as much i did.

I'll try to clean a bit my code and release it. Its fully working. That would be a little kick in the ass of those companies that tries to sell you a Win32hooks API for ***ONLY $4999!*** =)

Peace, harmony.

Lone Runner/Aegis Corp Big hello to all the Fravias staff and gurus. You 0wn me

advanced
Advanced reversing

redhomepage red links red anonymity +ORC redstudents' essays redacademy database redbots wars
redantismut redtools redcocktails redjavascript wars redsearch_forms redmail_Fravia
redIs reverse engineering illegal?