Solved

Hook bug hunting

Posted on 2001-08-09
14
590 Views
Last Modified: 2010-04-06
Hi

I'm trying to create a system wide keyboard hook. I have found a piece of code on delphifaq.com that I have modified a bit, but it doesn't work quite well.
When I run the app and hits some keys on the app. it works fine, I then switch to another app. and it still works for a single keystroke and then it doesn't work at all.

I will try to post the code, but it's quite large I'm afraid.

I hope some of you experts can see the problem here.


****HookConsts.pas*****
unit HookConsts;
interface

const
    CallBackCount   = 2;
    WM_HOOK         = 87;
    HookDllName     = 'Hook.dll';
    GetNotification = 'getnotification';
    NoNotification  = 'nonotification';


implementation
end.



****Component - Hook.pas*********************
unit Hook;

interface

uses
  Windows, Messages, SysUtils, Classes, Dialogs;

type
  THook = class(tcomponent)
  private
    FEnabled: Boolean;
    HookDllHandle: integer;
    FOnKbdMouseNotify: TNotifyEvent;
    CallBackHandle: THandle;
    procedure SetEnabled(const Value: Boolean);
    function AddToHook: boolean;
    function RemoveFromHook: boolean;
    procedure HookWindowProc(var Message: TMessage);
  public
    Destructor Destroy; override;
  published
    property Enabled: Boolean read FEnabled write SetEnabled;
    property OnKbdMouseNotify: TNotifyEvent read FOnKbdMouseNotify write FOnKbdMouseNotify;
  end;

procedure Register;

implementation

{$R *.DCR}

uses HookConsts;

procedure Register;
begin
    RegisterComponents('Samples', [THook]);
end;

destructor THook.Destroy;
begin
    Enabled := False;
    Inherited;
end;


procedure THook.HookWindowProc(var Message: TMessage);
begin
    if (Message.Msg = WM_HOOK) and Assigned(FOnKbdMouseNotify) then
        FOnKbdMouseNotify(Self);
end;

function THook.AddToHook: boolean;
type
    TAddToHookProc = function(hwnd: THandle): boolean; stdcall;
var
    AddToHookProc: TAddToHookProc;
begin
    Result := False;
    HookDllHandle := LoadLibrary(HookDllName);
    if HookDllHandle <> 0 then
    begin
        AddToHookProc := GetProcAddress(HookDllHandle, GetNotification);
        if Assigned(AddToHookProc) then
        begin
            CallBackHandle := AllocateHWnd(HookWindowProc);
            Result := AddToHookProc(CallBackHandle);
        end
        else
            MessageDlg('Could not call '+GetNotification +' in '+ HookDllName, mtError, [mbOK], 0);
    end
    else
        MessageDlg('Could not load '+HookDllName, mtError, [mbOK], 0);
end;

function THook.RemoveFromHook: boolean;
type
    TRemoveFromHookProc = function(hwnd: THandle): boolean; stdcall;
var
    RemoveFromHookProc: TRemoveFromHookProc;
begin
    Result := False;
    RemoveFromHookProc := GetProcAddress(HookDllHandle, NoNotification);
    if Assigned(RemoveFromHookProc) then
    begin
        DeallocateHWnd(CallBackHandle);
        Result := RemoveFromHookProc(CallBackHandle) and FreeLibrary(HookDllHandle);
    end;

    if not Result then
        MessageDlg('Could not unload '+HookDllName, mtError, [mbOK], 0);
end;

procedure THook.SetEnabled(const Value: Boolean);
begin
    if FEnabled <> Value then
    begin
        FEnabled := Value;

        if csDesigning in ComponentState then Exit;

        if Value then
            FEnabled := AddToHook
        else
            RemoveFromHook;
    end;
end;

end.





**Dll file - Hook.dpr*******************
library Hook;

uses
  Windows,
  Messages,
  SysUtils,
  HookConsts in 'HookConsts.pas';

//Define a record for recording and passing information process wide
type
  PHookRec = ^THookRec;
  THookRec = Packed Record
    HookHandle: THandle;
    CallBackHandle: Array[0..CallBackCount-1] of THandle;
  end;

var
  MemFileHandle: THandle; //Variable for the file mapping object
  PtrHookRec: PHookRec;   //Pointer to our hook record

//Add callback handle to array of windows to call
function AddCallBackHandle(Hwnd: THandle): boolean;
var
    i: integer;
begin
    Result := False;
    for i := 0 to CallBackCount-1 do
        if PtrHookRec^.CallBackHandle[i] = 0 then
        begin
