Thứ Tư, 30 tháng 12, 2009

// // Leave a Comment

C++ Hook

Hook – Ví dụ nhỏ dành cho “người mù”
Hook là gì?

Trong Windows, khi chúng ta thực hiện các thao tác nhấp chuột, nhấn phím… thì hệ điều hành sẽ chuyển các sự kiện này thành các thông điệp (message) rồi đưa vào hàng đợi (queue) của hệ thống. Sau đó, các thông điệp được trao lại cho từng ứng dụng cụ thể để xử lý.

Hook là một kỹ thuật cho phép một hàm có thể chặn, theo dõi, xử lý, hoặc hủy bỏ các thông điệp trước khi chúng “mò” đến được ứng dụng.

Hai ví dụ thường gặp của Hook là ứng dụng soạn thảo văn bản tiếng Việt (Unikey, Vietkey…) và ứng dụng tra từ điển trực tiếp trên màn hình (Click’n’See, Lạc Việt MTD, English Study…). Chúng xử lý thông điệp từ bàn phím để đổi văn bản sang tiếng Việt, hoặc xử lý thông điệp từ con chuột để lấy văn vản dưới con trỏ. Chương trình KeyLogger chuyên ăn cắp mật khẩu cũng sử dụng kỹ thuật này.

Hook hoạt động như thế nào?

Xét ở mặt phạm vi hoạt động thì có 2 loại Hook: Hook toàn cục (có phạm vi ảnh hưởng đến toàn hệ thống) và Hook cục bộ (chỉ có tác dụng trên ứng dụng được cài Hook).

Xét về mặt chức năng, Hook có 15 loại ứng với nhóm sự kiện mà nó sẽ xử lý. Ví dụ:
- WH_KEYBOARD: cho phép đón nhận các thông điệp từ bàn phím
- WH_MOUSE cho phép đón nhận các sự kiện có liên quan đến con trỏ chuột
- WH_MSGFILTER và WH_SYSMSGFILTER: cho phép theo dõi chính các thông điệp được xử lý bởi menu, scrollbar, dialog…
Các loại còn lại bạn có thể tìm thấy trong CD MSDN tại /winui/winui/windowsuserinterface/windowing/hooks/abouthooks.htm

Ứng với mỗi loại Hook, Windows sẽ có một chuỗi các hàm lọc (filter function) để xử lý. Ví dụ, khi người dùng nhấn phím, thông điệp này sẽ được truyền qua tất cả các hàm lọc thuộc nhóm WH_KEYBOARD.

Các hàm lọc này sẽ do chúng ta tùy ý định nghĩa. Mục tiêu của hàm lọc và bắt lấy thông điệp để giám sát, sửa đổi và chuyển tiếp, hoặc ngăn chặn không cho nó đến được ứng dụng. Hàm lọc được gọi bởi Windows nên còn gọi là hàm CallBack.

Muốn cài đặt một Hook toàn cục (Hook hệ thống), thì hàm lọc phải được đặt trong file DLL.

Cài đặt Hook có khó không?

Về cơ bản, chỉ cần biết chút kiến thức về hệ điều hành Windows (cơ chế hoạt động và quản lý thông điệp). Thêm một ít kiến thức về C++, API, và DLL (Dynamic Link Library). Tuy nhiên, Hook là một… nghệ thuật (và người Hook cũng là 1 nghệ sỹ ), và chúng ta có thể làm đủ trò tùy vào trí tưởng tượng và “nội công” của mình.

Sơ lượt các bước cài đặt (một Hook hệ thống)

1. Bước 1: tạo file DLL chứa hàm lọc. Mọi hàm lọc đều có dạng như sau:
Code:

LRESULT CALLBACK <tên hàm>(int nCode, WPARAM wParam, LPARAM lParam)

Bên trong hàm lọc, chúng ta có thể tùy ý làm đủ chuyện trời đất. Tuy nhiên, phải tuân thủ theo một số nguyên tắt sau:
- Nếu nCode < 0 thì không được làm gì mà phải gọi ngay hàm CallNextHookEx() (sẽ trình bày sau) và trả về giá trị... return bởi nó.
- Nếu có xử lý ít nhiều thì hàm lọc phải trả về giá trị khác 0

