V – CHƯƠNG TRÌNH WINDOWS TIẾP NHẬN THÔNG ĐIỆP CHUỘT:
TÌM HIỂU VỀ HOOKS
Hook là một cơ chế cực mạnh cho phép ta cài đặt một thủ tục để điều khiển hoặc chận hứng các thông điệp trước khi các thông điệp này tới được nơi tiếp nhận.
Hay nói một cách khác hook là một điểm trong kỹ thuật message-handling hệ thống, nơi mà một ứng dụng có thể đặt một thủ tục để quản lý sự lưu thông của các thông điệp trong hệ thống và xử lý một kiểu thông báo nào đó trước khi chúng tới được thủ tục cửa sổ đích.
Do có khả năng can thiệp mạnh nên hook có xu hướng làm chậm lại hệ thống vì chúng làm tăng số lượng các hoạt động của hệ thống đối với mỗi thông điệp. Chỉ đặt hook khi thực sự cần thiết và dỡ bỏ chúng khi không cần đến.
1 - Chuỗi hook:
Hệ thống cho phép nhiều kiểu hook khác nhau: mỗi kiểu cung cấp việc truy xuất tới một khía cạnh khác nhau của kỹ thuật message-handling. Chẳng hạn, một ứng dụng có thể sử dụng hook WM_MOUSE để quản lý những thông điệp chuột trong luồng thông điệp.
Hệ thống duy trì một chuỗi hook riêng lẻ đối với mỗi kiểu hook. Một chuỗi hook là một danh sách các pointer chỉ tới các hàm callback application-defined đặc biệt mà những hàm này gọi các thủ tục hook. Khi một thông điệp xảy ra là nó đã được tổ chức với một kiểu hook cụ thể, hệ thống chuyển thông điệp tới mỗi thủ tục hook có mặt trong chuỗi hook, theo trật tự cái nọ sau cái kia. Hoạt động của một thủ tục hook có thể phụ thuộc vào kiểu hook mà nó liên quan. Các thủ tục hook cho một vài kiểu hook có thể chỉ quản lý những thông điệp; những cái khác có thể thay đổi những thông điệp hay dừng sự phát triển của nó trong chuỗi, ngăn chặn chúng tìm tới thủ tục hook kế tiếp hay cửa sổ cuối cùng (đích).
2 – Thủ tục hook:
Để có được sự tiện lợi của một loại hook chi tiết, người lập trình cung cấp một thủ tục hook và sử dụng hàm SetWindowsHookEx để đặt nó vào trong chuỗi hook. Một thủ tục hook có cú pháp:
- LRESULT CALLBACK HookProc ( Int nCode,
WPARAM wParam, LPARAM lParam
HookProc là một placeholder cho một ứng dụng đã được định nghĩa trước. Thông số nCode là một mã hook phụ thuộc vào kiểu hook; mỗi kiểu có một tập các đặc tính các mã hook. Những giá trị của thông số wParam và lParam phụ thuộc vào mã hook, nhưng điển hình thì chúng chứa thông tin về một thông điệp được send hay được post. Hàm SetWindowsHookEx luôn luôn đặt một thủ tục hook ở đầu một chuỗi hook. Khi một sự kiện xảy ra mà sự kiện này được quản lý bởi một kiểu hook cụ thể thì hệ thống gọi thủ tục ở đầu chuỗi hook đã được tổ chức. Mỗi thủ tục hook trong chuỗi quyết định nên chuyển hay không chuyển sự kiện tới thủ tục tiếp theo. Một thủ tục hook chuyển một sự kiện tới thủ tục tiếp theo bằng cách gọi hàm CallNextHookEx. Chú ý rằng những thủ tục hook dành cho các kiểu hook chỉ có thể quản lý các thông điệp, hệ thống chuyển thông điệp tới mỗi thủ tục hook, chứ không dính dáng gì đến có hay không một thủ tục cụ thể gọi CallNextHookEx. Một thủ tục hook có thể toàn cục, quản lý những thông điệp đối với tất cả các thread trong hệ thống hay nó có thể là thread cụ thể, quản lý các thông điệp cho chỉ một thread riêng biệt. Một thủ tục hook toàn cục có thể được gọi trong ngữ cảnh của bất kỳ ứng dụng nào, bởi thế thủ tục phải ở trong một module DLL riêng lẻ. Một thủ tục hook loại thread cụ thể chỉ được gọi trong ngữ cảnh của thread đã được tổ chức. Nếu một ứng dụng đặt một thủ tục hook cho một trong các thread của riêng nó thì thủ tục hook có thể ở trong cả module giống nhau như quãng nghĩ giữa các mã ứng dụng hoặc trong một DLL. Nếu ứng dụng đặt một thủ tục hook cho một thread của một ứng dụng khác thì thủ tục phải ở trong một DLL. Chú ý chỉ sử dụng hook toàn cục cho mục đích debug còn không thì nên tránh không sử dụng. Hook toàn cục có thể gây tổn hại cho hoạt động của hệ thống và gây nên xung đột với những ứng dụng khác có cùng kiểu hook toàn cục.
3 – Các loại hook:
Một loại hook làm cho một ứng dụng có thể quản lý một mặt khác nhau của kỹ thuật message-handling hệ thống.
Bao gồm các loại hook sau đây:
- WH_CALLWNDPROC hook quản lý các thông điệp trước lúc hệ thống gởi chúng tới cửa sổ đích.
- WH_CALLWNDPROCRET hook quản lý các thông điệp sau khi chúng được xử lý bởi thủ tục cửa sổ đích.
- WH_CBT hook nhận những thông báo có ích tới ứng dụng huấn luyện trên cơ sở tính toán (CBT).
- WH_DEBUG hook có ích cho việc debug những thủ tục hook khác. - WH_FOREGROUNDIDLE hook sẽ được gọi khi thread foreground của ứng dụng sẽ trở thành không dùng đến. Hook này có ích cho hoạt động những nhiệm vụ (task) độ ưu tiên thấp trong thời gian không được dùng đến.
- WH_GETMESSAGE hook quản lý các thông điệp được post tới hàng thông điệp.
- WH_JOURNALPLAYBACK hook post những thông điệp được ghi trước đó bởi thủ tục hook WH_JOURNALRECORD.
- WH_JOURNALRECORD hook ghi những thông điệp đầu vào được post tới hàng thông điệp hệ thống. Hook này có ích cho việc ghi các macro. - WH_KEYBOARD hook quản lý các thông điệp keystroke.
- WH_KEYBOARD_LL Windows NT: hook quản lý những sự kiện nhập vào từ keyboard mức thấp.
- WH_MOUSE hook quản lý các thông điệp chuột.
- WH_MOUSE_LL Windows NT: hook quản lý những sự kiện đầu vào chuột mức thấp.
- WH_MSGFILTER hook quản lý các thông điệp được kết sinh như là một kết quả cuả sự kiện đầu vào ở trong dialog box, message box, menu hay scroll bar.
- WH_SHELL hook quản lý các thông điệp nhận thông báo hữu ích để shell các ứng dụng.
- WH_SYSMSGFILTER đặt một ứng dụng các thông điệp được kết sinh như là kết quả của một sự kiện đầu vào ở trong dialog box, message box, menu hay scroll bar. Thủ tục hook quản lý những thông điệp này cho tất cả các ứng dụng trong hệ thống.
4 – Sử dụng hook: a) Cài đặt hook:
Đặt một thủ tục hook bằng cách gọi hàm SetWindowsHookEx đặc tả kiểu hook gọi thủ tục, thủ tục có phải được tổ chức với tất cả các thread hay chỉ được tổ chức với một thread cụ thể và một pointer chỉ tới một điểm vào thủ tục. Phải đặt một thủ tục hook toàn cục vào một DLL riêng biệt từ ứng dụng cài đặt thủ tục hook. Ứng dụng cài đặt phải có handle chỉ tới module DLL trước khi nó có thể đặt thủ tục hook. Hàm LoadLibrary khi được đưa tên của DLL sẽ trả handle cho module DLL. Sau khi có handle, có thể gọi hàm GetProcAddress để lấy lại địa chỉ của thủ tục hook. Cuối cùng sử dụng hàm SetWindowsHookEx để đặt địa chỉ hook vào trong chuỗi hook dành riêng. SetWindowsHookEx chuyển handle module, một pointer chỉ tới điểm vào thủ tục hook và cho danh hiệu thread, chỉ ra rằng thủ tục hook phải được tổ chức với tất cả các thread trong hệ thống.
Ví dụ minh họa:
HOOKPROC hkprcSysMsg; static HINSTANCE hinstDLL;
static HHOOK hhookSysMsg;
hinstDLL = LoadLibrary("c:\windows\sysmsg.dll");
hkprcSysMsg = (HOOKPROC)GetProcAddress(hinstDLL, SysMessageProc);
hhookSysMsg = SetWindowsHookEx( WH_SYSMSGFILTER, hkprcSysMsg, hinstDLL, 0 );
b) Giải phóng hook:
Có thể giải phóng một procedure loại thread cụ thể tức xóa địa chỉ của nó khỏi chuỗi hook bằng cách gọi hàm UnhookWindowsHookEx nhưng hàm này không trả tự do cho DLL chứa thủ tục hook. Đây là lý do mà thủ tục hook toàn cục được gọi trong ngữ cảnh quá trình của mỗi ứng dụng trong hệ thống, gây nên một gọi tuyệt đối tới hàm LoadLibrary cho tất cả các quá trình đó. Bởi vì một gọi tới hàm FreeLibrary không thể được tạo ra cho một quá trình khác, rồi thì không có cách nào để giải phóng cho DLL. Hệ thống cuối cùng giải phóng DLL
sau khi tất cả các quá trình nối hoàn toàn với DLL đã kết thúc hay gọi
FreeLibrary và tất cả các quá trình đã gọi thủ tục hook lại tiếp tục quá trình xử lý bên ngoài DLL. Một phương pháp lựa chọn cho việc đặt thủ tục toàn cục là cung cấp một hàm cài đặt trong DLL, cùng với thủ tục hook. Với phương pháp này, ứng dụng cài đặt không cần handle chỉ tới module DLL. Bằng cách nối với DLL, ứng dụng gia tăng việc truy xuất tới hàm cài đặt. Hàm cài đặt có thể cung cấp handle module DLL và những chi tiết khác ở trong cái gọi tới
SetWindowsHookEx. DLL cũng có thể chứa một hàm giải phóng thủ tục hook toàn cục; ứng dụng có thể gọi hàm giải phóng hook này khi kết thúc.
5 – Hook trong Windows 3.x:
Hook là một điểm trong kỹ thuật message-handling của Windows mà một ứng dụng có thể sử dụng để làm tăng khả năng truy xuất tới dòng thông điệp. Windows cung cấp nhiều kiểu hook khác nhau, mỗi kiểu cho phép truy xuất tới một kiểu thông điệp cụ thể hay một vùng các thông điệp. Để có được một hook cụ thể thì một ứng dụng có thể cài đặt một hàm lọc (filter function) để xử lý những thông điệp được tổ chức với hook. Một hàm lọc xử lý những thông điệp trước khi chúng đến thủ tục cửa sổ đích.
Chuỗi hàm lọc (Filter-Function Chain):
Một chuỗi hàm lọc là một loạt những hàm lọc họ hàng cho một hook hệ thống cụ thể. Một ứng dụng chuyển một hàm lọc cho một hook hệ thống bằng cách gọi hàm SetWindowsHook. Mỗi lần gọi thì cộng thêm một hàm lọc mới vào đầu của chuỗi. Bất cứ khi nào một ứng dụng chyển địa chỉ một hàm lọc cho hook hệ thống nó phải giữ vùng nhớ cho địa chỉ hàm lọc kế tiếp trong chuỗi. SetWindowsHook cài một hàm hook vào chuỗi hook và trả về handle của hook. Khi mỗi hàm lọc hoàn thành nhiệm vụ nó phải gọi hàm DefHookProc, hàm này sử dụng địa chỉ được lưu trong vị trí được giữ bởi ứng dụng để truy xuất hàm lọc kế tiếp trong chuỗi. Để xóa một hàm lọc trong chuỗi lọc một ứng dụng phải gọi hàm UnhookWindowsHook với thông số là kiểu hook và một con trỏ chỉ tới hàm.
Sau đây là các hook cửa sổ tiêu chuẩn:
Kiểu Chức năng
WH_CALLWNDPROC Cài đặt lọc cửa sổ
WH_CBT Cài đặt lọc computer-based training (CBT)
WH_DEBUG Cài đặt lọc debug
WH_GETMESSAGE Cài đặt lọc thông điệp (chỉ trên những version debug)
WH_HARDWARE Cài đặt lọc thông điệp-phần cứng không
tiêu chuẩn
WH_JOURNALPLAYBACK Cài đặt lọc journaling playback WH_JOURNALRECORD Cài đặt lọc journaling record WH_KEYBOARD Cài đặt lọc bàn phím
WH_MOUSE Cài đặt lọc thông điệp chuột WH_MSGFILTER Cài đặt lọc thông điệp
WH_SYSMSGFILTER Cài đặt lọc thông điệp trên toàn hệ thống Trong đó hook WH_CALLWNDPROC và WH_GETMESSAGE ảnh hưởng hoạt động của hệ thống, chỉ sử dụng để debug.
Để cài đặt một hàm lọc thì ứng dụng phải trải qua 4 bước: 1 - Export hàm trong file .DEF
2 - Lấy địa chỉ hàm bằng cách dùng hàm GetProcAddress (MakeProcInstance chỉ được sử dụng khi hàm lọc không ở trong một DLL)
3 - Gọi SetWindowsHook , xác định kiểu hàm hook và địa chỉ của hàm (được trả về bởi GetProcAddress)
4 - Lưu giá trị trả về từ SetWindowsHook trong vị trí được giữ. Giá trị này là handle của hàm lọc trước.
Những hàm cho hook toàn hệ thống phải được cho vào trong một DLL. Như vậy qua những lý thuyết về hook trong môi trường Win32 hay trong môi trường Win16 cho chúng ta có nhận xét: dùng kỹ thuật hook cho phép ta đặt một thủ tục để theo dõi, điều khiển và xử lý các thông điệp của hệ thống Windows theo ý của ta trước lúc thông điệp đó đến được cửa sổ đích. Tuy nhiên
kỹ thuật hook ở trong môi trường Win32 mạnh hơn, nó có thể cho phép ta thực hiện được các công việc có vai trò tương tự như kỹ thuật override trong Win16.
6 - Giới thiệu một số hàm có liên quan đến hook: a) CallMsgFilter:
- Cú pháp:
BOOL CallMsgFilter(lpmsg, nCode) MSG FAR* lpmsg;
int nCode;
- CallMsgFilter chuyển thông điệp đưa ra và mã tới hàm lọc thông điệp hiện hành. Hàm lọc thông điệp là một hàm đặc tả ứng dụng kiểm tra và sửa đổi tất cả các thông điệp. Một ứng dụng xác định hàm bằng cách sử dụng hàm SetWindowsHook.
- Thông số:
lpmsg trỏ tới cấu trúc MSG chứa thông điệp được lọc.
nCode xác định một mã được sử dụng bởi hàm lọc để quyết định làm thế nào để truy xuất thông điệp.
- Giá trị trả về: chỉ ra trạng thái của quá trình xử lý thông điệp. Trả về 0 nếu thông điệp được xử lý và khác 0 nếu thông điệp không được xử lý.
- Chú ý: hàm CallMsgFilter thường được gọi bởi Windows để cho các ứng dụng kiểm tra và điều khiển luồng thông điệp trong suốt quá trình xử lý nội ở trong menu, scroll bar hay khi di chuyển hoặc làm thay đổi kích thước một cửa sổ.
b) SetWindowsHookEx:
- Cú pháp:
HHOOK SetWindowsHookEx(idHook, hkprc, hinst, htask) int idHook;
HOOKPROC hkprc; HINSTANCE hinst; HTASK htask;
- Hàm SetWindowsHookEx cài đặt một hàm hook application-defined vào trong một chuỗi hook. Hàm này là version mở rộng của hàm SetWindowsHook.
- Thông số:
idHook Kiểu của hook được cài đặt. Thông số này gồm các kiểu hook như đã trình bày ở phần lý thuyết nêu trên.
hkprc Địa chỉ procedure-instance của thủ tục hook application-defined được cài đặt.
hinst Instance của module chứa hàm hook.
htask Nhiệm vụ cho hook được cài đặt. Nếu NULL thì hàm hook được cài đặt có tầm vực hệ thống và có thể được gọi ở trong ngữ cảnh của bất kỳ quá trình nào hay nhiệm vụ nào ở trong hệ thống. - Giá trị trả về: giá trị trả về là handle của hook được cài đặt nếu hàm thành công. Ứng dụng hay thư viện phải sử dụng handle này để xác định hook khi nó gọi hàm CallNextHookeEx và UnhookWindowsHookEx. Giá trị trả về là NULL nếu có lỗi.
c) Hàm gỡ bỏ một hàm hook UnhookWindowsHookEx:
- Cú pháp:
BOOL UnhookWindowsHookEx(hhook) HHOOK hhook;
- Hàm UnhookWindowsHookEx bỏ đi một hàm hook application ra khỏi một chuỗi hàm hook. Một hàm hook xử lý các sự kiện trước khi chúng được gởi tới vòng lặp thông điệp ứng dụng trong hàm WinMain.
- Thông số:
hhook Chỉ ra hàm hook được dỡ bỏ. Đây là giá trị được trả về bởi hàm SetWindowsHookExIdentifies khi hàm hook được cài đặt.
- Chú ý: Hàm UnhookWindowsHookEx phải được sử dụng trong sự kiết hợp với hàm SetWindowsHookEx.
d) Hàm gọi hook kế tiếp trong hook-chain CallNextHookEx:
LRESULT CallNextHookEx(hHook, nCode, wParam, lParam) HHOOK hHook;
int nCode;
WPARAM wParam; LPARAM lParam;
- Hàm CallNextHookEx function chuyển các thông tin hook đến hàm xử lý hook kế tiếp trong hook chain.
- Thông số
hHook Chỉ định hook handle
nCode Chỉ định hook code để gởi đến hook kế tiếp. Hàm xử lý hook dùng giá trị này để chỉ định xử lý thông điệp được gởi từ hook thế nào.
wParam Chỉ định 16 bits thông tin mở rộng của thông điệp lParam Chỉ định 32 bits thông tin mở rộng của thông điệp
- Giá trị trả về: Giá trị trả về là kết quả của quá trình xử lý và tùy thuộc vào thông số nCode.
Chương 3: