[질문]마우스/키보드 메시지 후킹 예제소스(vc++)

psycoder의 이미지

마우스와 키보드 메시지를 후킹하여 특정시간(예를들어 30분)동안 이벤트가 발생했는지 점검하는 루틴이 필요합니다.

발생한 메시지를 가공하거나 그러진 않구 단지 특정시간동안 발생했는지만 점검하면 됩니다.

즉 스크린세이버와 유사한 겁니다. (스크린 세이버를 만들고 있는건 아니구요)

제가 지금 보는 책엔 자세한 설명이 없고 데브피아, 코드구루등 검색해봤는데 마땅한 예제 소스를 못찾겠네요.

혹시 이러한 예제 소스 아시는분 답변 부탁드립니다.

bugiii의 이미지

윈도우 OS별로 메시지를 후킹하는 방법과 구현은 Advanced Windows 라는 책에 잘 설명되어 있습니다. (책이름이 판에 따라 틀려졌을겁니다.) 서로의 장단점이 있구요. 되는 것과 안되는 것들이 다 틀리고 구현방법도 다양합니다.

키보드와 마우스의 후킹은 딱 그용도로 하는 방법이 있는데 후킹 루틴이 들어 있는 DLL을 모든 프로세스에 Injection하는 것으로 쉽게 구현할 수 있습니다.

이걸 이용해서 구현해본 것은 ICQ의 채팅문자를 입력할 때 '탁탁탁' 하고 리턴칠 때 '챙~~~' 하는 것처럼 모든 어플리케이션에 적용하는 경우... 그럴 때 아주 잘 동작했습니다. 혹은 DebugOutputString 같은 것을 후킹해서 옛날의 DbWin같은 것을 NT 이후 계열에서 구현하기도 했습니다. (sysinternal의 DbView인가는 DLL없이 이 기능을 구현했는데 어떻게 했는지 참 궁금합니다.)

하지만, 일반적인 메시지 후킹은 윈도우 메시지 루프가 없는 어플리케이션에는 DLL을 집어 넣을 수 없습니다. 그 챙~~~ 소리나게 하는 프로그램도 NT 계열의 cmd 에서는 소용없었습니다.

만약 타켓 OS가 NT 계열이라면 아예 다른 프로세스에 바로 DLL을 밀어넣거나 (아마 권한이 되어야...) 레지스트리에 등록해서 프로세스마다 모두 띄우게 하거나 하는 방법이 있을 겁니다.

요즘에는 어떤 신기한 방법이 더 추가되었는지는 모르겠지만 현재의 윈도우 OS라면 어떻게든 DLL을 다른 프로세스에 밀어넣는 방법이 될 것 같습니다.

buelgsk8er의 이미지

hook중에 journal hook이란거 쓰면 대략 DLL없이 독립 실행화일로도 할 수 있기는 합니다
자세한건 msdn참조하시길

bugiii의 이미지

4r7yc0d3 wrote:
hook중에 journal hook이란거 쓰면 대략 DLL없이 독립 실행화일로도 할 수 있기는 합니다
자세한건 msdn참조하시길
그건 Win16일때 가능한 것 아니었나요? 32로 넘어오면서 글로벌 훅은 불가능하다고 기억합니다만...
buelgsk8er의 이미지

글쎄요.. 그런 얘긴 못봤습니다. Minimum OS requirement가 win95, NT3.1인걸로 봐서는 상관없을 듯 합니다만. 실제로 2000에서 대충 동작했었구요.

참고로 아래는 MSDN에서 JournalRecordProc (WH_JOURNALRECORD)의 내용 중 일부입니다.

A JournalRecordProc hook procedure does not need to live in a dynamic-link library. A JournalRecordProc hook procedure can live in the application itself.

Unlike most other global hook procedures, the JournalRecordProc and JournalPlaybackProc hook procedures are always called in the context of the thread that set the hook.

kuma의 이미지

코드 구루에 보면 그 좋은예제가 있는데....

멜 주소라도 주시면 제가 쓰고 있는 전역 훜 소스를 보내드리겠습니다.

kuma의 이미지

따로 나뒀던 소스가 어디로 갔는지 행방불명입니다. :cry:

저도 Hook 에 대해서는 잘 모르는데요 bugiii 님이 쓰신것 처럼 메세지 펌핑을 하지 않는 프로그램 들은 Hook 이 되지 않습니다. 즉, Hook 이란 일반적으로 윈도우즈 메세지 에 대한 처리루틴을 추가 해주는겁니다.
그래서 일반적으로 타 프로세서의 주소공간에서 동작하여야 하므로 DLL ( System Area 에 적재되므로... ) 로 작성되어야 합니다.

그래서 Hook 을 위해서는 Hook DLL 과 Hook DLL 을 로딩시키는 실행 프로그램으로 나뉩니다.

여기서 주의해야하는것은 SetWindowsHookEx 와 UnhookWindowsHookEx 가 DLL 에서 call 되어져야만 한다는겁니다.

코드구루소스에서 이부분이 로딩 프로그램에 적재되어 있어서 일주일간 고생한 기억이 ..... :cry:

아마도 이부분만 생각하시고 코드구루에서 소스를 받아서 하시면 될것 같습니다.

그럼 즐프하시기를..

madkoala의 이미지

단순히 WH_KEYBOARD, WH_MOUSE에 대해서만 후킹하시면 되는 것같은데요.
이건 윈도우니 아니니 이런 건 상관없는,
단순한 전역 후킹에 관한 문제고,
전역 후킹에 관한 자료는 데브피아에 보시면 부지기수로 많습니다.

kuma의 이미지

console 윈도는 윈도 메세지를 사용하지 않습니다.

못믿으시겠다면 spy 로 한번 메세지를 얻어보세요.

제가 알기로는 Console 을 위한 API 가 따로 있는걸로 알고 있습니다.
ReadConsole, WriteConsole 과 같은 API 를 사용하는것으로....

여기서 Windows NT System 과 Windows Message 는 서로 상관없다는걸 바로 느끼실수 있습니다.

조금만 더 보시면 NT System Service API 부류와 윈도 Message 및 기타 부류는 완전 구분되는것 같습니다.

NT System Service API 류의 특징은 Event, IOCP를 항상 동행해서 사용할수 있다는 특징을 가지고 있는것 같습니다.

buelgsk8er의 이미지

음냐 WH_JOURNALRECORD는 쓰레드 큐의 메시지를 가로채는 게 아니라 시스템 메시지 큐에서 가로채는 겁니다. 따라서 콘솔이건 머건 상관없고 injection용 DLL도 필요없는거죠. WH_JOURNALRECORD와 WH_JOURNALPLAY 훅은 다른 훅들하고 성격이 조금 다릅니다. 자세한 건 MSDN을 참조하시길.

madkoala의 이미지

질문하신 분이 원하신 답이 별로 안 나오는 것 같군요.
제가 작업했던 소스를 살짝 올려드리니 참조하세요.
여기서 uTickCount 라는 변수를 잘 이용하시면
원하시는 일을 하실 수 있습니다.


#pragma data_seg(".shared")

static HINSTANCE			hInstance = NULL;
static HHOOK				hKeyHook = NULL;
static HHOOK				hMouseHook = NULL;
static UINT					uTickCount = 0;

#pragma data_seg()

#pragma comment(linker, "/section:.shared,RWS")

BOOL APIENTRY DllMain( HANDLE hModule, 
                       DWORD  ul_reason_for_call, 
                       LPVOID lpReserved
					 )
{
	switch(ul_reason_for_call) {
	case DLL_PROCESS_ATTACH:
		hInstance = (HINSTANCE)hModule;
		break;
	
	case DLL_PROCESS_DETACH:
		break;
	}

    return TRUE;
}

LRESULT CALLBACK fnMouseHook(int nCode, WORD wParam, DWORD lParam)
{
	uTickCount = 0;
	return ::CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}

LRESULT CALLBACK fnKeyboardHook(int nCode, WORD wParam, DWORD lParam)
{
	uTickCount = 0;
	return ::CallNextHookEx(hKeyHook, nCode, wParam, lParam);
}

extern "C" __declspec(dllexport) BOOL HookInitialize(void)
{
 	hKeyHook = (HHOOK)::SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)fnKeyboardHook, hInstance, 0);
	if(!hKeyHook) {
		return FALSE;
	}

	hMouseHook = (HHOOK)::SetWindowsHookEx(WH_MOUSE, (HOOKPROC)fnMouseHook, hInstance, 0);
	if(!hMouseHook) {
		if(hKeyHook) { UnhookWindowsHookEx(hKeyHook); }
		return FALSE;
	}

	return TRUE;
}

extern "C" __declspec(dllexport) BOOL HookFinalize(void)
{
	if(hKeyHook) { UnhookWindowsHookEx(hKeyHook); }
	if(hMouseHook) { UnhookWindowsHookEx(hMouseHook); }
	
	return TRUE;
}
bugiii의 이미지

madkoala님의 소스는 DLL injection이군요. 그리고 이런 종류의 구현은 메시지 펌프를 쓰는 어플리케이션에만 적용됩니다.

madkoala의 이미지

그게.. 질문하신 분께서 원하시는 답이라고 생각하는데요.. 다른 분들의 쓰레드를 보고 질문하신 분께서 답을 얻기가 쉽지 않아보여서 답을 달아드린 거지요.

그리고 제가 올려드린 소스는 단순히 키보드 / 마우스 움직임을 감지하는 Global hooking 방법이라
특정 어플리케이션이 메시지 펌프를 쓰니 안 쓰니 하는 것은 크게 문제가 되지 않을 것으로 생각합니다.
어차피 질문하신 분께서 원하신 것은 스크린세이버같은 역할을 하는 녀석 아니었던가요? ^^

