WIN32 Api Hooks, The stub approach
Advanced reversing
by Lone Runner
30 October 1998
![red]()
Well, well, well...
another OUTSTANDING reverser 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