2. Bước 2: tạo chương trình cài đặt Hook. Chương trình này sử dụng 3 hàm API sau đây để “móc” hàm lọc vào các sự kiện:

a) Hàm cài đặt một hàm lọc
Code:

HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId)

Các tham số có ý nghĩa như sau:
+ idHook: xác định loại hook mà ta muốn cài đặt (ví dụ: WH_KEYBOARD, WH_MSGFILTER…)
+ lpfn : địa chỉ hàm lọc mà ta muốn gắn với hook
+ hMod : handle của module chứa hàm lọc (là handle của file DLL trong trường hợp Hook toàn cục)
+ dwThreadID : định danh của thread ứng với hook đang được cài đặt (nếu = 0 thì Hook có phạm vi toàn hệ thống và bao hảm cả mọi thread đang tồn tại)

b) Hàm gỡ bỏ một hàm lọc
Code:

BOOL UnhookWindowsHookEx(HHOOK hHook)

Tham số:
+ hHook: handle của hook cần gỡ bỏ (chính là giá trị được trả vể bởi hàm SetWindowsHookEx khi cài đặt)

c) Hàm gọi hàm lọc kế tiếp trong chuỗi
Như đã nói ở trên, hệ thống duy trì một chuỗi các hàm lọc cho mỗi loại sự kiện. Do đó, sau khi một hàm lọc thực hiện xong “nghĩ vụ” của mình, nó sẽ gọi hàm lọc tiếp theo trong chuỗi. Nhờ đó, người ta có thể cài nhiều Hook tại một sự kiện.
Code:

LRESULT CallNextHookEx(HHOOK hHook, int nCode, WPARAM wParam, LPARAM lParam)

Ý nghĩa tham số:
+ hHook: là handle của hook hiện hành
+ nCode : hook code để gởi đến hook kế tiếp
+ wParam và lParam: chứa thông tin mở rộng của thông điệp

Nhứt đầu quá! Ví dụ đi

Ví dụ dưới đây (có file đính kèm) sẽ minh họa cách cài một cái Hook đơn giản bằng Visual C++: chặn bắt sự kiện nhấn phím. Khi chạy, chương trình sẽ “tàng hình” (chỉ nhìn thấy trong Task Manager). Nếu người dùng “lỡ tay” nhấn phím Enter (ở bất kỳ đâu) sẽ bị “bắn” ra một cái message box có dòng chữ “sonhn say hello” (rất dễ nổi khùng!).

1. Bước 1:

Trước hết, tạo một Project VC++ Win32 DLL (xin đừng nhầm với dạng DLL Class Library) với 1 file duy nhất có nội dung như sau:

File “TestDLL.cpp”

Code:

#include <windows.h>

//vùng nhớ dùng chung, chứa biến handle của Hook
#pragma data_seg("SHARED_DATA")
HHOOK hGlobalHook = NULL;
#pragma data_seg()

//hàm lọc sự kiện nhấn phím
__declspec(dllexport) LRESULT CALLBACK FillKeyboard(int nCode, WPARAM wParam, LPARAM lParam)
{
//nếu sự kiện là nhấn phím và mã phím là Enter
if ((nCode == HC_ACTION) && (wParam == 13))
{
MessageBox(0, "sonhn say hello Smile", "Hello", 0);
return 1;
}

//gọi Filter Function kế tiếp trong chuỗi các Filter Function
return CallNextHookEx(hGlobalHook, nCode, wParam, lParam);
}

//hàm ấn định biến hGlobalHook tại vùng nhớ dùng chung
__declspec(dllexport) void SetGlobalHookHandle(HHOOK hHook)
{
hGlobalHook = hHook;
}