bugiii의 이미지

그게... WH_KEYBOARD, MOUSE, GETMESSAGE 같은 훅은... 메시지 루프가 있는 (GetMessage나 PeekMessage를 한번이라도 부르는) 어플리케이션에서 메시지가 하나 발생할 때 자동으로 그 DLL이 끼어들어서 동작하기 때문에.... 일반 cmd 어플에는 동작하지 않았기 때문에 드린 말씀입니다.

그리고, WH_JOURNALRECORD가 입력의 비동기를 무시하고 직렬화된 입력으로 만들어서 입력간의 충돌이 발생할 수 있다고 어디선가 본 것 같은데, 찾을 수가 없네요. 이 후킹의 사용상 주의점 같은 것은 없습니까? 잘 사용할 수 있다면 여러가지 경우에 손쉽게 적용할 수 있을 것 같은데요. 경험담 부탁드립니다.

p.s. 저위의 Win16에서만 되었다는 것은 잘못된 것입니다. 왜 그렇게 기억을 했을까요... :oops: WH_JOURNALRECORD의 특성상 예외적인 것이라는 것을 몰라서 그랬던 것 같습니다. (역시 늙으면... x어야 하나...)

psycoder의 이미지

많은 답변 감사합니다. :D
덕분에 메시지 후킹이란걸 배웠습니다.
이거 정말 잼있는 놈이네요.
잘 이용하면 삐리리(?)한걸 만들수도 있겠더군요.
그래서 공부한 참에 키보드 메시지 후킹한걸 파일로 저장하도록 만들어봤습니다. (원래 이걸 만들려던게 아닌데..ㅡ.ㅡ)
(dll injection을 이용했고 이 후킹 dll에서 SendMessage()로 보내준 메시지의 wParam을 파일로 저장하도록 만들어봤습니다.)
근데 영문 대/소문자와 한/영을 구분하지 않고 모두 영문 대문자로만 기록되네요.
키보드상의 문자말구 윈도상에 디스플레이되는 문자 그대로 저장할려고 이것저것 해봤는데 내공부족으로 잘 모르겠네요. :oops:

그리고, 제가 원래 만들려던건 키보드와 마우스 메시지를 후킹해서 30분동안 아무런 이벤트가 발생하지 않으면 즉, 컴퓨터가 쉬고 있으면 뭔가를 하는 겁니다.
근데 스크린세이버를 30분으로 맞춰놓고 분명 30분 후에 스크린세이버가 동작하고 있는데 뭔가를 하지 않습니다.
혹시 사용자가 마우스나 키보드를 건드리지 않아도 시스템 내부적으로 키보드/마우스 메시지가 발생할수 있나요?

댓글 달기

Filtered HTML

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

BBCode

  • 텍스트에 BBCode 태그를 사용할 수 있습니다. URL은 자동으로 링크 됩니다.
  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param>
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.

Textile

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • You can use Textile markup to format text.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Markdown

  • 다음 태그를 이용하여 소스 코드 구문 강조를 할 수 있습니다: <code>, <blockcode>, <apache>, <applescript>, <autoconf>, <awk>, <bash>, <c>, <cpp>, <css>, <diff>, <drupal5>, <drupal6>, <gdb>, <html>, <html5>, <java>, <javascript>, <ldif>, <lua>, <make>, <mysql>, <perl>, <perl6>, <php>, <pgsql>, <proftpd>, <python>, <reg>, <spec>, <ruby>. 지원하는 태그 형식: <foo>, [foo].
  • Quick Tips:
    • Two or more spaces at a line's end = Line break
    • Double returns = Paragraph
    • *Single asterisks* or _single underscores_ = Emphasis
    • **Double** or __double__ = Strong
    • This is [a link](http://the.link.example.com "The optional title text")
    For complete details on the Markdown syntax, see the Markdown documentation and Markdown Extra documentation for tables, footnotes, and more.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 사용할 수 있는 HTML 태그: <p><div><span><br><a><em><strong><del><ins><b><i><u><s><pre><code><cite><blockquote><ul><ol><li><dl><dt><dd><table><tr><td><th><thead><tbody><h1><h2><h3><h4><h5><h6><img><embed><object><param><hr>

Plain text

  • HTML 태그를 사용할 수 없습니다.
  • web 주소와/이메일 주소를 클릭할 수 있는 링크로 자동으로 바꿉니다.
  • 줄과 단락은 자동으로 분리됩니다.
댓글 첨부 파일
이 댓글에 이미지나 파일을 업로드 합니다.
파일 크기는 8 MB보다 작아야 합니다.
허용할 파일 형식: txt pdf doc xls gif jpg jpeg mp3 png rar zip.
CAPTCHA
이것은 자동으로 스팸을 올리는 것을 막기 위해서 제공됩니다.