MessageBox(0, pchar('add '+inttostr(hwnd)), 'Hook DLL', mb_Ok);
            PtrHookRec^.CallBackHandle[i] := Hwnd;
            Result := True;
            Break;
        end;

    if not Result then
        MessageBox(0, 'Numbers of callback windows exceeded', 'Hook DLL', mb_Ok);
end;

//Remove handle from callback array
function RemoveCallBackHandle(Hwnd: THandle): boolean;
var
    i: integer;
begin
    Result := False;
    for i := 0 to CallBackCount-1 do
        if PtrHookRec^.CallBackHandle[i] = Hwnd then
        begin
MessageBox(0, pchar('del '+inttostr(hwnd)), 'Hook DLL', mb_Ok);
            PtrHookRec^.CallBackHandle[i] := 0;
            Result := True;
            Break;
        end;

    if not Result then
        MessageBox(0, 'Handle not found', 'Hook DLL', mb_Ok);
end;

//Set notification to window(s)
procedure DoCallBack;
var
    i: integer;
begin
    for i := 0 to CallBackCount-1 do
        if PtrHookRec^.CallBackHandle[i] > 0 then
            PostMessage( PtrHookRec^.CallBackHandle[i], WM_HOOK, 0, 0);
end;


procedure MapFileMemory (dwAllocSize: DWord);
begin
    MemFileHandle := OpenFileMapping (FILE_MAP_WRITE, False, 'PrettyUniqueNameToHookMemMappedFile');
    if MemFileHandle = 0 then
        //Create a process wide memory mapped variable
        MemFileHandle := CreateFileMapping ($FFFFFFFF, nil, PAGE_READWRITE, 0, dwAllocSize, 'PrettyUniqueNameToHookMemMappedFile');
    if MemFileHandle = 0 then
    begin
        MessageBox(0, 'Could not create file map object', 'Hook DLL', mb_Ok);
        Exit;
    end;
    //Get a pointer to our process wide memory mapped variable
    PtrHookRec := MapViewOfFile(MemFileHandle, FILE_MAP_WRITE, 0, 0, dwAllocSize);
    if PtrHookRec = nil then
    begin
        CloseHandle(MemFileHandle);
        MessageBox(0, 'Could not map file', 'Hook DLL', mb_Ok);
        Exit;
    end;
MessageBox(0, pchar('mapfile handle '+inttostr(MemFileHandle)), 'Hook DLL', mb_Ok);    
end;

procedure UnMapFileMemory;
begin
MessageBox(0, 'unmap', 'Hook DLL', mb_Ok);
    //Delete our process wide memory mapped variable}
    if PtrHookRec <> nil then
    begin
        UnMapViewOfFile(PtrHookRec);
        PtrHookRec := nil
    end;

    if MemFileHandle > 0 then
    begin
        CloseHandle(MemFileHandle);
        MemFileHandle := 0
    end;
end;

//The function that actually processes the keystrokes for our hook
function KeyBoardProc(Code: Integer; wParam: Integer; lParam: Integer): Integer; stdcall;
begin
MessageBox(0, 'keydbproc', 'Hook DLL', mb_Ok);
    case Code of
      HC_ACTION: DoCallBack;
      HC_NOREMOVE: ;//Do nothing
    end;

    //Call the next hook in the hook chain
    if Code < 0 then
        Result := CallNextHookEx( PtrHookRec^.HookHandle, Code, wParam, lParam)
    else
        Result := 0;
end;


function InstallHook: boolean; stdcall;
begin
    //if we have a process wide memory variable and the hook has not already been set...
    if (PtrHookRec <> nil) and (PtrHookRec^.HookHandle = 0) then
begin
MessageBox(0, 'installhook', 'Hook DLL', mb_Ok);
        // Set the hook and remember our hook handle
        PtrHookRec^.HookHandle := SetWindowsHookEx(WH_KEYBOARD, @KeyBoardProc, HInstance, 0);
end;
    Result := PtrHookRec^.HookHandle > 0;
end;


function UnInstallHook: boolean; stdcall;
begin
MessageBox(0, 'uninstallhook', 'Hook DLL', mb_Ok);
    //if we have a process wide memory variable and the hook has already been set...
    if (PtrHookRec <> nil) and (PtrHookRec^.HookHandle <> 0) then
        //Remove our hook
        Result := UnHookWindowsHookEx( PtrHookRec^.HookHandle ) <> False
    else
        Result := False;
end;