Đoạn code trên có 3 điểm cần chú ý:
- Đoạn khai báo vùng nhớ dùng chung “SHARED_DATA”: vùng nhớ này chứa handle của Hook được cài. Sở dĩ nó phải được đặt trong vùng nhớ dùng chung vì cả 2 chương trình (DLL và EXE) đều cần truy phải truy xuất vào.
- Hàm lọc sự kiện nhấn phím: hàm này thực hiện nhiệm vụ: khi sự kiện là nhấn phím (nCode == HC_ACTION) và phím được nhấn là Enter (wParam == 13) thì “bắn” cái message box ra.
- Hàm ấn định biến hGlobalHook: hàm này sẽ được gọi bởi chương trình EXE sau đây để ấn định handle cho Hook mà nó sẽ cài.

Tạo file Module-Definition (.def) có nội dung như bên dưới để export các hàm bên trong file DLL và khai báo vùng nhớ dùng chung.

File “TestDLL.def”

Code:

LIBRARY "TestDLL"
EXPORTS
FillKeyboard
SetGlobalHookHandle
SECTIONS

SHARED_DATA Read Write Shared

Biên project trên để thu được file “TestDLL.dll”

2. Bước 2:

Tạo project VC++ dạng Win32 Application để thực hiện cài hàm lọc (trong file TestDLL.dll) vào sự kiện nhấn phím.

File “TestHook.cpp”

Code:

#include <windows.h>

//các biến toàn cục
HHOOK hHook = NULL; //handle của hook
HMODULE hDll; //handle của DLL

//định nghĩa con trỏ hàm SetGlobalHookHandle() trong file DLL
typedef VOID (*LOADPROC)(HHOOK hHook);

BOOL InstallHook();

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow)
{
//cài đặt hook, exit nếu thất bại
if (InstallHook() == FALSE)
{
MessageBox(0, "Can not install hook!", "Error", 0);
return -1;
}

MSG msg;
BOOL bRet;
//vòng lặp đón và xử lý các message
while((bRet = GetMessage(&msg, NULL, 0, 0)) != 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}

return 0;
}

//Hàm cài đặt hook
BOOL InstallHook()
{
//load file DLL
if (LoadLibrary("TestDLL.dll") == NULL)
{
MessageBox(0, "Can not load DLL file.", "Error", 0);
return FALSE;
}

//lấy handle của file DLL
HMODULE hDLL = GetModuleHandle("TestDLL");

//exit nếu load DLL không thành công
if (hDLL == NULL)
{
return FALSE;
}

//cài đặt hook, phạm vi toàn cục
hHook = SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)GetProcAddress(hDLL,"FillKeyboard"), hDLL, 0);

//exit nếu cài đặt Hook không thành công
if (hHook == NULL)
{
return FALSE;
}

//lấy địa chỉ hàm SetGlobalHookHandle() trong file DLL
LOADPROC fPtrFcnt;
fPtrFcnt = (LOADPROC)GetProcAddress(hDLL, "SetGlobalHookHandle");
if (fPtrFcnt == NULL)
{
return FALSE;
}

//ấn định handle của hook vào vùng nhớ dùng chung (giữa DLL và ứng dụng này)
fPtrFcnt(hHook);

return TRUE;
}

Đừng hốt hoảng vì độ dài của nó. Đọc kỹ 1 chút, bạn sẽ hiểu được vấn đề vì chúng đã được ghi chú khá chi tiết rồi.

Chú ý:
- Nhằm đơn giản hóa quá trình cài đặt, ví dụ này không có thao tác gỡ bỏ Hook. Khi chạy, chương trình sẽ “tàng hình” và muốn tắt nó, bạn phải “end task” nó bằng Task Manager(!). Lúc đó, Hook cũng sẽ được tự động được gỡ bỏ.
- Nếu bạn muốn cài đặt thao tác gỡ bỏ Hook, hãy viết thêm đoạn code xử lý sự kiện WM_DESTROY và gọi hàm dưới đây:
Code:

//hàm gỡ bỏ hook, với hHook là handle của hook
BOOL UninstallHook()
{
if ((hHook != 0) && (UnhookWindowsHookEx(hHook) == TRUE))
{
hHook = NULL;
return TRUE;
}
return FALSE;
}

Kết luận

Hook là một kỹ thuật tương đối khó và kiến thức về nó cũng khá rộng, địa chỉ học hỏi và tham khảo trên http://msdn.microsoft.com

0 comments: