This tutorial is the first of a
series, or at least that is what I plan to, of tutorials about Windows Hooking.
Windows hook is a term defining the process intalling application defined hook
procedure into a hook chain. Main purpose of hooking is to monitor the system
for certain types of events. Example of events you can monitor: mouse
movements/events, keyboard events, and shell events.
Why do we need hooking? There is OnMouseMove to get the position or OnKeyDown from my Form to get what key the user presses. Okay, the most interesting point of hooking is that you get to monitor events that initially not belong or intended for your application. For example, event OnMouseMove and OnKeyDown won't fired if you application is not the one has the focus. Now what if you need to know the mouse movements, or mouse clicks, or keyboard pressing even when your application is not having focus?
Please note that although the technique explain here can be used for keylogger application, but this tutorial does not approve or promote writing any application with intentions against law and moral/social norms. This is purely for knowledge sharing and educational purpose.
The Project
The application that we will develop is asked to answer the following requirement.
Why do we need hooking? There is OnMouseMove to get the position or OnKeyDown from my Form to get what key the user presses. Okay, the most interesting point of hooking is that you get to monitor events that initially not belong or intended for your application. For example, event OnMouseMove and OnKeyDown won't fired if you application is not the one has the focus. Now what if you need to know the mouse movements, or mouse clicks, or keyboard pressing even when your application is not having focus?
Please note that although the technique explain here can be used for keylogger application, but this tutorial does not approve or promote writing any application with intentions against law and moral/social norms. This is purely for knowledge sharing and educational purpose.
The Project
The application that we will develop is asked to answer the following requirement.
Whenever
mouse pointer is over any of its form, even if the form is behind window from
other application (ie. when the application is not having focus), it must make
beep sound.
The Basics
To install your hook, you need to use Windows API SetWindowsHookEx. Go find its full explanation in Windows SDK or in MSDN. In brief we have to tell it what kind of event we want to monitor, and provide callback routine matches the event we want to monitor. When we finish with the hook (i.e. stop monitoring) we have to release the hook by calling Windows API UnhookWindowsHookEx along with hook handle we got previously from SetWindowsHookEx.
The Implementation
From the Basic section above, it sounds like we have three routines ready to write. So here we go.
Code:
type
PData = ^TData;
TData = packed record
MouseHook: HHOOK;
AppHandle: HWnd;
end;
var
Data: PData;
function MouseHookHandler(ACode: Integer; WParam: WParam;
LParam: LParam): LResult; stdcall;
var
vMouseInfo: PMouseHookStruct;
begin
if ACode < 0 then
Result := CallNextHookEx(Data^.MouseHook, ACode, WParam, LParam)
else begin
vMouseInfo := PMouseHookStruct(Pointer(LParam));
PostMessage(Data^.AppHandle, WM_CUSTOM_MOUSE, vMouseInfo^.pt.X, vMouseInfo^.pt.Y);
// we dont want to block the mouse messages
Result := 0;
end;
end;
procedure HookMouse(const AppHandle: HWnd); stdcall;
begin
Data^.MouseHook := SetWindowsHookEx(
WH_MOUSE // hook to mouse events
, MouseHookHandler // the callback routine
, HINSTANCE // library (dll) identifier
, 0
);
Data^.AppHandle := AppHandle;
end;
procedure UnhookMouse;
begin
UnhookWindowsHookEx(Data^.MouseHook);
Data^.MouseHook := 0;
end;
MouseHookHandler
This is the callback function that gets called each time the system generates mouse events. Note that if you get ACode with value of 0, you have to IMMEDIATELY pass the processing to the next hook and do no processing. When we get our share of events (inside the else begin end block) we just parse the parameter to get mouse location and pass that information to our application main window (stored in Data^.AppHandle). Since we don't need to wait for the result we can safely use PostMessage here.
HookMouse
This procedure is the one that installs the mouse hook. It also store the passed application window handle into a variable for easy reference later.
UnhookMouse
This procedure is the one that uninstalls our mouse hook.
Dynamic-Link Library
These routines must be placed inside a dynamic-link library (dll) file. Otherwise we only get events belong to our application, not including events for other applications. Placing these routines inside a dll introduces one important issue: we want the Data variable to be global, i.e. shared among all applications loading the dll.
This issue can be solved by creating a map to the data variable. All applications must use the same map. With the same map, it will be guaranteed that the Data variable is shared (there is only a single instance of Data variable). And of course after we are done, we need to erase the map. Sounds like two new routines ready.
This is the callback function that gets called each time the system generates mouse events. Note that if you get ACode with value of 0, you have to IMMEDIATELY pass the processing to the next hook and do no processing. When we get our share of events (inside the else begin end block) we just parse the parameter to get mouse location and pass that information to our application main window (stored in Data^.AppHandle). Since we don't need to wait for the result we can safely use PostMessage here.
HookMouse
This procedure is the one that installs the mouse hook. It also store the passed application window handle into a variable for easy reference later.
UnhookMouse
This procedure is the one that uninstalls our mouse hook.
Dynamic-Link Library
These routines must be placed inside a dynamic-link library (dll) file. Otherwise we only get events belong to our application, not including events for other applications. Placing these routines inside a dll introduces one important issue: we want the Data variable to be global, i.e. shared among all applications loading the dll.
This issue can be solved by creating a map to the data variable. All applications must use the same map. With the same map, it will be guaranteed that the Data variable is shared (there is only a single instance of Data variable). And of course after we are done, we need to erase the map. Sounds like two new routines ready.
Code:
const
MMF_KEY = '{D2B9306D-EB08-41F9-AC43-9D0DD84CBD4C}';
var
MMF: THandle;
procedure CreateGlobalData;
begin
Randomize;
MMF := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
SizeOf(TGlobalData), MMF_KEY);
if MMF = 0 then
exit;
Data := MapViewOfFile(MMF, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TData));
if Data = nil then
CloseHandle(MMF);
end;
procedure DeleteGlobalData;
begin
if Data <> nil then
UnmapViewOfFile(Data);
if MMF <> INVALID_HANDLE_VALUE then
CloseHandle(MMF);
end;
constant MMF_KEY is
required to guarantee that our map is the same across all applications that
loads the dll. Because they all will execute CreateFileMapping API with
the same key which giving the same map.
Now we have our library of map functions ready. Time to define when to execute them. The following routine will be executed every time a process/a thread use the dll, so it will be our best bet to manage the map.
Now we have our library of map functions ready. Time to define when to execute them. The following routine will be executed every time a process/a thread use the dll, so it will be our best bet to manage the map.
Code:
procedure DLLEntry(dwReason: DWORD);
begin
case dwReason of
// run when a process starts using our dll
DLL_PROCESS_ATTACH: CreateGlobalData;
// run when a process stop unload (stop using) our dll
DLL_PROCESS_DETACH: DeleteGlobalData;
end;
end;
Now for the complete dll code:
Code:
library MouseHook;
type
PData = ^TData;
TData = packed record
MouseHook: HHOOK;
AppHandle: HWnd;
end;
var
Data: PData;
const
MMF_KEY = '{D2B9306D-EB08-41F9-AC43-9D0DD84CBD4C}';
WM_CUSTOM_MOUSE = WM_USER + 111;
var
MMF: THandle;
function MouseHookHandler(ACode: Integer; WParam: WParam;
LParam: LParam): LResult; stdcall;
var
vMouseInfo: PMouseHookStruct;
begin
if ACode < 0 then
Result := CallNextHookEx(Data^.MouseHook, ACode, WParam, LParam)
else begin
vMouseInfo := PMouseHookStruct(Pointer(LParam));
PostMessage(Data^.AppHandle, WM_CUSTOM_MOUSE, vMouseInfo^.pt.X, vMouseInfo^.pt.Y);
// we dont want to block the mouse messages
Result := 0;
end;
end;
procedure HookMouse(const AppHandle: HWnd); stdcall;
begin
Data^.MouseHook := SetWindowsHookEx(
WH_MOUSE // hook to mouse events
, MouseHookHandler // the callback routine
, HINSTANCE // library (dll) identifier
, 0
);
Data^.AppHandle := AppHandle;
end;
procedure UnhookMouse;
begin
UnhookWindowsHookEx(Data^.MouseHook);
Data^.MouseHook := 0;
end;
procedure CreateGlobalData;
begin
Randomize;
MMF := CreateFileMapping(INVALID_HANDLE_VALUE, nil, PAGE_READWRITE, 0,
SizeOf(TGlobalData), MMF_KEY);
if MMF = 0 then
exit;
Data := MapViewOfFile(MMF, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TData));
if Data = nil then
CloseHandle(MMF);
end;
procedure DeleteGlobalData;
begin
if Data <> nil then
UnmapViewOfFile(Data);
if MMF <> INVALID_HANDLE_VALUE then
CloseHandle(MMF);
end;
procedure DLLEntry(dwReason: DWORD);
begin
case dwReason of
// run when a process starts using our dll
DLL_PROCESS_ATTACH: CreateGlobalData;
// run when a process stop unload (stop using) our dll
DLL_PROCESS_DETACH: DeleteGlobalData;
end;
end;
exports
HookMouse
, UnhookMouse
;
begin
DLLProc := @DLLEntry;
DLLEntry(DLL_PROCESS_ATTACH);
end.
Application Part
In this part we only need to deal with notification message from our dll and compare with the areas of our form. When the mouse is over one of our form, even if that form is under other application's window, we have to play beep sound.
You can start by creating a new Delphi project, and add several forms to it. Make sure all the forms have "Visible" property set to true. Add two buttons in the main form, give a button a title of Install Mouse Hook and Uninstall Mouse Hook for the other. In the form declaration add a few lines like the following.
Code:
const
WM_CUSTOM_MOUSE = WM_USER + 111;
type
TForm1 = class(TForm)
...
...
private
// flag to see if we already have mouse over our form previously
FMouseOver: Boolean;
procedure HandleCustomMouseMsg(var AMsg: TMessage); message WM_CUSTOM_MOUSE;
procedure Beeps;
end;
procedure HookMouse(const AppHandle: HWnd); stdcall external 'MouseHook.dll';
procedure UnhookMouse; stdcall external 'MouseHook.dll';
Note that we now declare a
method named HandleCustomMouseMsg which will be fired whenever the main
form receives windows message with ID WM_CUSTOM_MOUSE. Implement that
method goes like:
Code:
procedure TForm1.HandleCustomMouseMsg(var AMsg: TMessage);
function IsMouseWithinOurRange: Boolean;
var
i: Integer;
begin
Result := False;
for i := 0 to Screen.FormCount-1 do
with Screen.Forms[i] do
if Visible then
begin
Result := (AMsg.WParam >= Left)
and (AMsg.WParam <= Left + Width)
and (AMsg.LParam >= Top)
and (AMsg.LParam <= Top + Height);
if Result then Break;
end;
end;
begin
if IsMouseWithinOurRange then
begin
// if we had handled this, just exit
if FMouseOver then Exit;
FMouseOver := True;
Beeps;
end
else
// mark that mouse is not over any of our forms
FMouseOver := False;
end;
Whenever we receive WM_CUSTOM_MOUSE
message from our dll, it will contain mouse cursor coordinate. The X part
passed in WParam part of the message, and the Y part passed in LParam part of
the message. So now we can check (using local function IsMouseWithinOurRange)
if the coordinate is above one of our forms or not. If it is, then we play
beeps.
The Beeps method is simply playing a series of Beep.
The Beeps method is simply playing a series of Beep.
Code:
procedure TForm1.Beeps;
begin
Beep;
Beep;
Beep;
end;
For the OnClick event handler
of "Install Mouse Hook" put the following codes:
Code:
procedure TForm1.Button1Click(Sender: TObject);
begin
HookMouse(Self.Handle);
end;
It tells our dll to install the
mouse hook and to send the windows message notifications to our window handle.
And lastly the button of "Uninstall Mouse Hook" we give the following code for its OnClick event.
And lastly the button of "Uninstall Mouse Hook" we give the following code for its OnClick event.
Code:
procedure TForm1.FormDestroy(Sender: TObject);
begin
UnhookMouse;
end;
Testing
After compiling both projects (the dll and exe) make sure that they both in the same folder before running the exe. When the application is running, click the "Install Mouse Hook", then activate other application. Make sure our application completely covered by window from other application. Then move around the mouse. You will notice that there will be beeps sound whenever the mouse is over one of your forms, even if it's completely covered.
After compiling both projects (the dll and exe) make sure that they both in the same folder before running the exe. When the application is running, click the "Install Mouse Hook", then activate other application. Make sure our application completely covered by window from other application. Then move around the mouse. You will notice that there will be beeps sound whenever the mouse is over one of your forms, even if it's completely covered.
No comments:
Post a Comment