procedure DllEntryPoint (dwReason: DWord);
begin
    case dwReason of
      Dll_Process_Attach:
      begin
          //if we are getting mapped into a process,
          //then get a pointer to our process wide memory mapped variable
          MemFileHandle := 0;
          PtrHookRec    := nil;
          MapFileMemory( Sizeof(THookRec) );
          InstallHook;
      end;
      Dll_Process_Detach:
      begin
          UnInstallHook;
          //if we are getting unmapped from a process then, remove
          //the pointer to our process wide memory mapped variable
          UnMapFileMemory;
      end;
    end;
end;


exports
    KeyBoardProc         name 'KEYBOARDPROC',
    AddCallBackHandle    name GetNotification,
    RemoveCallBackHandle name NoNotification;

begin
    //Set our Dll's main entry point
    DLLProc := @DllEntryPoint;
    //And call our Dll's main entry point
    DllEntryPoint(Dll_Process_Attach)
end.
0
Comment
Question by:tom_
14 Comments
 
LVL 1

Expert Comment

by:SenDog
ID: 6370936
I have a keylogger that works fine and is probably better than the code you have. If you want it, I could post it here.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6371557
Some hints about your code:

(1)
Don't use WM_HOOK = 87. That's a bad idea. Private message should >= WM_USER, which is $400. Even better would be to use RegisterWindowMessage.

(2)
In the application you're using this:

type
   TAddToHookProc = function(hwnd: THandle): boolean; stdcall;

And in the dll you're using this:

function AddCallBackHandle(Hwnd: THandle): boolean;

That's quite wrong. Either use "stdcall" in both definitions or in none. But this way it's wrong.

(3)
When installing a system wide keyboard hook, your dll gets loaded into each and every process system wide. Now in your DllEntryPoint procedure you install the hook. That's a bad idea, because the DllEntryPoint is called once for each and every process your dll is loaded into. That means if 5 GUI processes are running, you're calling SetWindowsHookEx 5 times. That are 4 times too much. And the shared PtrHookRec^.HookHandle variable just holds the hook handle, that was installed at last. That's very bad. You should remove the InstallHook call from your DllEntryPoint and instead export it. Then in your application you should call InstallHook once.

(4)
You should not open a MessageBox in your keyboard callback function, because this can lead to an endless loop, if you're pressing a key, while the MessageBox is still open. Or if you're pressing a key to close the MessageBox.

Regards, Madshi.
0
 
LVL 1

Expert Comment

by:dluedi
ID: 6374182
Listening
0
 

Author Comment

by:tom_
ID: 6376259
SenDog: I you want to I would like you to put your code up, but I really want my own to work.

Madshi
1) Why use RegisterWindowMessage? :)
2) Yes I tried to change this and it seems to help. Can you explain why? It works a bit and then it doesn't work anymore. perhaps its nothing
3) I think it is OK, because in the InstallHook method I check if there already exists a hookhandle.

Another thing. The UnInstallHook method never gets called. I call FreeLibrary should that not call the DllEntryPoint with Dll_Process_Detach param.

I know the code is a bit funny:

Result := RemoveFromHookProc(CallBackHandle) and FreeLibrary(HookDllHandle);

When I debug, result is true so FreeLibrary must have been called.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6376940
(1) You can use "WM_USER + xxx" or RegisterWindowMessage alternatively. Both is okay. But please don't use 87.

(2) Why? The reason is simply: If your application calls the exported function with stdcall, the parameters are pushed on the stack. And if now your dll doesn't use stdcall for the exported function, the function expects the parameters to be in the registers. As a result two things happen: (a) the parameters are not passed correctly and (b) the stack is going on the wild side. You *should* even get crashes, don't know why you don't...

(3) Missed that. You're right.

>> Another thing. The UnInstallHook method never gets called. I call FreeLibrary should that not call the
DllEntryPoint with Dll_Process_Detach param.

Calling FreeLibrary should really make DllEntryPoint called with Process_detach. But another thing: What if a copy of your dll is loaded into let's say Notepad. Then you exit Notepad. In that moment the copy of your dll that was loaded into Notepad runs through DllEntryPoint - thus exiting Notepad will uninstall the hook. I don't think that is what you want. So I say it again: Please don't automatically call InstallHook/UnInstallHook in DllEntryPoint. There's a lot happening behind the scenes. Please export those functions and call them manually. I'm quite sure that will solve the remaining problems...

Regards, Madshi.
0
 

Author Comment

by:tom_
ID: 6377003
I'm not sure UnInstallHook ever gets called. When it's called a messagebox should pop up. Is it because things are shutting down that it doesn't appear?
I also tried to call messagebeep(0)
0
 

Author Comment

by:tom_
ID: 6377164
Madshi I found out myself

"You can release a global hook procedure by using UnhookWindowsHookEx, but this function does not free the DLL containing the hook procedure. This is because global hook procedures are called in the process context of every application in the system, causing an implicit call to the LoadLibrary function for all of those processes. Because a call to the FreeLibrary function cannot be made for another process, there is then no way to free the DLL. Windows eventually frees the DLL after all processes explicitly linked to the DLL have either terminated or called FreeLibrary and all processes that called the hook procedure have resumed processing outside the DLL."

May I ask one last yes/no question ?
I also read that hooks are a bad choice for getting keyboard and mouse events, but is there a better way, they seems to be quite common?

If yes, I will just ask a new question.
0
Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

 

Author Comment

by:tom_
ID: 6377175
hmm by ask a new question I mean post a new topic ;)
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6377260
>> Madshi I found out myself  "[...]"

This paragraph has IMHO nothing to do with what we were talking about. Whether copies of your dll are still loaded in other processes after you've called UnhookWindowsHookEx, is of no meaning. The only meaning of that paragraph is that you can't recompile (=change) the dll as long as it is loaded by a process. It has nothing to do with the hook functionality.

>> I also read that hooks are a bad choice for getting keyboard and mouse events, but is there a better way, they seems to be quite common?

If you read that, then whoever claimed that should have also said what the better way is! In win9x you might find other ways, but not in NT. A keyboard (mouse) hook is the official/documented way to do things.

Let me ask: What are the remaining problems you have with your code? Does everything work as expected now? If not, please try what I've suggested namely export the Install/UninstallHook functions and call them seperately from the application.

Regards, Madshi.
0
 

Author Comment

by:tom_
ID: 6377283
I have exportet the un/install functions and it works well. The only problem is that the library never is released. I will just post the modified entry point code. The messagebox never pops up.

I tried this code and it didn't either give me the message popup.

procedure TForm1.Button1Click(Sender: TObject);
var
    i: thandle;
begin
    i := LoadLibrary('Hook.dll');
    FreeLibrary(i);
end;



--dll--
.
.
procedure DllEntryPoint (dwReason: DWord);
begin
    case dwReason of
      Dll_Process_Attach:
      begin
          MemFileHandle := 0;
          PtrHookRec := nil;
          MapFileMemory( Sizeof(THookRec) );
      end;
      Dll_Process_Detach:
      begin
messagebox(0, 'un mapping','',mb_ok);      
          UnMapFileMemory;
      end;
    end;
end;


exports
    HookProc,
    InstallHook,
    UnInstallHook;

begin
    DLLProc := @DllEntryPoint;
    DllEntryPoint(Dll_Process_Attach)
end.
0
 
LVL 20

Expert Comment

by:Madshi
ID: 6377470
Your DllEntryPoint definition is totally wrong. It has to be like this:

BOOL WINAPI DllEntryPoint(

    HINSTANCE hinstDLL,     // handle to DLL module
    DWORD fdwReason,     // reason for calling function
    LPVOID lpvReserved      // reserved
   );    

Which is in Delphi:

function DllEntryPoint(hInst, reason, reserved: dword) : bool; stdcall;

Regards, Madshi.
0
 

Author Comment

by:tom_
ID: 6377619
Are you sure if read about DLLProc in the Delphi help files, it want a procedure like this

procedure LibraryProc(Reason: Integer);
0
 
LVL 20

Accepted Solution

by:
Madshi earned 300 total points
ID: 6377857
Ooops. I'm sorry. Delphi encapsulates the Windows-typical DllEntryPoint function, it's right what you did...

I've tested it, the DllEntryPoint function seems to NOT be called for DLL_PROCESS_DETACH. I don't know why. Perhaps a Delphi bug, not sure...   :-(

What you can do is add a little unit to your dll project, the finalization part of the unit DOES get called.

Regards, Madshi.
0
 

Author Comment

by:tom_
ID: 6377888
OK I will do that, if it one day hits you what could be wrong please write.
Thanks Madshi
0

Featured Post

How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

Join & Write a Comment

In this tutorial I will show you how to use the Windows Speech API in Delphi. I will only cover basic functions such as text to speech and controlling the speed of the speech. SAPI Installation First you need to install the SAPI type library, th…
Introduction Raise your hands if you were as upset with FireMonkey as I was when I discovered that there was no TListview.  I use TListView in almost all of my applications I've written, and I was not going to compromise by resorting to TStringGrid…
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

743 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

11 Experts available now in Live!

Get 1:1 Help Now